Skip to content

Commit b6896b3

Browse files
Merge pull request #218 from supertokens/feat/user-id-mapping-new
feat: Add User ID Mapping feature
2 parents 351ce84 + 4508d6d commit b6896b3

File tree

13 files changed

+938
-5
lines changed

13 files changed

+938
-5
lines changed

CHANGELOG.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
66

77
## [unreleased]
88

9+
## Features:
10+
- 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
11+
912
## [0.10.3] - 2022-08-29
1013

1114
### Bug fix

coreDriverInterfaceSupported.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
"2.11",
77
"2.12",
88
"2.13",
9-
"2.14"
9+
"2.14",
10+
"2.15"
1011
]
1112
}

supertokens_python/asyncio/__init__.py

Lines changed: 53 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,9 +11,19 @@
1111
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
1212
# License for the specific language governing permissions and limitations
1313
# under the License.
14-
from typing import List, Union
14+
from typing import List, Union, Optional
1515

1616
from supertokens_python import Supertokens
17+
from supertokens_python.interfaces import (
18+
CreateUserIdMappingOkResult,
19+
UnknownSupertokensUserIDError,
20+
UserIdMappingAlreadyExistsError,
21+
UserIDTypes,
22+
UnknownMappingError,
23+
GetUserIdMappingOkResult,
24+
DeleteUserIdMappingOkResult,
25+
UpdateOrDeleteUserIdMappingInfoOkResult,
26+
)
1727
from supertokens_python.types import UsersResponse
1828

1929

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

4454
async def delete_user(user_id: str) -> None:
4555
return await Supertokens.get_instance().delete_user(user_id)
56+
57+
58+
async def create_user_id_mapping(
59+
supertokens_user_id: str,
60+
external_user_id: str,
61+
external_user_id_info: Optional[str] = None,
62+
force: Optional[bool] = None,
63+
) -> Union[
64+
CreateUserIdMappingOkResult,
65+
UnknownSupertokensUserIDError,
66+
UserIdMappingAlreadyExistsError,
67+
]:
68+
return await Supertokens.get_instance().create_user_id_mapping(
69+
supertokens_user_id, external_user_id, external_user_id_info, force
70+
)
71+
72+
73+
async def get_user_id_mapping(
74+
user_id: str,
75+
user_id_type: Optional[UserIDTypes] = None,
76+
) -> Union[GetUserIdMappingOkResult, UnknownMappingError]:
77+
return await Supertokens.get_instance().get_user_id_mapping(user_id, user_id_type)
78+
79+
80+
async def delete_user_id_mapping(
81+
user_id: str,
82+
user_id_type: Optional[UserIDTypes] = None,
83+
force: Optional[bool] = None,
84+
) -> DeleteUserIdMappingOkResult:
85+
return await Supertokens.get_instance().delete_user_id_mapping(
86+
user_id, user_id_type, force
87+
)
88+
89+
90+
async def update_or_delete_user_id_mapping_info(
91+
user_id: str,
92+
user_id_type: Optional[UserIDTypes] = None,
93+
external_user_id_info: Optional[str] = None,
94+
) -> Union[UpdateOrDeleteUserIdMappingInfoOkResult, UnknownMappingError]:
95+
return await Supertokens.get_instance().update_or_delete_user_id_mapping_info(
96+
user_id, user_id_type, external_user_id_info
97+
)

supertokens_python/constants.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@
1111
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
1212
# License for the specific language governing permissions and limitations
1313
# under the License.
14-
SUPPORTED_CDI_VERSIONS = ["2.9", "2.10", "2.11", "2.12", "2.13", "2.14"]
14+
SUPPORTED_CDI_VERSIONS = ["2.9", "2.10", "2.11", "2.12", "2.13", "2.14", "2.15"]
1515
VERSION = "0.10.3"
1616
TELEMETRY = "/telemetry"
1717
USER_COUNT = "/users/count"

supertokens_python/interfaces.py

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
# Copyright (c) 2021, VRAI Labs and/or its affiliates. All rights reserved.
2+
#
3+
# This software is licensed under the Apache License, Version 2.0 (the
4+
# "License") as published by the Apache Software Foundation.
5+
#
6+
# You may not use this file except in compliance with the License. You may
7+
# obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0
8+
#
9+
# Unless required by applicable law or agreed to in writing, software
10+
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
11+
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
12+
# License for the specific language governing permissions and limitations
13+
# under the License.
14+
15+
from typing import Optional
16+
17+
from typing_extensions import Literal
18+
19+
20+
class UnknownSupertokensUserIDError:
21+
pass
22+
23+
24+
class CreateUserIdMappingOkResult:
25+
pass
26+
27+
28+
class UserIdMappingAlreadyExistsError:
29+
def __init__(
30+
self, does_super_tokens_user_id_exist: bool, does_external_user_id_exist: str
31+
):
32+
self.does_super_tokens_user_id_exist = does_super_tokens_user_id_exist
33+
self.does_external_user_id_exist = does_external_user_id_exist
34+
35+
36+
UserIDTypes = Literal["SUPERTOKENS", "EXTERNAL", "ANY"]
37+
38+
39+
class GetUserIdMappingOkResult:
40+
def __init__(
41+
self,
42+
supertokens_user_id: str,
43+
external_user_id: str,
44+
external_user_info: Optional[str] = None,
45+
):
46+
self.supertokens_user_id = supertokens_user_id
47+
self.external_user_id = external_user_id
48+
self.external_user_info = external_user_info
49+
50+
51+
class UnknownMappingError:
52+
pass
53+
54+
55+
class DeleteUserIdMappingOkResult:
56+
def __init__(self, did_mapping_exist: bool):
57+
self.did_mapping_exist = did_mapping_exist
58+
59+
60+
class UpdateOrDeleteUserIdMappingInfoOkResult:
61+
pass

