From eae7e2de900467fc1d8213c9bfc2b64cd87dc6a9 Mon Sep 17 00:00:00 2001 From: rishabhpoddar Date: Mon, 30 Sep 2024 15:44:24 +0530 Subject: [PATCH 1/2] cyclic import issue --- supertokens_python/auth_utils.py | 8 +++- .../multifactorauth/api/implementation.py | 23 +++++---- .../api/resync_session_and_fetch_mfa_info.py | 2 +- .../multifactorauth/asyncio/__init__.py | 16 +++++-- .../recipe/multifactorauth/interfaces.py | 6 +-- .../multi_factor_auth_claim.py | 46 +++++++++++------- .../recipe/multifactorauth/recipe.py | 7 +-- .../multifactorauth/recipe_implementation.py | 5 +- .../recipe/multifactorauth/syncio/__init__.py | 4 -- .../recipe/multifactorauth/types.py | 4 +- .../recipe/multifactorauth/utils.py | 47 +++++++++++++------ .../recipe/multitenancy/asyncio/__init__.py | 23 +++++++-- .../recipe/multitenancy/interfaces.py | 11 ++++- .../multitenancy/recipe_implementation.py | 6 ++- .../recipe/multitenancy/syncio/__init__.py | 6 +-- .../recipe/session/exceptions.py | 11 +++-- .../recipe/thirdparty/asyncio/__init__.py | 2 +- .../recipe/thirdparty/syncio/__init__.py | 2 +- supertokens_python/recipe/totp/interfaces.py | 19 +++++++- .../recipe/totp/syncio/__init__.py | 4 -- supertokens_python/recipe/totp/types.py | 8 ---- 21 files changed, 171 insertions(+), 89 deletions(-) diff --git a/supertokens_python/auth_utils.py b/supertokens_python/auth_utils.py index da67d7b8..ff3315ba 100644 --- a/supertokens_python/auth_utils.py +++ b/supertokens_python/auth_utils.py @@ -923,8 +923,14 @@ async def get_factors_set_up_for_user(): async def get_mfa_requirements_for_auth(): nonlocal mfa_info_prom if mfa_info_prom is None: + from .recipe.multifactorauth.multi_factor_auth_claim import ( + MultiFactorAuthClaim, + ) + mfa_info_prom = await update_and_get_mfa_related_info_in_session( - input_session=session, user_context=user_context + MultiFactorAuthClaim, + input_session=session, + user_context=user_context, ) return mfa_info_prom.mfa_requirements_for_auth diff --git a/supertokens_python/recipe/multifactorauth/api/implementation.py b/supertokens_python/recipe/multifactorauth/api/implementation.py index 9f3dd774..d6fe15e9 100644 --- a/supertokens_python/recipe/multifactorauth/api/implementation.py +++ b/supertokens_python/recipe/multifactorauth/api/implementation.py @@ -12,15 +12,15 @@ # License for the specific language governing permissions and limitations # under the License. from __future__ import annotations +import importlib -from typing import TYPE_CHECKING, Any, Dict, List, Union +from typing import Any, Dict, List, Union, TYPE_CHECKING from supertokens_python.recipe.session import SessionContainer from supertokens_python.recipe.multifactorauth.utils import ( update_and_get_mfa_related_info_in_session, ) from supertokens_python.recipe.multitenancy.asyncio import get_tenant -from ..multi_factor_auth_claim import MultiFactorAuthClaim from supertokens_python.asyncio import get_user from supertokens_python.recipe.session.exceptions import ( InvalidClaimsError, @@ -28,12 +28,6 @@ UnauthorisedError, ) -if TYPE_CHECKING: - from supertokens_python.recipe.multifactorauth.interfaces import ( - APIInterface, - APIOptions, - ) - from supertokens_python.types import GeneralErrorResponse from ..interfaces import ( APIInterface, @@ -42,6 +36,11 @@ ResyncSessionAndFetchMFAInfoPUTOkResult, ) +if TYPE_CHECKING: + from ..multi_factor_auth_claim import ( + MultiFactorAuthClaimClass as MultiFactorAuthClaimType, + ) + class APIImplementation(APIInterface): async def resync_session_and_fetch_mfa_info_put( @@ -50,6 +49,11 @@ async def resync_session_and_fetch_mfa_info_put( session: SessionContainer, user_context: Dict[str, Any], ) -> Union[ResyncSessionAndFetchMFAInfoPUTOkResult, GeneralErrorResponse]: + + mfa = importlib.import_module("supertokens_python.recipe.multifactorauth") + + MultiFactorAuthClaim: MultiFactorAuthClaimType = mfa.MultiFactorAuthClaim + session_user = await get_user(session.get_user_id(), user_context) if session_user is None: @@ -58,6 +62,7 @@ async def resync_session_and_fetch_mfa_info_put( ) mfa_info = await update_and_get_mfa_related_info_in_session( + MultiFactorAuthClaim, input_session=session, user_context=user_context, ) @@ -144,7 +149,7 @@ async def get_mfa_requirements_for_auth(): ) return ResyncSessionAndFetchMFAInfoPUTOkResult( factors=NextFactors( - next=next_factors, + next_=next_factors, already_setup=factors_setup_for_user, allowed_to_setup=factors_allowed_to_setup, ), diff --git a/supertokens_python/recipe/multifactorauth/api/resync_session_and_fetch_mfa_info.py b/supertokens_python/recipe/multifactorauth/api/resync_session_and_fetch_mfa_info.py index 1048253e..8d7f1e8e 100644 --- a/supertokens_python/recipe/multifactorauth/api/resync_session_and_fetch_mfa_info.py +++ b/supertokens_python/recipe/multifactorauth/api/resync_session_and_fetch_mfa_info.py @@ -27,7 +27,7 @@ async def handle_resync_session_and_fetch_mfa_info_api( - tenant_id: str, + _tenant_id: str, api_implementation: APIInterface, api_options: APIOptions, user_context: Dict[str, Any], diff --git a/supertokens_python/recipe/multifactorauth/asyncio/__init__.py b/supertokens_python/recipe/multifactorauth/asyncio/__init__.py index 56817eb5..4c8af2e5 100644 --- a/supertokens_python/recipe/multifactorauth/asyncio/__init__.py +++ b/supertokens_python/recipe/multifactorauth/asyncio/__init__.py @@ -21,7 +21,6 @@ from ..types import ( MFARequirementList, ) -from ..recipe import MultiFactorAuthRecipe from ..utils import update_and_get_mfa_related_info_in_session from supertokens_python.recipe.accountlinking.asyncio import get_user @@ -34,13 +33,17 @@ async def assert_allowed_to_setup_factor_else_throw_invalid_claim_error( if user_context is None: user_context = {} + from ..multi_factor_auth_claim import MultiFactorAuthClaim + mfa_info = await update_and_get_mfa_related_info_in_session( + MultiFactorAuthClaim, input_session=session, user_context=user_context, ) factors_set_up_for_user = await get_factors_setup_for_user( session.get_user_id(), user_context ) + from ..recipe import MultiFactorAuthRecipe recipe = MultiFactorAuthRecipe.get_instance_or_throw_error() @@ -66,7 +69,10 @@ async def get_mfa_requirements_for_auth( if user_context is None: user_context = {} + from ..multi_factor_auth_claim import MultiFactorAuthClaim + mfa_info = await update_and_get_mfa_related_info_in_session( + MultiFactorAuthClaim, input_session=session, user_context=user_context, ) @@ -81,6 +87,7 @@ async def mark_factor_as_complete_in_session( ) -> None: if user_context is None: user_context = {} + from ..recipe import MultiFactorAuthRecipe recipe = MultiFactorAuthRecipe.get_instance_or_throw_error() await recipe.recipe_implementation.mark_factor_as_complete_in_session( @@ -100,6 +107,7 @@ async def get_factors_setup_for_user( user = await get_user(user_id, user_context) if user is None: raise Exception("Unknown user id") + from ..recipe import MultiFactorAuthRecipe recipe = MultiFactorAuthRecipe.get_instance_or_throw_error() return await recipe.recipe_implementation.get_factors_setup_for_user( @@ -114,6 +122,7 @@ async def get_required_secondary_factors_for_user( ) -> List[str]: if user_context is None: user_context = {} + from ..recipe import MultiFactorAuthRecipe recipe = MultiFactorAuthRecipe.get_instance_or_throw_error() return await recipe.recipe_implementation.get_required_secondary_factors_for_user( @@ -129,6 +138,7 @@ async def add_to_required_secondary_factors_for_user( ) -> None: if user_context is None: user_context = {} + from ..recipe import MultiFactorAuthRecipe recipe = MultiFactorAuthRecipe.get_instance_or_throw_error() await recipe.recipe_implementation.add_to_required_secondary_factors_for_user( @@ -145,6 +155,7 @@ async def remove_from_required_secondary_factors_for_user( ) -> None: if user_context is None: user_context = {} + from ..recipe import MultiFactorAuthRecipe recipe = MultiFactorAuthRecipe.get_instance_or_throw_error() await recipe.recipe_implementation.remove_from_required_secondary_factors_for_user( @@ -152,6 +163,3 @@ async def remove_from_required_secondary_factors_for_user( factor_id=factor_id, user_context=user_context, ) - - -init = MultiFactorAuthRecipe.init diff --git a/supertokens_python/recipe/multifactorauth/interfaces.py b/supertokens_python/recipe/multifactorauth/interfaces.py index 9f237ca9..f960e9ff 100644 --- a/supertokens_python/recipe/multifactorauth/interfaces.py +++ b/supertokens_python/recipe/multifactorauth/interfaces.py @@ -123,15 +123,15 @@ async def resync_session_and_fetch_mfa_info_put( class NextFactors: def __init__( - self, next: List[str], already_setup: List[str], allowed_to_setup: List[str] + self, next_: List[str], already_setup: List[str], allowed_to_setup: List[str] ): - self.next = next + self.next_ = next_ self.already_setup = already_setup self.allowed_to_setup = allowed_to_setup def to_json(self) -> Dict[str, Any]: return { - "next": self.next, + "next": self.next_, "alreadySetup": self.already_setup, "allowedToSetup": self.allowed_to_setup, } diff --git a/supertokens_python/recipe/multifactorauth/multi_factor_auth_claim.py b/supertokens_python/recipe/multifactorauth/multi_factor_auth_claim.py index 4545f3c6..4b9eeef2 100644 --- a/supertokens_python/recipe/multifactorauth/multi_factor_auth_claim.py +++ b/supertokens_python/recipe/multifactorauth/multi_factor_auth_claim.py @@ -1,3 +1,17 @@ +# Copyright (c) 2023, 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 __future__ import annotations from typing import Any, Dict, Optional, Set @@ -15,7 +29,6 @@ MFAClaimValue, MFARequirementList, ) -from .utils import update_and_get_mfa_related_info_in_session class HasCompletedRequirementListSCV(SessionClaimValidator): @@ -29,14 +42,10 @@ def __init__( self.claim: MultiFactorAuthClaimClass = claim self.requirement_list = requirement_list - async def should_refetch( + def should_refetch( self, payload: Dict[str, Any], user_context: Dict[str, Any] ) -> bool: - return ( - True - if self.claim.key not in payload or not payload[self.claim.key] - else False - ) + return bool(self.claim.key not in payload or not payload[self.claim.key]) async def validate( self, payload: JSONObject, user_context: Dict[str, Any] @@ -65,7 +74,7 @@ async def validate( factor_ids = next_set_of_unsatisfied_factors.factor_ids - if next_set_of_unsatisfied_factors.type == "string": + if next_set_of_unsatisfied_factors.type_ == "string": return ClaimValidationResult( is_valid=False, reason={ @@ -74,7 +83,7 @@ async def validate( }, ) - elif next_set_of_unsatisfied_factors.type == "oneOf": + elif next_set_of_unsatisfied_factors.type_ == "oneOf": return ClaimValidationResult( is_valid=False, reason={ @@ -101,15 +110,11 @@ def __init__( super().__init__(id_) self.claim = claim - async def should_refetch( + def should_refetch( self, payload: Dict[str, Any], user_context: Dict[str, Any] ) -> bool: assert self.claim is not None - return ( - True - if self.claim.key not in payload or not payload[self.claim.key] - else False - ) + return bool(self.claim.key not in payload or not payload[self.claim.key]) async def validate( self, payload: JSONObject, user_context: Dict[str, Any] @@ -161,13 +166,16 @@ def __init__(self, key: Optional[str] = None): key = key or "st-mfa" async def fetch_value( - user_id: str, + _user_id: str, recipe_user_id: RecipeUserId, tenant_id: str, current_payload: Dict[str, Any], user_context: Dict[str, Any], ) -> MFAClaimValue: + from .utils import update_and_get_mfa_related_info_in_session + mfa_info = await update_and_get_mfa_related_info_in_session( + self, input_session_recipe_user_id=recipe_user_id, input_tenant_id=tenant_id, input_access_token_payload=current_payload, @@ -209,9 +217,11 @@ def get_next_set_of_unsatisfied_factors( ) if len(next_factors) > 0: - return FactorIdsAndType(factor_ids=list(next_factors), type=factor_type) + return FactorIdsAndType( + factor_ids=list(next_factors), type_=factor_type + ) - return FactorIdsAndType(factor_ids=[], type="string") + return FactorIdsAndType(factor_ids=[], type_="string") def add_to_payload_( self, diff --git a/supertokens_python/recipe/multifactorauth/recipe.py b/supertokens_python/recipe/multifactorauth/recipe.py index f45db731..f60c30c3 100644 --- a/supertokens_python/recipe/multifactorauth/recipe.py +++ b/supertokens_python/recipe/multifactorauth/recipe.py @@ -47,9 +47,6 @@ GetEmailsForFactorOkResult, GetPhoneNumbersForFactorsOkResult, ) -from .utils import validate_and_normalise_user_input -from .recipe_implementation import RecipeImplementation -from .api.implementation import APIImplementation from .interfaces import APIOptions @@ -79,10 +76,13 @@ def __init__( ] = [] self.is_get_mfa_requirements_for_auth_overridden: bool = False + from .utils import validate_and_normalise_user_input + self.config = validate_and_normalise_user_input( first_factors, override, ) + from .recipe_implementation import RecipeImplementation recipe_implementation = RecipeImplementation( Querier.get_instance(recipe_id), self @@ -92,6 +92,7 @@ def __init__( if self.config.override.functions is None else self.config.override.functions(recipe_implementation) ) + from .api.implementation import APIImplementation api_implementation = APIImplementation() self.api_implementation = ( diff --git a/supertokens_python/recipe/multifactorauth/recipe_implementation.py b/supertokens_python/recipe/multifactorauth/recipe_implementation.py index e2bc6425..9ae8f4a9 100644 --- a/supertokens_python/recipe/multifactorauth/recipe_implementation.py +++ b/supertokens_python/recipe/multifactorauth/recipe_implementation.py @@ -58,10 +58,10 @@ def __init__( self.factor_id = factor_id self.mfa_requirement_for_auth = mfa_requirement_for_auth - async def should_refetch( + def should_refetch( self, payload: Dict[str, Any], user_context: Dict[str, Any] ) -> bool: - return True if self.claim.get_value_from_payload(payload) is None else False + return self.claim.get_value_from_payload(payload) is None async def validate( self, payload: JSONObject, user_context: Dict[str, Any] @@ -174,6 +174,7 @@ async def mark_factor_as_complete_in_session( self, session: SessionContainer, factor_id: str, user_context: Dict[str, Any] ): await update_and_get_mfa_related_info_in_session( + MultiFactorAuthClaim, input_session=session, input_updated_factor_id=factor_id, user_context=user_context, diff --git a/supertokens_python/recipe/multifactorauth/syncio/__init__.py b/supertokens_python/recipe/multifactorauth/syncio/__init__.py index c12a6ef4..6bd9bf9f 100644 --- a/supertokens_python/recipe/multifactorauth/syncio/__init__.py +++ b/supertokens_python/recipe/multifactorauth/syncio/__init__.py @@ -22,7 +22,6 @@ from ..interfaces import ( MFARequirementList, ) -from ..recipe import MultiFactorAuthRecipe def assert_allowed_to_setup_factor_else_throw_invalid_claim_error( @@ -125,6 +124,3 @@ def remove_from_required_secondary_factors_for_user( ) return sync(async_func(user_id, factor_id, user_context)) - - -init = MultiFactorAuthRecipe.init diff --git a/supertokens_python/recipe/multifactorauth/types.py b/supertokens_python/recipe/multifactorauth/types.py index d4347729..779c8ad7 100644 --- a/supertokens_python/recipe/multifactorauth/types.py +++ b/supertokens_python/recipe/multifactorauth/types.py @@ -85,10 +85,10 @@ class FactorIdsAndType: def __init__( self, factor_ids: List[str], - type: Union[Literal["string"], Literal["oneOf"], Literal["allOfInAnyOrder"]], + type_: Union[Literal["string"], Literal["oneOf"], Literal["allOfInAnyOrder"]], ): self.factor_ids = factor_ids - self.type = type + self.type_ = type_ class GetFactorsSetupForUserFromOtherRecipesFunc: diff --git a/supertokens_python/recipe/multifactorauth/utils.py b/supertokens_python/recipe/multifactorauth/utils.py index cd9dcbad..d28a4817 100644 --- a/supertokens_python/recipe/multifactorauth/utils.py +++ b/supertokens_python/recipe/multifactorauth/utils.py @@ -12,19 +12,17 @@ # License for the specific language governing permissions and limitations # under the License. from __future__ import annotations +import importlib -from typing import TYPE_CHECKING, List, Optional, Union -from typing import Dict, Any, Union, List -from supertokens_python.recipe.multitenancy.recipe import MultitenancyRecipe +from typing import TYPE_CHECKING, List, Optional, Union, Dict, Any from supertokens_python.recipe.session import SessionContainer from supertokens_python.recipe.session.asyncio import get_session_information from supertokens_python.recipe.session.exceptions import UnauthorisedError -from supertokens_python.recipe.multitenancy.asyncio import get_tenant from supertokens_python.recipe.accountlinking.recipe import AccountLinkingRecipe -from supertokens_python.recipe.multifactorauth.types import FactorIds from supertokens_python.recipe.multifactorauth.types import ( MFAClaimValue, MFARequirementList, + FactorIds, ) from supertokens_python.types import RecipeUserId import math @@ -34,6 +32,12 @@ if TYPE_CHECKING: from .types import OverrideConfig, MultiFactorAuthConfig + from supertokens_python.recipe.multifactorauth.multi_factor_auth_claim import ( + MultiFactorAuthClaimClass, + ) + from supertokens_python.recipe.multitenancy.recipe import ( + MultitenancyRecipe as MTRecipeType, + ) def validate_and_normalise_user_input( @@ -43,10 +47,12 @@ def validate_and_normalise_user_input( if first_factors is not None and len(first_factors) == 0: raise ValueError("'first_factors' can be either None or a non-empty list") + from .types import OverrideConfig as OC, MultiFactorAuthConfig as MFAC + if override is None: - override = OverrideConfig() + override = OC() - return MultiFactorAuthConfig( + return MFAC( first_factors=first_factors, override=override, ) @@ -67,6 +73,7 @@ def __init__( async def update_and_get_mfa_related_info_in_session( + MultiFactorAuthClaim: MultiFactorAuthClaimClass, user_context: Dict[str, Any], input_session_recipe_user_id: Optional[RecipeUserId] = None, input_tenant_id: Optional[str] = None, @@ -74,9 +81,6 @@ async def update_and_get_mfa_related_info_in_session( input_session: Optional[SessionContainer] = None, input_updated_factor_id: Optional[str] = None, ) -> UpdateAndGetMFARelatedInfoInSessionResult: - from supertokens_python.recipe.multifactorauth.multi_factor_auth_claim import ( - MultiFactorAuthClaim, - ) from supertokens_python.recipe.multifactorauth.recipe import ( MultiFactorAuthRecipe as Recipe, ) @@ -199,7 +203,16 @@ async def user_getter(): async def get_required_secondary_factors_for_tenant( tenant_id: str, user_context: Dict[str, Any] ) -> List[str]: - tenant_info = await get_tenant(tenant_id, user_context) + + MultitenancyRecipe = importlib.import_module( + "supertokens_python.recipe.multitenancy.recipe" + ) + + mt_recipe: MTRecipeType = MultitenancyRecipe.get_instance() + + tenant_info = await mt_recipe.recipe_implementation.get_tenant( + tenant_id=tenant_id, user_context=user_context + ) if tenant_info is None: raise UnauthorisedError("Tenant not found") return ( @@ -262,14 +275,20 @@ async def get_required_secondary_factors_for_tenant_helper() -> List[str]: async def is_valid_first_factor( tenant_id: str, factor_id: str, user_context: Dict[str, Any] ) -> Literal["OK", "INVALID_FIRST_FACTOR_ERROR", "TENANT_NOT_FOUND_ERROR"]: - tenant_info = await get_tenant(tenant_id=tenant_id, user_context=user_context) + + MultitenancyRecipe = importlib.import_module( + "supertokens_python.recipe.multitenancy.recipe" + ) + + mt_recipe: MTRecipeType = MultitenancyRecipe.get_instance() + tenant_info = await mt_recipe.recipe_implementation.get_tenant( + tenant_id=tenant_id, user_context=user_context + ) if tenant_info is None: return "TENANT_NOT_FOUND_ERROR" tenant_config = tenant_info - mt_recipe = MultitenancyRecipe.get_instance() - first_factors_from_mfa = mt_recipe.static_first_factors log_debug_message( diff --git a/supertokens_python/recipe/multitenancy/asyncio/__init__.py b/supertokens_python/recipe/multitenancy/asyncio/__init__.py index 4f802040..c9b1c6ce 100644 --- a/supertokens_python/recipe/multitenancy/asyncio/__init__.py +++ b/supertokens_python/recipe/multitenancy/asyncio/__init__.py @@ -17,6 +17,7 @@ from supertokens_python.types import RecipeUserId from ..interfaces import ( + AssociateUserToTenantNotAllowedError, TenantConfig, CreateOrUpdateTenantOkResult, DeleteTenantOkResult, @@ -31,7 +32,6 @@ DisassociateUserFromTenantOkResult, TenantConfigCreateOrUpdate, ) -from ..recipe import MultitenancyRecipe if TYPE_CHECKING: from ..interfaces import ProviderConfig @@ -44,6 +44,8 @@ async def create_or_update_tenant( ) -> CreateOrUpdateTenantOkResult: if user_context is None: user_context = {} + from ..recipe import MultitenancyRecipe + recipe = MultitenancyRecipe.get_instance() return await recipe.recipe_implementation.create_or_update_tenant( @@ -56,6 +58,8 @@ async def delete_tenant( ) -> DeleteTenantOkResult: if user_context is None: user_context = {} + from ..recipe import MultitenancyRecipe + recipe = MultitenancyRecipe.get_instance() return await recipe.recipe_implementation.delete_tenant(tenant_id, user_context) @@ -66,6 +70,8 @@ async def get_tenant( ) -> Optional[TenantConfig]: if user_context is None: user_context = {} + from ..recipe import MultitenancyRecipe + recipe = MultitenancyRecipe.get_instance() return await recipe.recipe_implementation.get_tenant(tenant_id, user_context) @@ -77,6 +83,8 @@ async def list_all_tenants( if user_context is None: user_context = {} + from ..recipe import MultitenancyRecipe + recipe = MultitenancyRecipe.get_instance() return await recipe.recipe_implementation.list_all_tenants(user_context) @@ -91,6 +99,8 @@ async def create_or_update_third_party_config( if user_context is None: user_context = {} + from ..recipe import MultitenancyRecipe + recipe = MultitenancyRecipe.get_instance() return await recipe.recipe_implementation.create_or_update_third_party_config( @@ -106,6 +116,8 @@ async def delete_third_party_config( if user_context is None: user_context = {} + from ..recipe import MultitenancyRecipe + recipe = MultitenancyRecipe.get_instance() return await recipe.recipe_implementation.delete_third_party_config( @@ -123,10 +135,13 @@ async def associate_user_to_tenant( AssociateUserToTenantEmailAlreadyExistsError, AssociateUserToTenantPhoneNumberAlreadyExistsError, AssociateUserToTenantThirdPartyUserAlreadyExistsError, + AssociateUserToTenantNotAllowedError, ]: if user_context is None: user_context = {} + from ..recipe import MultitenancyRecipe + recipe = MultitenancyRecipe.get_instance() return await recipe.recipe_implementation.associate_user_to_tenant( @@ -134,7 +149,7 @@ async def associate_user_to_tenant( ) -async def dissociate_user_from_tenant( +async def disassociate_user_from_tenant( tenant_id: str, recipe_user_id: RecipeUserId, user_context: Optional[Dict[str, Any]] = None, @@ -142,8 +157,10 @@ async def dissociate_user_from_tenant( if user_context is None: user_context = {} + from ..recipe import MultitenancyRecipe + recipe = MultitenancyRecipe.get_instance() - return await recipe.recipe_implementation.dissociate_user_from_tenant( + return await recipe.recipe_implementation.disassociate_user_from_tenant( tenant_id, recipe_user_id, user_context ) diff --git a/supertokens_python/recipe/multitenancy/interfaces.py b/supertokens_python/recipe/multitenancy/interfaces.py index 4fc5ef55..52e66b3f 100644 --- a/supertokens_python/recipe/multitenancy/interfaces.py +++ b/supertokens_python/recipe/multitenancy/interfaces.py @@ -136,6 +136,14 @@ class AssociateUserToTenantThirdPartyUserAlreadyExistsError: status = "THIRD_PARTY_USER_ALREADY_EXISTS_ERROR" +class AssociateUserToTenantNotAllowedError: + status = "ASSOCIATION_NOT_ALLOWED_ERROR" + + def __init__(self, reason: str): + self.status = "ASSOCIATION_NOT_ALLOWED_ERROR" + self.reason = reason + + class DisassociateUserFromTenantOkResult: status = "OK" @@ -213,11 +221,12 @@ async def associate_user_to_tenant( AssociateUserToTenantEmailAlreadyExistsError, AssociateUserToTenantPhoneNumberAlreadyExistsError, AssociateUserToTenantThirdPartyUserAlreadyExistsError, + AssociateUserToTenantNotAllowedError, ]: pass @abstractmethod - async def dissociate_user_from_tenant( + async def disassociate_user_from_tenant( self, tenant_id: str, recipe_user_id: RecipeUserId, diff --git a/supertokens_python/recipe/multitenancy/recipe_implementation.py b/supertokens_python/recipe/multitenancy/recipe_implementation.py index 432ffc07..c60f148f 100644 --- a/supertokens_python/recipe/multitenancy/recipe_implementation.py +++ b/supertokens_python/recipe/multitenancy/recipe_implementation.py @@ -25,6 +25,7 @@ from supertokens_python.types import RecipeUserId from .interfaces import ( + AssociateUserToTenantNotAllowedError, RecipeInterface, TenantConfig, CreateOrUpdateTenantOkResult, @@ -254,6 +255,7 @@ async def associate_user_to_tenant( AssociateUserToTenantEmailAlreadyExistsError, AssociateUserToTenantPhoneNumberAlreadyExistsError, AssociateUserToTenantThirdPartyUserAlreadyExistsError, + AssociateUserToTenantNotAllowedError, ]: response = await self.querier.send_post_request( NormalisedURLPath( @@ -283,10 +285,12 @@ async def associate_user_to_tenant( == AssociateUserToTenantThirdPartyUserAlreadyExistsError.status ): return AssociateUserToTenantThirdPartyUserAlreadyExistsError() + if response["status"] == AssociateUserToTenantNotAllowedError.status: + return AssociateUserToTenantNotAllowedError(response["reason"]) raise Exception("Should never come here") - async def dissociate_user_from_tenant( + async def disassociate_user_from_tenant( self, tenant_id: Optional[str], recipe_user_id: RecipeUserId, diff --git a/supertokens_python/recipe/multitenancy/syncio/__init__.py b/supertokens_python/recipe/multitenancy/syncio/__init__.py index 7384ee6b..5448f261 100644 --- a/supertokens_python/recipe/multitenancy/syncio/__init__.py +++ b/supertokens_python/recipe/multitenancy/syncio/__init__.py @@ -108,7 +108,7 @@ def associate_user_to_tenant( return sync(associate_user_to_tenant(tenant_id, recipe_user_id, user_context)) -def dissociate_user_from_tenant( +def disassociate_user_from_tenant( tenant_id: str, recipe_user_id: RecipeUserId, user_context: Optional[Dict[str, Any]] = None, @@ -117,7 +117,7 @@ def dissociate_user_from_tenant( user_context = {} from supertokens_python.recipe.multitenancy.asyncio import ( - dissociate_user_from_tenant, + disassociate_user_from_tenant, ) - return sync(dissociate_user_from_tenant(tenant_id, recipe_user_id, user_context)) + return sync(disassociate_user_from_tenant(tenant_id, recipe_user_id, user_context)) diff --git a/supertokens_python/recipe/session/exceptions.py b/supertokens_python/recipe/session/exceptions.py index 637cf486..d9eedddc 100644 --- a/supertokens_python/recipe/session/exceptions.py +++ b/supertokens_python/recipe/session/exceptions.py @@ -87,12 +87,15 @@ def __init__(self, msg: str, payload: List[ClaimValidationError]): class ClaimValidationError: - def __init__(self, id_: str, reason: Optional[Dict[str, Any]]): - self.id = id_ - self.reason = reason + id_: str + reason: Optional[Union[str, Dict[str, Any]]] + + def __init__(self, id_: str, reason: Optional[Union[str, Dict[str, Any]]]): + self.id_: str = id_ + self.reason: Optional[Union[str, Dict[str, Any]]] = reason def to_json(self): - result: Dict[str, Any] = {"id": self.id} + result: Dict[str, Any] = {"id": self.id_} if self.reason is not None: result["reason"] = self.reason diff --git a/supertokens_python/recipe/thirdparty/asyncio/__init__.py b/supertokens_python/recipe/thirdparty/asyncio/__init__.py index e4735ac7..b374e041 100644 --- a/supertokens_python/recipe/thirdparty/asyncio/__init__.py +++ b/supertokens_python/recipe/thirdparty/asyncio/__init__.py @@ -30,7 +30,7 @@ async def manually_create_or_update_user( third_party_user_id: str, email: str, is_verified: bool, - session: Optional[SessionContainer], + session: Optional[SessionContainer] = None, user_context: Union[None, Dict[str, Any]] = None, ) -> Union[ ManuallyCreateOrUpdateUserOkResult, diff --git a/supertokens_python/recipe/thirdparty/syncio/__init__.py b/supertokens_python/recipe/thirdparty/syncio/__init__.py index 429b4b02..4481c813 100644 --- a/supertokens_python/recipe/thirdparty/syncio/__init__.py +++ b/supertokens_python/recipe/thirdparty/syncio/__init__.py @@ -29,7 +29,7 @@ def manually_create_or_update_user( third_party_user_id: str, email: str, is_verified: bool, - session: Optional[SessionContainer], + session: Optional[SessionContainer] = None, user_context: Union[None, Dict[str, Any]] = None, ) -> Union[ ManuallyCreateOrUpdateUserOkResult, diff --git a/supertokens_python/recipe/totp/interfaces.py b/supertokens_python/recipe/totp/interfaces.py index 3cb4d484..64c45f78 100644 --- a/supertokens_python/recipe/totp/interfaces.py +++ b/supertokens_python/recipe/totp/interfaces.py @@ -13,11 +13,26 @@ # under the License. from __future__ import annotations -from typing import Dict, Any, Union, TYPE_CHECKING +from typing import Dict, Any, Union, TYPE_CHECKING, Optional from abc import ABC, abstractmethod if TYPE_CHECKING: - from .types import * + from .types import ( + UserIdentifierInfoOkResult, + UnknownUserIdError, + UserIdentifierInfoDoesNotExistError, + CreateDeviceOkResult, + DeviceAlreadyExistsError, + UpdateDeviceOkResult, + RemoveDeviceOkResult, + VerifyDeviceOkResult, + VerifyTOTPOkResult, + InvalidTOTPError, + LimitReachedError, + UnknownDeviceError, + ListDevicesOkResult, + TOTPNormalisedConfig, + ) from supertokens_python.recipe.session import SessionContainer from supertokens_python import AppInfo from supertokens_python.framework import BaseRequest, BaseResponse diff --git a/supertokens_python/recipe/totp/syncio/__init__.py b/supertokens_python/recipe/totp/syncio/__init__.py index 2eaecf37..24eb4d2e 100644 --- a/supertokens_python/recipe/totp/syncio/__init__.py +++ b/supertokens_python/recipe/totp/syncio/__init__.py @@ -18,7 +18,6 @@ from supertokens_python.async_to_sync_wrapper import sync -from ..recipe import TOTPRecipe from supertokens_python.recipe.totp.types import ( CreateDeviceOkResult, DeviceAlreadyExistsError, @@ -124,6 +123,3 @@ def verify_totp( from supertokens_python.recipe.totp.asyncio import verify_totp as async_func return sync(async_func(tenant_id, user_id, totp, user_context)) - - -init = TOTPRecipe.init diff --git a/supertokens_python/recipe/totp/types.py b/supertokens_python/recipe/totp/types.py index fae156fe..f9599bf6 100644 --- a/supertokens_python/recipe/totp/types.py +++ b/supertokens_python/recipe/totp/types.py @@ -75,9 +75,6 @@ def to_json(self) -> Dict[str, Any]: class UpdateDeviceOkResult(OkResult): - def __init__(self): - super().__init__() - def to_json(self) -> Dict[str, Any]: raise NotImplementedError() @@ -174,11 +171,6 @@ def to_json(self) -> Dict[str, Any]: class VerifyTOTPOkResult(OkResult): - def __init__( - self, - ): - super().__init__() - def to_json(self) -> Dict[str, Any]: return {"status": self.status} From 0cfec6f26a73c71e712f10d4c6584bb5eabc6ac4 Mon Sep 17 00:00:00 2001 From: rishabhpoddar Date: Mon, 30 Sep 2024 16:13:13 +0530 Subject: [PATCH 2/2] fixes all cyclic imports --- supertokens_python/auth_utils.py | 3 ++- .../recipe/multifactorauth/api/implementation.py | 9 +++++---- .../recipe/multifactorauth/multi_factor_auth_claim.py | 7 +++++-- supertokens_python/recipe/multifactorauth/recipe.py | 10 +++++++--- .../recipe/multifactorauth/recipe_implementation.py | 8 ++++++-- .../recipe/multitenancy/api/implementation.py | 9 +++++++-- supertokens_python/supertokens.py | 7 +++++-- 7 files changed, 37 insertions(+), 16 deletions(-) diff --git a/supertokens_python/auth_utils.py b/supertokens_python/auth_utils.py index ff3315ba..08d77cb4 100644 --- a/supertokens_python/auth_utils.py +++ b/supertokens_python/auth_utils.py @@ -912,12 +912,13 @@ async def filter_out_invalid_second_factors_or_throw_if_all_are_invalid( factors_set_up_for_user_prom: Optional[List[str]] = None mfa_info_prom = None - async def get_factors_set_up_for_user(): + async def get_factors_set_up_for_user() -> List[str]: nonlocal factors_set_up_for_user_prom if factors_set_up_for_user_prom is None: factors_set_up_for_user_prom = await mfa_instance.recipe_implementation.get_factors_setup_for_user( user=session_user, user_context=user_context ) + assert factors_set_up_for_user_prom is not None return factors_set_up_for_user_prom async def get_mfa_requirements_for_auth(): diff --git a/supertokens_python/recipe/multifactorauth/api/implementation.py b/supertokens_python/recipe/multifactorauth/api/implementation.py index d6fe15e9..dea5fe91 100644 --- a/supertokens_python/recipe/multifactorauth/api/implementation.py +++ b/supertokens_python/recipe/multifactorauth/api/implementation.py @@ -17,9 +17,6 @@ from typing import Any, Dict, List, Union, TYPE_CHECKING from supertokens_python.recipe.session import SessionContainer -from supertokens_python.recipe.multifactorauth.utils import ( - update_and_get_mfa_related_info_in_session, -) from supertokens_python.recipe.multitenancy.asyncio import get_tenant from supertokens_python.asyncio import get_user from supertokens_python.recipe.session.exceptions import ( @@ -54,6 +51,10 @@ async def resync_session_and_fetch_mfa_info_put( MultiFactorAuthClaim: MultiFactorAuthClaimType = mfa.MultiFactorAuthClaim + module = importlib.import_module( + "supertokens_python.recipe.multifactorauth.utils" + ) + session_user = await get_user(session.get_user_id(), user_context) if session_user is None: @@ -61,7 +62,7 @@ async def resync_session_and_fetch_mfa_info_put( "Session user not found", ) - mfa_info = await update_and_get_mfa_related_info_in_session( + mfa_info = await module.update_and_get_mfa_related_info_in_session( MultiFactorAuthClaim, input_session=session, user_context=user_context, diff --git a/supertokens_python/recipe/multifactorauth/multi_factor_auth_claim.py b/supertokens_python/recipe/multifactorauth/multi_factor_auth_claim.py index 4b9eeef2..2780bd5f 100644 --- a/supertokens_python/recipe/multifactorauth/multi_factor_auth_claim.py +++ b/supertokens_python/recipe/multifactorauth/multi_factor_auth_claim.py @@ -13,6 +13,7 @@ # under the License. from __future__ import annotations +import importlib from typing import Any, Dict, Optional, Set @@ -172,9 +173,11 @@ async def fetch_value( current_payload: Dict[str, Any], user_context: Dict[str, Any], ) -> MFAClaimValue: - from .utils import update_and_get_mfa_related_info_in_session + module = importlib.import_module( + "supertokens_python.recipe.multifactorauth.utils" + ) - mfa_info = await update_and_get_mfa_related_info_in_session( + mfa_info = await module.update_and_get_mfa_related_info_in_session( self, input_session_recipe_user_id=recipe_user_id, input_tenant_id=tenant_id, diff --git a/supertokens_python/recipe/multifactorauth/recipe.py b/supertokens_python/recipe/multifactorauth/recipe.py index f60c30c3..13982676 100644 --- a/supertokens_python/recipe/multifactorauth/recipe.py +++ b/supertokens_python/recipe/multifactorauth/recipe.py @@ -12,6 +12,7 @@ # License for the specific language governing permissions and limitations # under the License. from __future__ import annotations +import importlib from os import environ from typing import Any, Dict, Optional, List, Union @@ -31,7 +32,6 @@ MultiFactorAuthClaim, ) from supertokens_python.recipe.multitenancy.interfaces import TenantConfig -from supertokens_python.recipe.multitenancy.recipe import MultitenancyRecipe from supertokens_python.recipe.session.recipe import SessionRecipe from supertokens_python.recipe_module import APIHandled, RecipeModule from supertokens_python.supertokens import AppInfo @@ -76,9 +76,11 @@ def __init__( ] = [] self.is_get_mfa_requirements_for_auth_overridden: bool = False - from .utils import validate_and_normalise_user_input + module = importlib.import_module( + "supertokens_python.recipe.multifactorauth.utils" + ) - self.config = validate_and_normalise_user_input( + self.config = module.validate_and_normalise_user_input( first_factors, override, ) @@ -102,6 +104,8 @@ def __init__( ) def callback(): + from supertokens_python.recipe.multitenancy.recipe import MultitenancyRecipe + mt_recipe = MultitenancyRecipe.get_instance() mt_recipe.static_first_factors = self.config.first_factors diff --git a/supertokens_python/recipe/multifactorauth/recipe_implementation.py b/supertokens_python/recipe/multifactorauth/recipe_implementation.py index 9ae8f4a9..f2cd77d4 100644 --- a/supertokens_python/recipe/multifactorauth/recipe_implementation.py +++ b/supertokens_python/recipe/multifactorauth/recipe_implementation.py @@ -12,6 +12,7 @@ # License for the specific language governing permissions and limitations # under the License. from __future__ import annotations +import importlib from typing import TYPE_CHECKING, Any, Awaitable, Dict, Set, Callable, List @@ -35,7 +36,6 @@ from supertokens_python.recipe.session import SessionContainer from supertokens_python.types import User -from .utils import update_and_get_mfa_related_info_in_session from .interfaces import RecipeInterface if TYPE_CHECKING: @@ -173,7 +173,11 @@ async def assert_allowed_to_setup_factor_else_throw_invalid_claim_error( async def mark_factor_as_complete_in_session( self, session: SessionContainer, factor_id: str, user_context: Dict[str, Any] ): - await update_and_get_mfa_related_info_in_session( + module = importlib.import_module( + "supertokens_python.recipe.multifactorauth.utils" + ) + + await module.update_and_get_mfa_related_info_in_session( MultiFactorAuthClaim, input_session=session, input_updated_factor_id=factor_id, diff --git a/supertokens_python/recipe/multitenancy/api/implementation.py b/supertokens_python/recipe/multitenancy/api/implementation.py index 27a58624..565773e7 100644 --- a/supertokens_python/recipe/multitenancy/api/implementation.py +++ b/supertokens_python/recipe/multitenancy/api/implementation.py @@ -12,6 +12,7 @@ # License for the specific language governing permissions and limitations # under the License. +import importlib from typing import Any, Dict, Optional, Union, List from ..constants import DEFAULT_TENANT_ID @@ -35,7 +36,9 @@ async def login_methods_get( api_options: APIOptions, user_context: Dict[str, Any], ) -> Union[LoginMethodsGetOkResult, GeneralErrorResponse]: - from ...multifactorauth.utils import is_valid_first_factor + module = importlib.import_module( + "supertokens_python.recipe.multifactorauth.utils" + ) from supertokens_python.recipe.thirdparty.providers.config_utils import ( merge_providers_from_core_and_static, find_and_create_provider_instance, @@ -91,7 +94,9 @@ async def login_methods_get( valid_first_factors: List[str] = [] for factor_id in first_factors: - valid_res = await is_valid_first_factor(tenant_id, factor_id, user_context) + valid_res = await module.is_valid_first_factor( + tenant_id, factor_id, user_context + ) if valid_res == "OK": valid_first_factors.append(factor_id) if valid_res == "TENANT_NOT_FOUND_ERROR": diff --git a/supertokens_python/supertokens.py b/supertokens_python/supertokens.py index 6aae8aa9..51ec74b8 100644 --- a/supertokens_python/supertokens.py +++ b/supertokens_python/supertokens.py @@ -13,6 +13,7 @@ # under the License. from __future__ import annotations +import importlib from os import environ from typing import TYPE_CHECKING, Any, Callable, Dict, List, Optional, Set, Union, Tuple @@ -276,9 +277,11 @@ def make_recipe(recipe: Callable[[AppInfo], RecipeModule]) -> RecipeModule: self.recipe_modules: List[RecipeModule] = list(map(make_recipe, recipe_list)) if not multitenancy_found: - from supertokens_python.recipe.multitenancy.recipe import MultitenancyRecipe + module = importlib.import_module( + "supertokens_python.recipe.multitenancy.recipe" + ) - self.recipe_modules.append(MultitenancyRecipe.init()(self.app_info)) + self.recipe_modules.append(module.init()(self.app_info)) if totp_found and not multi_factor_auth_found: raise Exception("Please initialize the MultiFactorAuth recipe to use TOTP.") if not user_metadata_found: