Skip to content

feat: Add User ID Mapping feature #218

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 8 commits into from
Aug 30, 2022
Merged
3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

## [unreleased]

## Features:
- Add support for User ID Mapping using `create_user_id_mapping`, `get_user_id_mapping`, `delete_user_id_mapping`, `update_or_delete_user_id_mapping` functions

## [0.10.3] - 2022-08-29

### Bug fix
Expand Down
3 changes: 2 additions & 1 deletion coreDriverInterfaceSupported.json
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
"2.11",
"2.12",
"2.13",
"2.14"
"2.14",
"2.15"
]
}
54 changes: 53 additions & 1 deletion supertokens_python/asyncio/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,9 +11,19 @@
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
from typing import List, Union
from typing import List, Union, Optional

from supertokens_python import Supertokens
from supertokens_python.interfaces import (
CreateUserIdMappingOkResult,
UnknownSupertokensUserIDError,
UserIdMappingAlreadyExistsError,
UserIDTypes,
UnknownMappingError,
GetUserIdMappingOkResult,
DeleteUserIdMappingOkResult,
UpdateOrDeleteUserIdMappingInfoOkResult,
)
from supertokens_python.types import UsersResponse


Expand Down Expand Up @@ -43,3 +53,45 @@ async def get_user_count(include_recipe_ids: Union[None, List[str]] = None) -> i

async def delete_user(user_id: str) -> None:
return await Supertokens.get_instance().delete_user(user_id)


async def create_user_id_mapping(
supertokens_user_id: str,
external_user_id: str,
external_user_id_info: Optional[str] = None,
force: Optional[bool] = None,
) -> Union[
CreateUserIdMappingOkResult,
UnknownSupertokensUserIDError,
UserIdMappingAlreadyExistsError,
]:
return await Supertokens.get_instance().create_user_id_mapping(
supertokens_user_id, external_user_id, external_user_id_info, force
)


async def get_user_id_mapping(
user_id: str,
user_id_type: Optional[UserIDTypes] = None,
) -> Union[GetUserIdMappingOkResult, UnknownMappingError]:
return await Supertokens.get_instance().get_user_id_mapping(user_id, user_id_type)


async def delete_user_id_mapping(
user_id: str,
user_id_type: Optional[UserIDTypes] = None,
force: Optional[bool] = None,
) -> DeleteUserIdMappingOkResult:
return await Supertokens.get_instance().delete_user_id_mapping(
user_id, user_id_type, force
)


async def update_or_delete_user_id_mapping_info(
user_id: str,
user_id_type: Optional[UserIDTypes] = None,
external_user_id_info: Optional[str] = None,
) -> Union[UpdateOrDeleteUserIdMappingInfoOkResult, UnknownMappingError]:
return await Supertokens.get_instance().update_or_delete_user_id_mapping_info(
user_id, user_id_type, external_user_id_info
)
2 changes: 1 addition & 1 deletion supertokens_python/constants.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
SUPPORTED_CDI_VERSIONS = ["2.9", "2.10", "2.11", "2.12", "2.13", "2.14"]
SUPPORTED_CDI_VERSIONS = ["2.9", "2.10", "2.11", "2.12", "2.13", "2.14", "2.15"]
VERSION = "0.10.3"
TELEMETRY = "/telemetry"
USER_COUNT = "/users/count"
Expand Down
61 changes: 61 additions & 0 deletions supertokens_python/interfaces.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
# Copyright (c) 2021, VRAI Labs and/or its affiliates. All rights reserved.
#
# This software is licensed under the Apache License, Version 2.0 (the
# "License") as published by the Apache Software Foundation.
#
# You may not use this file except in compliance with the License. You may
# obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.

from typing import Optional

from typing_extensions import Literal


class UnknownSupertokensUserIDError:
pass


class CreateUserIdMappingOkResult:
pass


class UserIdMappingAlreadyExistsError:
def __init__(
self, does_super_tokens_user_id_exist: bool, does_external_user_id_exist: str
):
self.does_super_tokens_user_id_exist = does_super_tokens_user_id_exist
self.does_external_user_id_exist = does_external_user_id_exist


UserIDTypes = Literal["SUPERTOKENS", "EXTERNAL", "ANY"]