supertokens_python/supertokens.py

Lines changed: 141 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@
1414

1515
from __future__ import annotations
1616

17-
from typing import TYPE_CHECKING, Any, Callable, List, Set, Union
17+
from typing import TYPE_CHECKING, Any, Callable, List, Set, Union, Optional, Dict
1818

1919
from typing_extensions import Literal
2020

@@ -31,6 +31,16 @@
3131
USERS,
3232
)
3333
from .exceptions import SuperTokensError
34+
from .interfaces import (
35+
CreateUserIdMappingOkResult,
36+
UnknownSupertokensUserIDError,
37+
UserIdMappingAlreadyExistsError,
38+
UnknownMappingError,
39+
GetUserIdMappingOkResult,
40+
UserIDTypes,
41+
DeleteUserIdMappingOkResult,
42+
UpdateOrDeleteUserIdMappingInfoOkResult,
43+
)
3444
from .normalised_url_domain import NormalisedURLDomain
3545
from .normalised_url_path import NormalisedURLPath
3646
from .querier import Querier
@@ -388,6 +398,136 @@ async def get_users( # pylint: disable=no-self-use
388398

389399
return UsersResponse(users, next_pagination_token)
390400

401+
async def create_user_id_mapping( # pylint: disable=no-self-use
402+
self,
403+
supertokens_user_id: str,
404+
external_user_id: str,
405+
external_user_id_info: Optional[str] = None,
406+
force: Optional[bool] = None,
407+
) -> Union[
408+
CreateUserIdMappingOkResult,
409+
UnknownSupertokensUserIDError,
410+
UserIdMappingAlreadyExistsError,
411+
]:
412+
querier = Querier.get_instance(None)
413+
414+
cdi_version = await querier.get_api_version()
415+
416+
if is_version_gte(cdi_version, "2.15"):
417+
body: Dict[str, Any] = {
418+
"superTokensUserId": supertokens_user_id,
419+
"externalUserId": external_user_id,
420+
"externalUserIdInfo": external_user_id_info,
421+
}
422+
if force:
423+
body["force"] = force
424+
425+
res = await querier.send_post_request(
426+
NormalisedURLPath("/recipe/userid/map"), body
427+
)
428+
if res["status"] == "OK":
429+
return CreateUserIdMappingOkResult()
430+
if res["status"] == "UNKNOWN_SUPERTOKENS_USER_ID_ERROR":
431+
return UnknownSupertokensUserIDError()
432+
if res["status"] == "USER_ID_MAPPING_ALREADY_EXISTS_ERROR":
433+
return UserIdMappingAlreadyExistsError(
434+
does_super_tokens_user_id_exist=res["doesSuperTokensUserIdExist"],
435+
does_external_user_id_exist=res["does_external_user_id_exist"],
436+
)
437+
438+
raise_general_exception("Unknown response")
439+
440+
raise_general_exception("Please upgrade the SuperTokens core to >= 3.15.0")
441+
442+
async def get_user_id_mapping( # pylint: disable=no-self-use
443+
self,
444+
user_id: str,
445+
user_id_type: Optional[UserIDTypes] = None,
446+
) -> Union[GetUserIdMappingOkResult, UnknownMappingError]:
447+
querier = Querier.get_instance(None)
448+
449+
cdi_version = await querier.get_api_version()
450+
451+
if is_version_gte(cdi_version, "2.15"):
452+
body = {
453+
"userId": user_id,
454+
}
455+
if user_id_type:
456+
body["userIdType"] = user_id_type
457+
res = await querier.send_get_request(
458+
NormalisedURLPath("/recipe/userid/map"),
459+
body,
460+
)
461+
if res["status"] == "OK":
462+
return GetUserIdMappingOkResult(
463+
supertokens_user_id=res["superTokensUserId"],
464+
external_user_id=res["externalUserId"],
465+
external_user_info=res.get("externalUserIdInfo"),
466+
)
467+
if res["status"] == "UNKNOWN_MAPPING_ERROR":
468+
return UnknownMappingError()
469+
470+
raise_general_exception("Unknown response")
471+
472+
raise_general_exception("Please upgrade the SuperTokens core to >= 3.15.0")
473+
474+
async def delete_user_id_mapping( # pylint: disable=no-self-use
475+
self,
476+
user_id: str,
477+
user_id_type: Optional[UserIDTypes] = None,
478+
force: Optional[bool] = None,
479+
) -> DeleteUserIdMappingOkResult:
480+
querier = Querier.get_instance(None)
481+
482+
cdi_version = await querier.get_api_version()
483+
484+
if is_version_gte(cdi_version, "2.15"):
485+
body: Dict[str, Any] = {
486+
"userId": user_id,
487+
"userIdType": user_id_type,
488+
}
489+
if force:
490+
body["force"] = force
491+
res = await querier.send_post_request(
492+
NormalisedURLPath("/recipe/userid/map/remove"), body
493+
)
494+
if res["status"] == "OK":
495+
return DeleteUserIdMappingOkResult(
496+
did_mapping_exist=res["didMappingExist"]
497+
)
498+
499+
raise_general_exception("Unknown response")
500+
501+
raise_general_exception("Please upgrade the SuperTokens core to >= 3.15.0")
502+
503+
async def update_or_delete_user_id_mapping_info( # pylint: disable=no-self-use
504+
self,
505+
user_id: str,
506+
user_id_type: Optional[UserIDTypes] = None,
507+
external_user_id_info: Optional[str] = None,
508+
) -> Union[UpdateOrDeleteUserIdMappingInfoOkResult, UnknownMappingError]:
509+
querier = Querier.get_instance(None)
510+
511+
cdi_version = await querier.get_api_version()
512+
513+
if is_version_gte(cdi_version, "2.15"):
514+
res = await querier.send_post_request(
515+
NormalisedURLPath("/recipe/userid/external-user-id-info"),
516+
{
517+
"userId": user_id,
518+
"userIdType": user_id_type,
519+
"externalUserIdInfo": external_user_id_info,
520+
},
521+
)
522+
if res["status"] == "OK":
523+
return UpdateOrDeleteUserIdMappingInfoOkResult()
524+
if res["status"] == "UNKNOWN_MAPPING_ERROR":
525+
return UnknownMappingError()
526+
527+
raise_general_exception("Unknown response")
528+
529+
raise_general_exception("Please upgrade the SuperTokens core to >= 3.15.0")
530+
391531
async def middleware( # pylint: disable=no-self-use
392532
self, request: BaseRequest, response: BaseResponse
393533
) -> Union[BaseResponse, None]:

supertokens_python/syncio/__init__.py

Lines changed: 54 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,10 +11,20 @@
1111
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
1212
# License for the specific language governing permissions and limitations
1313
# under the License.
14-
from typing import List, Union
14+
from typing import List, Union, Optional
1515

1616
from supertokens_python import Supertokens
1717
from supertokens_python.async_to_sync_wrapper import sync
18+
from supertokens_python.interfaces import (
19+
CreateUserIdMappingOkResult,
20+
UnknownSupertokensUserIDError,
21+
UserIdMappingAlreadyExistsError,
22+
UserIDTypes,
23+
UnknownMappingError,
24+
GetUserIdMappingOkResult,
25+
DeleteUserIdMappingOkResult,
26+
UpdateOrDeleteUserIdMappingInfoOkResult,
27+
)
1828
from supertokens_python.types import UsersResponse
1929

2030

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

4959
def delete_user(user_id: str) -> None:
5060
return sync(Supertokens.get_instance().delete_user(user_id))
61+
62+
63+
def create_user_id_mapping(
64+
supertokens_user_id: str,
65+
external_user_id: str,
66+
external_user_id_info: Optional[str] = None,
67+
) -> Union[
68+
CreateUserIdMappingOkResult,
69+
UnknownSupertokensUserIDError,
70+
UserIdMappingAlreadyExistsError,
71+
]:
72+
return sync(
73+
Supertokens.get_instance().create_user_id_mapping(
74+
supertokens_user_id, external_user_id, external_user_id_info
75+
)
76+
)
77+
78+
79+
def get_user_id_mapping(
80+
user_id: str,
81+
user_id_type: Optional[UserIDTypes] = None,
82+
) -> Union[GetUserIdMappingOkResult, UnknownMappingError]:
83+
return sync(Supertokens.get_instance().get_user_id_mapping(user_id, user_id_type))
84+
85+
86+
def delete_user_id_mapping(
87+
user_id: str, user_id_type: Optional[UserIDTypes] = None
88+
) -> DeleteUserIdMappingOkResult:
89+
return sync(
90+
Supertokens.get_instance().delete_user_id_mapping(user_id, user_id_type)
91+
)
92+
93+
94+
def update_or_delete_user_id_mapping_info(
95+
user_id: str,
96+
user_id_type: Optional[UserIDTypes] = None,
97+
external_user_id_info: Optional[str] = None,
98+
) -> Union[UpdateOrDeleteUserIdMappingInfoOkResult, UnknownMappingError]:
99+
return sync(
100+
Supertokens.get_instance().update_or_delete_user_id_mapping_info(
101+
user_id, user_id_type, external_user_id_info
102+
)
103+
)

tests/useridmapping/__init__.py

Whitespace-only changes.

0 commit comments

Comments
 (0)