Module supertokens_python.recipe.emailpassword.utils
Expand source code
# Copyright (c) 2021, VRAI Labs and/or its affiliates. All rights reserved.
#
# This software is licensed under the Apache License, Version 2.0 (the
# "License") as published by the Apache Software Foundation.
#
# You may not use this file except in compliance with the License. You may
# obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
from __future__ import annotations
from re import fullmatch
from typing import TYPE_CHECKING, Any, Callable, Dict, List, Optional, Union
from supertokens_python.framework import BaseRequest
from supertokens_python.ingredients.emaildelivery.types import (
EmailDeliveryConfig,
EmailDeliveryConfigWithService,
)
from supertokens_python.recipe.emailpassword.emaildelivery.services.backward_compatibility import (
BackwardCompatibilityService,
)
from .interfaces import APIInterface, RecipeInterface
from .types import EmailTemplateVars, InputFormField, NormalisedFormField
if TYPE_CHECKING:
from supertokens_python.supertokens import AppInfo
from supertokens_python.utils import get_filtered_list
from .constants import FORM_FIELD_EMAIL_ID, FORM_FIELD_PASSWORD_ID
async def default_validator(_: str, __: str) -> Union[str, None]:
return None
async def default_password_validator(value: str, _tenant_id: str) -> Union[str, None]:
# length >= 8 && < 100
# must have a number and a character
# as per
# https://github.com/supertokens/supertokens-auth-react/issues/5#issuecomment-709512438
if len(value) < 8:
return "Password must contain at least 8 characters, including a number"
if len(value) >= 100:
return "Password's length must be lesser than 100 characters"
if fullmatch(r"^.*[A-Za-z]+.*$", value) is None:
return "Password must contain at least one alphabet"
if fullmatch(r"^.*[0-9]+.*$", value) is None:
return "Password must contain at least one number"
return None
async def default_email_validator(value: Any, _tenant_id: str) -> Union[str, None]:
# We check if the email syntax is correct
# As per https://github.com/supertokens/supertokens-auth-react/issues/5#issuecomment-709512438
# Regex from https://stackoverflow.com/a/46181/3867175
if (not isinstance(value, str)) or (
fullmatch(
r'^(([^<>()\[\]\\.,;:\s@"]+(\.[^<>()\[\]\\.,;:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,'
r"3}\.[0-9]{1,3}\])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$",
value,
)
is None
):
return "Email is not valid"
return None
class InputSignUpFeature:
def __init__(self, form_fields: Union[List[InputFormField], None] = None):
if form_fields is None:
form_fields = []
self.form_fields = normalise_sign_up_form_fields(form_fields)
class SignUpFeature:
def __init__(self, form_fields: List[NormalisedFormField]):
self.form_fields = form_fields
def normalise_sign_up_form_fields(
form_fields: List[InputFormField],
) -> List[NormalisedFormField]:
normalised_form_fields: List[NormalisedFormField] = []
for field in form_fields:
if field.id == FORM_FIELD_PASSWORD_ID:
validator = (
field.validate
if field.validate is not None
else default_password_validator
)
normalised_form_fields.append(
NormalisedFormField(field.id, validator, False)
)
elif field.id == FORM_FIELD_EMAIL_ID:
validator = (
field.validate
if field.validate is not None
else default_email_validator
)
normalised_form_fields.append(
NormalisedFormField(field.id, validator, False)
)
else:
validator = (
field.validate if field.validate is not None else default_validator
)
optional = field.optional if field.optional is not None else False
normalised_form_fields.append(
NormalisedFormField(field.id, validator, optional)
)
if (
len(
get_filtered_list(
lambda x: x.id == FORM_FIELD_PASSWORD_ID, normalised_form_fields
)
)
== 0
):
normalised_form_fields.append(
NormalisedFormField(
FORM_FIELD_PASSWORD_ID, default_password_validator, False
)
)
if (
len(
get_filtered_list(
lambda x: x.id == FORM_FIELD_EMAIL_ID, normalised_form_fields
)
)
== 0
):
normalised_form_fields.append(
NormalisedFormField(FORM_FIELD_EMAIL_ID, default_email_validator, False)
)
return normalised_form_fields
class SignInFeature:
def __init__(self, form_fields: List[NormalisedFormField]):
self.form_fields = form_fields
def normalise_sign_in_form_fields(
form_fields: List[NormalisedFormField],
) -> List[NormalisedFormField]:
return list(
map(
lambda y: NormalisedFormField(
y.id,
y.validate if y.id == FORM_FIELD_EMAIL_ID else default_validator,
False,
),
get_filtered_list(
lambda x: x.id in (FORM_FIELD_PASSWORD_ID, FORM_FIELD_EMAIL_ID),
form_fields,
),
)
)
def validate_and_normalise_sign_in_config(
sign_up_config: SignUpFeature,
) -> SignInFeature:
form_fields = normalise_sign_in_form_fields(sign_up_config.form_fields)
return SignInFeature(form_fields)
class ResetPasswordUsingTokenFeature:
def __init__(
self,
form_fields_for_password_reset_form: List[NormalisedFormField],
form_fields_for_generate_token_form: List[NormalisedFormField],
):
self.form_fields_for_password_reset_form = form_fields_for_password_reset_form
self.form_fields_for_generate_token_form = form_fields_for_generate_token_form
def validate_and_normalise_reset_password_using_token_config(
sign_up_config: InputSignUpFeature,
) -> ResetPasswordUsingTokenFeature:
form_fields_for_password_reset_form = list(
map(
lambda y: NormalisedFormField(y.id, y.validate, False),
get_filtered_list(
lambda x: x.id == FORM_FIELD_PASSWORD_ID, sign_up_config.form_fields
),
)
)
form_fields_for_generate_token_form = list(
map(
lambda y: NormalisedFormField(y.id, y.validate, False),
get_filtered_list(
lambda x: x.id == FORM_FIELD_EMAIL_ID, sign_up_config.form_fields
),
)
)
return ResetPasswordUsingTokenFeature(
form_fields_for_password_reset_form,
form_fields_for_generate_token_form,
)
class InputOverrideConfig:
def __init__(
self,
functions: Union[Callable[[RecipeInterface], RecipeInterface], None] = None,
apis: Union[Callable[[APIInterface], APIInterface], None] = None,
):
self.functions = functions
self.apis = apis
class OverrideConfig:
def __init__(
self,
functions: Union[Callable[[RecipeInterface], RecipeInterface], None] = None,
apis: Union[Callable[[APIInterface], APIInterface], None] = None,
):
self.functions = functions
self.apis = apis
class EmailPasswordConfig:
def __init__(
self,
sign_up_feature: SignUpFeature,
sign_in_feature: SignInFeature,
reset_password_using_token_feature: ResetPasswordUsingTokenFeature,
override: OverrideConfig,
get_email_delivery_config: Callable[
[RecipeInterface], EmailDeliveryConfigWithService[EmailTemplateVars]
],
):
self.sign_up_feature = sign_up_feature
self.sign_in_feature = sign_in_feature
self.reset_password_using_token_feature = reset_password_using_token_feature
self.override = override
self.get_email_delivery_config = get_email_delivery_config
def validate_and_normalise_user_input(
app_info: AppInfo,
sign_up_feature: Union[InputSignUpFeature, None] = None,
override: Union[InputOverrideConfig, None] = None,
email_delivery: Union[EmailDeliveryConfig[EmailTemplateVars], None] = None,
) -> EmailPasswordConfig:
# NOTE: We don't need to check the instance of sign_up_feature and override
# as they will always be either None or the specified type.
if override is None:
override = InputOverrideConfig()
if sign_up_feature is None:
sign_up_feature = InputSignUpFeature()
def get_email_delivery_config(
ep_recipe: RecipeInterface,
) -> EmailDeliveryConfigWithService[EmailTemplateVars]:
if email_delivery and email_delivery.service:
return EmailDeliveryConfigWithService(
service=email_delivery.service, override=email_delivery.override
)
email_service = BackwardCompatibilityService(
app_info=app_info,
recipe_interface_impl=ep_recipe,
)
if email_delivery is not None and email_delivery.override is not None:
override = email_delivery.override
else:
override = None
return EmailDeliveryConfigWithService(email_service, override=override)
return EmailPasswordConfig(
SignUpFeature(sign_up_feature.form_fields),
SignInFeature(normalise_sign_in_form_fields(sign_up_feature.form_fields)),
validate_and_normalise_reset_password_using_token_config(sign_up_feature),
OverrideConfig(functions=override.functions, apis=override.apis),
get_email_delivery_config=get_email_delivery_config,
)
def get_password_reset_link(
app_info: AppInfo,
token: str,
tenant_id: str,
request: Optional[BaseRequest],
user_context: Dict[str, Any],
) -> str:
return (
app_info.get_origin(request, user_context).get_as_string_dangerous()
+ app_info.website_base_path.get_as_string_dangerous()
+ "/reset-password?token="
+ token
+ "&tenantId="
+ tenant_id
)
Functions
async def default_email_validator(value: Any, _tenant_id: str) ‑> Optional[str]
-
Expand source code
async def default_email_validator(value: Any, _tenant_id: str) -> Union[str, None]: # We check if the email syntax is correct # As per https://github.com/supertokens/supertokens-auth-react/issues/5#issuecomment-709512438 # Regex from https://stackoverflow.com/a/46181/3867175 if (not isinstance(value, str)) or ( fullmatch( r'^(([^<>()\[\]\\.,;:\s@"]+(\.[^<>()\[\]\\.,;:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,' r"3}\.[0-9]{1,3}\])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$", value, ) is None ): return "Email is not valid" return None
async def default_password_validator(value: str, _tenant_id: str) ‑> Optional[str]
-
Expand source code
async def default_password_validator(value: str, _tenant_id: str) -> Union[str, None]: # length >= 8 && < 100 # must have a number and a character # as per # https://github.com/supertokens/supertokens-auth-react/issues/5#issuecomment-709512438 if len(value) < 8: return "Password must contain at least 8 characters, including a number" if len(value) >= 100: return "Password's length must be lesser than 100 characters" if fullmatch(r"^.*[A-Za-z]+.*$", value) is None: return "Password must contain at least one alphabet" if fullmatch(r"^.*[0-9]+.*$", value) is None: return "Password must contain at least one number" return None
async def default_validator(_: str, __: str) ‑> Optional[str]
-
Expand source code
async def default_validator(_: str, __: str) -> Union[str, None]: return None
def get_password_reset_link(app_info: AppInfo, token: str, tenant_id: str, request: Optional[BaseRequest], user_context: Dict[str, Any]) ‑> str
-
Expand source code
def get_password_reset_link( app_info: AppInfo, token: str, tenant_id: str, request: Optional[BaseRequest], user_context: Dict[str, Any], ) -> str: return ( app_info.get_origin(request, user_context).get_as_string_dangerous() + app_info.website_base_path.get_as_string_dangerous() + "/reset-password?token=" + token + "&tenantId=" + tenant_id )
def normalise_sign_in_form_fields(form_fields: List[NormalisedFormField]) ‑> List[NormalisedFormField]
-
Expand source code
def normalise_sign_in_form_fields( form_fields: List[NormalisedFormField], ) -> List[NormalisedFormField]: return list( map( lambda y: NormalisedFormField( y.id, y.validate if y.id == FORM_FIELD_EMAIL_ID else default_validator, False, ), get_filtered_list( lambda x: x.id in (FORM_FIELD_PASSWORD_ID, FORM_FIELD_EMAIL_ID), form_fields, ), ) )
def normalise_sign_up_form_fields(form_fields: List[InputFormField]) ‑> List[NormalisedFormField]
-
Expand source code
def normalise_sign_up_form_fields( form_fields: List[InputFormField], ) -> List[NormalisedFormField]: normalised_form_fields: List[NormalisedFormField] = [] for field in form_fields: if field.id == FORM_FIELD_PASSWORD_ID: validator = ( field.validate if field.validate is not None else default_password_validator ) normalised_form_fields.append( NormalisedFormField(field.id, validator, False) ) elif field.id == FORM_FIELD_EMAIL_ID: validator = ( field.validate if field.validate is not None else default_email_validator ) normalised_form_fields.append( NormalisedFormField(field.id, validator, False) ) else: validator = ( field.validate if field.validate is not None else default_validator ) optional = field.optional if field.optional is not None else False normalised_form_fields.append( NormalisedFormField(field.id, validator, optional) ) if ( len( get_filtered_list( lambda x: x.id == FORM_FIELD_PASSWORD_ID, normalised_form_fields ) ) == 0 ): normalised_form_fields.append( NormalisedFormField( FORM_FIELD_PASSWORD_ID, default_password_validator, False ) ) if ( len( get_filtered_list( lambda x: x.id == FORM_FIELD_EMAIL_ID, normalised_form_fields ) ) == 0 ): normalised_form_fields.append( NormalisedFormField(FORM_FIELD_EMAIL_ID, default_email_validator, False) ) return normalised_form_fields
def validate_and_normalise_reset_password_using_token_config(sign_up_config: InputSignUpFeature) ‑> ResetPasswordUsingTokenFeature
-
Expand source code
def validate_and_normalise_reset_password_using_token_config( sign_up_config: InputSignUpFeature, ) -> ResetPasswordUsingTokenFeature: form_fields_for_password_reset_form = list( map( lambda y: NormalisedFormField(y.id, y.validate, False), get_filtered_list( lambda x: x.id == FORM_FIELD_PASSWORD_ID, sign_up_config.form_fields ), ) ) form_fields_for_generate_token_form = list( map( lambda y: NormalisedFormField(y.id, y.validate, False), get_filtered_list( lambda x: x.id == FORM_FIELD_EMAIL_ID, sign_up_config.form_fields ), ) ) return ResetPasswordUsingTokenFeature( form_fields_for_password_reset_form, form_fields_for_generate_token_form, )
def validate_and_normalise_sign_in_config(sign_up_config: SignUpFeature) ‑> SignInFeature
-
Expand source code
def validate_and_normalise_sign_in_config( sign_up_config: SignUpFeature, ) -> SignInFeature: form_fields = normalise_sign_in_form_fields(sign_up_config.form_fields) return SignInFeature(form_fields)
def validate_and_normalise_user_input(app_info: AppInfo, sign_up_feature: Union[InputSignUpFeature, None] = None, override: Union[InputOverrideConfig, None] = None, email_delivery: Union[EmailDeliveryConfig[EmailTemplateVars], None] = None) ‑> EmailPasswordConfig
-
Expand source code
def validate_and_normalise_user_input( app_info: AppInfo, sign_up_feature: Union[InputSignUpFeature, None] = None, override: Union[InputOverrideConfig, None] = None, email_delivery: Union[EmailDeliveryConfig[EmailTemplateVars], None] = None, ) -> EmailPasswordConfig: # NOTE: We don't need to check the instance of sign_up_feature and override # as they will always be either None or the specified type. if override is None: override = InputOverrideConfig() if sign_up_feature is None: sign_up_feature = InputSignUpFeature() def get_email_delivery_config( ep_recipe: RecipeInterface, ) -> EmailDeliveryConfigWithService[EmailTemplateVars]: if email_delivery and email_delivery.service: return EmailDeliveryConfigWithService( service=email_delivery.service, override=email_delivery.override ) email_service = BackwardCompatibilityService( app_info=app_info, recipe_interface_impl=ep_recipe, ) if email_delivery is not None and email_delivery.override is not None: override = email_delivery.override else: override = None return EmailDeliveryConfigWithService(email_service, override=override) return EmailPasswordConfig( SignUpFeature(sign_up_feature.form_fields), SignInFeature(normalise_sign_in_form_fields(sign_up_feature.form_fields)), validate_and_normalise_reset_password_using_token_config(sign_up_feature), OverrideConfig(functions=override.functions, apis=override.apis), get_email_delivery_config=get_email_delivery_config, )
Classes
class EmailPasswordConfig (sign_up_feature: SignUpFeature, sign_in_feature: SignInFeature, reset_password_using_token_feature: ResetPasswordUsingTokenFeature, override: OverrideConfig, get_email_delivery_config: Callable[[RecipeInterface], EmailDeliveryConfigWithService[EmailTemplateVars]])
-
Expand source code
class EmailPasswordConfig: def __init__( self, sign_up_feature: SignUpFeature, sign_in_feature: SignInFeature, reset_password_using_token_feature: ResetPasswordUsingTokenFeature, override: OverrideConfig, get_email_delivery_config: Callable[ [RecipeInterface], EmailDeliveryConfigWithService[EmailTemplateVars] ], ): self.sign_up_feature = sign_up_feature self.sign_in_feature = sign_in_feature self.reset_password_using_token_feature = reset_password_using_token_feature self.override = override self.get_email_delivery_config = get_email_delivery_config
class InputOverrideConfig (functions: Union[Callable[[RecipeInterface], RecipeInterface], None] = None, apis: Union[Callable[[APIInterface], APIInterface], None] = None)
-
Expand source code
class InputOverrideConfig: def __init__( self, functions: Union[Callable[[RecipeInterface], RecipeInterface], None] = None, apis: Union[Callable[[APIInterface], APIInterface], None] = None, ): self.functions = functions self.apis = apis
class InputSignUpFeature (form_fields: Union[List[InputFormField], None] = None)
-
Expand source code
class InputSignUpFeature: def __init__(self, form_fields: Union[List[InputFormField], None] = None): if form_fields is None: form_fields = [] self.form_fields = normalise_sign_up_form_fields(form_fields)
class OverrideConfig (functions: Union[Callable[[RecipeInterface], RecipeInterface], None] = None, apis: Union[Callable[[APIInterface], APIInterface], None] = None)
-
Expand source code
class OverrideConfig: def __init__( self, functions: Union[Callable[[RecipeInterface], RecipeInterface], None] = None, apis: Union[Callable[[APIInterface], APIInterface], None] = None, ): self.functions = functions self.apis = apis
class ResetPasswordUsingTokenFeature (form_fields_for_password_reset_form: List[NormalisedFormField], form_fields_for_generate_token_form: List[NormalisedFormField])
-
Expand source code
class ResetPasswordUsingTokenFeature: def __init__( self, form_fields_for_password_reset_form: List[NormalisedFormField], form_fields_for_generate_token_form: List[NormalisedFormField], ): self.form_fields_for_password_reset_form = form_fields_for_password_reset_form self.form_fields_for_generate_token_form = form_fields_for_generate_token_form
class SignInFeature (form_fields: List[NormalisedFormField])
-
Expand source code
class SignInFeature: def __init__(self, form_fields: List[NormalisedFormField]): self.form_fields = form_fields
class SignUpFeature (form_fields: List[NormalisedFormField])
-
Expand source code
class SignUpFeature: def __init__(self, form_fields: List[NormalisedFormField]): self.form_fields = form_fields