class GetUserIdMappingOkResult:
def __init__(
self,
supertokens_user_id: str,
external_user_id: str,
external_user_info: Optional[str] = None,
):
self.supertokens_user_id = supertokens_user_id
self.external_user_id = external_user_id
self.external_user_info = external_user_info


class UnknownMappingError:
pass


class DeleteUserIdMappingOkResult:
def __init__(self, did_mapping_exist: bool):
self.did_mapping_exist = did_mapping_exist


class UpdateOrDeleteUserIdMappingInfoOkResult:
pass
142 changes: 141 additions & 1 deletion supertokens_python/supertokens.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@

from __future__ import annotations

from typing import TYPE_CHECKING, Any, Callable, List, Set, Union
from typing import TYPE_CHECKING, Any, Callable, List, Set, Union, Optional, Dict

from typing_extensions import Literal

Expand All @@ -31,6 +31,16 @@
USERS,
)
from .exceptions import SuperTokensError
from .interfaces import (
CreateUserIdMappingOkResult,
UnknownSupertokensUserIDError,
UserIdMappingAlreadyExistsError,
UnknownMappingError,
GetUserIdMappingOkResult,
UserIDTypes,
DeleteUserIdMappingOkResult,
UpdateOrDeleteUserIdMappingInfoOkResult,
)
from .normalised_url_domain import NormalisedURLDomain
from .normalised_url_path import NormalisedURLPath
from .querier import Querier
Expand Down Expand Up @@ -388,6 +398,136 @@ async def get_users( # pylint: disable=no-self-use

return UsersResponse(users, next_pagination_token)

async def create_user_id_mapping( # pylint: disable=no-self-use
self,
supertokens_user_id: str,
external_user_id: str,
external_user_id_info: Optional[str] = None,
force: Optional[bool] = None,
) -> Union[
CreateUserIdMappingOkResult,
UnknownSupertokensUserIDError,
UserIdMappingAlreadyExistsError,
]:
querier = Querier.get_instance(None)

cdi_version = await querier.get_api_version()

if is_version_gte(cdi_version, "2.15"):
body: Dict[str, Any] = {
"superTokensUserId": supertokens_user_id,
"externalUserId": external_user_id,
"externalUserIdInfo": external_user_id_info,
}
if force:
body["force"] = force

res = await querier.send_post_request(
NormalisedURLPath("/recipe/userid/map"), body
)
if res["status"] == "OK":
return CreateUserIdMappingOkResult()
if res["status"] == "UNKNOWN_SUPERTOKENS_USER_ID_ERROR":
return UnknownSupertokensUserIDError()
if res["status"] == "USER_ID_MAPPING_ALREADY_EXISTS_ERROR":
return UserIdMappingAlreadyExistsError(
does_super_tokens_user_id_exist=res["doesSuperTokensUserIdExist"],
does_external_user_id_exist=res["does_external_user_id_exist"],
)

raise_general_exception("Unknown response")

raise_general_exception("Please upgrade the SuperTokens core to >= 3.15.0")

async def get_user_id_mapping( # pylint: disable=no-self-use
self,
user_id: str,
user_id_type: Optional[UserIDTypes] = None,
) -> Union[GetUserIdMappingOkResult, UnknownMappingError]:
querier = Querier.get_instance(None)

cdi_version = await querier.get_api_version()

if is_version_gte(cdi_version, "2.15"):
body = {
"userId": user_id,
}
if user_id_type:
body["userIdType"] = user_id_type
res = await querier.send_get_request(
NormalisedURLPath("/recipe/userid/map"),
body,
)
if res["status"] == "OK":
return GetUserIdMappingOkResult(
supertokens_user_id=res["superTokensUserId"],
external_user_id=res["externalUserId"],
external_user_info=res.get("externalUserIdInfo"),
)
if res["status"] == "UNKNOWN_MAPPING_ERROR":
return UnknownMappingError()

raise_general_exception("Unknown response")

raise_general_exception("Please upgrade the SuperTokens core to >= 3.15.0")

async def delete_user_id_mapping( # pylint: disable=no-self-use
self,
user_id: str,
user_id_type: Optional[UserIDTypes] = None,
force: Optional[bool] = None,
) -> DeleteUserIdMappingOkResult:
querier = Querier.get_instance(None)

cdi_version = await querier.get_api_version()

if is_version_gte(cdi_version, "2.15"):
body: Dict[str, Any] = {
"userId": user_id,
"userIdType": user_id_type,
}
if force:
body["force"] = force
res = await querier.send_post_request(
NormalisedURLPath("/recipe/userid/map/remove"), body
)
if res["status"] == "OK":
return DeleteUserIdMappingOkResult(
did_mapping_exist=res["didMappingExist"]
)

raise_general_exception("Unknown response")

raise_general_exception("Please upgrade the SuperTokens core to >= 3.15.0")

async def update_or_delete_user_id_mapping_info( # pylint: disable=no-self-use
self,
user_id: str,
user_id_type: Optional[UserIDTypes] = None,
external_user_id_info: Optional[str] = None,
) -> Union[UpdateOrDeleteUserIdMappingInfoOkResult, UnknownMappingError]:
querier = Querier.get_instance(None)

cdi_version = await querier.get_api_version()

if is_version_gte(cdi_version, "2.15"):
res = await querier.send_post_request(
NormalisedURLPath("/recipe/userid/external-user-id-info"),
{
"userId": user_id,
"userIdType": user_id_type,
"externalUserIdInfo": external_user_id_info,
},
)
if res["status"] == "OK":
return UpdateOrDeleteUserIdMappingInfoOkResult()
if res["status"] == "UNKNOWN_MAPPING_ERROR":
return UnknownMappingError()

raise_general_exception("Unknown response")

raise_general_exception("Please upgrade the SuperTokens core to >= 3.15.0")

async def middleware( # pylint: disable=no-self-use
self, request: BaseRequest, response: BaseResponse
) -> Union[BaseResponse, None]:
Expand Down
55 changes: 54 additions & 1 deletion supertokens_python/syncio/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,10 +11,20 @@
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
from typing import List, Union
from typing import List, Union, Optional

from supertokens_python import Supertokens
from supertokens_python.async_to_sync_wrapper import sync
from supertokens_python.interfaces import (
CreateUserIdMappingOkResult,
UnknownSupertokensUserIDError,
UserIdMappingAlreadyExistsError,
UserIDTypes,
UnknownMappingError,
GetUserIdMappingOkResult,
DeleteUserIdMappingOkResult,
UpdateOrDeleteUserIdMappingInfoOkResult,
)
from supertokens_python.types import UsersResponse


Expand Down Expand Up @@ -48,3 +58,46 @@ def get_user_count(include_recipe_ids: Union[None, List[str]] = None) -> int:

def delete_user(user_id: str) -> None:
return sync(Supertokens.get_instance().delete_user(user_id))


def create_user_id_mapping(
supertokens_user_id: str,
external_user_id: str,
external_user_id_info: Optional[str] = None,
) -> Union[
CreateUserIdMappingOkResult,
UnknownSupertokensUserIDError,
UserIdMappingAlreadyExistsError,
]:
return sync(
Supertokens.get_instance().create_user_id_mapping(
supertokens_user_id, external_user_id, external_user_id_info
)
)


def get_user_id_mapping(
user_id: str,
user_id_type: Optional[UserIDTypes] = None,
) -> Union[GetUserIdMappingOkResult, UnknownMappingError]:
return sync(Supertokens.get_instance().get_user_id_mapping(user_id, user_id_type))


def delete_user_id_mapping(
user_id: str, user_id_type: Optional[UserIDTypes] = None
) -> DeleteUserIdMappingOkResult:
return sync(
Supertokens.get_instance().delete_user_id_mapping(user_id, user_id_type)
)


def update_or_delete_user_id_mapping_info(
user_id: str,
user_id_type: Optional[UserIDTypes] = None,
external_user_id_info: Optional[str] = None,
) -> Union[UpdateOrDeleteUserIdMappingInfoOkResult, UnknownMappingError]:
return sync(
Supertokens.get_instance().update_or_delete_user_id_mapping_info(
user_id, user_id_type, external_user_id_info
)
)
Empty file added tests/useridmapping/__init__.py
Empty file.
Loading