Skip to content

Commit 73f41ae

Browse files
committed
fix: Update multitenancy recipe
1 parent 177b31a commit 73f41ae

File tree

8 files changed

+340
-144
lines changed

8 files changed

+340
-144
lines changed
Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,84 @@
1+
# Copyright (c) 2023, 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 Any, Dict, Optional, Union, List
16+
17+
from supertokens_python.recipe.multitenancy.interfaces import (
18+
APIOptions,
19+
LoginMethodsGetOkResult,
20+
LoginMethodEmailPassword,
21+
LoginMethodPasswordless,
22+
LoginMethodThirdParty,
23+
)
24+
from supertokens_python.types import GeneralErrorResponse
25+
26+
from ..interfaces import APIInterface, ThirdPartyProvider
27+
28+
from supertokens_python.recipe.thirdparty.providers.config_utils import (
29+
merge_providers_from_core_and_static,
30+
find_and_create_provider_instance,
31+
)
32+
from supertokens_python.recipe.thirdparty.exceptions import ClientTypeNotFoundError
33+
34+
35+
class APIImplementation(APIInterface):
36+
async def login_methods_get(
37+
self,
38+
tenant_id: Optional[str],
39+
client_type: Optional[str],
40+
api_options: APIOptions,
41+
user_context: Dict[str, Any],
42+
) -> Union[LoginMethodsGetOkResult, GeneralErrorResponse]:
43+
tenant_config_res = await api_options.recipe_implementation.get_tenant(
44+
tenant_id, user_context
45+
)
46+
47+
provider_inputs_from_static = api_options.static_third_party_providers
48+
provider_configs_from_core = tenant_config_res.third_party.providers
49+
50+
merged_providers = merge_providers_from_core_and_static(
51+
tenant_id, provider_configs_from_core, provider_inputs_from_static
52+
)
53+
54+
final_provider_list: List[ThirdPartyProvider] = []
55+
56+
for provider_input in merged_providers:
57+
try:
58+
provider_instance = await find_and_create_provider_instance(
59+
merged_providers,
60+
provider_input.config.third_party_id,
61+
client_type,
62+
user_context,
63+
)
64+
final_provider_list.append(
65+
ThirdPartyProvider(
66+
provider_instance.id, provider_instance.config.name
67+
)
68+
)
69+
except Exception as e:
70+
if isinstance(e, ClientTypeNotFoundError):
71+
continue
72+
raise e
73+
74+
return LoginMethodsGetOkResult(
75+
email_password=LoginMethodEmailPassword(
76+
tenant_config_res.email_password.enabled
77+
),
78+
passwordless=LoginMethodPasswordless(
79+
tenant_config_res.passwordless.enabled
80+
),
81+
third_party=LoginMethodThirdParty(
82+
tenant_config_res.third_party.enabled, final_provider_list
83+
),
84+
)

supertokens_python/recipe/multitenancy/asyncio/__init__.py

Lines changed: 85 additions & 91 deletions
Original file line numberDiff line numberDiff line change
@@ -12,129 +12,123 @@
1212
# under the License.
1313
from typing import Any, Dict, Union, Optional
1414

