Module supertokens_python.recipe.webauthn
Expand source code
# Copyright (c) 2025, 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 supertokens_python.recipe.webauthn.functions import (
consume_recover_account_token,
create_recover_account_link,
generate_recover_account_token,
get_credential,
get_generated_options,
get_user_from_recover_account_token,
list_credentials,
recover_account,
register_credential,
register_options,
remove_credential,
remove_generated_options,
send_email,
send_recover_account_email,
sign_in,
sign_in_options,
sign_up,
verify_credentials,
)
from supertokens_python.recipe.webauthn.interfaces.api import APIInterface, APIOptions
from supertokens_python.recipe.webauthn.interfaces.recipe import RecipeInterface
from supertokens_python.recipe.webauthn.recipe import WebauthnRecipe
from supertokens_python.recipe.webauthn.types.config import (
NormalisedWebauthnConfig,
OverrideConfig,
WebauthnConfig,
)
# Some Pydantic models need a rebuild to resolve ForwardRefs
# Referencing imports here to prevent lint errors.
# Caveat: These will be available for import from this module directly.
APIInterface # type: ignore
RecipeInterface # type: ignore
NormalisedWebauthnConfig # type: ignore
# APIOptions - ApiInterface -> WebauthnConfig/NormalisedWebauthnConfig -> RecipeInterface
APIOptions.model_rebuild()
def init(config: Optional[WebauthnConfig] = None):
return WebauthnRecipe.init(config=config)
__all__ = [
"init",
"APIInterface",
"RecipeInterface",
"OverrideConfig",
"WebauthnConfig",
"WebauthnRecipe",
"consume_recover_account_token",
"create_recover_account_link",
"generate_recover_account_token",
"get_credential",
"get_generated_options",
"get_user_from_recover_account_token",
"list_credentials",
"recover_account",
"register_credential",
"register_options",
"remove_credential",
"remove_generated_options",
"send_email",
"send_recover_account_email",
"sign_in",
"sign_in_options",
"sign_up",
"verify_credentials",
]
Sub-modules
supertokens_python.recipe.webauthn.api
supertokens_python.recipe.webauthn.constants
supertokens_python.recipe.webauthn.emaildelivery
supertokens_python.recipe.webauthn.exceptions
supertokens_python.recipe.webauthn.functions
supertokens_python.recipe.webauthn.interfaces
supertokens_python.recipe.webauthn.recipe
supertokens_python.recipe.webauthn.recipe_implementation
supertokens_python.recipe.webauthn.types
supertokens_python.recipe.webauthn.utils
Functions
async def consume_recover_account_token(*, token: str, tenant_id: str = 'public', user_context: Optional[Dict[str, Any]] = None)
async def create_recover_account_link(*, tenant_id: str = 'public', user_id: str, email: str, user_context: Optional[Dict[str, Any]] = None) ‑> Union[CreateRecoverAccountLinkResponse, UnknownUserIdErrorResponse]
async def generate_recover_account_token(*, user_id: str, email: str, tenant_id: str = 'public', user_context: Optional[Dict[str, Any]] = None)
async def get_credential(*, webauthn_credential_id: str, recipe_user_id: str, user_context: Optional[Dict[str, Any]] = None)
async def get_generated_options(*, webauthn_generated_options_id: str, tenant_id: str = 'public', user_context: Optional[Dict[str, Any]] = None)
async def get_user_from_recover_account_token(*, token: str, tenant_id: str = 'public', user_context: Optional[Dict[str, Any]] = None)
def init(config: Optional[WebauthnConfig] = None)
async def list_credentials(*, recipe_user_id: str, user_context: Optional[Dict[str, Any]] = None)
async def recover_account(*, tenant_id: str = 'public', webauthn_generated_options_id: str, token: str, credential: RegistrationPayload, user_context: Optional[Dict[str, Any]] = None) ‑> Union[OkResponseBaseModel, RecoverAccountTokenInvalidErrorResponse, InvalidCredentialsErrorResponse, OptionsNotFoundErrorResponse, InvalidOptionsErrorResponse, InvalidAuthenticatorErrorResponse]
async def register_credential(*, webauthn_generated_options_id: str, credential: RegistrationPayload, recipe_user_id: str, user_context: Optional[Dict[str, Any]] = None)
async def register_options(*, relying_party_id: str, relying_party_name: str, origin: str, resident_key: Optional[Literal['required', 'preferred', 'discouraged']] = None, user_verification: Optional[Literal['required', 'preferred', 'discouraged']] = None, user_presence: Optional[bool] = None, attestation: Optional[Literal['none', 'indirect', 'direct', 'enterprise']] = None, supported_algorithm_ids: Optional[List[int]] = None, timeout: Optional[int] = None, tenant_id: str = 'public', user_context: Optional[Dict[str, Any]] = None, **kwargs: Unpack[RegisterOptionsKwargsInput])
async def remove_credential(*, webauthn_credential_id: str, recipe_user_id: str, user_context: Optional[Dict[str, Any]] = None)
async def remove_generated_options(*, webauthn_generated_options_id: str, tenant_id: str = 'public', user_context: Optional[Dict[str, Any]] = None)
async def send_email(*, template_vars: TypeWebauthnRecoverAccountEmailDeliveryInput, user_context: Optional[Dict[str, Any]] = None)
async def send_recover_account_email(*, tenant_id: str = 'public', user_id: str, email: str, user_context: Optional[Dict[str, Any]] = None) ‑> Union[OkResponseBaseModel, UnknownUserIdErrorResponse]
async def sign_in(*, credential: AuthenticationPayload, webauthn_generated_options_id: str, tenant_id: str = 'public', session: Optional[SessionContainer] = None, user_context: Optional[Dict[str, Any]] = None)
async def sign_in_options(*, relying_party_id: str, relying_party_name: str, origin: str, timeout: Optional[int] = None, user_verification: Optional[Literal['required', 'preferred', 'discouraged']] = None, user_presence: Optional[bool] = None, tenant_id: str = 'public', user_context: Optional[Dict[str, Any]] = None)
async def sign_up(*, webauthn_generated_options_id: str, credential: RegistrationPayload, tenant_id: str = 'public', session: Optional[SessionContainer] = None, user_context: Optional[Dict[str, Any]] = None)
async def verify_credentials(*, credential: AuthenticationPayload, webauthn_generated_options_id: str, tenant_id: str = 'public', user_context: Optional[Dict[str, Any]] = None)
Classes
class APIInterface
-
Helper class that provides a standard way to create an ABC using inheritance.
Expand source code
class APIInterface(ABC): disable_register_options_post: bool = False disable_sign_in_options_post: bool = False disable_sign_up_post: bool = False disable_sign_in_post: bool = False disable_generate_recover_account_token_post: bool = False disable_recover_account_post: bool = False disable_register_credential_post: bool = False disable_email_exists_get: bool = False @abstractmethod async def register_options_post( self, *, tenant_id: str, options: APIOptions, user_context: UserContext, **kwargs: Unpack[RegisterOptionsPOSTKwargsInput], ) -> Union[ RegisterOptionsPOSTResponse, GeneralErrorResponse, RegisterOptionsPOSTErrorResponse, ]: ... @abstractmethod async def sign_in_options_post( self, *, tenant_id: str, options: APIOptions, user_context: UserContext, ) -> Union[ SignInOptionsPOSTResponse, GeneralErrorResponse, SignInOptionsPOSTErrorResponse ]: ... @abstractmethod async def sign_up_post( self, *, webauthn_generated_options_id: str, credential: RegistrationPayload, tenant_id: str, session: Optional[SessionContainer], should_try_linking_with_session_user: Optional[bool], options: APIOptions, user_context: UserContext, ) -> Union[SignUpPOSTResponse, GeneralErrorResponse, SignUpPOSTErrorResponse]: ... @abstractmethod async def sign_in_post( self, *, webauthn_generated_options_id: str, credential: AuthenticationPayload, tenant_id: str, session: Optional[SessionContainer], should_try_linking_with_session_user: Optional[bool], options: APIOptions, user_context: UserContext, ) -> Union[SignInPOSTResponse, GeneralErrorResponse, SignInPOSTErrorResponse]: ... @abstractmethod async def generate_recover_account_token_post( self, *, email: str, tenant_id: str, options: APIOptions, user_context: UserContext, ) -> Union[ OkResponseBaseModel, GeneralErrorResponse, GenerateRecoverAccountTokenPOSTErrorResponse, ]: ... @abstractmethod async def recover_account_post( self, *, token: str, webauthn_generated_options_id: str, credential: RegistrationPayload, tenant_id: str, options: APIOptions, user_context: UserContext, ) -> Union[ RecoverAccountPOSTResponse, GeneralErrorResponse, RecoverAccountPOSTErrorResponse, ]: ... @abstractmethod async def register_credential_post( self, *, webauthn_generated_options_id: str, credential: RegistrationPayload, tenant_id: str, session: SessionContainer, options: APIOptions, user_context: UserContext, ) -> Union[ OkResponseBaseModel, GeneralErrorResponse, RegisterCredentialPOSTErrorResponse ]: ... @abstractmethod async def email_exists_get( self, *, email: str, tenant_id: str, options: APIOptions, user_context: UserContext, ) -> Union[EmailExistsGetResponse, GeneralErrorResponse]: ...
Ancestors
- abc.ABC
Subclasses
Class variables
var disable_email_exists_get : bool
var disable_generate_recover_account_token_post : bool
var disable_recover_account_post : bool
var disable_register_credential_post : bool
var disable_register_options_post : bool
var disable_sign_in_options_post : bool
var disable_sign_in_post : bool
var disable_sign_up_post : bool
Methods
async def email_exists_get(self, *, email: str, tenant_id: str, options: APIOptions, user_context: Dict[str, Any]) ‑> Union[EmailExistsGetResponse, GeneralErrorResponse]
async def generate_recover_account_token_post(self, *, email: str, tenant_id: str, options: APIOptions, user_context: Dict[str, Any]) ‑> Union[OkResponseBaseModel, GeneralErrorResponse, RecoverAccountNotAllowedErrorResponse]
async def recover_account_post(self, *, token: str, webauthn_generated_options_id: str, credential: RegistrationPayload, tenant_id: str, options: APIOptions, user_context: Dict[str, Any]) ‑> Union[RecoverAccountPOSTResponse, GeneralErrorResponse, RecoverAccountTokenInvalidErrorResponse, InvalidCredentialsErrorResponse, OptionsNotFoundErrorResponse, InvalidOptionsErrorResponse, InvalidAuthenticatorErrorResponse]
async def register_credential_post(self, *, webauthn_generated_options_id: str, credential: RegistrationPayload, tenant_id: str, session: SessionContainer, options: APIOptions, user_context: Dict[str, Any]) ‑> Union[OkResponseBaseModel, GeneralErrorResponse, InvalidCredentialsErrorResponse, OptionsNotFoundErrorResponse, InvalidOptionsErrorResponse, RegisterCredentialNotAllowedErrorResponse, InvalidAuthenticatorErrorResponse]
async def register_options_post(self, *, tenant_id: str, options: APIOptions, user_context: Dict[str, Any], **kwargs: Unpack[RegisterOptionsPOSTKwargsInput]) ‑> Union[RegisterOptionsPOSTResponse, GeneralErrorResponse, RecoverAccountTokenInvalidErrorResponse, InvalidOptionsErrorResponse, InvalidEmailErrorResponse]
async def sign_in_options_post(self, *, tenant_id: str, options: APIOptions, user_context: Dict[str, Any]) ‑> Union[SignInOptionsPOSTResponse, GeneralErrorResponse, InvalidOptionsErrorResponse]
async def sign_in_post(self, *, webauthn_generated_options_id: str, credential: AuthenticationPayload, tenant_id: str, session: Optional[SessionContainer], should_try_linking_with_session_user: Optional[bool], options: APIOptions, user_context: Dict[str, Any]) ‑> Union[SignInPOSTResponse, GeneralErrorResponse, InvalidCredentialsErrorResponse, SignInNotAllowedErrorResponse]
async def sign_up_post(self, *, webauthn_generated_options_id: str, credential: RegistrationPayload, tenant_id: str, session: Optional[SessionContainer], should_try_linking_with_session_user: Optional[bool], options: APIOptions, user_context: Dict[str, Any]) ‑> Union[SignUpPOSTResponse, GeneralErrorResponse, SignUpNotAllowedErrorResponse, InvalidAuthenticatorErrorResponse, EmailAlreadyExistsErrorResponse, InvalidCredentialsErrorResponse, OptionsNotFoundErrorResponse, InvalidOptionsErrorResponse]
class OverrideConfig (functions: Optional[InterfaceOverride[RecipeInterface]] = None, apis: Optional[InterfaceOverride[APIInterface]] = None)
-
Expand source code
@dataclass class OverrideConfig: """ `WebauthnConfig.override` """ functions: Optional[InterfaceOverride[RecipeInterface]] = None apis: Optional[InterfaceOverride[APIInterface]] = None
Class variables
var apis : Optional[InterfaceOverride[APIInterface]]
var functions : Optional[InterfaceOverride[RecipeInterface]]
class RecipeInterface
-
Helper class that provides a standard way to create an ABC using inheritance.
Expand source code
class RecipeInterface(ABC): @abstractmethod async def register_options( self, *, relying_party_id: str, relying_party_name: str, origin: str, resident_key: Optional[ResidentKey] = None, user_verification: Optional[UserVerification] = None, user_presence: Optional[bool] = None, attestation: Optional[Attestation] = None, supported_algorithm_ids: Optional[List[int]] = None, timeout: Optional[int] = None, tenant_id: str, user_context: UserContext, **kwargs: Unpack[RegisterOptionsKwargsInput], ) -> Union[RegisterOptionsResponse, RegisterOptionsErrorResponse]: ... @abstractmethod async def sign_in_options( self, *, relying_party_id: str, relying_party_name: str, origin: str, user_verification: Optional[UserVerification] = None, user_presence: Optional[bool] = None, timeout: Optional[int] = None, tenant_id: str, user_context: UserContext, ) -> Union[SignInOptionsResponse, SignInOptionsErrorResponse]: ... @abstractmethod async def sign_up( self, *, webauthn_generated_options_id: str, credential: RegistrationPayload, session: Optional[SessionContainer] = None, should_try_linking_with_session_user: Optional[bool] = None, tenant_id: str, user_context: UserContext, ) -> Union[SignUpReponse, SignUpErrorResponse]: ... @abstractmethod async def sign_in( self, *, webauthn_generated_options_id: str, credential: AuthenticationPayload, session: Optional[SessionContainer] = None, should_try_linking_with_session_user: Optional[bool] = None, tenant_id: str, user_context: UserContext, ) -> Union[SignInResponse, SignInErrorResponse]: ... @abstractmethod async def verify_credentials( self, *, webauthn_generated_options_id: str, credential: AuthenticationPayload, tenant_id: str, user_context: UserContext, ) -> Union[VerifyCredentialsResponse, VerifyCredentialsErrorResponse]: ... @abstractmethod async def create_new_recipe_user( self, *, webauthn_generated_options_id: str, credential: RegistrationPayload, tenant_id: str, user_context: UserContext, ) -> Union[CreateNewRecipeUserResponse, CreateNewRecipeUserErrorResponse]: """ This function is meant only for creating the recipe in the core and nothing else. We added this even though signUp exists cause devs may override signup expecting it to be called just during sign up. But we also need a version of signing up which can be called during operations like creating a user during account recovery flow. """ ... @abstractmethod async def generate_recover_account_token( self, *, user_id: str, email: str, tenant_id: str, user_context: UserContext, ) -> Union[ GenerateRecoverAccountTokenResponse, GenerateRecoverAccountTokenErrorResponse, ]: """ We pass in the email as well to this function cause the input userId may not be associated with an webauthn account. In this case, we need to know which email to use to create an webauthn account later on. """ @abstractmethod async def consume_recover_account_token( self, *, token: str, tenant_id: str, user_context: UserContext, ) -> Union[ ConsumeRecoverAccountTokenResponse, ConsumeRecoverAccountTokenErrorResponse ]: ... @abstractmethod async def register_credential( self, *, webauthn_generated_options_id: str, credential: RegistrationPayload, user_context: UserContext, recipe_user_id: str, ) -> Union[OkResponseBaseModel, RegisterCredentialErrorResponse]: ... @abstractmethod async def get_user_from_recover_account_token( self, *, token: str, tenant_id: str, user_context: UserContext, ) -> Union[ GetUserFromRecoverAccountTokenResponse, GetUserFromRecoverAccountTokenErrorResponse, ]: ... @abstractmethod async def remove_credential( self, *, webauthn_credential_id: str, recipe_user_id: str, user_context: UserContext, ) -> Union[OkResponseBaseModel, RemoveCredentialErrorResponse]: ... @abstractmethod async def get_credential( self, *, webauthn_credential_id: str, recipe_user_id: str, user_context: UserContext, ) -> Union[GetCredentialResponse, GetCredentialErrorResponse]: ... @abstractmethod async def list_credentials( self, *, recipe_user_id: str, user_context: UserContext, ) -> ListCredentialsResponse: ... @abstractmethod async def remove_generated_options( self, *, webauthn_generated_options_id: str, tenant_id: str, user_context: UserContext, ) -> Union[OkResponseBaseModel, RemoveGeneratedOptionsErrorResponse]: ... @abstractmethod async def get_generated_options( self, *, webauthn_generated_options_id: str, tenant_id: str, user_context: UserContext, ) -> Union[GetGeneratedOptionsResponse, GetGeneratedOptionsErrorResponse]: ... @abstractmethod async def update_user_email( self, *, recipe_user_id: str, email: str, tenant_id: str, user_context: UserContext, ) -> Union[OkResponseBaseModel, UpdateUserEmailErrorResponse]: ...
Ancestors
- abc.ABC
Subclasses
Methods
async def consume_recover_account_token(self, *, token: str, tenant_id: str, user_context: Dict[str, Any]) ‑> Union[ConsumeRecoverAccountTokenResponse, RecoverAccountTokenInvalidErrorResponse]
async def create_new_recipe_user(self, *, webauthn_generated_options_id: str, credential: RegistrationPayload, tenant_id: str, user_context: Dict[str, Any]) ‑> Union[CreateNewRecipeUserResponse, EmailAlreadyExistsErrorResponse, OptionsNotFoundErrorResponse, InvalidOptionsErrorResponse, InvalidCredentialsErrorResponse, InvalidAuthenticatorErrorResponse]
-
This function is meant only for creating the recipe in the core and nothing else. We added this even though signUp exists cause devs may override signup expecting it to be called just during sign up. But we also need a version of signing up which can be called during operations like creating a user during account recovery flow.
async def generate_recover_account_token(self, *, user_id: str, email: str, tenant_id: str, user_context: Dict[str, Any]) ‑> Union[GenerateRecoverAccountTokenResponse, UnknownUserIdErrorResponse]
-
We pass in the email as well to this function cause the input userId may not be associated with an webauthn account. In this case, we need to know which email to use to create an webauthn account later on.
async def get_credential(self, *, webauthn_credential_id: str, recipe_user_id: str, user_context: Dict[str, Any]) ‑> Union[GetCredentialResponse, CredentialNotFoundErrorResponse]
async def get_generated_options(self, *, webauthn_generated_options_id: str, tenant_id: str, user_context: Dict[str, Any]) ‑> Union[GetGeneratedOptionsResponse, OptionsNotFoundErrorResponse]
async def get_user_from_recover_account_token(self, *, token: str, tenant_id: str, user_context: Dict[str, Any]) ‑> Union[GetUserFromRecoverAccountTokenResponse, RecoverAccountTokenInvalidErrorResponse]
async def list_credentials(self, *, recipe_user_id: str, user_context: Dict[str, Any]) ‑> ListCredentialsResponse
async def register_credential(self, *, webauthn_generated_options_id: str, credential: RegistrationPayload, user_context: Dict[str, Any], recipe_user_id: str) ‑> Union[OkResponseBaseModel, InvalidCredentialsErrorResponse, OptionsNotFoundErrorResponse, InvalidOptionsErrorResponse, InvalidAuthenticatorErrorResponse]
async def register_options(self, *, relying_party_id: str, relying_party_name: str, origin: str, resident_key: Optional[Literal['required', 'preferred', 'discouraged']] = None, user_verification: Optional[Literal['required', 'preferred', 'discouraged']] = None, user_presence: Optional[bool] = None, attestation: Optional[Literal['none', 'indirect', 'direct', 'enterprise']] = None, supported_algorithm_ids: Optional[List[int]] = None, timeout: Optional[int] = None, tenant_id: str, user_context: Dict[str, Any], **kwargs: Unpack[RegisterOptionsKwargsInput]) ‑> Union[RegisterOptionsResponse, RecoverAccountTokenInvalidErrorResponse, InvalidOptionsErrorResponse, InvalidEmailErrorResponse]
async def remove_credential(self, *, webauthn_credential_id: str, recipe_user_id: str, user_context: Dict[str, Any]) ‑> Union[OkResponseBaseModel, CredentialNotFoundErrorResponse]
async def remove_generated_options(self, *, webauthn_generated_options_id: str, tenant_id: str, user_context: Dict[str, Any]) ‑> Union[OkResponseBaseModel, OptionsNotFoundErrorResponse]
async def sign_in(self, *, webauthn_generated_options_id: str, credential: AuthenticationPayload, session: Optional[SessionContainer] = None, should_try_linking_with_session_user: Optional[bool] = None, tenant_id: str, user_context: Dict[str, Any]) ‑> Union[SignInResponse, InvalidCredentialsErrorResponse, InvalidOptionsErrorResponse, InvalidAuthenticatorErrorResponse, CredentialNotFoundErrorResponse, UnknownUserIdErrorResponse, OptionsNotFoundErrorResponse, LinkingToSessionUserFailedError]
async def sign_in_options(self, *, relying_party_id: str, relying_party_name: str, origin: str, user_verification: Optional[Literal['required', 'preferred', 'discouraged']] = None, user_presence: Optional[bool] = None, timeout: Optional[int] = None, tenant_id: str, user_context: Dict[str, Any]) ‑> Union[SignInOptionsResponse, InvalidOptionsErrorResponse]
async def sign_up(self, *, webauthn_generated_options_id: str, credential: RegistrationPayload, session: Optional[SessionContainer] = None, should_try_linking_with_session_user: Optional[bool] = None, tenant_id: str, user_context: Dict[str, Any]) ‑> Union[SignUpReponse, EmailAlreadyExistsErrorResponse, OptionsNotFoundErrorResponse, InvalidOptionsErrorResponse, InvalidCredentialsErrorResponse, InvalidAuthenticatorErrorResponse, LinkingToSessionUserFailedError]
async def update_user_email(self, *, recipe_user_id: str, email: str, tenant_id: str, user_context: Dict[str, Any]) ‑> Union[OkResponseBaseModel, EmailAlreadyExistsErrorResponse, UnknownUserIdErrorResponse]
async def verify_credentials(self, *, webauthn_generated_options_id: str, credential: AuthenticationPayload, tenant_id: str, user_context: Dict[str, Any]) ‑> Union[VerifyCredentialsResponse, InvalidCredentialsErrorResponse, InvalidOptionsErrorResponse, InvalidAuthenticatorErrorResponse, CredentialNotFoundErrorResponse, UnknownUserIdErrorResponse, OptionsNotFoundErrorResponse]
class WebauthnConfig (get_relying_party_id: Optional[Union[str, GetRelyingPartyId]] = None, get_relying_party_name: Optional[Union[str, GetRelyingPartyName]] = None, get_origin: Optional[GetOrigin] = None, email_delivery: Optional[EmailDeliveryConfig[TypeWebauthnEmailDeliveryInput]] = None, validate_email_address: Optional[ValidateEmailAddress] = None, override: Optional[OverrideConfig] = None)
-
WebauthnConfig(get_relying_party_id: 'Optional[Union[str, GetRelyingPartyId]]' = None, get_relying_party_name: 'Optional[Union[str, GetRelyingPartyName]]' = None, get_origin: 'Optional[GetOrigin]' = None, email_delivery: 'Optional[EmailDeliveryConfig[TypeWebauthnEmailDeliveryInput]]' = None, validate_email_address: 'Optional[ValidateEmailAddress]' = None, override: 'Optional[OverrideConfig]' = None)
Expand source code
@dataclass class WebauthnConfig: get_relying_party_id: Optional[Union[str, GetRelyingPartyId]] = None get_relying_party_name: Optional[Union[str, GetRelyingPartyName]] = None get_origin: Optional[GetOrigin] = None email_delivery: Optional[EmailDeliveryConfig[TypeWebauthnEmailDeliveryInput]] = None validate_email_address: Optional[ValidateEmailAddress] = None override: Optional[OverrideConfig] = None
Class variables
var email_delivery : Optional[EmailDeliveryConfig[TypeWebauthnEmailDeliveryInput]]
var get_origin : Optional[GetOrigin]
var get_relying_party_id : Optional[Union[str, GetRelyingPartyId]]
var get_relying_party_name : Optional[Union[str, GetRelyingPartyName]]
var override : Optional[OverrideConfig]
var validate_email_address : Optional[ValidateEmailAddress]
class WebauthnRecipe (recipe_id: str, app_info: AppInfo, config: Optional[WebauthnConfig], ingredients: WebauthnIngredients)
-
Helper class that provides a standard way to create an ABC using inheritance.
Expand source code
class WebauthnRecipe(RecipeModule): __instance: Optional["WebauthnRecipe"] = None recipe_id = "webauthn" config: NormalisedWebauthnConfig recipe_implementation: RecipeInterface api_implementation: "APIInterface" email_delivery: EmailDeliveryIngredient["TypeWebauthnEmailDeliveryInput"] def __init__( self, recipe_id: str, app_info: AppInfo, config: Optional[WebauthnConfig], ingredients: WebauthnIngredients, ): super().__init__(recipe_id=recipe_id, app_info=app_info) self.config = validate_and_normalise_user_input( app_info=app_info, config=config ) querier = Querier.get_instance(rid_to_core=recipe_id) recipe_implementation = RecipeImplementation( querier=querier, config=self.config, ) self.recipe_implementation = ( recipe_implementation if self.config.override.functions is None else self.config.override.functions(recipe_implementation) ) api_implementation = APIImplementation() self.api_implementation = ( api_implementation if self.config.override.apis is None else self.config.override.apis(api_implementation) ) if ingredients.email_delivery is None: self.email_delivery = EmailDeliveryIngredient( config=self.config.get_email_delivery_config() ) else: self.email_delivery = ingredients.email_delivery def callback(): mfa_instance = MultiFactorAuthRecipe.get_instance() if mfa_instance is not None: async def get_available_secondary_factor_ids( _: TenantConfig, ) -> List[str]: return ["emailpassword"] mfa_instance.add_func_to_get_all_available_secondary_factor_ids_from_other_recipes( GetAllAvailableSecondaryFactorIdsFromOtherRecipesFunc( get_available_secondary_factor_ids ) ) async def user_setup(user: User, _: Dict[str, Any]) -> List[str]: for login_method in user.login_methods: # We don't check for tenantId here because if we find the user # with emailpassword loginMethod from different tenant, then # we assume the factor is setup for this user. And as part of factor # completion, we associate that loginMethod with the session's tenantId if login_method.recipe_id == self.recipe_id: return ["emailpassword"] return [] mfa_instance.add_func_to_get_factors_setup_for_user_from_other_recipes( GetFactorsSetupForUserFromOtherRecipesFunc(user_setup) ) async def get_emails_for_factor( user: User, session_recipe_user_id: RecipeUserId ): session_login_method = None for login_method in user.login_methods: if ( login_method.recipe_user_id.get_as_string() == session_recipe_user_id.get_as_string() ): session_login_method = login_method break if session_login_method is None: # this can happen maybe cause this login method # was unlinked from the user or deleted entirely return GetEmailsForFactorUnknownSessionRecipeUserIdResult() # We order the login methods based on `time_joined` (oldest first) ordered_login_methods = sorted( user.login_methods, key=lambda lm: lm.time_joined, reverse=True ) # We take the ones that belong to this recipe recipe_ordered_login_methods = list( filter( lambda lm: lm.recipe_id == self.recipe_id, ordered_login_methods, ) ) result: List[str] = [] if len(recipe_ordered_login_methods) == 0: # If there are login methods belonging to this recipe, the factor is set up # In this case we only list email addresses that have a password associated with them # First we take the verified real emails associated with emailpassword login methods ordered by timeJoined (oldest first) result.extend( [ cast(str, lm.email) for lm in recipe_ordered_login_methods if not is_fake_email(cast(str, lm.email)) and lm.verified ] ) # Then we take the non-verified real emails associated with emailpassword login methods ordered by timeJoined (oldest first) result.extend( [ cast(str, lm.email) for lm in recipe_ordered_login_methods if not is_fake_email(cast(str, lm.email)) and not lm.verified ] ) # Lastly, fake emails associated with emailpassword login methods ordered by timeJoined (oldest first) # We also add these into the list because they already have a password added to them so they can be a valid choice when signing in # We do not want to remove the previously added "MFA password", because a new email password user was linked # E.g.: # 1. A discord user adds a password for MFA (which will use the fake email associated with the discord user) # 2. Later they also sign up and (manually) link a full emailpassword user that they intend to use as a first factor # 3. The next time they sign in using Discord, they could be asked for a secondary password. # In this case, they'd be checked against the first user that they originally created for MFA, not the one later linked to the account result.extend( [ cast(str, lm.email) for lm in recipe_ordered_login_methods if is_fake_email(cast(str, lm.email)) ] ) # We handle moving the session email to the top of the list later else: # This factor hasn't been set up, we list all emails belonging to the user if any( [ (lm.email is not None and not is_fake_email(lm.email)) for lm in ordered_login_methods ] ): # If there is at least one real email address linked to the user, we only suggest real addresses result = [ lm.email for lm in recipe_ordered_login_methods if lm.email is not None and not is_fake_email(lm.email) ] else: # Else we use the fake ones result = [ lm.email for lm in recipe_ordered_login_methods if lm.email is not None and is_fake_email(lm.email) ] # We handle moving the session email to the top of the list later # Since in this case emails are not guaranteed to be unique, we de-duplicate the results, keeping the oldest one in the list. # Using a dict keeps the original insertion order, but de-duplicates the items, Python sets are not ordered. # keeping the first one added (so keeping the older one if there are two entries with the same email) # e.g.: [4,2,3,2,1] -> [4,2,3,1] result = list(dict.fromkeys(result)) # If the loginmethod associated with the session has an email address, we move it to the top of the list (if it's already in the list) if ( session_login_method.email is not None and session_login_method.email in result ): result = [session_login_method.email] + [ email for email in result if email != session_login_method.email ] # If the list is empty we generate an email address to make the flow where the user is never asked for # an email address easier to implement. In many cases when the user adds an email-password factor, they # actually only want to add a password and do not care about the associated email address. # Custom implementations can choose to ignore this, and ask the user for the email anyway. if len(result) == 0: result.append( f"{session_recipe_user_id.get_as_string()}@stfakeemail.supertokens.com" ) return GetEmailsForFactorOkResult( factor_id_to_emails_map={"emailpassword": result} ) mfa_instance.add_func_to_get_emails_for_factor_from_other_recipes( GetEmailsForFactorFromOtherRecipesFunc(get_emails_for_factor) ) mt_recipe = MultitenancyRecipe.get_instance_optional() if mt_recipe is not None: mt_recipe.all_available_first_factors.append(FactorIds.WEBAUTHN) PostSTInitCallbacks.add_post_init_callback(callback) @staticmethod def get_instance() -> "WebauthnRecipe": if WebauthnRecipe.__instance is not None: return WebauthnRecipe.__instance raise_general_exception( "Initialisation not done. Did you forget to call the SuperTokens.init function?" ) @staticmethod def get_instance_optional() -> Optional["WebauthnRecipe"]: return WebauthnRecipe.__instance @staticmethod def init(config: Optional[WebauthnConfig]): def func(app_info: AppInfo): if WebauthnRecipe.__instance is None: WebauthnRecipe.__instance = WebauthnRecipe( recipe_id=WebauthnRecipe.recipe_id, app_info=app_info, config=config, ingredients=WebauthnIngredients(email_delivery=None), ) return WebauthnRecipe.__instance else: raise_general_exception( "Webauthn recipe has already been initialised. Please check your code for bugs." ) return func @staticmethod def reset(): if os.environ.get("SUPERTOKENS_ENV") != "testing": raise_general_exception("calling testing function in non testing env") WebauthnRecipe.__instance = None def get_apis_handled(self) -> List[APIHandled]: return [ APIHandled( method="post", path_without_api_base_path=NormalisedURLPath(REGISTER_OPTIONS_API), request_id=REGISTER_OPTIONS_API, disabled=self.api_implementation.disable_register_options_post, ), APIHandled( method="post", path_without_api_base_path=NormalisedURLPath(SIGNIN_OPTIONS_API), request_id=SIGNIN_OPTIONS_API, disabled=self.api_implementation.disable_sign_in_options_post, ), APIHandled( method="post", path_without_api_base_path=NormalisedURLPath(SIGN_UP_API), request_id=SIGN_UP_API, disabled=self.api_implementation.disable_sign_up_post, ), APIHandled( method="post", path_without_api_base_path=NormalisedURLPath(SIGN_IN_API), request_id=SIGN_IN_API, disabled=self.api_implementation.disable_sign_in_post, ), APIHandled( method="post", path_without_api_base_path=NormalisedURLPath( GENERATE_RECOVER_ACCOUNT_TOKEN_API ), request_id=GENERATE_RECOVER_ACCOUNT_TOKEN_API, disabled=self.api_implementation.disable_generate_recover_account_token_post, ), APIHandled( method="post", path_without_api_base_path=NormalisedURLPath(RECOVER_ACCOUNT_API), request_id=RECOVER_ACCOUNT_API, disabled=self.api_implementation.disable_recover_account_post, ), APIHandled( method="get", path_without_api_base_path=NormalisedURLPath(SIGNUP_EMAIL_EXISTS_API), request_id=SIGNUP_EMAIL_EXISTS_API, disabled=self.api_implementation.disable_email_exists_get, ), APIHandled( method="post", path_without_api_base_path=NormalisedURLPath(REGISTER_CREDENTIAL_API), request_id=REGISTER_CREDENTIAL_API, disabled=self.api_implementation.disable_register_credential_post, ), ] async def handle_api_request( self, request_id: str, tenant_id: str, request: BaseRequest, path: NormalisedURLPath, method: str, response: BaseResponse, user_context: UserContext, ) -> Optional[BaseResponse]: from supertokens_python.recipe.webauthn.interfaces.api import APIOptions # APIOptions.model_rebuild() options = APIOptions( config=self.config, recipe_id=self.get_recipe_id(), recipe_implementation=self.recipe_implementation, req=request, res=response, email_delivery=self.email_delivery, app_info=self.get_app_info(), ) if request_id == REGISTER_OPTIONS_API: return await register_options_api( self.api_implementation, tenant_id, options, user_context ) if request_id == SIGNIN_OPTIONS_API: return await sign_in_options_api( self.api_implementation, tenant_id, options, user_context ) if request_id == SIGN_UP_API: return await sign_up_api( self.api_implementation, tenant_id, options, user_context ) if request_id == SIGN_IN_API: return await sign_in_api( self.api_implementation, tenant_id, options, user_context ) if request_id == GENERATE_RECOVER_ACCOUNT_TOKEN_API: return await generate_recover_account_token_api( self.api_implementation, tenant_id, options, user_context ) if request_id == RECOVER_ACCOUNT_API: return await recover_account_api( self.api_implementation, tenant_id, options, user_context ) if request_id == SIGNUP_EMAIL_EXISTS_API: return await email_exists_api( self.api_implementation, tenant_id, options, user_context ) if request_id == REGISTER_CREDENTIAL_API: return await register_credential_api( self.api_implementation, tenant_id, options, user_context ) return None def is_error_from_this_recipe_based_on_instance(self, err: Exception): return isinstance(err, WebauthnError) async def handle_error( self, request: BaseRequest, err: Exception, response: BaseResponse, user_context: UserContext, ): raise err def get_all_cors_headers(self) -> List[str]: return []
Ancestors
- RecipeModule
- abc.ABC
Class variables
var api_implementation : APIInterface
var config : NormalisedWebauthnConfig
var email_delivery : EmailDeliveryIngredient[TypeWebauthnEmailDeliveryInput]
var recipe_id
var recipe_implementation : RecipeInterface
Static methods
def get_instance() ‑> WebauthnRecipe
def get_instance_optional() ‑> Optional[WebauthnRecipe]
def init(config: Optional[WebauthnConfig])
def reset()
Methods
def get_all_cors_headers(self) ‑> List[str]
def get_apis_handled(self) ‑> List[APIHandled]
async def handle_api_request(self, request_id: str, tenant_id: str, request: BaseRequest, path: NormalisedURLPath, method: str, response: BaseResponse, user_context: Dict[str, Any]) ‑> Optional[BaseResponse]
async def handle_error(self, request: BaseRequest, err: Exception, response: BaseResponse, user_context: Dict[str, Any])
def is_error_from_this_recipe_based_on_instance(self, err: Exception)