15-
from supertokens_python.recipe.emailverification.interfaces import (
16-
GetEmailForUserIdOkResult,
17-
EmailDoesNotExistError,
18-
CreateEmailVerificationTokenEmailAlreadyVerifiedError,
19-
UnverifyEmailOkResult,
20-
CreateEmailVerificationTokenOkResult,
21-
RevokeEmailVerificationTokensOkResult,
15+
from ..interfaces import (
16+
TenantConfig,
17+
ProviderConfig,
18+
CreateOrUpdateTenantOkResult,
19+
DeleteTenantOkResult,
20+
GetTenantOkResult,
21+
ListAllTenantsOkResult,
22+
CreateOrUpdateThirdPartyConfigOkResult,
23+
DeleteThirdPartyConfigOkResult,
24+
AssociateUserToTenantOkResult,
25+
AssociateUserToTenantErrorResult,
26+
DisassociateUserFromTenantOkResult,
2227
)
23-
from supertokens_python.recipe.emailverification.types import EmailTemplateVars
24-
from supertokens_python.recipe.emailverification.recipe import EmailVerificationRecipe
28+
from ..recipe import MultitenancyRecipe
2529

2630

27-
async def create_email_verification_token(
28-
user_id: str,
29-
email: Optional[str] = None,
30-
user_context: Union[None, Dict[str, Any]] = None,
31-
) -> Union[
32-
CreateEmailVerificationTokenOkResult,
33-
CreateEmailVerificationTokenEmailAlreadyVerifiedError,
34-
]:
31+
async def create_or_update_tenant(
32+
tenant_id: Optional[str],
33+
config: TenantConfig,
34+
user_context: Optional[Dict[str, Any]] = None,
35+
) -> CreateOrUpdateTenantOkResult:
3536
if user_context is None:
3637
user_context = {}
37-
recipe = EmailVerificationRecipe.get_instance()
38-
if email is None:
39-
email_info = await recipe.get_email_for_user_id(user_id, user_context)
40-
if isinstance(email_info, GetEmailForUserIdOkResult):
41-
email = email_info.email
42-
elif isinstance(email_info, EmailDoesNotExistError):
43-
return CreateEmailVerificationTokenEmailAlreadyVerifiedError()
44-
else:
45-
raise Exception("Unknown User ID provided without email")
46-
47-
return await recipe.recipe_implementation.create_email_verification_token(
48-
user_id, email, user_context
38+
recipe = MultitenancyRecipe.get_instance()
39+
40+
return await recipe.recipe_implementation.create_or_update_tenant(
41+
tenant_id, config, user_context
4942
)
5043

5144

52-
async def verify_email_using_token(
53-
token: str, user_context: Union[None, Dict[str, Any]] = None
54-
):
45+
async def delete_tenant(
46+
tenant_id: str, user_context: Optional[Dict[str, Any]] = None
47+
) -> DeleteTenantOkResult:
5548
if user_context is None:
5649
user_context = {}
57-
return await EmailVerificationRecipe.get_instance().recipe_implementation.verify_email_using_token(
58-
token, user_context
59-
)
50+
recipe = MultitenancyRecipe.get_instance()
6051

52+
return await recipe.recipe_implementation.delete_tenant(tenant_id, user_context)
6153

62-
async def is_email_verified(
63-
user_id: str,
64-
email: Optional[str] = None,
65-
user_context: Union[None, Dict[str, Any]] = None,
66-
):
54+
55+
async def get_tenant(
56+
tenant_id: Optional[str], user_context: Optional[Dict[str, Any]] = None
57+
) -> GetTenantOkResult:
58+
if user_context is None:
59+
user_context = {}
60+
recipe = MultitenancyRecipe.get_instance()
61+
62+
return await recipe.recipe_implementation.get_tenant(tenant_id, user_context)
63+
64+
65+
async def list_all_tenants(
66+
user_context: Optional[Dict[str, Any]] = None
67+
) -> ListAllTenantsOkResult:
6768
if user_context is None:
6869
user_context = {}
6970

70-
recipe = EmailVerificationRecipe.get_instance()
71-
if email is None:
72-
email_info = await recipe.get_email_for_user_id(user_id, user_context)
73-
if isinstance(email_info, GetEmailForUserIdOkResult):
74-
email = email_info.email
75-
elif isinstance(email_info, EmailDoesNotExistError):
76-
return True
77-
else:
78-
raise Exception("Unknown User ID provided without email")
79-
80-
return await recipe.recipe_implementation.is_email_verified(
81-
user_id, email, user_context
71+
recipe = MultitenancyRecipe.get_instance()
72+
73+
return await recipe.recipe_implementation.list_all_tenants(user_context)
74+
75+
76+
async def create_or_update_third_party_config(
77+
tenant_id: Optional[str],
78+
config: ProviderConfig,
79+
skip_validation: Optional[bool],
80+
user_context: Optional[Dict[str, Any]] = None,
81+
) -> CreateOrUpdateThirdPartyConfigOkResult:
82+
if user_context is None:
83+
user_context = {}
84+
85+
recipe = MultitenancyRecipe.get_instance()
86+
87+
return await recipe.recipe_implementation.create_or_update_third_party_config(
88+
tenant_id, config, skip_validation, user_context
8289
)
8390

8491

85-
async def revoke_email_verification_tokens(
86-
user_id: str,
87-
email: Optional[str] = None,
92+
async def delete_third_party_config(
93+
tenant_id: Optional[str],
94+
third_party_id: str,
8895
user_context: Optional[Dict[str, Any]] = None,
89-
) -> RevokeEmailVerificationTokensOkResult:
96+
) -> DeleteThirdPartyConfigOkResult:
9097
if user_context is None:
9198
user_context = {}
9299

93-
recipe = EmailVerificationRecipe.get_instance()
94-
if email is None:
95-
email_info = await recipe.get_email_for_user_id(user_id, user_context)
96-
if isinstance(email_info, GetEmailForUserIdOkResult):
97-
email = email_info.email
98-
elif isinstance(email_info, EmailDoesNotExistError):
99-
return RevokeEmailVerificationTokensOkResult()
100-
else:
101-
raise Exception("Unknown User ID provided without email")
102-
103-
return await EmailVerificationRecipe.get_instance().recipe_implementation.revoke_email_verification_tokens(
104-
user_id, email, user_context
100+
recipe = MultitenancyRecipe.get_instance()
101+
102+
return await recipe.recipe_implementation.delete_third_party_config(
103+
tenant_id, third_party_id, user_context
105104
)
106105

107106

108-
async def unverify_email(
107+
async def associate_user_to_tenant(
108+
tenant_id: Optional[str],
109109
user_id: str,
110-
email: Optional[str] = None,
111-
user_context: Union[None, Dict[str, Any]] = None,
112-
):
110+
user_context: Optional[Dict[str, Any]] = None,
111+
) -> Union[AssociateUserToTenantOkResult, AssociateUserToTenantErrorResult]:
113112
if user_context is None:
114113
user_context = {}
115114

116-
recipe = EmailVerificationRecipe.get_instance()
117-
if email is None:
118-
email_info = await recipe.get_email_for_user_id(user_id, user_context)
119-
if isinstance(email_info, GetEmailForUserIdOkResult):
120-
email = email_info.email
121-
elif isinstance(email_info, EmailDoesNotExistError):
122-
# Here we are returning OK since that's how it used to work, but a later call
123-
# to is_verified will still return true
124-
return UnverifyEmailOkResult
125-
else:
126-
raise Exception("Unknown User ID provided without email")
127-
128-
return await EmailVerificationRecipe.get_instance().recipe_implementation.unverify_email(
129-
user_id, email, user_context
115+
recipe = MultitenancyRecipe.get_instance()
116+
117+
return await recipe.recipe_implementation.associate_user_to_tenant(
118+
tenant_id, user_id, user_context
130119
)
131120

132121

133-
async def send_email(
134-
input_: EmailTemplateVars, user_context: Union[None, Dict[str, Any]] = None
135-
):
122+
async def dissociate_user_from_tenant(
123+
tenant_id: Optional[str],
124+
user_id: str,
125+
user_context: Optional[Dict[str, Any]] = None,
126+
) -> DisassociateUserFromTenantOkResult:
136127
if user_context is None:
137128
user_context = {}
138-
return await EmailVerificationRecipe.get_instance().email_delivery.ingredient_interface_impl.send_email(
139-
input_, user_context
129+
130+
recipe = MultitenancyRecipe.get_instance()
131+
132+
return await recipe.recipe_implementation.dissociate_user_from_tenant(
133+
tenant_id, user_id, user_context
140134
)

0 commit comments

Comments
 (0)