From a7e4b6a98ad217810835d39b5201496de1754f67 Mon Sep 17 00:00:00 2001 From: KShivendu Date: Sun, 7 Aug 2022 15:20:18 +0530 Subject: [PATCH 01/18] feat: Add and use email verification claim --- .../email_verification_implementation.py | 49 ---------- .../email_verification_claim.py | 0 tests/auth-react/django3x/mysite/utils.py | 38 +++++--- tests/auth-react/fastapi-server/app.py | 36 +++++--- tests/auth-react/flask-server/app.py | 35 ++++--- tests/emailpassword/test_emaildelivery.py | 14 ++- tests/emailpassword/test_emailverify.py | 91 ++++++++++--------- .../test_email_delivery.py | 31 +++++-- 8 files changed, 154 insertions(+), 140 deletions(-) delete mode 100644 supertokens_python/recipe/emailpassword/emaildelivery/services/smtp/service_implementation/email_verification_implementation.py create mode 100644 supertokens_python/recipe/emailverification/email_verification_claim.py diff --git a/supertokens_python/recipe/emailpassword/emaildelivery/services/smtp/service_implementation/email_verification_implementation.py b/supertokens_python/recipe/emailpassword/emaildelivery/services/smtp/service_implementation/email_verification_implementation.py deleted file mode 100644 index 254a0ed01..000000000 --- a/supertokens_python/recipe/emailpassword/emaildelivery/services/smtp/service_implementation/email_verification_implementation.py +++ /dev/null @@ -1,49 +0,0 @@ -# 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 typing import Any, Dict - -from supertokens_python.ingredients.emaildelivery.types import ( - EmailContent, - SMTPServiceInterface, -) -from supertokens_python.recipe.emailpassword.types import EmailTemplateVars -from supertokens_python.recipe.emailverification.types import ( - VerificationEmailTemplateVars, -) - - -class ServiceImplementation(SMTPServiceInterface[VerificationEmailTemplateVars]): - def __init__( - self, - email_password_service_implementation: SMTPServiceInterface[EmailTemplateVars], - ) -> None: - super().__init__(email_password_service_implementation.transporter) - self.email_password_service_implementation = ( - email_password_service_implementation - ) - - async def send_raw_email( - self, content: EmailContent, user_context: Dict[str, Any] - ) -> None: - return await self.email_password_service_implementation.send_raw_email( - content, user_context - ) - - async def get_content( - self, template_vars: VerificationEmailTemplateVars, user_context: Dict[str, Any] - ) -> EmailContent: - return await self.email_password_service_implementation.get_content( - template_vars, user_context - ) diff --git a/supertokens_python/recipe/emailverification/email_verification_claim.py b/supertokens_python/recipe/emailverification/email_verification_claim.py new file mode 100644 index 000000000..e69de29bb diff --git a/tests/auth-react/django3x/mysite/utils.py b/tests/auth-react/django3x/mysite/utils.py index a348f181d..3cb5db4e2 100644 --- a/tests/auth-react/django3x/mysite/utils.py +++ b/tests/auth-react/django3x/mysite/utils.py @@ -1,5 +1,5 @@ import os -from typing import Any, Dict, List, Union +from typing import Any, Dict, List, Union, Optional from dotenv import load_dotenv from supertokens_python import InputAppInfo, Supertokens, SupertokensConfig, init @@ -11,6 +11,7 @@ thirdparty, thirdpartyemailpassword, thirdpartypasswordless, + emailverification, ) from supertokens_python.recipe.emailpassword import EmailPasswordRecipe from supertokens_python.recipe.emailpassword.interfaces import ( @@ -24,7 +25,10 @@ InputFormField, User, ) -from supertokens_python.recipe.emailverification import EmailVerificationRecipe +from supertokens_python.recipe.emailverification import ( + EmailVerificationRecipe, + ParentRecipeEmailVerificationConfig, +) from supertokens_python.recipe.emailverification import ( InputOverrideConfig as EVInputOverrideConfig, ) @@ -47,7 +51,7 @@ APIInterface as PasswordlessAPIInterface, ) from supertokens_python.recipe.passwordless.interfaces import APIOptions as PAPIOptions -from supertokens_python.recipe.session import SessionRecipe +from supertokens_python.recipe.session import SessionRecipe, SessionContainer from supertokens_python.recipe.session.interfaces import ( APIInterface as SessionAPIInterface, ) @@ -253,17 +257,24 @@ def override_email_verification_apis( ) async def email_verify_post( - token: str, api_options: EVAPIOptions, user_context: Dict[str, Any] + token: str, + api_options: EVAPIOptions, + user_context: Dict[str, Any], + session: Optional[SessionContainer] = None, ): is_general_error = await check_for_general_error( "body", api_options.request ) if is_general_error: return GeneralErrorResponse("general error from API email verify") - return await original_email_verify_post(token, api_options, user_context) + return await original_email_verify_post( + token, api_options, user_context, session + ) async def generate_email_verify_token_post( - api_options: EVAPIOptions, user_context: Dict[str, Any] + api_options: EVAPIOptions, + user_context: Dict[str, Any], + session: SessionContainer, ): is_general_error = await check_for_general_error( "body", api_options.request @@ -273,7 +284,7 @@ async def generate_email_verify_token_post( "general error from API email verification code" ) return await original_generate_email_verify_token_post( - api_options, user_context + api_options, user_context, session ) original_implementation_email_verification.email_verify_post = email_verify_post @@ -828,18 +839,19 @@ async def authorisation_url_get( recipe_list = [ session.init(override=session.InputOverrideConfig(apis=override_session_apis)), + emailverification.init( + ParentRecipeEmailVerificationConfig( + mode="REQUIRED", + create_and_send_custom_email=create_and_send_custom_email, + override=EVInputOverrideConfig(apis=override_email_verification_apis), + ) + ), emailpassword.init( sign_up_feature=emailpassword.InputSignUpFeature(form_fields), reset_password_using_token_feature=emailpassword.InputResetPasswordUsingTokenFeature( create_and_send_custom_email=create_and_send_custom_email ), - email_verification_feature=emailpassword.InputEmailVerificationConfig( - create_and_send_custom_email=create_and_send_custom_email - ), override=emailpassword.InputOverrideConfig( - email_verification_feature=EVInputOverrideConfig( - apis=override_email_verification_apis - ), apis=override_email_password_apis, ), ), diff --git a/tests/auth-react/fastapi-server/app.py b/tests/auth-react/fastapi-server/app.py index 4573066ad..c238702f5 100644 --- a/tests/auth-react/fastapi-server/app.py +++ b/tests/auth-react/fastapi-server/app.py @@ -13,7 +13,7 @@ # under the License. import os import typing -from typing import Any, Dict, List, Union +from typing import Any, Dict, List, Union, Optional import uvicorn # type: ignore from dotenv import load_dotenv @@ -41,6 +41,7 @@ thirdparty, thirdpartyemailpassword, thirdpartypasswordless, + emailverification, ) from supertokens_python.recipe.emailpassword import EmailPasswordRecipe from supertokens_python.recipe.emailpassword.interfaces import ( @@ -64,6 +65,9 @@ from supertokens_python.recipe.emailverification.interfaces import ( APIOptions as EVAPIOptions, ) +from supertokens_python.recipe.emailverification.utils import ( + ParentRecipeEmailVerificationConfig, +) from supertokens_python.recipe.jwt import JWTRecipe from supertokens_python.recipe.passwordless import ( ContactEmailOnlyConfig, @@ -307,17 +311,24 @@ def override_email_verification_apis( ) async def email_verify_post( - token: str, api_options: EVAPIOptions, user_context: Dict[str, Any] + token: str, + api_options: EVAPIOptions, + user_context: Dict[str, Any], + session: Optional[SessionContainer] = None, ): is_general_error = await check_for_general_error( "body", api_options.request ) if is_general_error: return GeneralErrorResponse("general error from API email verify") - return await original_email_verify_post(token, api_options, user_context) + return await original_email_verify_post( + token, api_options, user_context, session + ) async def generate_email_verify_token_post( - api_options: EVAPIOptions, user_context: Dict[str, Any] + api_options: EVAPIOptions, + user_context: Dict[str, Any], + session: SessionContainer, ): is_general_error = await check_for_general_error( "body", api_options.request @@ -327,7 +338,9 @@ async def generate_email_verify_token_post( "general error from API email verification code" ) return await original_generate_email_verify_token_post( - api_options, user_context + api_options, + user_context, + session, ) original_implementation_email_verification.email_verify_post = email_verify_post @@ -882,18 +895,19 @@ async def authorisation_url_get( recipe_list = [ session.init(override=session.InputOverrideConfig(apis=override_session_apis)), + emailverification.init( + ParentRecipeEmailVerificationConfig( + mode="REQUIRED", + create_and_send_custom_email=create_and_send_custom_email, + override=EVInputOverrideConfig(apis=override_email_verification_apis), + ) + ), emailpassword.init( sign_up_feature=emailpassword.InputSignUpFeature(form_fields), reset_password_using_token_feature=emailpassword.InputResetPasswordUsingTokenFeature( create_and_send_custom_email=create_and_send_custom_email ), - email_verification_feature=emailpassword.InputEmailVerificationConfig( - create_and_send_custom_email=create_and_send_custom_email - ), override=emailpassword.InputOverrideConfig( - email_verification_feature=EVInputOverrideConfig( - apis=override_email_verification_apis - ), apis=override_email_password_apis, ), ), diff --git a/tests/auth-react/flask-server/app.py b/tests/auth-react/flask-server/app.py index 1aa1455e3..c18e45878 100644 --- a/tests/auth-react/flask-server/app.py +++ b/tests/auth-react/flask-server/app.py @@ -12,7 +12,7 @@ # License for the specific language governing permissions and limitations # under the License. import os -from typing import Any, Dict, List, Union +from typing import Any, Dict, List, Union, Optional from dotenv import load_dotenv from flask import Flask, g, jsonify, make_response, request @@ -33,6 +33,7 @@ thirdparty, thirdpartyemailpassword, thirdpartypasswordless, + emailverification, ) from supertokens_python.recipe.emailpassword import EmailPasswordRecipe from supertokens_python.recipe.emailpassword.interfaces import ( @@ -56,6 +57,9 @@ from supertokens_python.recipe.emailverification.interfaces import ( APIOptions as EVAPIOptions, ) +from supertokens_python.recipe.emailverification.utils import ( + ParentRecipeEmailVerificationConfig, +) from supertokens_python.recipe.jwt import JWTRecipe from supertokens_python.recipe.passwordless import ( ContactEmailOnlyConfig, @@ -73,6 +77,7 @@ from supertokens_python.recipe.session.framework.flask import verify_session from supertokens_python.recipe.session.interfaces import ( APIInterface as SessionAPIInterface, + SessionContainer, ) from supertokens_python.recipe.session.interfaces import APIOptions as SAPIOptions from supertokens_python.recipe.thirdparty import ThirdPartyRecipe @@ -275,17 +280,24 @@ def override_email_verification_apis( ) async def email_verify_post( - token: str, api_options: EVAPIOptions, user_context: Dict[str, Any] + token: str, + api_options: EVAPIOptions, + user_context: Dict[str, Any], + session: Optional[SessionContainer], ): is_general_error = await check_for_general_error( "body", api_options.request ) if is_general_error: return GeneralErrorResponse("general error from API email verify") - return await original_email_verify_post(token, api_options, user_context) + return await original_email_verify_post( + token, api_options, user_context, session + ) async def generate_email_verify_token_post( - api_options: EVAPIOptions, user_context: Dict[str, Any] + api_options: EVAPIOptions, + user_context: Dict[str, Any], + session: SessionContainer, ): is_general_error = await check_for_general_error( "body", api_options.request @@ -295,7 +307,7 @@ async def generate_email_verify_token_post( "general error from API email verification code" ) return await original_generate_email_verify_token_post( - api_options, user_context + api_options, user_context, session ) original_implementation_email_verification.email_verify_post = email_verify_post @@ -870,18 +882,19 @@ async def authorisation_url_get( recipe_list = [ session.init(override=session.InputOverrideConfig(apis=override_session_apis)), + emailverification.init( + ParentRecipeEmailVerificationConfig( + mode="REQUIRED", + create_and_send_custom_email=create_and_send_custom_email, + override=EVInputOverrideConfig(apis=override_email_verification_apis), + ) + ), emailpassword.init( sign_up_feature=emailpassword.InputSignUpFeature(form_fields), reset_password_using_token_feature=emailpassword.InputResetPasswordUsingTokenFeature( create_and_send_custom_email=create_and_send_custom_email ), - email_verification_feature=emailpassword.InputEmailVerificationConfig( - create_and_send_custom_email=create_and_send_custom_email - ), override=emailpassword.InputOverrideConfig( - email_verification_feature=EVInputOverrideConfig( - apis=override_email_verification_apis - ), apis=override_email_password_apis, ), ), diff --git a/tests/emailpassword/test_emaildelivery.py b/tests/emailpassword/test_emaildelivery.py index 910c351fe..591d4fa2f 100644 --- a/tests/emailpassword/test_emaildelivery.py +++ b/tests/emailpassword/test_emaildelivery.py @@ -31,9 +31,8 @@ SMTPServiceInterface, SMTPSettings, ) -from supertokens_python.recipe import emailpassword, session +from supertokens_python.recipe import emailpassword, session, emailverification from supertokens_python.recipe.emailpassword import ( - InputEmailVerificationConfig, InputResetPasswordUsingTokenFeature, ) from supertokens_python.recipe.emailpassword.emaildelivery.services import SMTPService @@ -45,6 +44,9 @@ from supertokens_python.recipe.emailverification.types import ( VerificationEmailTemplateVars, ) +from supertokens_python.recipe.emailverification.utils import ( + ParentRecipeEmailVerificationConfig, +) from supertokens_python.recipe.session import SessionRecipe from supertokens_python.recipe.session.recipe_implementation import ( RecipeImplementation as SessionRecipeImplementation, @@ -653,11 +655,13 @@ async def custom_create_and_send_custom_email( ), framework="fastapi", recipe_list=[ - emailpassword.init( - email_verification_feature=InputEmailVerificationConfig( - create_and_send_custom_email=custom_create_and_send_custom_email + emailverification.init( + ParentRecipeEmailVerificationConfig( + mode="REQUIRED", + create_and_send_custom_email=custom_create_and_send_custom_email, ) ), + emailpassword.init(), session.init(), ], ) diff --git a/tests/emailpassword/test_emailverify.py b/tests/emailpassword/test_emailverify.py index f596575d7..ed6e08eb2 100644 --- a/tests/emailpassword/test_emailverify.py +++ b/tests/emailpassword/test_emailverify.py @@ -23,8 +23,8 @@ from supertokens_python.exceptions import BadInputError from supertokens_python.framework.fastapi import get_middleware from supertokens_python.querier import Querier -from supertokens_python.recipe import emailpassword, session -from supertokens_python.recipe.emailpassword.asyncio import ( +from supertokens_python.recipe import emailpassword, session, emailverification +from supertokens_python.recipe.emailverification.asyncio import ( create_email_verification_token, is_email_verified, revoke_email_verification_token, @@ -40,7 +40,10 @@ VerifyEmailUsingTokenInvalidTokenError, ) from supertokens_python.recipe.emailverification.types import User as EVUser -from supertokens_python.recipe.emailverification.utils import OverrideConfig +from supertokens_python.recipe.emailverification.utils import ( + OverrideConfig, + ParentRecipeEmailVerificationConfig, +) from supertokens_python.recipe.session import SessionContainer from supertokens_python.recipe.session.asyncio import ( create_new_session, @@ -317,11 +320,12 @@ async def custom_f( framework="fastapi", recipe_list=[ session.init(anti_csrf="VIA_TOKEN"), - emailpassword.init( - email_verification_feature=emailpassword.InputEmailVerificationConfig( - create_and_send_custom_email=custom_f + emailverification.init( + ParentRecipeEmailVerificationConfig( + mode="REQUIRED", create_and_send_custom_email=custom_f ) ), + emailpassword.init(), ], ) start_st() @@ -372,11 +376,12 @@ async def custom_f(_: User, email_verification_url_token: str, __: Dict[str, Any framework="fastapi", recipe_list=[ session.init(anti_csrf="VIA_TOKEN"), - emailpassword.init( - email_verification_feature=emailpassword.InputEmailVerificationConfig( - create_and_send_custom_email=custom_f + emailverification.init( + ParentRecipeEmailVerificationConfig( + mode="REQUIRED", create_and_send_custom_email=custom_f ) ), + emailpassword.init(), ], ) start_st() @@ -442,11 +447,12 @@ async def custom_f(_: User, email_verification_url_token: str, __: Dict[str, Any framework="fastapi", recipe_list=[ session.init(anti_csrf="VIA_TOKEN"), - emailpassword.init( - email_verification_feature=emailpassword.InputEmailVerificationConfig( - create_and_send_custom_email=custom_f + emailverification.init( + ParentRecipeEmailVerificationConfig( + mode="REQUIRED", create_and_send_custom_email=custom_f ) ), + emailpassword.init(), ], ) start_st() @@ -512,11 +518,12 @@ async def custom_f(_: User, email_verification_url_token: str, __: Dict[str, Any framework="fastapi", recipe_list=[ session.init(anti_csrf="VIA_TOKEN"), - emailpassword.init( - email_verification_feature=emailpassword.InputEmailVerificationConfig( - create_and_send_custom_email=custom_f + emailverification.init( + ParentRecipeEmailVerificationConfig( + mode="REQUIRED", create_and_send_custom_email=custom_f ) ), + emailpassword.init(), ], ) start_st() @@ -602,16 +609,14 @@ async def email_verify_post( framework="fastapi", recipe_list=[ session.init(anti_csrf="VIA_TOKEN"), - emailpassword.init( - email_verification_feature=emailpassword.InputEmailVerificationConfig( - create_and_send_custom_email=custom_f - ), - override=emailpassword.InputOverrideConfig( - email_verification_feature=OverrideConfig( - apis=apis_override_email_password - ) - ), + emailverification.init( + ParentRecipeEmailVerificationConfig( + mode="REQUIRED", + create_and_send_custom_email=custom_f, + override=OverrideConfig(apis=apis_override_email_password), + ) ), + emailpassword.init(), ], ) start_st() @@ -684,11 +689,13 @@ async def custom_f(_: User, email_verification_url_token: str, __: Dict[str, Any framework="fastapi", recipe_list=[ session.init(anti_csrf="VIA_TOKEN"), - emailpassword.init( - email_verification_feature=emailpassword.InputEmailVerificationConfig( - create_and_send_custom_email=custom_f + emailverification.init( + ParentRecipeEmailVerificationConfig( + mode="REQUIRED", + create_and_send_custom_email=custom_f, ) ), + emailpassword.init(), ], ) start_st() @@ -805,16 +812,14 @@ async def email_verify_post( framework="fastapi", recipe_list=[ session.init(anti_csrf="VIA_TOKEN"), - emailpassword.init( - email_verification_feature=emailpassword.InputEmailVerificationConfig( - create_and_send_custom_email=custom_f - ), - override=emailpassword.InputOverrideConfig( - email_verification_feature=OverrideConfig( - apis=apis_override_email_password - ) - ), + emailverification.init( + ParentRecipeEmailVerificationConfig( + mode="REQUIRED", + create_and_send_custom_email=custom_f, + override=OverrideConfig(apis=apis_override_email_password), + ) ), + emailpassword.init(), ], ) start_st() @@ -898,16 +903,16 @@ async def email_verify_post( framework="fastapi", recipe_list=[ session.init(anti_csrf="VIA_TOKEN"), - emailpassword.init( - email_verification_feature=emailpassword.InputEmailVerificationConfig( - create_and_send_custom_email=custom_f - ), - override=emailpassword.InputOverrideConfig( - email_verification_feature=OverrideConfig( + emailverification.init( + ParentRecipeEmailVerificationConfig( + mode="REQUIRED", + create_and_send_custom_email=custom_f, + override=emailverification.InputOverrideConfig( apis=apis_override_email_password - ) + ), ), ), + emailpassword.init(), ], ) start_st() diff --git a/tests/thirdpartyemailpassword/test_email_delivery.py b/tests/thirdpartyemailpassword/test_email_delivery.py index 0bb0b92df..393cd5ddb 100644 --- a/tests/thirdpartyemailpassword/test_email_delivery.py +++ b/tests/thirdpartyemailpassword/test_email_delivery.py @@ -31,8 +31,15 @@ EmailContent, SMTPServiceInterface, ) -from supertokens_python.recipe import session, thirdpartyemailpassword +from supertokens_python.recipe import ( + session, + thirdpartyemailpassword, + emailverification, +) from supertokens_python.recipe.emailpassword.types import User as EPUser +from supertokens_python.recipe.emailverification.utils import ( + ParentRecipeEmailVerificationConfig, +) from supertokens_python.recipe.session import SessionRecipe from supertokens_python.recipe.session.recipe_implementation import ( RecipeImplementation as SessionRecipeImplementation, @@ -50,9 +57,11 @@ ) from supertokens_python.recipe.thirdpartyemailpassword.types import ( EmailTemplateVars, - VerificationEmailTemplateVars, PasswordResetEmailTemplateVars, ) +from supertokens_python.recipe.emailverification.types import ( + VerificationEmailTemplateVars, +) from supertokens_python.recipe.thirdpartyemailpassword.types import User as TPEPUser from tests.utils import ( clean_st, @@ -639,11 +648,13 @@ async def test_email_verification_custom_override(driver_config_client: TestClie email = "" email_verify_url = "" - def email_delivery_override(oi: EmailDeliveryInterface[EmailTemplateVars]): + def email_delivery_override( + oi: EmailDeliveryInterface[VerificationEmailTemplateVars], + ): oi_send_email = oi.send_email async def send_email( - template_vars: EmailTemplateVars, user_context: Dict[str, Any] + template_vars: VerificationEmailTemplateVars, user_context: Dict[str, Any] ): nonlocal email, email_verify_url email = template_vars.user.email @@ -664,12 +675,16 @@ async def send_email( ), framework="fastapi", recipe_list=[ - thirdpartyemailpassword.init( - email_delivery=EmailDeliveryConfig( - service=None, - override=email_delivery_override, + emailverification.init( + ParentRecipeEmailVerificationConfig( + mode="REQUIRED", + email_delivery=EmailDeliveryConfig( + service=None, + override=email_delivery_override, + ), ) ), + thirdpartyemailpassword.init(), session.init(), ], ) From 918e15ffeafa0fd3033491f5e216144cd1f292d4 Mon Sep 17 00:00:00 2001 From: KShivendu Date: Sun, 7 Aug 2022 15:21:56 +0530 Subject: [PATCH 02/18] feat: Add and use email verification claim --- .../recipe/emailpassword/__init__.py | 4 +- .../emailpassword/api/implementation.py | 6 +- .../recipe/emailpassword/asyncio/__init__.py | 62 -------- .../backward_compatibility/__init__.py | 69 ++------ .../emaildelivery/services/smtp/__init__.py | 21 --- .../smtp/service_implementation/__init__.py | 12 -- .../recipe/emailpassword/interfaces.py | 14 +- .../recipe/emailpassword/recipe.py | 68 +++----- .../recipe/emailpassword/syncio/__init__.py | 40 ----- .../recipe/emailpassword/types.py | 11 +- .../recipe/emailpassword/utils.py | 149 +++++++----------- .../recipe/emailverification/__init__.py | 3 +- .../emailverification/api/email_verify.py | 22 ++- .../api/generate_email_verify_token.py | 9 +- .../emailverification/api/implementation.py | 126 +++++++++------ .../email_verification_claim.py | 103 ++++++++++++ .../recipe/emailverification/interfaces.py | 44 +++++- .../recipe/emailverification/recipe.py | 71 ++++++--- .../recipe/emailverification/utils.py | 62 +++----- .../recipe/session/asyncio/__init__.py | 4 + .../claim_base_classes/primitive_claim.py | 4 + .../recipe/session/interfaces.py | 4 + .../recipe/session/session_class.py | 4 +- .../session/with_jwt/recipe_implementation.py | 2 + supertokens_python/recipe/thirdparty/utils.py | 13 +- .../backward_compatibility/__init__.py | 56 +------ .../recipe/thirdpartyemailpassword/recipe.py | 2 - .../recipe/thirdpartyemailpassword/types.py | 2 +- .../recipe/thirdpartyemailpassword/utils.py | 15 +- .../recipe/thirdpartypasswordless/utils.py | 8 +- 30 files changed, 454 insertions(+), 556 deletions(-) diff --git a/supertokens_python/recipe/emailpassword/__init__.py b/supertokens_python/recipe/emailpassword/__init__.py index 7ab61b1df..d116b1b08 100644 --- a/supertokens_python/recipe/emailpassword/__init__.py +++ b/supertokens_python/recipe/emailpassword/__init__.py @@ -26,7 +26,7 @@ exceptions = ex InputOverrideConfig = utils.InputOverrideConfig InputResetPasswordUsingTokenFeature = utils.InputResetPasswordUsingTokenFeature -InputEmailVerificationConfig = utils.InputEmailVerificationConfig +# InputEmailVerificationConfig = utils.InputEmailVerificationConfig InputSignUpFeature = utils.InputSignUpFeature InputFormField = utils.InputFormField SMTPService = emaildelivery_services.SMTPService @@ -42,14 +42,12 @@ def init( reset_password_using_token_feature: Union[ utils.InputResetPasswordUsingTokenFeature, None ] = None, - email_verification_feature: Union[utils.InputEmailVerificationConfig, None] = None, override: Union[utils.InputOverrideConfig, None] = None, email_delivery: Union[EmailDeliveryConfig[EmailTemplateVars], None] = None, ) -> Callable[[AppInfo], RecipeModule]: return EmailPasswordRecipe.init( sign_up_feature, reset_password_using_token_feature, - email_verification_feature, override, email_delivery, ) diff --git a/supertokens_python/recipe/emailpassword/api/implementation.py b/supertokens_python/recipe/emailpassword/api/implementation.py index 2307ba3e0..af4ec24e0 100644 --- a/supertokens_python/recipe/emailpassword/api/implementation.py +++ b/supertokens_python/recipe/emailpassword/api/implementation.py @@ -92,9 +92,9 @@ async def generate_password_reset_token_post( token = token_result.token password_reset_link = ( - await api_options.config.reset_password_using_token_feature.get_reset_password_url( - user, user_context - ) + # TODO: Delete get_reset_password_url implementation + api_options.app_info.website_domain.get_as_string_dangerous() + + api_options.app_info.website_base_path.get_as_string_dangerous() + "?token=" + token + "&rid=" diff --git a/supertokens_python/recipe/emailpassword/asyncio/__init__.py b/supertokens_python/recipe/emailpassword/asyncio/__init__.py index 5c203f1aa..c43e696b0 100644 --- a/supertokens_python/recipe/emailpassword/asyncio/__init__.py +++ b/supertokens_python/recipe/emailpassword/asyncio/__init__.py @@ -18,55 +18,6 @@ from ..types import EmailTemplateVars, User -async def create_email_verification_token( - user_id: str, user_context: Union[None, Dict[str, Any]] = None -): - if user_context is None: - user_context = {} - email = await EmailPasswordRecipe.get_instance().get_email_for_user_id( - user_id, user_context - ) - return await EmailPasswordRecipe.get_instance().email_verification_recipe.recipe_implementation.create_email_verification_token( - user_id, email, user_context - ) - - -async def verify_email_using_token( - token: str, user_context: Union[None, Dict[str, Any]] = None -): - if user_context is None: - user_context = {} - return await EmailPasswordRecipe.get_instance().email_verification_recipe.recipe_implementation.verify_email_using_token( - token, user_context - ) - - -async def unverify_email( - user_id: str, user_context: Union[None, Dict[str, Any]] = None -): - if user_context is None: - user_context = {} - email = await EmailPasswordRecipe.get_instance().get_email_for_user_id( - user_id, user_context - ) - return await EmailPasswordRecipe.get_instance().email_verification_recipe.recipe_implementation.unverify_email( - user_id, email, user_context - ) - - -async def is_email_verified( - user_id: str, user_context: Union[None, Dict[str, Any]] = None -): - if user_context is None: - user_context = {} - email = await EmailPasswordRecipe.get_instance().get_email_for_user_id( - user_id, user_context - ) - return await EmailPasswordRecipe.get_instance().email_verification_recipe.recipe_implementation.is_email_verified( - user_id, email, user_context - ) - - async def update_email_or_password( user_id: str, email: Union[str, None] = None, @@ -142,19 +93,6 @@ async def sign_up( ) -async def revoke_email_verification_token( - user_id: str, user_context: Union[None, Dict[str, Any]] = None -): - if user_context is None: - user_context = {} - email = await EmailPasswordRecipe.get_instance().get_email_for_user_id( - user_id, user_context - ) - return await EmailPasswordRecipe.get_instance().email_verification_recipe.recipe_implementation.revoke_email_verification_tokens( - user_id, email, user_context - ) - - async def send_email( input_: EmailTemplateVars, user_context: Union[None, Dict[str, Any]] = None ): diff --git a/supertokens_python/recipe/emailpassword/emaildelivery/services/backward_compatibility/__init__.py b/supertokens_python/recipe/emailpassword/emaildelivery/services/backward_compatibility/__init__.py index 77de9105a..365638211 100644 --- a/supertokens_python/recipe/emailpassword/emaildelivery/services/backward_compatibility/__init__.py +++ b/supertokens_python/recipe/emailpassword/emaildelivery/services/backward_compatibility/__init__.py @@ -25,20 +25,12 @@ ) from supertokens_python.recipe.emailpassword.types import ( User, - VerificationEmailTemplateVars, -) -from supertokens_python.recipe.emailverification.emaildelivery.services.backward_compatibility import ( - BackwardCompatibilityService as EmailVerificationBackwardCompatibilityService, -) -from supertokens_python.recipe.emailverification.types import ( - User as EmailVerificationUser, ) from supertokens_python.supertokens import AppInfo from supertokens_python.utils import handle_httpx_client_exceptions if TYPE_CHECKING: from supertokens_python.recipe.emailpassword.utils import ( - InputEmailVerificationConfig, InputResetPasswordUsingTokenFeature, ) @@ -68,7 +60,6 @@ async def func(user: User, password_reset_url_with_token: str, _: Dict[str, Any] class BackwardCompatibilityService(EmailDeliveryInterface[EmailTemplateVars]): app_info: AppInfo - ev_backward_compatibility_service: EmailVerificationBackwardCompatibilityService def __init__( self, @@ -77,7 +68,6 @@ def __init__( reset_password_using_token_feature: Union[ InputResetPasswordUsingTokenFeature, None ] = None, - email_verification_feature: Union[InputEmailVerificationConfig, None] = None, ) -> None: self.recipe_interface_impl = recipe_interface_impl @@ -97,55 +87,18 @@ def __init__( reset_password_feature_send_email_func ) - create_and_send_custom_email: Union[ - Callable[[EmailVerificationUser, str, Dict[str, Any]], Awaitable[None]], - None, - ] = None - if email_verification_feature: - if email_verification_feature.create_and_send_custom_email is not None: - ev_create_and_send_custom_email = ( - email_verification_feature.create_and_send_custom_email - ) - - async def create_and_send_custom_email_wrapper( - user: EmailVerificationUser, link: str, user_context: Dict[str, Any] - ): - user_info = await recipe_interface_impl.get_user_by_id( - user.user_id, user_context - ) - if user_info is None: - raise Exception("Unknown User ID provided") - - return await ev_create_and_send_custom_email( - user_info, link, user_context - ) - - create_and_send_custom_email = create_and_send_custom_email_wrapper - - self.ev_backward_compatibility_service = ( - EmailVerificationBackwardCompatibilityService( - app_info, create_and_send_custom_email=create_and_send_custom_email - ) - ) - async def send_email( self, template_vars: EmailTemplateVars, user_context: Dict[str, Any] ) -> None: - if isinstance(template_vars, VerificationEmailTemplateVars): - await self.ev_backward_compatibility_service.send_email( - template_vars, user_context - ) - else: - user = await self.recipe_interface_impl.get_user_by_id( - user_id=template_vars.user.id, user_context=user_context - ) - - if user is None: - raise Exception("Should never come here") + user = await self.recipe_interface_impl.get_user_by_id( + user_id=template_vars.user.id, user_context=user_context + ) + if user is None: + raise Exception("Should never come here") - try: - await self.reset_password_feature_send_email_func( - user, template_vars.password_reset_link, user_context - ) - except Exception: - pass + try: + await self.reset_password_feature_send_email_func( + user, template_vars.password_reset_link, user_context + ) + except Exception: + pass diff --git a/supertokens_python/recipe/emailpassword/emaildelivery/services/smtp/__init__.py b/supertokens_python/recipe/emailpassword/emaildelivery/services/smtp/__init__.py index 716c75eca..8cc9216b9 100644 --- a/supertokens_python/recipe/emailpassword/emaildelivery/services/smtp/__init__.py +++ b/supertokens_python/recipe/emailpassword/emaildelivery/services/smtp/__init__.py @@ -24,17 +24,8 @@ EmailTemplateVars, SMTPOverrideInput, ) -from supertokens_python.recipe.emailverification.emaildelivery.services.smtp import ( - SMTPService as EmailVerificationSMTPService, -) -from supertokens_python.recipe.emailverification.types import ( - VerificationEmailTemplateVars, -) from .service_implementation import ServiceImplementation -from .service_implementation.email_verification_implementation import ( - ServiceImplementation as EmailVerificationServiceImpl, -) class SMTPService(EmailDeliveryInterface[EmailTemplateVars]): @@ -50,21 +41,9 @@ def __init__( oi = ServiceImplementation(transporter) self.service_implementation = oi if override is None else override(oi) - self.email_verification_smtp_service = EmailVerificationSMTPService( - smtp_settings=smtp_settings, - override=lambda _: EmailVerificationServiceImpl( - self.service_implementation - ), - ) - async def send_email( self, template_vars: EmailTemplateVars, user_context: Dict[str, Any] ) -> None: - if isinstance(template_vars, VerificationEmailTemplateVars): - return await self.email_verification_smtp_service.send_email( - template_vars, user_context - ) - content = await self.service_implementation.get_content( template_vars, user_context ) diff --git a/supertokens_python/recipe/emailpassword/emaildelivery/services/smtp/service_implementation/__init__.py b/supertokens_python/recipe/emailpassword/emaildelivery/services/smtp/service_implementation/__init__.py index 41b349fd6..c4fada039 100644 --- a/supertokens_python/recipe/emailpassword/emaildelivery/services/smtp/service_implementation/__init__.py +++ b/supertokens_python/recipe/emailpassword/emaildelivery/services/smtp/service_implementation/__init__.py @@ -30,10 +30,6 @@ VerificationEmailTemplateVars, ) -from .email_verification_implementation import ( - ServiceImplementation as DerivedEVServiceImplementation, -) - class ServiceImplementation(SMTPServiceInterface[EmailTemplateVars]): def __init__(self, transporter: Transporter) -> None: @@ -45,14 +41,6 @@ def __init__(self, transporter: Transporter) -> None: ) self.ev_get_content = email_verification_service_implementation.get_content - derived_ev_service_implementation = DerivedEVServiceImplementation(self) - email_verification_service_implementation.send_raw_email = ( - derived_ev_service_implementation.send_raw_email - ) - email_verification_service_implementation.get_content = ( - derived_ev_service_implementation.get_content - ) - async def send_raw_email( self, content: EmailContent, user_context: Dict[str, Any] ) -> None: diff --git a/supertokens_python/recipe/emailpassword/interfaces.py b/supertokens_python/recipe/emailpassword/interfaces.py index 68151b67d..38f0b3281 100644 --- a/supertokens_python/recipe/emailpassword/interfaces.py +++ b/supertokens_python/recipe/emailpassword/interfaces.py @@ -18,11 +18,9 @@ from supertokens_python.ingredients.emaildelivery import EmailDeliveryIngredient from supertokens_python.recipe.emailpassword.types import EmailTemplateVars +from ...supertokens import AppInfo from ...types import APIResponse, GeneralErrorResponse -from ..emailverification.interfaces import ( - RecipeInterface as EmailVerificationRecipeInterface, -) if TYPE_CHECKING: from supertokens_python.framework import BaseRequest, BaseResponse @@ -145,7 +143,8 @@ def __init__( recipe_id: str, config: EmailPasswordConfig, recipe_implementation: RecipeInterface, - email_verification_recipe_implementation: EmailVerificationRecipeInterface, + app_info: AppInfo, + # email_verification_recipe_implementation: EmailVerificationRecipeInterface, email_delivery: EmailDeliveryIngredient[EmailTemplateVars], ): self.request: BaseRequest = request @@ -153,9 +152,10 @@ def __init__( self.recipe_id: str = recipe_id self.config: EmailPasswordConfig = config self.recipe_implementation: RecipeInterface = recipe_implementation - self.email_verification_recipe_implementation: EmailVerificationRecipeInterface = ( - email_verification_recipe_implementation - ) + self.app_info = app_info + # self.email_verification_recipe_implementation: EmailVerificationRecipeInterface = ( + # email_verification_recipe_implementation + # ) self.email_delivery = email_delivery diff --git a/supertokens_python/recipe/emailpassword/recipe.py b/supertokens_python/recipe/emailpassword/recipe.py index 8c1f3bdab..0f26b3178 100644 --- a/supertokens_python/recipe/emailpassword/recipe.py +++ b/supertokens_python/recipe/emailpassword/recipe.py @@ -14,7 +14,7 @@ from __future__ import annotations from os import environ -from typing import TYPE_CHECKING, Any, Dict, List, Union, cast +from typing import TYPE_CHECKING, Any, Dict, List, Union from supertokens_python.ingredients.emaildelivery import EmailDeliveryIngredient from supertokens_python.ingredients.emaildelivery.types import EmailDeliveryConfig @@ -23,13 +23,13 @@ EmailPasswordIngredients, EmailTemplateVars, ) -from supertokens_python.recipe.emailverification.types import ( - EmailVerificationIngredients, - VerificationEmailTemplateVars, -) from supertokens_python.recipe_module import APIHandled, RecipeModule +from ..emailverification.interfaces import ( + UnknownUserIdError, + GetEmailForUserIdOkResult, + EmailDoesnotExistError, +) -from ...exceptions import SuperTokensError from .api.implementation import APIImplementation from .exceptions import FieldError, SuperTokensEmailPasswordError from .interfaces import APIOptions @@ -59,7 +59,6 @@ USER_PASSWORD_RESET_TOKEN, ) from .utils import ( - InputEmailVerificationConfig, InputOverrideConfig, InputResetPasswordUsingTokenFeature, InputSignUpFeature, @@ -81,9 +80,7 @@ def __init__( reset_password_using_token_feature: Union[ InputResetPasswordUsingTokenFeature, None ] = None, - email_verification_feature: Union[InputEmailVerificationConfig, None] = None, override: Union[InputOverrideConfig, None] = None, - email_verification_recipe: Union[EmailVerificationRecipe, None] = None, email_delivery: Union[EmailDeliveryConfig[EmailTemplateVars], None] = None, ): super().__init__(recipe_id, app_info) @@ -92,7 +89,6 @@ def __init__( app_info, sign_up_feature, reset_password_using_token_feature, - email_verification_feature, override, email_delivery, ) @@ -111,24 +107,6 @@ def __init__( else: self.email_delivery = email_delivery_ingredient - if email_verification_recipe is not None: - self.email_verification_recipe = email_verification_recipe - else: - ev_email_delivery_ingredient = cast( - EmailDeliveryIngredient[VerificationEmailTemplateVars], - self.email_delivery, - ) - - email_verification_ingredients = EmailVerificationIngredients( - email_delivery=ev_email_delivery_ingredient - ) - self.email_verification_recipe = EmailVerificationRecipe( - recipe_id, - app_info, - self.config.email_verification_feature, - ingredients=email_verification_ingredients, - ) - api_implementation = APIImplementation() self.api_implementation = ( api_implementation @@ -136,12 +114,16 @@ def __init__( else self.config.override.apis(api_implementation) ) + # TODO: Postinit callbacks + email_veriifcation_recipe = EmailVerificationRecipe.get_instance() + if email_veriifcation_recipe is not None: + email_veriifcation_recipe.add_get_email_for_user_id_func( + self.get_email_for_user_id + ) + def is_error_from_this_recipe_based_on_instance(self, err: Exception) -> bool: return isinstance(err, SuperTokensError) and ( isinstance(err, SuperTokensEmailPasswordError) - or self.email_verification_recipe.is_error_from_this_recipe_based_on_instance( - err - ) ) def get_apis_handled(self) -> List[APIHandled]: @@ -176,7 +158,7 @@ def get_apis_handled(self) -> List[APIHandled]: SIGNUP_EMAIL_EXISTS, self.api_implementation.disable_email_exists_get, ), - ] + self.email_verification_recipe.get_apis_handled() + ] async def handle_api_request( self, @@ -192,7 +174,7 @@ async def handle_api_request( self.recipe_id, self.config, self.recipe_implementation, - self.email_verification_recipe.recipe_implementation, + self.get_app_info(), self.email_delivery, ) if request_id == SIGNUP: @@ -207,9 +189,8 @@ async def handle_api_request( ) if request_id == USER_PASSWORD_RESET: return await handle_password_reset_api(self.api_implementation, api_options) - return await self.email_verification_recipe.handle_api_request( - request_id, request, path, method, response - ) + # TODO: FIXME Should be False as per Node PR but the spec here don't allow it. + return None async def handle_error( self, request: BaseRequest, err: SuperTokensError, response: BaseResponse @@ -220,10 +201,10 @@ async def handle_error( {"status": "FIELD_ERROR", "formFields": err.get_json_form_fields()} ) return response - return await self.email_verification_recipe.handle_error(request, err, response) + raise err def get_all_cors_headers(self) -> List[str]: - return self.email_verification_recipe.get_all_cors_headers() + return [] @staticmethod def init( @@ -231,7 +212,6 @@ def init( reset_password_using_token_feature: Union[ InputResetPasswordUsingTokenFeature, None ] = None, - email_verification_feature: Union[InputEmailVerificationConfig, None] = None, override: Union[InputOverrideConfig, None] = None, email_delivery: Union[EmailDeliveryConfig[EmailTemplateVars], None] = None, ): @@ -244,7 +224,6 @@ def func(app_info: AppInfo): ingredients, sign_up_feature, reset_password_using_token_feature, - email_verification_feature, override, email_delivery=email_delivery, ) @@ -276,10 +255,11 @@ def reset(): async def get_email_for_user_id( self, user_id: str, user_context: Dict[str, Any] - ) -> str: + ) -> Union[UnknownUserIdError, GetEmailForUserIdOkResult, EmailDoesnotExistError]: user_info = await self.recipe_implementation.get_user_by_id( user_id, user_context ) - if user_info is None: - raise Exception("Unknown User ID provided") - return user_info.email + if user_info is not None: + return GetEmailForUserIdOkResult(user_info.email) + + return UnknownUserIdError() diff --git a/supertokens_python/recipe/emailpassword/syncio/__init__.py b/supertokens_python/recipe/emailpassword/syncio/__init__.py index 309c40a5b..35d6febd7 100644 --- a/supertokens_python/recipe/emailpassword/syncio/__init__.py +++ b/supertokens_python/recipe/emailpassword/syncio/__init__.py @@ -19,36 +19,6 @@ from ..types import EmailTemplateVars, User -def create_email_verification_token( - user_id: str, user_context: Union[None, Dict[str, Any]] = None -): - from supertokens_python.recipe.emailpassword.asyncio import ( - create_email_verification_token, - ) - - return sync(create_email_verification_token(user_id, user_context)) - - -def verify_email_using_token( - token: str, user_context: Union[None, Dict[str, Any]] = None -): - from supertokens_python.recipe.emailpassword.asyncio import verify_email_using_token - - return sync(verify_email_using_token(token, user_context)) - - -def unverify_email(user_id: str, user_context: Union[None, Dict[str, Any]] = None): - from supertokens_python.recipe.emailpassword.asyncio import unverify_email - - return sync(unverify_email(user_id, user_context)) - - -def is_email_verified(user_id: str, user_context: Union[None, Dict[str, Any]] = None): - from supertokens_python.recipe.emailpassword.asyncio import is_email_verified - - return sync(is_email_verified(user_id, user_context)) - - def update_email_or_password( user_id: str, email: Union[str, None] = None, @@ -112,16 +82,6 @@ def sign_up( return sync(sign_up(email, password, user_context)) -def revoke_email_verification_token( - user_id: str, user_context: Union[None, Dict[str, Any]] = None -): - from supertokens_python.recipe.emailpassword.asyncio import ( - revoke_email_verification_token, - ) - - return sync(revoke_email_verification_token(user_id, user_context)) - - def send_email( input_: EmailTemplateVars, user_context: Union[None, Dict[str, Any]] = None ): diff --git a/supertokens_python/recipe/emailpassword/types.py b/supertokens_python/recipe/emailpassword/types.py index 4895fd33a..c45b87e55 100644 --- a/supertokens_python/recipe/emailpassword/types.py +++ b/supertokens_python/recipe/emailpassword/types.py @@ -18,7 +18,6 @@ EmailDeliveryInterface, SMTPServiceInterface, ) -from supertokens_python.recipe.emailverification import types as ev_types class User: @@ -93,11 +92,13 @@ def __init__( # Export: -EmailTemplateVars = Union[ - PasswordResetEmailTemplateVars, ev_types.VerificationEmailTemplateVars -] +EmailTemplateVars = PasswordResetEmailTemplateVars + +# Union[ +# PasswordResetEmailTemplateVars, ev_types.VerificationEmailTemplateVars # TODO: REMOVE COMMENTED +# ] # PasswordResetEmailTemplateVars (Already exported because it's defined in the same) -VerificationEmailTemplateVars = ev_types.VerificationEmailTemplateVars +# VerificationEmailTemplateVars = ev_types.VerificationEmailTemplateVars # TODO: REMOVE SMTPOverrideInput = SMTPServiceInterface[EmailTemplateVars] diff --git a/supertokens_python/recipe/emailpassword/utils.py b/supertokens_python/recipe/emailpassword/utils.py index 322450ecd..356289fe8 100644 --- a/supertokens_python/recipe/emailpassword/utils.py +++ b/supertokens_python/recipe/emailpassword/utils.py @@ -33,17 +33,12 @@ from supertokens_python.supertokens import AppInfo from typing import Dict - -from supertokens_python.recipe.emailverification.utils import ( - OverrideConfig as EmailVerificationOverrideConfig, -) -from supertokens_python.recipe.emailverification.utils import ( - ParentRecipeEmailVerificationConfig, -) from supertokens_python.utils import deprecated_warn, get_filtered_list -from ..emailverification.utils import ParentRecipeEmailVerificationConfig -from .constants import FORM_FIELD_EMAIL_ID, FORM_FIELD_PASSWORD_ID, RESET_PASSWORD +from .constants import ( + FORM_FIELD_EMAIL_ID, + FORM_FIELD_PASSWORD_ID, +) # TODO: RESET_PASSWORD async def default_validator(_: str) -> Union[str, None]: @@ -87,19 +82,6 @@ async def default_email_validator(value: str) -> Union[str, None]: return None -def default_get_reset_password_url( - app_info: AppInfo, -) -> Callable[[User, Dict[str, Any]], Awaitable[str]]: - async def func(_: User, __: Dict[str, Any]): - return ( - app_info.website_domain.get_as_string_dangerous() - + app_info.website_base_path.get_as_string_dangerous() - + RESET_PASSWORD - ) - - return func - - class InputSignUpFeature: def __init__(self, form_fields: Union[List[InputFormField], None] = None): if form_fields is None: @@ -203,14 +185,10 @@ def validate_and_normalise_sign_in_config( class InputResetPasswordUsingTokenFeature: def __init__( self, - get_reset_password_url: Union[ - Callable[[User, Dict[str, Any]], Awaitable[str]], None - ] = None, create_and_send_custom_email: Union[ Callable[[User, str, Dict[str, Any]], Awaitable[None]], None ] = None, ): - self.get_reset_password_url = get_reset_password_url self.create_and_send_custom_email = create_and_send_custom_email if create_and_send_custom_email: @@ -224,36 +202,33 @@ def __init__( self, form_fields_for_password_reset_form: List[NormalisedFormField], form_fields_for_generate_token_form: List[NormalisedFormField], - get_reset_password_url: Callable[[User, Dict[str, Any]], Awaitable[str]], ): 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 - self.get_reset_password_url = get_reset_password_url -class InputEmailVerificationConfig: - def __init__( - self, - get_email_verification_url: Union[ - Callable[[User, Dict[str, Any]], Awaitable[str]], None - ] = None, - create_and_send_custom_email: Union[ - Callable[[User, str, Dict[str, Any]], Awaitable[None]], None - ] = None, - ): - self.get_email_verification_url = get_email_verification_url - self.create_and_send_custom_email = create_and_send_custom_email - - if create_and_send_custom_email: - deprecated_warn( - "create_and_send_custom_email is deprecated. Please use email delivery config instead" - ) +# +# class InputEmailVerificationConfig: +# def __init__( +# self, +# get_email_verification_url: Union[ +# Callable[[User, Dict[str, Any]], Awaitable[str]], None +# ] = None, +# create_and_send_custom_email: Union[ +# Callable[[User, str, Dict[str, Any]], Awaitable[None]], None +# ] = None, +# ): +# self.get_email_verification_url = get_email_verification_url +# self.create_and_send_custom_email = create_and_send_custom_email +# +# if create_and_send_custom_email: +# deprecated_warn( +# "create_and_send_custom_email is deprecated. Please use email delivery config instead" +# ) def validate_and_normalise_reset_password_using_token_config( - app_info: AppInfo, sign_up_config: InputSignUpFeature, - config: InputResetPasswordUsingTokenFeature, ) -> ResetPasswordUsingTokenFeature: form_fields_for_password_reset_form = list( map( @@ -271,16 +246,10 @@ def validate_and_normalise_reset_password_using_token_config( ), ) ) - get_reset_password_url = ( - config.get_reset_password_url - if config.get_reset_password_url is not None - else default_get_reset_password_url(app_info) - ) return ResetPasswordUsingTokenFeature( form_fields_for_password_reset_form, form_fields_for_generate_token_form, - get_reset_password_url, ) @@ -318,29 +287,29 @@ async def func(user: EmailVerificationUser, user_context: Dict[str, Any]): return func -def validate_and_normalise_email_verification_config( - recipe: EmailPasswordRecipe, - config: Union[InputEmailVerificationConfig, None], - override: InputOverrideConfig, -) -> ParentRecipeEmailVerificationConfig: - create_and_send_custom_email = None - get_email_verification_url = None - if config is None: - config = InputEmailVerificationConfig() - if config.create_and_send_custom_email is not None: - create_and_send_custom_email = email_verification_create_and_send_custom_email( - recipe, config.create_and_send_custom_email - ) - if config.get_email_verification_url is not None: - get_email_verification_url = email_verification_get_email_verification_url( - recipe, config.get_email_verification_url - ) - return ParentRecipeEmailVerificationConfig( - get_email_for_user_id=recipe.get_email_for_user_id, - create_and_send_custom_email=create_and_send_custom_email, - get_email_verification_url=get_email_verification_url, - override=override.email_verification_feature, - ) +# def validate_and_normalise_email_verification_config( +# recipe: EmailPasswordRecipe, +# config: Union[InputEmailVerificationConfig, None], +# override: InputOverrideConfig, +# ) -> ParentRecipeEmailVerificationConfig: +# create_and_send_custom_email = None +# get_email_verification_url = None +# if config is None: +# config = InputEmailVerificationConfig() +# if config.create_and_send_custom_email is not None: +# create_and_send_custom_email = email_verification_create_and_send_custom_email( +# recipe, config.create_and_send_custom_email +# ) +# if config.get_email_verification_url is not None: +# get_email_verification_url = email_verification_get_email_verification_url( +# recipe, config.get_email_verification_url +# ) +# return ParentRecipeEmailVerificationConfig( +# get_email_for_user_id=recipe.get_email_for_user_id, +# create_and_send_custom_email=create_and_send_custom_email, +# get_email_verification_url=get_email_verification_url, +# override=override.email_verification_feature, +# ) class InputOverrideConfig: @@ -348,11 +317,9 @@ def __init__( self, functions: Union[Callable[[RecipeInterface], RecipeInterface], None] = None, apis: Union[Callable[[APIInterface], APIInterface], None] = None, - email_verification_feature: Union[EmailVerificationOverrideConfig, None] = None, ): self.functions = functions self.apis = apis - self.email_verification_feature = email_verification_feature class OverrideConfig: @@ -371,7 +338,7 @@ def __init__( sign_up_feature: SignUpFeature, sign_in_feature: SignInFeature, reset_password_using_token_feature: ResetPasswordUsingTokenFeature, - email_verification_feature: ParentRecipeEmailVerificationConfig, + # email_verification_feature: ParentRecipeEmailVerificationConfig, override: OverrideConfig, get_email_delivery_config: Callable[ [RecipeInterface], EmailDeliveryConfigWithService[EmailTemplateVars] @@ -380,7 +347,7 @@ def __init__( 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.email_verification_feature = email_verification_feature + # self.email_verification_feature = email_verification_feature self.override = override self.get_email_delivery_config = get_email_delivery_config @@ -392,7 +359,7 @@ def validate_and_normalise_user_input( reset_password_using_token_feature: Union[ InputResetPasswordUsingTokenFeature, None ] = None, - email_verification_feature: Union[InputEmailVerificationConfig, None] = None, + # email_verification_feature: Union[InputEmailVerificationConfig, None] = None, override: Union[InputOverrideConfig, None] = None, email_delivery: Union[EmailDeliveryConfig[EmailTemplateVars], None] = None, ) -> EmailPasswordConfig: @@ -405,10 +372,10 @@ def validate_and_normalise_user_input( "reset_password_using_token_feature must be of type InputResetPasswordUsingTokenFeature or None" ) - if email_verification_feature is not None and not isinstance(email_verification_feature, InputEmailVerificationConfig): # type: ignore - raise ValueError( - "email_verification_feature must be of type InputEmailVerificationConfig or None" - ) + # if email_verification_feature is not None and not isinstance(email_verification_feature, InputEmailVerificationConfig): # type: ignore + # raise ValueError( + # "email_verification_feature must be of type InputEmailVerificationConfig or None" + # ) if override is not None and not isinstance(override, InputOverrideConfig): # type: ignore raise ValueError("override must be of type InputOverrideConfig or None") @@ -433,7 +400,7 @@ def get_email_delivery_config( app_info=app_info, recipe_interface_impl=ep_recipe, reset_password_using_token_feature=reset_password_using_token_feature, - email_verification_feature=email_verification_feature, + # email_verification_feature=email_verification_feature, ) if email_delivery is not None and email_delivery.override is not None: override = email_delivery.override @@ -444,12 +411,10 @@ def get_email_delivery_config( 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( - app_info, sign_up_feature, reset_password_using_token_feature - ), - validate_and_normalise_email_verification_config( - recipe, email_verification_feature, override - ), + validate_and_normalise_reset_password_using_token_config(sign_up_feature), + # validate_and_normalise_email_verification_config( + # recipe, email_verification_feature, override + # ), OverrideConfig(functions=override.functions, apis=override.apis), get_email_delivery_config=get_email_delivery_config, ) diff --git a/supertokens_python/recipe/emailverification/__init__.py b/supertokens_python/recipe/emailverification/__init__.py index 13ad4489c..094c9ad68 100644 --- a/supertokens_python/recipe/emailverification/__init__.py +++ b/supertokens_python/recipe/emailverification/__init__.py @@ -16,7 +16,7 @@ from typing import TYPE_CHECKING, Callable from . import exceptions as ex -from . import utils +from . import utils, email_verification_claim from .emaildelivery import services as emaildelivery_services from .recipe import EmailVerificationRecipe @@ -24,6 +24,7 @@ ParentRecipeEmailVerificationConfig = utils.ParentRecipeEmailVerificationConfig exception = ex SMTPService = emaildelivery_services.SMTPService +EmailVerificationClaim = email_verification_claim.EmailVerificationClaim if TYPE_CHECKING: diff --git a/supertokens_python/recipe/emailverification/api/email_verify.py b/supertokens_python/recipe/emailverification/api/email_verify.py index 11a4274ba..a458dae5c 100644 --- a/supertokens_python/recipe/emailverification/api/email_verify.py +++ b/supertokens_python/recipe/emailverification/api/email_verify.py @@ -13,6 +13,7 @@ # under the License. from __future__ import annotations + from supertokens_python.exceptions import raise_bad_input_exception from supertokens_python.recipe.emailverification.interfaces import ( APIInterface, @@ -23,11 +24,13 @@ normalise_http_method, send_200_response, ) +from supertokens_python.recipe.session.asyncio import get_session async def handle_email_verify_api( api_implementation: APIInterface, api_options: APIOptions ): + user_context = default_user_context(api_options.request) if normalise_http_method(api_options.request.method()) == "post": if api_implementation.disable_email_verify_post: return None @@ -40,18 +43,29 @@ async def handle_email_verify_api( raise_bad_input_exception("The email verification token must be a string") token = body["token"] - user_context = default_user_context(api_options.request) + + session = await get_session( + api_options.request, + session_required=False, + override_global_claim_validators=lambda _, __, ___: [], + user_context=user_context, + ) result = await api_implementation.email_verify_post( - token, api_options, user_context + token, api_options, user_context, session ) else: if api_implementation.disable_is_email_verified_get: return None - user_context = default_user_context(api_options.request) + session = await get_session( + api_options.request, + override_global_claim_validators=lambda _, __, ___: [], + user_context=user_context, + ) + result = await api_implementation.is_email_verified_get( - api_options, user_context + api_options, user_context, session ) return send_200_response(result.to_json(), api_options.response) diff --git a/supertokens_python/recipe/emailverification/api/generate_email_verify_token.py b/supertokens_python/recipe/emailverification/api/generate_email_verify_token.py index 575ae4a1e..33844094f 100644 --- a/supertokens_python/recipe/emailverification/api/generate_email_verify_token.py +++ b/supertokens_python/recipe/emailverification/api/generate_email_verify_token.py @@ -18,6 +18,7 @@ APIOptions, ) from supertokens_python.utils import default_user_context, send_200_response +from supertokens_python.recipe.session.asyncio import get_session async def handle_generate_email_verify_token_api( @@ -26,8 +27,14 @@ async def handle_generate_email_verify_token_api( if api_implementation.disable_generate_email_verify_token_post: return None user_context = default_user_context(api_options.request) + session = await get_session( + api_options.request, + override_global_claim_validators=lambda _, __, ___: [], + user_context=user_context, + ) + assert session is not None result = await api_implementation.generate_email_verify_token_post( - api_options, user_context + api_options, user_context, session ) return send_200_response(result.to_json(), api_options.response) diff --git a/supertokens_python/recipe/emailverification/api/implementation.py b/supertokens_python/recipe/emailverification/api/implementation.py index d72aa6c96..cb7eb1726 100644 --- a/supertokens_python/recipe/emailverification/api/implementation.py +++ b/supertokens_python/recipe/emailverification/api/implementation.py @@ -13,9 +13,13 @@ # under the License. from __future__ import annotations -from typing import TYPE_CHECKING, Any, Dict, Union +from typing import TYPE_CHECKING, Any, Dict, Union, Optional from supertokens_python.logger import log_debug_message +from supertokens_python.recipe.emailverification import EmailVerificationRecipe +from supertokens_python.recipe.emailverification.email_verification_claim import ( + EmailVerificationClaim, +) from supertokens_python.recipe.emailverification.interfaces import ( APIInterface, CreateEmailVerificationTokenEmailAlreadyVerifiedError, @@ -25,93 +29,121 @@ GenerateEmailVerifyTokenPostOkResult, IsEmailVerifiedGetOkResult, VerifyEmailUsingTokenOkResult, + EmailDoesnotExistError, + GetEmailForUserIdOkResult, ) +from supertokens_python.recipe.session.interfaces import SessionContainer if TYPE_CHECKING: from supertokens_python.recipe.emailverification.interfaces import APIOptions from supertokens_python.recipe.emailverification.types import ( - User, VerificationEmailTemplateVarsUser, VerificationEmailTemplateVars, ) -from supertokens_python.recipe.session.asyncio import get_session class APIImplementation(APIInterface): async def email_verify_post( - self, token: str, api_options: APIOptions, user_context: Dict[str, Any] + self, + token: str, + api_options: APIOptions, + user_context: Dict[str, Any], + session: Optional[SessionContainer] = None, ) -> Union[EmailVerifyPostOkResult, EmailVerifyPostInvalidTokenError]: response = await api_options.recipe_implementation.verify_email_using_token( token, user_context ) if isinstance(response, VerifyEmailUsingTokenOkResult): + if session is not None: + await session.fetch_and_set_claim(EmailVerificationClaim, user_context) + return EmailVerifyPostOkResult(response.user) return EmailVerifyPostInvalidTokenError() async def is_email_verified_get( - self, api_options: APIOptions, user_context: Dict[str, Any] + self, + api_options: APIOptions, + user_context: Dict[str, Any], + session: Optional[SessionContainer] = None, ) -> IsEmailVerifiedGetOkResult: - session = await get_session(api_options.request) if session is None: raise Exception("Session is undefined. Should not come here.") + await session.fetch_and_set_claim(EmailVerificationClaim, user_context) + is_verified = await session.get_claim_value( + EmailVerificationClaim, user_context + ) + # TODO: Type of is_verified should be bool. It's any for now. - user_id = session.get_user_id(user_context) - email = await api_options.config.get_email_for_user_id(user_id, user_context) + if is_verified is None: + raise Exception( + "Should never come here: EmailVerificationClaim failed to set value" + ) - is_verified = await api_options.recipe_implementation.is_email_verified( - user_id, email, user_context - ) return IsEmailVerifiedGetOkResult(is_verified) async def generate_email_verify_token_post( - self, api_options: APIOptions, user_context: Dict[str, Any] + self, + api_options: APIOptions, + user_context: Dict[str, Any], + session: SessionContainer, ) -> Union[ GenerateEmailVerifyTokenPostOkResult, GenerateEmailVerifyTokenPostEmailAlreadyVerifiedError, ]: - session = await get_session(api_options.request) if session is None: raise Exception("Session is undefined. Should not come here.") user_id = session.get_user_id(user_context) - email = await api_options.config.get_email_for_user_id(user_id, user_context) - - token_result = ( - await api_options.recipe_implementation.create_email_verification_token( - user_id, email, user_context - ) + email_info = await EmailVerificationRecipe.get_instance().get_email_for_user_id( + user_id, user_context ) - if isinstance( - token_result, CreateEmailVerificationTokenEmailAlreadyVerifiedError - ): + + if isinstance(email_info, EmailDoesnotExistError): log_debug_message( - "Email verification email not sent to %s because it is already verified", - email, + "Email verification email not sent to user %s because it doesn't have an email address.", + user_id, ) return GenerateEmailVerifyTokenPostEmailAlreadyVerifiedError() + elif isinstance(email_info, GetEmailForUserIdOkResult): + response = ( + await api_options.recipe_implementation.create_email_verification_token( + user_id, + email_info.email, + user_context, + ) + ) - user = User(user_id, email) - - email_verify_link = ( - (await api_options.config.get_email_verification_url(user, user_context)) - + "?token=" - + token_result.token - + "&rid=" - + api_options.recipe_id - ) - - log_debug_message("Sending email verification email to %s", email) - email_delivery_user = VerificationEmailTemplateVarsUser( - user.user_id, user.email - ) - email_verification_email_delivery_input = VerificationEmailTemplateVars( - user=email_delivery_user, - email_verify_link=email_verify_link, - user_context=user_context, - ) - await api_options.email_delivery.ingredient_interface_impl.send_email( - email_verification_email_delivery_input, user_context - ) + if isinstance( + response, CreateEmailVerificationTokenEmailAlreadyVerifiedError + ): + log_debug_message( + "Email verification email not sent to %s because it is already verified.", + email_info.email, + ) + return GenerateEmailVerifyTokenPostEmailAlreadyVerifiedError() + + email_verify_link = ( + api_options.app_info.website_domain.get_as_string_dangerous() + + api_options.app_info.website_base_path.get_as_string_dangerous() + + "/verify-email/" + + "?token=" + + response.token + + "&rid=" + + api_options.recipe_id + ) - return GenerateEmailVerifyTokenPostOkResult() + log_debug_message("Sending email verification email to %s", email_info) + email_verification_email_delivery_input = VerificationEmailTemplateVars( + user=VerificationEmailTemplateVarsUser(user_id, email_info.email), + email_verify_link=email_verify_link, + user_context=user_context, + ) + await api_options.email_delivery.ingredient_interface_impl.send_email( + email_verification_email_delivery_input, user_context + ) + return GenerateEmailVerifyTokenPostOkResult() + else: + raise Exception( + "Should never come here: UNKNOWN_USER_ID or invalid result from get_email_for_user_id" + ) diff --git a/supertokens_python/recipe/emailverification/email_verification_claim.py b/supertokens_python/recipe/emailverification/email_verification_claim.py index e69de29bb..fc01c6233 100644 --- a/supertokens_python/recipe/emailverification/email_verification_claim.py +++ b/supertokens_python/recipe/emailverification/email_verification_claim.py @@ -0,0 +1,103 @@ +# 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 typing import Dict, Any, Optional + +from supertokens_python.recipe.emailverification import EmailVerificationRecipe +from supertokens_python.recipe.emailverification.interfaces import ( + GetEmailForUserIdOkResult, + EmailDoesnotExistError, +) +from supertokens_python.recipe.session.claim_base_classes.boolean_claim import ( + BooleanClaim, + BooleanClaimValidators, +) +from supertokens_python.recipe.session.interfaces import ( + SessionClaimValidator, + JSONObject, + ClaimValidationResult, +) +from supertokens_python.types import MaybeAwaitable +from supertokens_python.utils import get_timestamp_ms + + +class IsVerifiedSCV(SessionClaimValidator): + def __init__( + self, + claim: BooleanClaim, + has_value_validator: SessionClaimValidator, + refetch_time_on_false_in_seconds: int, + ): + super().__init__("st-ev-is-verified") + self.claim: BooleanClaim = claim # TODO: FIXME: One has to specify type of self.claim to avoid type erorr from pylint + self.has_value_validator = has_value_validator + self.refetch_time_on_false_in_ms = refetch_time_on_false_in_seconds * 1000 + + async def validate( + self, payload: JSONObject, user_context: Dict[str, Any] + ) -> ClaimValidationResult: + return await self.has_value_validator.validate(payload, user_context) + + def should_refetch( + self, payload: JSONObject, user_context: Dict[str, Any] + ) -> MaybeAwaitable[bool]: + value = self.claim.get_value_from_payload(payload, user_context) + last_refetch_time = self.claim.get_last_refetch_time(payload, user_context) + assert last_refetch_time is not None + return (value is None) or ( + value is False + and last_refetch_time + < (get_timestamp_ms() - self.refetch_time_on_false_in_ms) + ) + + +class EmailVerificationClaimValidators(BooleanClaimValidators): + def is_verified( + self, refetch_time_on_false_in_seconds: int = 10 + ) -> SessionClaimValidator: + has_value_res = self.has_value(True, "st-ev-is-verified") + assert isinstance(self.claim, BooleanClaim) + return IsVerifiedSCV( + self.claim, has_value_res, refetch_time_on_false_in_seconds + ) + + +class EmailVerificationClaimClass(BooleanClaim): + def __init__(self): + # TODO: see if it works when we use async instead of MaybeAwaitable? + def fetch_value( + user_id: str, user_context: Optional[Dict[str, Any]] + ) -> MaybeAwaitable[bool]: + if user_context is None: + user_context = {} # TODO: Verify if this is the right thing to do? + + recipe = EmailVerificationRecipe.get_instance() + email_info = recipe.get_email_for_user_id(user_id, user_context) + + if isinstance(email_info, GetEmailForUserIdOkResult): + return recipe.recipe_implementation.is_email_verified( + user_id, email_info.email, user_context + ) + if isinstance(email_info, EmailDoesnotExistError): + # we consider people without email addresses as validated + return True + raise Exception( + "Should never come here: UNKNOWN_USER_ID or invalid result from get_email_for_user" + ) + + super().__init__("st-ev", fetch_value) + + self.validators = EmailVerificationClaimValidators(claim=self) + + +EmailVerificationClaim = EmailVerificationClaimClass() diff --git a/supertokens_python/recipe/emailverification/interfaces.py b/supertokens_python/recipe/emailverification/interfaces.py index fcf1936ad..ebabe749c 100644 --- a/supertokens_python/recipe/emailverification/interfaces.py +++ b/supertokens_python/recipe/emailverification/interfaces.py @@ -14,10 +14,12 @@ from __future__ import annotations from abc import ABC, abstractmethod -from typing import TYPE_CHECKING, Any, Dict, Union +from typing import TYPE_CHECKING, Any, Dict, Union, Callable, Awaitable, Optional from supertokens_python.ingredients.emaildelivery import EmailDeliveryIngredient from supertokens_python.types import APIResponse, GeneralErrorResponse +from ..session.interfaces import SessionContainer +from ...supertokens import AppInfo if TYPE_CHECKING: from supertokens_python.framework import BaseRequest, BaseResponse @@ -98,6 +100,7 @@ def __init__( recipe_id: str, config: EmailVerificationConfig, recipe_implementation: RecipeInterface, + app_info: AppInfo, email_delivery: EmailDeliveryIngredient[VerificationEmailTemplateVars], ): self.request = request @@ -105,6 +108,7 @@ def __init__( self.recipe_id = recipe_id self.config = config self.recipe_implementation = recipe_implementation + self.app_info = app_info self.email_delivery = email_delivery @@ -161,7 +165,11 @@ def __init__(self): @abstractmethod async def email_verify_post( - self, token: str, api_options: APIOptions, user_context: Dict[str, Any] + self, + token: str, + api_options: APIOptions, + user_context: Dict[str, Any], + session: Optional[SessionContainer] = None, ) -> Union[ EmailVerifyPostOkResult, EmailVerifyPostInvalidTokenError, GeneralErrorResponse ]: @@ -169,16 +177,44 @@ async def email_verify_post( @abstractmethod async def is_email_verified_get( - self, api_options: APIOptions, user_context: Dict[str, Any] + self, + api_options: APIOptions, + user_context: Dict[str, Any], + session: Optional[SessionContainer] = None, ) -> Union[IsEmailVerifiedGetOkResult, GeneralErrorResponse]: pass @abstractmethod async def generate_email_verify_token_post( - self, api_options: APIOptions, user_context: Dict[str, Any] + self, + api_options: APIOptions, + user_context: Dict[str, Any], + session: SessionContainer, # TODO: Fix order of session arg to match the order is_email_verified_* (Applicable for all funcs in this file) ) -> Union[ GenerateEmailVerifyTokenPostOkResult, GenerateEmailVerifyTokenPostEmailAlreadyVerifiedError, GeneralErrorResponse, ]: pass + + +class GetEmailForUserIdOkResult: + def __init__(self, email: str): + # TODO: Write this? + self.email = email + + +class EmailDoesnotExistError(Exception): + pass + + +class UnknownUserIdError(Exception): + pass + + +TypeGetEmailForUserIdFunction = Callable[ + [str, Dict[str, Any]], + Awaitable[ + Union[GetEmailForUserIdOkResult, EmailDoesnotExistError, UnknownUserIdError] + ], +] diff --git a/supertokens_python/recipe/emailverification/recipe.py b/supertokens_python/recipe/emailverification/recipe.py index a13cfc5f7..136b8c848 100644 --- a/supertokens_python/recipe/emailverification/recipe.py +++ b/supertokens_python/recipe/emailverification/recipe.py @@ -14,7 +14,7 @@ from __future__ import annotations from os import environ -from typing import TYPE_CHECKING, List, Union +from typing import TYPE_CHECKING, List, Union, Any, Dict, Callable from supertokens_python.exceptions import SuperTokensError, raise_general_exception from supertokens_python.ingredients.emaildelivery import EmailDeliveryIngredient @@ -28,8 +28,16 @@ from supertokens_python.recipe_module import APIHandled, RecipeModule from .api.implementation import APIImplementation -from .interfaces import APIOptions +from .email_verification_claim import EmailVerificationClaim +from .interfaces import ( + APIOptions, + UnknownUserIdError, + TypeGetEmailForUserIdFunction, + GetEmailForUserIdOkResult, + EmailDoesnotExistError, +) from .recipe_implementation import RecipeImplementation +from ..session import SessionRecipe if TYPE_CHECKING: from supertokens_python.framework.request import BaseRequest @@ -87,6 +95,10 @@ def __init__( else: self.email_delivery = email_delivery_ingredient + self.get_email_for_user_id_funcs_from_other_recipes: List[ + TypeGetEmailForUserIdFunction + ] = [] + def is_error_from_this_recipe_based_on_instance(self, err: Exception) -> bool: return isinstance(err, SuperTokensError) and isinstance( err, SuperTokensEmailVerificationError @@ -122,28 +134,22 @@ async def handle_api_request( method: str, response: BaseResponse, ) -> Union[BaseResponse, None]: + api_options = APIOptions( + request, + response, + self.recipe_id, + self.config, + self.recipe_implementation, + self.get_app_info(), + self.email_delivery, + ) if request_id == USER_EMAIL_VERIFY_TOKEN: return await handle_generate_email_verify_token_api( - self.api_implementation, - APIOptions( - request, - response, - self.recipe_id, - self.config, - self.recipe_implementation, - self.email_delivery, - ), + self.api_implementation, api_options ) return await handle_email_verify_api( self.api_implementation, - APIOptions( - request, - response, - self.recipe_id, - self.config, - self.recipe_implementation, - self.email_delivery, - ), + api_options, ) async def handle_error( @@ -171,6 +177,15 @@ def func(app_info: AppInfo): config, ingredients=ingredients, ) + # TODO: Supertokens init callback: + SessionRecipe.get_instance().add_claim_from_other_recipe( + EmailVerificationClaim + ) + if config.mode == "REQUIRED": + SessionRecipe.get_instance().add_claim_validator_from_other_recipe( + EmailVerificationClaim.validators.is_verified() + ) + return EmailVerificationRecipe.__instance raise_general_exception( "Emailverification recipe has already been initialised. Please check your code for bugs." @@ -193,3 +208,21 @@ def reset(): ): raise_general_exception("calling testing function in non testing env") EmailVerificationRecipe.__instance = None + + async def get_email_for_user_id( + self, user_id: str, user_context: Dict[str, Any] + ) -> Union[GetEmailForUserIdOkResult, EmailDoesnotExistError, UnknownUserIdError]: + if self.config.get_email_for_user_id is not None: + res = await self.config.get_email_for_user_id(user_id, user_context) + if not isinstance(res, UnknownUserIdError): + return res + + for f in self.get_email_for_user_id_funcs_from_other_recipes: + res = await f(user_id, user_context) + if not isinstance(res, UnknownUserIdError): + return res + + return UnknownUserIdError() + + def add_get_email_for_user_id_func(self, f: Callable[[str, Dict[str, Any]], Any]): + self.get_email_for_user_id_funcs_from_other_recipes.append(f) diff --git a/supertokens_python/recipe/emailverification/utils.py b/supertokens_python/recipe/emailverification/utils.py index dc7c1f302..4d2f53bd9 100644 --- a/supertokens_python/recipe/emailverification/utils.py +++ b/supertokens_python/recipe/emailverification/utils.py @@ -13,8 +13,8 @@ # under the License. from __future__ import annotations - -from typing import TYPE_CHECKING, Any, Dict +from typing_extensions import Literal +from typing import TYPE_CHECKING, Any, Dict, Optional from supertokens_python.ingredients.emaildelivery.types import ( EmailDeliveryConfig, @@ -30,21 +30,8 @@ from supertokens_python.supertokens import AppInfo - from .interfaces import APIInterface, RecipeInterface - from .types import User, VerificationEmailTemplateVars - - -def default_get_email_verification_url( - app_info: AppInfo, -) -> Callable[[User, Dict[str, Any]], Awaitable[str]]: - async def func(_: User, __: Dict[str, Any]): - return ( - app_info.website_domain.get_as_string_dangerous() - + app_info.website_base_path.get_as_string_dangerous() - + "/verify-email" - ) - - return func + from .interfaces import APIInterface, RecipeInterface, TypeGetEmailForUserIdFunction + from .types import User, VerificationEmailTemplateVars, EmailTemplateVars class OverrideConfig: @@ -58,25 +45,22 @@ def __init__( class ParentRecipeEmailVerificationConfig: + # TODO: Now that this class is public, we might want to rename this? def __init__( self, - get_email_for_user_id: Callable[[str, Dict[str, Any]], Awaitable[str]], - override: Union[OverrideConfig, None] = None, - get_email_verification_url: Union[ - Callable[[User, Dict[str, Any]], Awaitable[str]], None - ] = None, + mode: Literal["REQUIRED", "OPTIONAL"], + email_delivery: Union[EmailDeliveryConfig[EmailTemplateVars], None] = None, + get_email_for_user_id: Optional[TypeGetEmailForUserIdFunction] = None, create_and_send_custom_email: Union[ Callable[[User, str, Dict[str, Any]], Awaitable[None]], None ] = None, - email_delivery: Union[ - EmailDeliveryConfig[VerificationEmailTemplateVars], None - ] = None, + override: Union[OverrideConfig, None] = None, ): - self.override = override - self.get_email_verification_url = get_email_verification_url - self.get_email_for_user_id = get_email_for_user_id + self.mode = mode self.email_delivery = email_delivery + self.get_email_for_user_id = get_email_for_user_id self.create_and_send_custom_email = create_and_send_custom_email + self.override = override if create_and_send_custom_email: # Note: This will appear twice because `InputEmailVerificationConfig` will also produce same warning. @@ -88,17 +72,17 @@ def __init__( class EmailVerificationConfig: def __init__( self, - override: OverrideConfig, - get_email_verification_url: Callable[[User, Dict[str, Any]], Awaitable[str]], - get_email_for_user_id: Callable[[str, Dict[str, Any]], Awaitable[str]], + mode: str, # TODO: Literal["REQUIRED", "OPTIONAL"] had to be removed because of pyright. Possible to avoid? get_email_delivery_config: Callable[ [], EmailDeliveryConfigWithService[VerificationEmailTemplateVars] ], + get_email_for_user_id: Optional[TypeGetEmailForUserIdFunction], + override: OverrideConfig, ): - self.get_email_for_user_id = get_email_for_user_id - self.get_email_verification_url = get_email_verification_url + self.mode: str = mode # TODO: How to avoid ": str" self.override = override self.get_email_delivery_config = get_email_delivery_config + self.get_email_for_user_id = get_email_for_user_id def validate_and_normalise_user_input( @@ -109,12 +93,6 @@ def validate_and_normalise_user_input( "config must be an instance of ParentRecipeEmailVerificationConfig" ) - get_email_verification_url = ( - config.get_email_verification_url - if config.get_email_verification_url is not None - else default_get_email_verification_url(app_info) - ) - def get_email_delivery_config() -> EmailDeliveryConfigWithService[ VerificationEmailTemplateVars ]: @@ -144,8 +122,8 @@ def get_email_delivery_config() -> EmailDeliveryConfigWithService[ override = OverrideConfig() return EmailVerificationConfig( - override, - get_email_verification_url, - config.get_email_for_user_id, + config.mode, get_email_delivery_config, + config.get_email_for_user_id, + override, ) diff --git a/supertokens_python/recipe/session/asyncio/__init__.py b/supertokens_python/recipe/session/asyncio/__init__.py index 2563fa84f..26fb2c121 100644 --- a/supertokens_python/recipe/session/asyncio/__init__.py +++ b/supertokens_python/recipe/session/asyncio/__init__.py @@ -13,6 +13,8 @@ # under the License. from typing import Any, Dict, List, Union, TypeVar, Callable, Optional +# TODO: Finalize how to import Optional + from supertokens_python.recipe.openid.interfaces import ( GetOpenIdDiscoveryConfigurationResult, ) @@ -37,6 +39,8 @@ GetJWKSResult, ) +# TODO: https://github.com/supertokens/supertokens-python/pull/209#discussion_r932049999 +# There have been changes to this function as well. See this: _T = TypeVar("_T") diff --git a/supertokens_python/recipe/session/claim_base_classes/primitive_claim.py b/supertokens_python/recipe/session/claim_base_classes/primitive_claim.py index 88b4f5e42..e71730f69 100644 --- a/supertokens_python/recipe/session/claim_base_classes/primitive_claim.py +++ b/supertokens_python/recipe/session/claim_base_classes/primitive_claim.py @@ -127,6 +127,7 @@ async def validate( class PrimitiveClaimValidators(Generic[_T]): + # TODO: We should discuss how someone would override existing validators def __init__(self, claim: SessionClaim[_T]) -> None: self.claim = claim @@ -171,6 +172,9 @@ def add_to_payload_( def remove_from_payload_by_merge_( self, payload: JSONObject, user_context: Dict[str, Any] ) -> JSONObject: + # TODO: https://github.com/supertokens/supertokens-python/pull/209#discussion_r931922854 + # Not sure if this will actually work in python -> cause there is no diff between "null" and + # "undefined" in python (both are None). So setting this to None is as good as deleting it from the map? payload[self.key] = None return payload diff --git a/supertokens_python/recipe/session/interfaces.py b/supertokens_python/recipe/session/interfaces.py index 42b4f1602..a2d038e64 100644 --- a/supertokens_python/recipe/session/interfaces.py +++ b/supertokens_python/recipe/session/interfaces.py @@ -211,6 +211,7 @@ async def update_access_token_payload( user_context: Dict[str, Any], ) -> bool: # TODO: Deprecate this method. + # TODO: need to mark updateAccessTokenPayload as deprecated """DEPRECATED: Use merge_into_access_token_payload instead""" @abstractmethod @@ -604,5 +605,8 @@ async def validate( def should_refetch( # pylint: disable=no-self-use self, payload: JSONObject, user_context: Dict[str, Any] ) -> MaybeAwaitable[bool]: + # TODO: https://github.com/supertokens/supertokens-python/pull/209#discussion_r932121943 + # TODO: This should also be an abstractmethod + # TODO: This should also be async _, __ = payload, user_context return False diff --git a/supertokens_python/recipe/session/session_class.py b/supertokens_python/recipe/session/session_class.py index e8b2483b5..aa3fd982b 100644 --- a/supertokens_python/recipe/session/session_class.py +++ b/supertokens_python/recipe/session/session_class.py @@ -171,8 +171,8 @@ async def set_claim_value( return await self.merge_into_access_token_payload(update, user_context) async def get_claim_value( - self, claim: SessionClaim[Any], user_context: Union[Dict[str, Any], None] = None - ) -> Union[Any, None]: + self, claim: SessionClaim[_T], user_context: Union[Dict[str, Any], None] = None + ) -> Union[_T, None]: if user_context is None: user_context = {} diff --git a/supertokens_python/recipe/session/with_jwt/recipe_implementation.py b/supertokens_python/recipe/session/with_jwt/recipe_implementation.py index d4ddecd93..56ab2a474 100644 --- a/supertokens_python/recipe/session/with_jwt/recipe_implementation.py +++ b/supertokens_python/recipe/session/with_jwt/recipe_implementation.py @@ -15,6 +15,8 @@ from typing import TYPE_CHECKING, Any, Dict, Union +# TODO: Missing changes for session_class inside with_jwt? supertokens/supertokens-node#278 (files) + from jwt import decode from supertokens_python.utils import get_timestamp_ms diff --git a/supertokens_python/recipe/thirdparty/utils.py b/supertokens_python/recipe/thirdparty/utils.py index ebdd92fe7..df13a34a9 100644 --- a/supertokens_python/recipe/thirdparty/utils.py +++ b/supertokens_python/recipe/thirdparty/utils.py @@ -90,14 +90,11 @@ def __init__(self, providers: List[Provider]): class InputEmailVerificationConfig: def __init__( self, - get_email_verification_url: Union[ - Callable[[User, Dict[str, Any]], Awaitable[str]], None - ] = None, + # TODO: Marker: Removed get_email_verification_url create_and_send_custom_email: Union[ Callable[[User, str, Dict[str, Any]], Awaitable[None]], None ] = None, ): - self.get_email_verification_url = get_email_verification_url self.create_and_send_custom_email = create_and_send_custom_email if create_and_send_custom_email: deprecated_warn( @@ -145,22 +142,16 @@ def validate_and_normalise_email_verification_config( override: InputOverrideConfig, ) -> ParentRecipeEmailVerificationConfig: create_and_send_custom_email = None - get_email_verification_url = None if config is None: config = InputEmailVerificationConfig() if config.create_and_send_custom_email is not None: create_and_send_custom_email = email_verification_create_and_send_custom_email( recipe, config.create_and_send_custom_email ) - if config.get_email_verification_url is not None: - get_email_verification_url = email_verification_get_email_verification_url( - recipe, config.get_email_verification_url - ) return ParentRecipeEmailVerificationConfig( - get_email_for_user_id=recipe.get_email_for_user_id, + mode="OPTIONAL", # TODO: FIXME? create_and_send_custom_email=create_and_send_custom_email, - get_email_verification_url=get_email_verification_url, override=override.email_verification_feature, ) diff --git a/supertokens_python/recipe/thirdpartyemailpassword/emaildelivery/services/backward_compatibility/__init__.py b/supertokens_python/recipe/thirdpartyemailpassword/emaildelivery/services/backward_compatibility/__init__.py index 86ed1adb6..f339812a8 100644 --- a/supertokens_python/recipe/thirdpartyemailpassword/emaildelivery/services/backward_compatibility/__init__.py +++ b/supertokens_python/recipe/thirdpartyemailpassword/emaildelivery/services/backward_compatibility/__init__.py @@ -13,7 +13,7 @@ # under the License. from __future__ import annotations -from typing import TYPE_CHECKING, Any, Dict, Union, cast +from typing import Any, Dict, Union from supertokens_python.ingredients.emaildelivery.types import EmailDeliveryInterface from supertokens_python.recipe.emailpassword.emaildelivery.services.backward_compatibility import ( @@ -22,89 +22,35 @@ from supertokens_python.recipe.emailpassword.interfaces import ( RecipeInterface as EPRecipeInterface, ) -from supertokens_python.recipe.emailpassword.utils import ( - InputEmailVerificationConfig as EPInputEmailVerificationConfig, -) from supertokens_python.recipe.emailpassword.utils import ( InputResetPasswordUsingTokenFeature, ) -from supertokens_python.recipe.emailverification.emaildelivery.services.backward_compatibility import ( - BackwardCompatibilityService as EVBackwardCompatibilityService, -) -from supertokens_python.recipe.emailverification.types import User as EVUser -from supertokens_python.recipe.thirdpartyemailpassword.interfaces import RecipeInterface from supertokens_python.recipe.thirdpartyemailpassword.types import ( EmailTemplateVars, - VerificationEmailTemplateVars, ) from supertokens_python.supertokens import AppInfo -if TYPE_CHECKING: - from supertokens_python.recipe.thirdpartyemailpassword.utils import ( - InputEmailVerificationConfig, - ) - class BackwardCompatibilityService(EmailDeliveryInterface[EmailTemplateVars]): ep_backward_compatiblity_service: EPBackwardCompatibilityService - ev_backward_compatiblity_service: EVBackwardCompatibilityService def __init__( self, app_info: AppInfo, - recipe_interface_impl: RecipeInterface, ep_recipe_interface_impl: EPRecipeInterface, reset_password_using_token_feature: Union[ InputResetPasswordUsingTokenFeature, None ] = None, - email_verification_feature: Union[InputEmailVerificationConfig, None] = None, ) -> None: - create_and_send_custom_email_wrapper = None - input_create_and_send_custom_email = ( - email_verification_feature.create_and_send_custom_email - if email_verification_feature is not None - else None - ) - - if input_create_and_send_custom_email: - - async def create_and_send_custom_email_( - user: EVUser, link: str, user_context: Dict[str, Any] - ): - user_info = await recipe_interface_impl.get_user_by_id( - user.user_id, user_context - ) - if user_info is None: - raise Exception("Unknown User ID provided") - - return await input_create_and_send_custom_email( - user_info, link, user_context - ) - - create_and_send_custom_email_wrapper = create_and_send_custom_email_ - - self.ev_backward_compatiblity_service = EVBackwardCompatibilityService( - app_info, create_and_send_custom_email_wrapper - ) - - ep_email_verification_feature = cast( - EPInputEmailVerificationConfig, email_verification_feature - ) self.ep_backward_compatiblity_service = EPBackwardCompatibilityService( app_info, ep_recipe_interface_impl, reset_password_using_token_feature, - ep_email_verification_feature, ) async def send_email( self, template_vars: EmailTemplateVars, user_context: Dict[str, Any] ) -> None: - if isinstance(template_vars, VerificationEmailTemplateVars): - await self.ev_backward_compatiblity_service.send_email( - template_vars, user_context - ) - await self.ep_backward_compatiblity_service.send_email( template_vars, user_context ) diff --git a/supertokens_python/recipe/thirdpartyemailpassword/recipe.py b/supertokens_python/recipe/thirdpartyemailpassword/recipe.py index 82dae0783..8bc0cfabd 100644 --- a/supertokens_python/recipe/thirdpartyemailpassword/recipe.py +++ b/supertokens_python/recipe/thirdpartyemailpassword/recipe.py @@ -178,11 +178,9 @@ def apis_override_email_password(_: EmailPasswordAPIInterface): ep_ingredients, self.config.sign_up_feature, self.config.reset_password_using_token_feature, - None, EPOverrideConfig( func_override_email_password, apis_override_email_password ), - self.email_verification_recipe, ) if third_party_recipe is not None: diff --git a/supertokens_python/recipe/thirdpartyemailpassword/types.py b/supertokens_python/recipe/thirdpartyemailpassword/types.py index 09f4b0ea2..8b74b0b7a 100644 --- a/supertokens_python/recipe/thirdpartyemailpassword/types.py +++ b/supertokens_python/recipe/thirdpartyemailpassword/types.py @@ -40,7 +40,7 @@ def __init__( # Export: EmailTemplateVars = ep_types.EmailTemplateVars -VerificationEmailTemplateVars = ep_types.VerificationEmailTemplateVars +# VerificationEmailTemplateVars = ep_types.VerificationEmailTemplateVars PasswordResetEmailTemplateVars = ep_types.PasswordResetEmailTemplateVars SMTPOverrideInput = SMTPServiceInterface[EmailTemplateVars] diff --git a/supertokens_python/recipe/thirdpartyemailpassword/utils.py b/supertokens_python/recipe/thirdpartyemailpassword/utils.py index 8c73ed017..1cf82d728 100644 --- a/supertokens_python/recipe/thirdpartyemailpassword/utils.py +++ b/supertokens_python/recipe/thirdpartyemailpassword/utils.py @@ -48,14 +48,11 @@ class InputEmailVerificationConfig: def __init__( self, - get_email_verification_url: Union[ - Callable[[User, Any], Awaitable[str]], None - ] = None, + # TODO: Marker: get_email_verification_url removed create_and_send_custom_email: Union[ Callable[[User, str, Any], Awaitable[None]], None ] = None, ): - self.get_email_verification_url = get_email_verification_url self.create_and_send_custom_email = create_and_send_custom_email if create_and_send_custom_email: deprecated_warn( @@ -103,22 +100,16 @@ def validate_and_normalise_email_verification_config( override: InputOverrideConfig, ) -> ParentRecipeEmailVerificationConfig: create_and_send_custom_email = None - get_email_verification_url = None if config is None: config = InputEmailVerificationConfig() if config.create_and_send_custom_email is not None: create_and_send_custom_email = email_verification_create_and_send_custom_email( recipe, config.create_and_send_custom_email ) - if config.get_email_verification_url is not None: - get_email_verification_url = email_verification_get_email_verification_url( - recipe, config.get_email_verification_url - ) return ParentRecipeEmailVerificationConfig( - get_email_for_user_id=recipe.get_email_for_user_id, + mode="OPTIONAL", # TODO: FIXME? create_and_send_custom_email=create_and_send_custom_email, - get_email_verification_url=get_email_verification_url, override=override.email_verification_feature, ) @@ -218,10 +209,8 @@ def get_email_delivery_config( email_service = BackwardCompatibilityService( app_info=recipe.app_info, - recipe_interface_impl=recipe_interface_impl, ep_recipe_interface_impl=ep_recipe_interface_impl, reset_password_using_token_feature=reset_password_using_token_feature, - email_verification_feature=email_verification_feature, ) if email_delivery is not None and email_delivery.override is not None: override = email_delivery.override diff --git a/supertokens_python/recipe/thirdpartypasswordless/utils.py b/supertokens_python/recipe/thirdpartypasswordless/utils.py index 060acc260..b310c0ef9 100644 --- a/supertokens_python/recipe/thirdpartypasswordless/utils.py +++ b/supertokens_python/recipe/thirdpartypasswordless/utils.py @@ -121,22 +121,16 @@ def validate_and_normalise_email_verification_config( override: InputOverrideConfig, ) -> ParentRecipeEmailVerificationConfig: create_and_send_custom_email = None - get_email_verification_url = None if config is None: config = InputEmailVerificationConfig() if config.create_and_send_custom_email is not None: create_and_send_custom_email = email_verification_create_and_send_custom_email( recipe, config.create_and_send_custom_email ) - if config.get_email_verification_url is not None: - get_email_verification_url = email_verification_get_email_verification_url( - recipe, config.get_email_verification_url - ) return ParentRecipeEmailVerificationConfig( - get_email_for_user_id=recipe.get_email_for_user_id, + mode="OPTIONAL", # TODO: FIXME? create_and_send_custom_email=create_and_send_custom_email, - get_email_verification_url=get_email_verification_url, override=override.email_verification_feature, ) From 07fe952dd3575872784ee8dd5d465bb1deb19250 Mon Sep 17 00:00:00 2001 From: KShivendu Date: Mon, 8 Aug 2022 12:47:38 +0530 Subject: [PATCH 03/18] fix: Fix lint errors and make email verification claims usable --- .../recipe/emailpassword/recipe.py | 1 - .../recipe/emailpassword/utils.py | 1 - .../emailverification/api/implementation.py | 10 +-- .../emailverification/asyncio/__init__.py | 82 +++++++++++++++++-- .../email_verification_claim.py | 1 + .../recipe/thirdpartyemailpassword/recipe.py | 4 +- .../recipe/thirdpartyemailpassword/utils.py | 3 +- tests/auth-react/django3x/mysite/utils.py | 9 +- tests/auth-react/fastapi-server/app.py | 10 ++- tests/auth-react/flask-server/app.py | 12 ++- tests/emailpassword/test_emaildelivery.py | 3 +- tests/emailpassword/test_emailverify.py | 64 +++++++++++---- .../input_validation/test_input_validation.py | 16 ++-- 13 files changed, 169 insertions(+), 47 deletions(-) diff --git a/supertokens_python/recipe/emailpassword/recipe.py b/supertokens_python/recipe/emailpassword/recipe.py index 0f26b3178..35c3f935b 100644 --- a/supertokens_python/recipe/emailpassword/recipe.py +++ b/supertokens_python/recipe/emailpassword/recipe.py @@ -85,7 +85,6 @@ def __init__( ): super().__init__(recipe_id, app_info) self.config = validate_and_normalise_user_input( - self, app_info, sign_up_feature, reset_password_using_token_feature, diff --git a/supertokens_python/recipe/emailpassword/utils.py b/supertokens_python/recipe/emailpassword/utils.py index 356289fe8..d94b9de2c 100644 --- a/supertokens_python/recipe/emailpassword/utils.py +++ b/supertokens_python/recipe/emailpassword/utils.py @@ -353,7 +353,6 @@ def __init__( def validate_and_normalise_user_input( - recipe: EmailPasswordRecipe, app_info: AppInfo, sign_up_feature: Union[InputSignUpFeature, None] = None, reset_password_using_token_feature: Union[ diff --git a/supertokens_python/recipe/emailverification/api/implementation.py b/supertokens_python/recipe/emailverification/api/implementation.py index cb7eb1726..64b3d2694 100644 --- a/supertokens_python/recipe/emailverification/api/implementation.py +++ b/supertokens_python/recipe/emailverification/api/implementation.py @@ -105,7 +105,7 @@ async def generate_email_verify_token_post( user_id, ) return GenerateEmailVerifyTokenPostEmailAlreadyVerifiedError() - elif isinstance(email_info, GetEmailForUserIdOkResult): + if isinstance(email_info, GetEmailForUserIdOkResult): response = ( await api_options.recipe_implementation.create_email_verification_token( user_id, @@ -143,7 +143,7 @@ async def generate_email_verify_token_post( email_verification_email_delivery_input, user_context ) return GenerateEmailVerifyTokenPostOkResult() - else: - raise Exception( - "Should never come here: UNKNOWN_USER_ID or invalid result from get_email_for_user_id" - ) + + raise Exception( + "Should never come here: UNKNOWN_USER_ID or invalid result from get_email_for_user_id" + ) diff --git a/supertokens_python/recipe/emailverification/asyncio/__init__.py b/supertokens_python/recipe/emailverification/asyncio/__init__.py index 54b7b633a..e848b520d 100644 --- a/supertokens_python/recipe/emailverification/asyncio/__init__.py +++ b/supertokens_python/recipe/emailverification/asyncio/__init__.py @@ -10,18 +10,35 @@ # 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 Any, Dict, Union +from typing import Any, Dict, Union, Optional +from supertokens_python.recipe.emailverification.interfaces import ( + GetEmailForUserIdOkResult, + EmailDoesnotExistError, + CreateEmailVerificationTokenEmailAlreadyVerifiedError, +) from supertokens_python.recipe.emailverification.types import EmailTemplateVars from supertokens_python.recipe.emailverification.recipe import EmailVerificationRecipe async def create_email_verification_token( - user_id: str, email: str, user_context: Union[None, Dict[str, Any]] = None + user_id: str, + email: Optional[str] = None, + user_context: Union[None, Dict[str, Any]] = None, ): if user_context is None: user_context = {} - return await EmailVerificationRecipe.get_instance().recipe_implementation.create_email_verification_token( + recipe = EmailVerificationRecipe.get_instance() + if email is None: + email_info = await recipe.get_email_for_user_id(user_id, user_context) + if isinstance(email_info, GetEmailForUserIdOkResult): + email = email_info.email + elif isinstance(email_info, EmailDoesnotExistError): + return CreateEmailVerificationTokenEmailAlreadyVerifiedError() + else: + raise Exception("Unknown User ID provided without email") + + return await recipe.recipe_implementation.create_email_verification_token( user_id, email, user_context ) @@ -37,20 +54,73 @@ async def verify_email_using_token( async def is_email_verified( - user_id: str, email: str, user_context: Union[None, Dict[str, Any]] = None + user_id: str, + email: Optional[str] = None, + user_context: Union[None, Dict[str, Any]] = None, +): + if user_context is None: + user_context = {} + + recipe = EmailVerificationRecipe.get_instance() + if email is None: + email_info = await recipe.get_email_for_user_id(user_id, user_context) + if isinstance(email_info, GetEmailForUserIdOkResult): + email = email_info.email + elif isinstance(email_info, EmailDoesnotExistError): + return True + else: + raise Exception("Unknown User ID provided without email") + + return await recipe.recipe_implementation.is_email_verified( + user_id, email, user_context + ) + + +async def revoke_email_verification_token( + user_id: str, + email: Optional[str] = None, + user_context: Optional[Dict[str, Any]] = None, ): if user_context is None: user_context = {} - return await EmailVerificationRecipe.get_instance().recipe_implementation.is_email_verified( + + recipe = EmailVerificationRecipe.get_instance() + if email is None: + email_info = await recipe.get_email_for_user_id(user_id, user_context) + if isinstance(email_info, GetEmailForUserIdOkResult): + email = email_info.email + elif isinstance(email_info, EmailDoesnotExistError): + # Here we are returning OK since that's how it used to work, but a later call + # to is_verified will still return true + return CreateEmailVerificationTokenEmailAlreadyVerifiedError() + else: + raise Exception("Unknown User ID provided without email") + + return await EmailVerificationRecipe.get_instance().recipe_implementation.revoke_email_verification_tokens( user_id, email, user_context ) async def unverify_email( - user_id: str, email: str, user_context: Union[None, Dict[str, Any]] = None + user_id: str, + email: Optional[str] = None, + user_context: Union[None, Dict[str, Any]] = None, ): if user_context is None: user_context = {} + + recipe = EmailVerificationRecipe.get_instance() + if email is None: + email_info = await recipe.get_email_for_user_id(user_id, user_context) + if isinstance(email_info, GetEmailForUserIdOkResult): + email = email_info.email + elif isinstance(email_info, EmailDoesnotExistError): + # Here we are returning OK since that's how it used to work, but a later call + # to is_verified will still return true + return "OK" # TODO: Make a class for this + else: + raise Exception("Unknown User ID provided without email") + return await EmailVerificationRecipe.get_instance().recipe_implementation.unverify_email( user_id, email, user_context ) diff --git a/supertokens_python/recipe/emailverification/email_verification_claim.py b/supertokens_python/recipe/emailverification/email_verification_claim.py index fc01c6233..3f6848e20 100644 --- a/supertokens_python/recipe/emailverification/email_verification_claim.py +++ b/supertokens_python/recipe/emailverification/email_verification_claim.py @@ -11,6 +11,7 @@ # 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 Dict, Any, Optional from supertokens_python.recipe.emailverification import EmailVerificationRecipe diff --git a/supertokens_python/recipe/thirdpartyemailpassword/recipe.py b/supertokens_python/recipe/thirdpartyemailpassword/recipe.py index 8bc0cfabd..893016538 100644 --- a/supertokens_python/recipe/thirdpartyemailpassword/recipe.py +++ b/supertokens_python/recipe/thirdpartyemailpassword/recipe.py @@ -138,9 +138,7 @@ def __init__( email_delivery_ingredient = ingredients.email_delivery if email_delivery_ingredient is None: self.email_delivery = EmailDeliveryIngredient( - self.config.get_email_delivery_config( - self.recipe_implementation, ep_recipe_implementation - ) + self.config.get_email_delivery_config(ep_recipe_implementation) ) else: self.email_delivery = email_delivery_ingredient diff --git a/supertokens_python/recipe/thirdpartyemailpassword/utils.py b/supertokens_python/recipe/thirdpartyemailpassword/utils.py index 1cf82d728..e931e4358 100644 --- a/supertokens_python/recipe/thirdpartyemailpassword/utils.py +++ b/supertokens_python/recipe/thirdpartyemailpassword/utils.py @@ -146,7 +146,7 @@ def __init__( InputResetPasswordUsingTokenFeature, None ], get_email_delivery_config: Callable[ - [RecipeInterface, EPRecipeInterface], + [EPRecipeInterface], EmailDeliveryConfigWithService[EmailTemplateVars], ], override: OverrideConfig, @@ -199,7 +199,6 @@ def validate_and_normalise_user_input( override = InputOverrideConfig() def get_email_delivery_config( - recipe_interface_impl: RecipeInterface, ep_recipe_interface_impl: EPRecipeInterface, ): if email_delivery and email_delivery.service: diff --git a/tests/auth-react/django3x/mysite/utils.py b/tests/auth-react/django3x/mysite/utils.py index 3cb5db4e2..ab8d03c0d 100644 --- a/tests/auth-react/django3x/mysite/utils.py +++ b/tests/auth-react/django3x/mysite/utils.py @@ -25,6 +25,7 @@ InputFormField, User, ) +from supertokens_python.recipe.emailverification.types import User as EVUser from supertokens_python.recipe.emailverification import ( EmailVerificationRecipe, ParentRecipeEmailVerificationConfig, @@ -117,6 +118,12 @@ async def save_code_text( ) +async def ev_create_and_send_custom_email( + _: EVUser, url_with_token: str, __: Dict[str, Any] +): + save_url_with_token(url_with_token) + + async def create_and_send_custom_email( _: User, url_with_token: str, __: Dict[str, Any] ): @@ -842,7 +849,7 @@ async def authorisation_url_get( emailverification.init( ParentRecipeEmailVerificationConfig( mode="REQUIRED", - create_and_send_custom_email=create_and_send_custom_email, + create_and_send_custom_email=ev_create_and_send_custom_email, # TODO: Is it correct to create a seperate func for this? override=EVInputOverrideConfig(apis=override_email_verification_apis), ) ), diff --git a/tests/auth-react/fastapi-server/app.py b/tests/auth-react/fastapi-server/app.py index c238702f5..94b6867ab 100644 --- a/tests/auth-react/fastapi-server/app.py +++ b/tests/auth-react/fastapi-server/app.py @@ -118,6 +118,7 @@ ) from supertokens_python.types import GeneralErrorResponse from typing_extensions import Literal +from supertokens_python.recipe.emailverification.types import User as EVUser load_dotenv() @@ -193,6 +194,13 @@ def get_website_domain(): latest_url_with_token = None +async def ev_create_and_send_custom_email( + _: EVUser, url_with_token: str, __: Dict[str, Any] +) -> None: + global latest_url_with_token + latest_url_with_token = url_with_token + + async def create_and_send_custom_email( _: User, url_with_token: str, __: Dict[str, Any] ) -> None: @@ -898,7 +906,7 @@ async def authorisation_url_get( emailverification.init( ParentRecipeEmailVerificationConfig( mode="REQUIRED", - create_and_send_custom_email=create_and_send_custom_email, + create_and_send_custom_email=ev_create_and_send_custom_email, # TODO: Is this correct? override=EVInputOverrideConfig(apis=override_email_verification_apis), ) ), diff --git a/tests/auth-react/flask-server/app.py b/tests/auth-react/flask-server/app.py index c18e45878..135cb06ef 100644 --- a/tests/auth-react/flask-server/app.py +++ b/tests/auth-react/flask-server/app.py @@ -109,6 +109,7 @@ ) from supertokens_python.types import GeneralErrorResponse from typing_extensions import Literal +from supertokens_python.recipe.emailverification.types import User as EVUser load_dotenv() @@ -160,6 +161,13 @@ async def save_code_text( code_store[param.pre_auth_session_id] = codes +async def ev_create_and_send_custom_email( + _: EVUser, url_with_token: str, __: Dict[str, Any] +) -> None: + global latest_url_with_token + latest_url_with_token = url_with_token + + async def create_and_send_custom_email( _: User, url_with_token: str, __: Dict[str, Any] ) -> None: @@ -283,7 +291,7 @@ async def email_verify_post( token: str, api_options: EVAPIOptions, user_context: Dict[str, Any], - session: Optional[SessionContainer], + session: Optional[SessionContainer] = None, ): is_general_error = await check_for_general_error( "body", api_options.request @@ -885,7 +893,7 @@ async def authorisation_url_get( emailverification.init( ParentRecipeEmailVerificationConfig( mode="REQUIRED", - create_and_send_custom_email=create_and_send_custom_email, + create_and_send_custom_email=ev_create_and_send_custom_email, override=EVInputOverrideConfig(apis=override_email_verification_apis), ) ), diff --git a/tests/emailpassword/test_emaildelivery.py b/tests/emailpassword/test_emaildelivery.py index 591d4fa2f..7f14d4bbe 100644 --- a/tests/emailpassword/test_emaildelivery.py +++ b/tests/emailpassword/test_emaildelivery.py @@ -44,6 +44,7 @@ from supertokens_python.recipe.emailverification.types import ( VerificationEmailTemplateVars, ) +from supertokens_python.recipe.emailverification.types import User as EVUser from supertokens_python.recipe.emailverification.utils import ( ParentRecipeEmailVerificationConfig, ) @@ -639,7 +640,7 @@ async def test_email_verification_backward_compatibility( email_verify_url = "" async def custom_create_and_send_custom_email( - user: EPUser, email_verification_link: str, _: Dict[str, Any] + user: EVUser, email_verification_link: str, _: Dict[str, Any] ): nonlocal email, email_verify_url email = user.email diff --git a/tests/emailpassword/test_emailverify.py b/tests/emailpassword/test_emailverify.py index ed6e08eb2..305216776 100644 --- a/tests/emailpassword/test_emailverify.py +++ b/tests/emailpassword/test_emailverify.py @@ -13,7 +13,7 @@ # under the License. import asyncio import json -from typing import Any, Dict, Union +from typing import Any, Dict, Union, Optional from fastapi import FastAPI from fastapi.requests import Request @@ -31,7 +31,6 @@ unverify_email, verify_email_using_token, ) -from supertokens_python.recipe.emailpassword.types import User from supertokens_python.recipe.emailverification.interfaces import ( APIInterface, APIOptions, @@ -299,11 +298,11 @@ async def test_the_generate_token_api_with_an_expired_access_token_and_see_that_ async def test_that_providing_your_own_email_callback_and_make_sure_it_is_called( driver_config_client: TestClient, ): - user_info: Union[None, User] = None + user_info: Union[None, EVUser] = None email_token = None async def custom_f( - user: User, email_verification_url_token: str, _: Dict[str, Any] + user: EVUser, email_verification_url_token: str, _: Dict[str, Any] ): nonlocal user_info, email_token user_info = user @@ -361,7 +360,9 @@ async def custom_f( async def test_the_email_verify_api_with_valid_input(driver_config_client: TestClient): token = None - async def custom_f(_: User, email_verification_url_token: str, __: Dict[str, Any]): + async def custom_f( + _: EVUser, email_verification_url_token: str, __: Dict[str, Any] + ): nonlocal token token = email_verification_url_token.split("?token=")[1].split("&rid=")[0] @@ -432,7 +433,9 @@ async def test_the_email_verify_api_with_invalid_token_and_check_error( ): token = None - async def custom_f(_: User, email_verification_url_token: str, __: Dict[str, Any]): + async def custom_f( + _: EVUser, email_verification_url_token: str, __: Dict[str, Any] + ): nonlocal token token = email_verification_url_token.split("?token=")[1].split("&rid=")[0] @@ -503,7 +506,9 @@ async def test_the_email_verify_api_with_token_of_not_type_string( ): token = None - async def custom_f(_: User, email_verification_url_token: str, __: Dict[str, Any]): + async def custom_f( + _: EVUser, email_verification_url_token: str, __: Dict[str, Any] + ): nonlocal token token = email_verification_url_token.split("?token=")[1].split("&rid=")[0] @@ -576,7 +581,9 @@ async def test_that_the_handle_post_email_verification_callback_is_called_on_suc token = None user_info_from_callback: Union[None, EVUser] = None - async def custom_f(_: User, email_verification_url_token: str, __: Dict[str, Any]): + async def custom_f( + _: EVUser, email_verification_url_token: str, __: Dict[str, Any] + ): nonlocal token token = email_verification_url_token.split("?token=")[1].split("&rid=")[0] @@ -584,11 +591,14 @@ def apis_override_email_password(param: APIInterface): temp = param.email_verify_post async def email_verify_post( - token: str, api_options: APIOptions, user_context: Dict[str, Any] + token: str, + api_options: APIOptions, + user_context: Dict[str, Any], + session: Optional[SessionContainer] = None, ): nonlocal user_info_from_callback - response = await temp(token, api_options, user_context) + response = await temp(token, api_options, user_context, session) if isinstance(response, EmailVerifyPostOkResult): user_info_from_callback = response.user @@ -674,7 +684,9 @@ async def test_the_email_verify_with_valid_input_using_the_get_method( ): token = None - async def custom_f(_: User, email_verification_url_token: str, __: Dict[str, Any]): + async def custom_f( + _: EVUser, email_verification_url_token: str, __: Dict[str, Any] + ): nonlocal token token = email_verification_url_token.split("?token=")[1].split("&rid=")[0] @@ -779,7 +791,9 @@ async def test_the_email_verify_api_with_valid_input_overriding_apis( token = None user_info_from_callback: Union[None, EVUser] = None - async def custom_f(_: User, email_verification_url_token: str, __: Dict[str, Any]): + async def custom_f( + _: EVUser, email_verification_url_token: str, __: Dict[str, Any] + ): nonlocal token token = email_verification_url_token.split("?token=")[1].split("&rid=")[0] @@ -787,11 +801,14 @@ def apis_override_email_password(param: APIInterface): temp = param.email_verify_post async def email_verify_post( - token: str, api_options: APIOptions, user_context: Dict[str, Any] + token: str, + api_options: APIOptions, + user_context: Dict[str, Any], + session: Optional[SessionContainer] = None, ): nonlocal user_info_from_callback - response = await temp(token, api_options, user_context) + response = await temp(token, api_options, user_context, session) if isinstance(response, EmailVerifyPostOkResult): user_info_from_callback = response.user @@ -870,7 +887,9 @@ async def test_the_email_verify_api_with_valid_input_overriding_apis_throws_erro token = None user_info_from_callback: Union[None, EVUser] = None - async def custom_f(_: User, email_verification_url_token: str, __: Dict[str, Any]): + async def custom_f( + _: EVUser, email_verification_url_token: str, __: Dict[str, Any] + ): nonlocal token token = email_verification_url_token.split("?token=")[1].split("&rid=")[0] @@ -878,11 +897,14 @@ def apis_override_email_password(param: APIInterface): temp = param.email_verify_post async def email_verify_post( - token: str, api_options: APIOptions, user_context: Dict[str, Any] + token: str, + api_options: APIOptions, + user_context: Dict[str, Any], + session: Optional[SessionContainer] = None, ): nonlocal user_info_from_callback - response = await temp(token, api_options, user_context) + response = await temp(token, api_options, user_context, session) if isinstance(response, EmailVerifyPostOkResult): user_info_from_callback = response.user @@ -1007,7 +1029,13 @@ async def test_the_generate_token_api_with_valid_input_verify_and_then_unverify_ api_base_path="/auth", ), framework="fastapi", - recipe_list=[session.init(anti_csrf="VIA_TOKEN"), emailpassword.init()], + recipe_list=[ + session.init(anti_csrf="VIA_TOKEN"), + emailverification.init( + ParentRecipeEmailVerificationConfig(mode="OPTIONAL") + ), + emailpassword.init(), + ], ) start_st() diff --git a/tests/input_validation/test_input_validation.py b/tests/input_validation/test_input_validation.py index f5099b26c..914f30b66 100644 --- a/tests/input_validation/test_input_validation.py +++ b/tests/input_validation/test_input_validation.py @@ -15,6 +15,9 @@ thirdpartypasswordless, usermetadata, ) +from supertokens_python.recipe.emailverification.interfaces import ( + GetEmailForUserIdOkResult, +) from supertokens_python.recipe.passwordless.utils import ContactEmailOrPhoneConfig from supertokens_python.recipe.thirdparty.provider import Provider @@ -80,9 +83,8 @@ async def test_init_validation_emailpassword(): ), framework="fastapi", recipe_list=[ - emailpassword.init( - email_verification_feature="email verify" # type: ignore - ), + emailverification.init("email verify"), # type: ignore + emailpassword.init(), ], ) assert ( @@ -107,8 +109,8 @@ async def test_init_validation_emailpassword(): assert "override must be of type InputOverrideConfig or None" == str(ex.value) -async def get_email_for_user_id(user_id: str, _: Dict[str, Any]) -> str: - return user_id +async def get_email_for_user_id(_: str, __: Dict[str, Any]): + return GetEmailForUserIdOkResult("foo@example.com") @pytest.mark.asyncio @@ -142,7 +144,9 @@ async def test_init_validation_emailverification(): recipe_list=[ emailverification.init( emailverification.ParentRecipeEmailVerificationConfig( - get_email_for_user_id=get_email_for_user_id, override="override" # type: ignore + mode="OPTIONAL", + get_email_for_user_id=get_email_for_user_id, + override="override", # type: ignore ) ) ], From 29d7cee07978c79fb4ac64b2b9c565bab8516376 Mon Sep 17 00:00:00 2001 From: KShivendu Date: Tue, 9 Aug 2022 14:13:23 +0530 Subject: [PATCH 04/18] refactor: Use email veriifcation claim in thirdparty recipe --- supertokens_python/post_init_callbacks.py | 17 ++++ .../emailpassword/api/implementation.py | 1 - .../recipe/emailpassword/recipe.py | 10 ++- .../recipe/emailpassword/types.py | 4 - .../recipe/emailpassword/utils.py | 2 +- .../recipe/emailverification/__init__.py | 5 +- .../emailverification/api/implementation.py | 5 +- .../emailverification/asyncio/__init__.py | 4 +- ...mail_verification_claim.py => ev_claim.py} | 18 ++-- .../recipe/emailverification/interfaces.py | 3 +- .../recipe/emailverification/recipe.py | 20 +++-- .../recipe/emailverification/utils.py | 11 +-- .../recipe/passwordless/recipe.py | 2 +- .../claim_base_classes/boolean_claim.py | 2 +- .../claim_base_classes/primitive_claim.py | 2 +- .../recipe/thirdparty/__init__.py | 9 +- .../recipe/thirdparty/api/implementation.py | 6 +- .../recipe/thirdparty/asyncio/__init__.py | 63 -------------- .../thirdparty/emaildelivery/__init__.py | 13 --- .../emaildelivery/services/__init__.py | 17 ---- .../backward_compatibility/__init__.py | 70 ---------------- .../emaildelivery/services/smtp/__init__.py | 43 ---------- .../recipe/thirdparty/interfaces.py | 7 -- .../recipe/thirdparty/recipe.py | 63 +++----------- .../recipe/thirdparty/syncio/__init__.py | 40 --------- supertokens_python/recipe/thirdparty/types.py | 7 +- supertokens_python/recipe/thirdparty/utils.py | 84 +------------------ .../recipe/thirdpartyemailpassword/recipe.py | 11 +-- .../recipe/thirdpartypasswordless/recipe.py | 13 +-- supertokens_python/supertokens.py | 2 + tests/thirdparty/test_emaildelivery.py | 47 +++++++---- 31 files changed, 114 insertions(+), 487 deletions(-) create mode 100644 supertokens_python/post_init_callbacks.py rename supertokens_python/recipe/emailverification/{email_verification_claim.py => ev_claim.py} (84%) delete mode 100644 supertokens_python/recipe/thirdparty/emaildelivery/__init__.py delete mode 100644 supertokens_python/recipe/thirdparty/emaildelivery/services/__init__.py delete mode 100644 supertokens_python/recipe/thirdparty/emaildelivery/services/backward_compatibility/__init__.py delete mode 100644 supertokens_python/recipe/thirdparty/emaildelivery/services/smtp/__init__.py diff --git a/supertokens_python/post_init_callbacks.py b/supertokens_python/post_init_callbacks.py new file mode 100644 index 000000000..f2ac09e04 --- /dev/null +++ b/supertokens_python/post_init_callbacks.py @@ -0,0 +1,17 @@ +from typing import Callable, List + + +class PostSTInitCallbacks: + """Callbacks that are called after the SuperTokens instance is initialized.""" + callbacks: List[Callable[[], None]] = [] + + @staticmethod + def add_post_init_callback(cb: Callable[[], None]) -> None: + PostSTInitCallbacks.callbacks.append(cb) + + @staticmethod + def run_post_init_callbacks() -> None: + for cb in PostSTInitCallbacks.callbacks: + cb() + + PostSTInitCallbacks.callbacks = [] diff --git a/supertokens_python/recipe/emailpassword/api/implementation.py b/supertokens_python/recipe/emailpassword/api/implementation.py index af4ec24e0..ca84eee3b 100644 --- a/supertokens_python/recipe/emailpassword/api/implementation.py +++ b/supertokens_python/recipe/emailpassword/api/implementation.py @@ -92,7 +92,6 @@ async def generate_password_reset_token_post( token = token_result.token password_reset_link = ( - # TODO: Delete get_reset_password_url implementation api_options.app_info.website_domain.get_as_string_dangerous() + api_options.app_info.website_base_path.get_as_string_dangerous() + "?token=" diff --git a/supertokens_python/recipe/emailpassword/recipe.py b/supertokens_python/recipe/emailpassword/recipe.py index 35c3f935b..1c5bde2f4 100644 --- a/supertokens_python/recipe/emailpassword/recipe.py +++ b/supertokens_python/recipe/emailpassword/recipe.py @@ -34,6 +34,7 @@ from .exceptions import FieldError, SuperTokensEmailPasswordError from .interfaces import APIOptions from .recipe_implementation import RecipeImplementation +from ...post_init_callbacks import PostSTInitCallbacks if TYPE_CHECKING: from supertokens_python.framework.request import BaseRequest @@ -113,13 +114,14 @@ def __init__( else self.config.override.apis(api_implementation) ) - # TODO: Postinit callbacks - email_veriifcation_recipe = EmailVerificationRecipe.get_instance() - if email_veriifcation_recipe is not None: + def callback(): + email_veriifcation_recipe = EmailVerificationRecipe.get_instance() email_veriifcation_recipe.add_get_email_for_user_id_func( self.get_email_for_user_id ) + PostSTInitCallbacks.add_post_init_callback(callback) + def is_error_from_this_recipe_based_on_instance(self, err: Exception) -> bool: return isinstance(err, SuperTokensError) and ( isinstance(err, SuperTokensEmailPasswordError) @@ -188,7 +190,7 @@ async def handle_api_request( ) if request_id == USER_PASSWORD_RESET: return await handle_password_reset_api(self.api_implementation, api_options) - # TODO: FIXME Should be False as per Node PR but the spec here don't allow it. + # FIXME: Should be False as per Node PR but the spec here don't allow it. return None async def handle_error( diff --git a/supertokens_python/recipe/emailpassword/types.py b/supertokens_python/recipe/emailpassword/types.py index c45b87e55..b8f9f9376 100644 --- a/supertokens_python/recipe/emailpassword/types.py +++ b/supertokens_python/recipe/emailpassword/types.py @@ -94,11 +94,7 @@ def __init__( # Export: EmailTemplateVars = PasswordResetEmailTemplateVars -# Union[ -# PasswordResetEmailTemplateVars, ev_types.VerificationEmailTemplateVars # TODO: REMOVE COMMENTED -# ] # PasswordResetEmailTemplateVars (Already exported because it's defined in the same) -# VerificationEmailTemplateVars = ev_types.VerificationEmailTemplateVars # TODO: REMOVE SMTPOverrideInput = SMTPServiceInterface[EmailTemplateVars] diff --git a/supertokens_python/recipe/emailpassword/utils.py b/supertokens_python/recipe/emailpassword/utils.py index d94b9de2c..ae4546f92 100644 --- a/supertokens_python/recipe/emailpassword/utils.py +++ b/supertokens_python/recipe/emailpassword/utils.py @@ -38,7 +38,7 @@ from .constants import ( FORM_FIELD_EMAIL_ID, FORM_FIELD_PASSWORD_ID, -) # TODO: RESET_PASSWORD +) async def default_validator(_: str) -> Union[str, None]: diff --git a/supertokens_python/recipe/emailverification/__init__.py b/supertokens_python/recipe/emailverification/__init__.py index 094c9ad68..9221fc07c 100644 --- a/supertokens_python/recipe/emailverification/__init__.py +++ b/supertokens_python/recipe/emailverification/__init__.py @@ -15,8 +15,9 @@ from typing import TYPE_CHECKING, Callable +from supertokens_python.recipe.emailverification import ev_claim from . import exceptions as ex -from . import utils, email_verification_claim +from . import utils from .emaildelivery import services as emaildelivery_services from .recipe import EmailVerificationRecipe @@ -24,7 +25,7 @@ ParentRecipeEmailVerificationConfig = utils.ParentRecipeEmailVerificationConfig exception = ex SMTPService = emaildelivery_services.SMTPService -EmailVerificationClaim = email_verification_claim.EmailVerificationClaim +EmailVerificationClaim = ev_claim.EmailVerificationClaim if TYPE_CHECKING: diff --git a/supertokens_python/recipe/emailverification/api/implementation.py b/supertokens_python/recipe/emailverification/api/implementation.py index 64b3d2694..ab9543f0f 100644 --- a/supertokens_python/recipe/emailverification/api/implementation.py +++ b/supertokens_python/recipe/emailverification/api/implementation.py @@ -16,10 +16,7 @@ from typing import TYPE_CHECKING, Any, Dict, Union, Optional from supertokens_python.logger import log_debug_message -from supertokens_python.recipe.emailverification import EmailVerificationRecipe -from supertokens_python.recipe.emailverification.email_verification_claim import ( - EmailVerificationClaim, -) +from supertokens_python.recipe.emailverification import EmailVerificationRecipe, EmailVerificationClaim from supertokens_python.recipe.emailverification.interfaces import ( APIInterface, CreateEmailVerificationTokenEmailAlreadyVerifiedError, diff --git a/supertokens_python/recipe/emailverification/asyncio/__init__.py b/supertokens_python/recipe/emailverification/asyncio/__init__.py index e848b520d..fbcf0b387 100644 --- a/supertokens_python/recipe/emailverification/asyncio/__init__.py +++ b/supertokens_python/recipe/emailverification/asyncio/__init__.py @@ -15,7 +15,7 @@ from supertokens_python.recipe.emailverification.interfaces import ( GetEmailForUserIdOkResult, EmailDoesnotExistError, - CreateEmailVerificationTokenEmailAlreadyVerifiedError, + CreateEmailVerificationTokenEmailAlreadyVerifiedError, UnverifyEmailOkResult, ) from supertokens_python.recipe.emailverification.types import EmailTemplateVars from supertokens_python.recipe.emailverification.recipe import EmailVerificationRecipe @@ -117,7 +117,7 @@ async def unverify_email( elif isinstance(email_info, EmailDoesnotExistError): # Here we are returning OK since that's how it used to work, but a later call # to is_verified will still return true - return "OK" # TODO: Make a class for this + return UnverifyEmailOkResult else: raise Exception("Unknown User ID provided without email") diff --git a/supertokens_python/recipe/emailverification/email_verification_claim.py b/supertokens_python/recipe/emailverification/ev_claim.py similarity index 84% rename from supertokens_python/recipe/emailverification/email_verification_claim.py rename to supertokens_python/recipe/emailverification/ev_claim.py index 3f6848e20..a06a821a6 100644 --- a/supertokens_python/recipe/emailverification/email_verification_claim.py +++ b/supertokens_python/recipe/emailverification/ev_claim.py @@ -12,7 +12,7 @@ # License for the specific language governing permissions and limitations # under the License. from __future__ import annotations -from typing import Dict, Any, Optional +from typing import Dict, Any from supertokens_python.recipe.emailverification import EmailVerificationRecipe from supertokens_python.recipe.emailverification.interfaces import ( @@ -40,7 +40,7 @@ def __init__( refetch_time_on_false_in_seconds: int, ): super().__init__("st-ev-is-verified") - self.claim: BooleanClaim = claim # TODO: FIXME: One has to specify type of self.claim to avoid type erorr from pylint + self.claim: BooleanClaim = claim # TODO: Should work without specifying type of self.claim (no pyright errors) self.has_value_validator = has_value_validator self.refetch_time_on_false_in_ms = refetch_time_on_false_in_seconds * 1000 @@ -75,18 +75,14 @@ def is_verified( class EmailVerificationClaimClass(BooleanClaim): def __init__(self): - # TODO: see if it works when we use async instead of MaybeAwaitable? - def fetch_value( - user_id: str, user_context: Optional[Dict[str, Any]] - ) -> MaybeAwaitable[bool]: - if user_context is None: - user_context = {} # TODO: Verify if this is the right thing to do? - + async def fetch_value( + user_id: str, user_context: Dict[str, Any] + ) -> bool: recipe = EmailVerificationRecipe.get_instance() - email_info = recipe.get_email_for_user_id(user_id, user_context) + email_info = await recipe.get_email_for_user_id(user_id, user_context) if isinstance(email_info, GetEmailForUserIdOkResult): - return recipe.recipe_implementation.is_email_verified( + return await recipe.recipe_implementation.is_email_verified( user_id, email_info.email, user_context ) if isinstance(email_info, EmailDoesnotExistError): diff --git a/supertokens_python/recipe/emailverification/interfaces.py b/supertokens_python/recipe/emailverification/interfaces.py index ebabe749c..c1f705a4b 100644 --- a/supertokens_python/recipe/emailverification/interfaces.py +++ b/supertokens_python/recipe/emailverification/interfaces.py @@ -189,7 +189,7 @@ async def generate_email_verify_token_post( self, api_options: APIOptions, user_context: Dict[str, Any], - session: SessionContainer, # TODO: Fix order of session arg to match the order is_email_verified_* (Applicable for all funcs in this file) + session: SessionContainer, ) -> Union[ GenerateEmailVerifyTokenPostOkResult, GenerateEmailVerifyTokenPostEmailAlreadyVerifiedError, @@ -200,7 +200,6 @@ async def generate_email_verify_token_post( class GetEmailForUserIdOkResult: def __init__(self, email: str): - # TODO: Write this? self.email = email diff --git a/supertokens_python/recipe/emailverification/recipe.py b/supertokens_python/recipe/emailverification/recipe.py index 136b8c848..d029e7f7b 100644 --- a/supertokens_python/recipe/emailverification/recipe.py +++ b/supertokens_python/recipe/emailverification/recipe.py @@ -28,7 +28,7 @@ from supertokens_python.recipe_module import APIHandled, RecipeModule from .api.implementation import APIImplementation -from .email_verification_claim import EmailVerificationClaim +from .ev_claim import EmailVerificationClaim from .interfaces import ( APIOptions, UnknownUserIdError, @@ -38,6 +38,7 @@ ) from .recipe_implementation import RecipeImplementation from ..session import SessionRecipe +from ...post_init_callbacks import PostSTInitCallbacks if TYPE_CHECKING: from supertokens_python.framework.request import BaseRequest @@ -177,14 +178,17 @@ def func(app_info: AppInfo): config, ingredients=ingredients, ) - # TODO: Supertokens init callback: - SessionRecipe.get_instance().add_claim_from_other_recipe( - EmailVerificationClaim - ) - if config.mode == "REQUIRED": - SessionRecipe.get_instance().add_claim_validator_from_other_recipe( - EmailVerificationClaim.validators.is_verified() + + def callback(): + SessionRecipe.get_instance().add_claim_from_other_recipe( + EmailVerificationClaim ) + if str(config.mode) == "REQUIRED": + SessionRecipe.get_instance().add_claim_validator_from_other_recipe( + EmailVerificationClaim.validators.is_verified() + ) + + PostSTInitCallbacks.add_post_init_callback(callback) return EmailVerificationRecipe.__instance raise_general_exception( diff --git a/supertokens_python/recipe/emailverification/utils.py b/supertokens_python/recipe/emailverification/utils.py index 4d2f53bd9..fa4ead1f5 100644 --- a/supertokens_python/recipe/emailverification/utils.py +++ b/supertokens_python/recipe/emailverification/utils.py @@ -43,12 +43,13 @@ def __init__( self.functions = functions self.apis = apis +MODE_TYPE = Literal["REQUIRED", "OPTIONAL"] class ParentRecipeEmailVerificationConfig: - # TODO: Now that this class is public, we might want to rename this? + # TODO: Now that this class will be used directly, we might want to rename this? def __init__( self, - mode: Literal["REQUIRED", "OPTIONAL"], + mode: MODE_TYPE, email_delivery: Union[EmailDeliveryConfig[EmailTemplateVars], None] = None, get_email_for_user_id: Optional[TypeGetEmailForUserIdFunction] = None, create_and_send_custom_email: Union[ @@ -56,7 +57,7 @@ def __init__( ] = None, override: Union[OverrideConfig, None] = None, ): - self.mode = mode + self.mode: MODE_TYPE = mode self.email_delivery = email_delivery self.get_email_for_user_id = get_email_for_user_id self.create_and_send_custom_email = create_and_send_custom_email @@ -72,14 +73,14 @@ def __init__( class EmailVerificationConfig: def __init__( self, - mode: str, # TODO: Literal["REQUIRED", "OPTIONAL"] had to be removed because of pyright. Possible to avoid? + mode: MODE_TYPE, get_email_delivery_config: Callable[ [], EmailDeliveryConfigWithService[VerificationEmailTemplateVars] ], get_email_for_user_id: Optional[TypeGetEmailForUserIdFunction], override: OverrideConfig, ): - self.mode: str = mode # TODO: How to avoid ": str" + self.mode = mode self.override = override self.get_email_delivery_config = get_email_delivery_config self.get_email_for_user_id = get_email_for_user_id diff --git a/supertokens_python/recipe/passwordless/recipe.py b/supertokens_python/recipe/passwordless/recipe.py index 844fa33fa..421f88425 100644 --- a/supertokens_python/recipe/passwordless/recipe.py +++ b/supertokens_python/recipe/passwordless/recipe.py @@ -202,7 +202,7 @@ async def handle_api_request( async def handle_error( self, request: BaseRequest, err: SuperTokensError, response: BaseResponse - ): + ) -> BaseResponse: # type: ignore raise err def get_all_cors_headers(self) -> List[str]: diff --git a/supertokens_python/recipe/session/claim_base_classes/boolean_claim.py b/supertokens_python/recipe/session/claim_base_classes/boolean_claim.py index 66ac20438..9ac7a48ae 100644 --- a/supertokens_python/recipe/session/claim_base_classes/boolean_claim.py +++ b/supertokens_python/recipe/session/claim_base_classes/boolean_claim.py @@ -36,7 +36,7 @@ def __init__( self, key: str, fetch_value: Callable[ - [str, Optional[Dict[str, Any]]], + [str, Dict[str, Any]], MaybeAwaitable[Optional[_T]], ], ): diff --git a/supertokens_python/recipe/session/claim_base_classes/primitive_claim.py b/supertokens_python/recipe/session/claim_base_classes/primitive_claim.py index e71730f69..1ad007ddb 100644 --- a/supertokens_python/recipe/session/claim_base_classes/primitive_claim.py +++ b/supertokens_python/recipe/session/claim_base_classes/primitive_claim.py @@ -149,7 +149,7 @@ def __init__( self, key: str, fetch_value: Callable[ - [str, Optional[Dict[str, Any]]], + [str, Dict[str, Any]], MaybeAwaitable[Optional[_T]], ], ) -> None: diff --git a/supertokens_python/recipe/thirdparty/__init__.py b/supertokens_python/recipe/thirdparty/__init__.py index 6ccc4eaf6..e13a1a592 100644 --- a/supertokens_python/recipe/thirdparty/__init__.py +++ b/supertokens_python/recipe/thirdparty/__init__.py @@ -16,15 +16,11 @@ from typing import TYPE_CHECKING, Callable, Union -from supertokens_python.ingredients.emaildelivery.types import EmailDeliveryConfig from . import exceptions as ex from . import providers, utils -from .emaildelivery import services as emaildelivery_services from .recipe import ThirdPartyRecipe -from .types import EmailTemplateVars -InputEmailVerificationConfig = utils.InputEmailVerificationConfig InputOverrideConfig = utils.InputOverrideConfig SignInAndUpFeature = utils.SignInAndUpFeature Apple = providers.Apple @@ -34,7 +30,6 @@ Google = providers.Google GoogleWorkspaces = providers.GoogleWorkspaces exceptions = ex -SMTPService = emaildelivery_services.SMTPService if TYPE_CHECKING: from supertokens_python.supertokens import AppInfo @@ -44,10 +39,8 @@ def init( sign_in_and_up_feature: SignInAndUpFeature, - email_verification_feature: Union[InputEmailVerificationConfig, None] = None, override: Union[InputOverrideConfig, None] = None, - email_delivery: Union[EmailDeliveryConfig[EmailTemplateVars], None] = None, ) -> Callable[[AppInfo], RecipeModule]: return ThirdPartyRecipe.init( - sign_in_and_up_feature, email_verification_feature, override, email_delivery + sign_in_and_up_feature, override ) diff --git a/supertokens_python/recipe/thirdparty/api/implementation.py b/supertokens_python/recipe/thirdparty/api/implementation.py index d51c3b75c..38cd3fafb 100644 --- a/supertokens_python/recipe/thirdparty/api/implementation.py +++ b/supertokens_python/recipe/thirdparty/api/implementation.py @@ -18,6 +18,7 @@ from httpx import AsyncClient from supertokens_python.exceptions import raise_general_exception +from supertokens_python.recipe.emailverification import EmailVerificationRecipe from supertokens_python.recipe.emailverification.interfaces import ( CreateEmailVerificationTokenOkResult, ) @@ -162,14 +163,15 @@ async def sign_in_up_post( ) if email_verified: - token_response = await api_options.email_verification_recipe_implementation.create_email_verification_token( + ev_instance = EmailVerificationRecipe.get_instance() + token_response = await ev_instance.recipe_implementation.create_email_verification_token( user_id=signinup_response.user.user_id, email=signinup_response.user.email, user_context=user_context, ) if isinstance(token_response, CreateEmailVerificationTokenOkResult): - await api_options.email_verification_recipe_implementation.verify_email_using_token( + await ev_instance.recipe_implementation.verify_email_using_token( token=token_response.token, user_context=user_context ) diff --git a/supertokens_python/recipe/thirdparty/asyncio/__init__.py b/supertokens_python/recipe/thirdparty/asyncio/__init__.py index 6152a7d0c..2f81723c5 100644 --- a/supertokens_python/recipe/thirdparty/asyncio/__init__.py +++ b/supertokens_python/recipe/thirdparty/asyncio/__init__.py @@ -18,69 +18,6 @@ from ..types import EmailTemplateVars, User - -async def create_email_verification_token( - user_id: str, user_context: Union[None, Dict[str, Any]] = None -): - if user_context is None: - user_context = {} - email = await ThirdPartyRecipe.get_instance().get_email_for_user_id( - user_id, user_context - ) - return await ThirdPartyRecipe.get_instance().email_verification_recipe.recipe_implementation.create_email_verification_token( - user_id, email, user_context - ) - - -async def verify_email_using_token( - token: str, user_context: Union[None, Dict[str, Any]] = None -): - if user_context is None: - user_context = {} - return await ThirdPartyRecipe.get_instance().email_verification_recipe.recipe_implementation.verify_email_using_token( - token, user_context - ) - - -async def is_email_verified( - user_id: str, user_context: Union[None, Dict[str, Any]] = None -): - if user_context is None: - user_context = {} - email = await ThirdPartyRecipe.get_instance().get_email_for_user_id( - user_id, user_context - ) - return await ThirdPartyRecipe.get_instance().email_verification_recipe.recipe_implementation.is_email_verified( - user_id, email, user_context - ) - - -async def unverify_email( - user_id: str, user_context: Union[None, Dict[str, Any]] = None -): - if user_context is None: - user_context = {} - email = await ThirdPartyRecipe.get_instance().get_email_for_user_id( - user_id, user_context - ) - return await ThirdPartyRecipe.get_instance().email_verification_recipe.recipe_implementation.unverify_email( - user_id, email, user_context - ) - - -async def revoke_email_verification_tokens( - user_id: str, user_context: Union[None, Dict[str, Any]] = None -): - if user_context is None: - user_context = {} - email = await ThirdPartyRecipe.get_instance().get_email_for_user_id( - user_id, user_context - ) - return await ThirdPartyRecipe.get_instance().email_verification_recipe.recipe_implementation.revoke_email_verification_tokens( - user_id, email, user_context - ) - - async def get_user_by_id( user_id: str, user_context: Union[None, Dict[str, Any]] = None ) -> Union[User, None]: diff --git a/supertokens_python/recipe/thirdparty/emaildelivery/__init__.py b/supertokens_python/recipe/thirdparty/emaildelivery/__init__.py deleted file mode 100644 index 98bbe3f6d..000000000 --- a/supertokens_python/recipe/thirdparty/emaildelivery/__init__.py +++ /dev/null @@ -1,13 +0,0 @@ -# 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. diff --git a/supertokens_python/recipe/thirdparty/emaildelivery/services/__init__.py b/supertokens_python/recipe/thirdparty/emaildelivery/services/__init__.py deleted file mode 100644 index f6d4d2e85..000000000 --- a/supertokens_python/recipe/thirdparty/emaildelivery/services/__init__.py +++ /dev/null @@ -1,17 +0,0 @@ -# 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 . import smtp - -SMTPService = smtp.SMTPService diff --git a/supertokens_python/recipe/thirdparty/emaildelivery/services/backward_compatibility/__init__.py b/supertokens_python/recipe/thirdparty/emaildelivery/services/backward_compatibility/__init__.py deleted file mode 100644 index d9362ffe4..000000000 --- a/supertokens_python/recipe/thirdparty/emaildelivery/services/backward_compatibility/__init__.py +++ /dev/null @@ -1,70 +0,0 @@ -# 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 typing import TYPE_CHECKING, Any, Dict, Union - -from supertokens_python.ingredients.emaildelivery.types import EmailDeliveryInterface -from supertokens_python.recipe.emailverification.emaildelivery.services.backward_compatibility import ( - BackwardCompatibilityService as EVBackwardCompatibilityService, -) -from supertokens_python.recipe.emailverification.types import User -from supertokens_python.recipe.thirdparty.interfaces import RecipeInterface -from supertokens_python.recipe.thirdparty.types import EmailTemplateVars -from supertokens_python.supertokens import AppInfo - -if TYPE_CHECKING: - from supertokens_python.recipe.thirdparty.utils import InputEmailVerificationConfig - - -class BackwardCompatibilityService(EmailDeliveryInterface[EmailTemplateVars]): - def __init__( - self, - app_info: AppInfo, - recipe_interface_impl: RecipeInterface, - email_verification_feature: Union[InputEmailVerificationConfig, None] = None, - ) -> None: - input_create_and_send_custom_email = ( - email_verification_feature.create_and_send_custom_email - if email_verification_feature is not None - else None - ) - if input_create_and_send_custom_email is None: - email_verification_feature_config = None - else: - - async def create_and_send_custom_email( - user: User, link: str, user_context: Dict[str, Any] - ): - user_info = await recipe_interface_impl.get_user_by_id( - user_id=user.user_id, user_context=user_context - ) - if user_info is None: - raise Exception("Unknown User ID provided") - return await input_create_and_send_custom_email( - user_info, link, user_context - ) - - email_verification_feature_config = create_and_send_custom_email - - self.ev_backward_compatibility_service = EVBackwardCompatibilityService( - app_info, email_verification_feature_config - ) - - async def send_email( - self, template_vars: EmailTemplateVars, user_context: Dict[str, Any] - ) -> None: - await self.ev_backward_compatibility_service.send_email( - template_vars, user_context - ) diff --git a/supertokens_python/recipe/thirdparty/emaildelivery/services/smtp/__init__.py b/supertokens_python/recipe/thirdparty/emaildelivery/services/smtp/__init__.py deleted file mode 100644 index 8359f7343..000000000 --- a/supertokens_python/recipe/thirdparty/emaildelivery/services/smtp/__init__.py +++ /dev/null @@ -1,43 +0,0 @@ -# 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 typing import Any, Dict, Callable, Union - -from supertokens_python.ingredients.emaildelivery.types import ( - EmailDeliveryInterface, - SMTPSettings, -) -from supertokens_python.recipe.emailverification.emaildelivery.services.smtp import ( - SMTPService as EmailVerificationSMTPService, -) -from supertokens_python.recipe.thirdparty.types import ( - EmailTemplateVars, - SMTPOverrideInput, -) - - -class SMTPService(EmailDeliveryInterface[EmailTemplateVars]): - ev_smtp_service: EmailVerificationSMTPService - - def __init__( - self, - smtp_settings: SMTPSettings, - override: Union[Callable[[SMTPOverrideInput], SMTPOverrideInput], None] = None, - ) -> None: - self.ev_smtp_service = EmailVerificationSMTPService(smtp_settings, override) - - async def send_email( - self, template_vars: EmailTemplateVars, user_context: Dict[str, Any] - ) -> None: - await self.ev_smtp_service.send_email(template_vars, user_context) diff --git a/supertokens_python/recipe/thirdparty/interfaces.py b/supertokens_python/recipe/thirdparty/interfaces.py index 9b04e923c..2adfefea3 100644 --- a/supertokens_python/recipe/thirdparty/interfaces.py +++ b/supertokens_python/recipe/thirdparty/interfaces.py @@ -17,9 +17,6 @@ from typing import TYPE_CHECKING, Any, Dict, List, Union from ...types import APIResponse, GeneralErrorResponse -from ..emailverification.interfaces import ( - RecipeInterface as EmailVerificationRecipeInterface, -) from .provider import Provider if TYPE_CHECKING: @@ -84,7 +81,6 @@ def __init__( recipe_implementation: RecipeInterface, providers: List[Provider], app_info: AppInfo, - email_verification_recipe_implementation: EmailVerificationRecipeInterface, ): self.request: BaseRequest = request self.response: BaseResponse = response @@ -93,9 +89,6 @@ def __init__( self.providers: List[Provider] = providers self.recipe_implementation: RecipeInterface = recipe_implementation self.app_info: AppInfo = app_info - self.email_verification_recipe_implementation: EmailVerificationRecipeInterface = ( - email_verification_recipe_implementation - ) class SignInUpPostOkResult(APIResponse): diff --git a/supertokens_python/recipe/thirdparty/recipe.py b/supertokens_python/recipe/thirdparty/recipe.py index a55821494..da0ba778e 100644 --- a/supertokens_python/recipe/thirdparty/recipe.py +++ b/supertokens_python/recipe/thirdparty/recipe.py @@ -16,18 +16,14 @@ from os import environ from typing import TYPE_CHECKING, Any, Dict, List, Union -from supertokens_python.ingredients.emaildelivery.types import EmailDeliveryConfig from supertokens_python.normalised_url_path import NormalisedURLPath from supertokens_python.querier import Querier -from supertokens_python.recipe.emailverification.types import ( - EmailVerificationIngredients, -) -from supertokens_python.recipe.thirdparty.types import ThirdPartyIngredients from supertokens_python.recipe_module import APIHandled, RecipeModule from .api.implementation import APIImplementation from .interfaces import APIInterface, APIOptions, RecipeInterface from .recipe_implementation import RecipeImplementation +from ...post_init_callbacks import PostSTInitCallbacks if TYPE_CHECKING: from supertokens_python.framework.request import BaseRequest @@ -47,7 +43,7 @@ from .constants import APPLE_REDIRECT_HANDLER, AUTHORISATIONURL, SIGNINUP from .exceptions import SuperTokensThirdPartyError from .types import ThirdPartyIngredients, EmailTemplateVars -from .utils import InputEmailVerificationConfig, validate_and_normalise_user_input +from .utils import validate_and_normalise_user_input class ThirdPartyRecipe(RecipeModule): @@ -60,19 +56,13 @@ def __init__( recipe_id: str, app_info: AppInfo, sign_in_and_up_feature: SignInAndUpFeature, - ingredients: ThirdPartyIngredients, - email_verification_feature: Union[InputEmailVerificationConfig, None] = None, + _ingredients: ThirdPartyIngredients, override: Union[InputOverrideConfig, None] = None, - email_verification_recipe: Union[EmailVerificationRecipe, None] = None, - email_delivery: Union[EmailDeliveryConfig[EmailTemplateVars], None] = None, ): super().__init__(recipe_id, app_info) self.config = validate_and_normalise_user_input( - self, sign_in_and_up_feature, - email_verification_feature, override, - email_delivery, ) self.providers = self.config.sign_in_and_up_feature.providers recipe_implementation = RecipeImplementation(Querier.get_instance(recipe_id)) @@ -88,34 +78,15 @@ def __init__( else self.config.override.apis(api_implementation) ) - email_delivery_ingredient = ingredients.email_delivery - if email_delivery_ingredient is None: - self.email_delivery = EmailDeliveryIngredient( - self.config.get_email_delivery_config(recipe_implementation) - ) - else: - self.email_delivery = email_delivery_ingredient - - if email_verification_recipe is not None: - self.email_verification_recipe = email_verification_recipe - else: - ev_email_delivery_ingredient = self.email_delivery - email_verification_ingredients = EmailVerificationIngredients( - email_delivery=ev_email_delivery_ingredient - ) - self.email_verification_recipe = EmailVerificationRecipe( - recipe_id, - app_info, - self.config.email_verification_feature, - ingredients=email_verification_ingredients, - ) + def callback(): + ev_recipe = EmailVerificationRecipe.get_instance() + ev_recipe.add_get_email_for_user_id_func(self.get_email_for_user_id) + + PostSTInitCallbacks.add_post_init_callback(callback) def is_error_from_this_recipe_based_on_instance(self, err: Exception) -> bool: return isinstance(err, SuperTokensError) and ( isinstance(err, SuperTokensThirdPartyError) - or self.email_verification_recipe.is_error_from_this_recipe_based_on_instance( - err - ) ) def get_apis_handled(self) -> List[APIHandled]: @@ -138,7 +109,7 @@ def get_apis_handled(self) -> List[APIHandled]: APPLE_REDIRECT_HANDLER, self.api_implementation.disable_apple_redirect_handler_post, ), - ] + self.email_verification_recipe.get_apis_handled() + ] async def handle_api_request( self, @@ -156,7 +127,6 @@ async def handle_api_request( self.recipe_implementation, self.providers, self.app_info, - self.email_verification_recipe.recipe_implementation, ) if request_id == SIGNINUP: @@ -168,38 +138,31 @@ async def handle_api_request( if request_id == APPLE_REDIRECT_HANDLER: return await handle_apple_redirect_api(self.api_implementation, api_options) - return await self.email_verification_recipe.handle_api_request( - request_id, request, path, method, response - ) + return None # TODO: Node PR returns False, but here signature is different. Verify if this is correct. async def handle_error( self, request: BaseRequest, err: SuperTokensError, response: BaseResponse - ) -> BaseResponse: + ) -> BaseResponse: # type: ignore if isinstance(err, SuperTokensThirdPartyError): raise err - return await self.email_verification_recipe.handle_error(request, err, response) def get_all_cors_headers(self) -> List[str]: - return self.email_verification_recipe.get_all_cors_headers() + return [] @staticmethod def init( sign_in_and_up_feature: SignInAndUpFeature, - email_verification_feature: Union[InputEmailVerificationConfig, None] = None, override: Union[InputOverrideConfig, None] = None, - email_delivery: Union[EmailDeliveryConfig[EmailTemplateVars], None] = None, ): def func(app_info: AppInfo): if ThirdPartyRecipe.__instance is None: - ingredients = ThirdPartyIngredients(None) + ingredients = ThirdPartyIngredients() ThirdPartyRecipe.__instance = ThirdPartyRecipe( ThirdPartyRecipe.recipe_id, app_info, sign_in_and_up_feature, ingredients, - email_verification_feature, override, - email_delivery=email_delivery, ) return ThirdPartyRecipe.__instance raise_general_exception( diff --git a/supertokens_python/recipe/thirdparty/syncio/__init__.py b/supertokens_python/recipe/thirdparty/syncio/__init__.py index 914ee4ce0..dbfeec566 100644 --- a/supertokens_python/recipe/thirdparty/syncio/__init__.py +++ b/supertokens_python/recipe/thirdparty/syncio/__init__.py @@ -18,46 +18,6 @@ from ..types import EmailTemplateVars, User -def create_email_verification_token( - user_id: str, user_context: Union[None, Dict[str, Any]] = None -): - from supertokens_python.recipe.thirdparty.asyncio import ( - create_email_verification_token, - ) - - return sync(create_email_verification_token(user_id, user_context)) - - -def verify_email_using_token( - token: str, user_context: Union[None, Dict[str, Any]] = None -): - from supertokens_python.recipe.thirdparty.asyncio import verify_email_using_token - - return sync(verify_email_using_token(token, user_context)) - - -def is_email_verified(user_id: str, user_context: Union[None, Dict[str, Any]] = None): - from supertokens_python.recipe.thirdparty.asyncio import is_email_verified - - return sync(is_email_verified(user_id, user_context)) - - -def unverify_email(user_id: str, user_context: Union[None, Dict[str, Any]] = None): - from supertokens_python.recipe.thirdparty.asyncio import unverify_email - - return sync(unverify_email(user_id, user_context)) - - -def revoke_email_verification_tokens( - user_id: str, user_context: Union[None, Dict[str, Any]] = None -): - from supertokens_python.recipe.thirdparty.asyncio import ( - revoke_email_verification_tokens, - ) - - return sync(revoke_email_verification_tokens(user_id, user_context)) - - def get_user_by_id( user_id: str, user_context: Union[None, Dict[str, Any]] = None ) -> Union[User, None]: diff --git a/supertokens_python/recipe/thirdparty/types.py b/supertokens_python/recipe/thirdparty/types.py index 1fd29f7bf..f5692a00b 100644 --- a/supertokens_python/recipe/thirdparty/types.py +++ b/supertokens_python/recipe/thirdparty/types.py @@ -14,7 +14,6 @@ from typing import Callable, Dict, List, Union from supertokens_python.framework.request import BaseRequest -from supertokens_python.ingredients.emaildelivery import EmailDeliveryIngredient from supertokens_python.ingredients.emaildelivery.types import ( EmailDeliveryInterface, SMTPServiceInterface, @@ -90,8 +89,4 @@ def __init__(self, users: List[User], next_pagination_token: Union[str, None]): class ThirdPartyIngredients: - def __init__( - self, - email_delivery: Union[EmailDeliveryIngredient[EmailTemplateVars], None] = None, - ) -> None: - self.email_delivery = email_delivery + pass diff --git a/supertokens_python/recipe/thirdparty/utils.py b/supertokens_python/recipe/thirdparty/utils.py index df13a34a9..8b3730139 100644 --- a/supertokens_python/recipe/thirdparty/utils.py +++ b/supertokens_python/recipe/thirdparty/utils.py @@ -16,10 +16,6 @@ from typing import TYPE_CHECKING, Any, Awaitable, Callable, Dict, List, Set, Union from supertokens_python.exceptions import raise_bad_input_exception -from supertokens_python.recipe.thirdparty.emaildelivery.services.backward_compatibility import ( - BackwardCompatibilityService, -) -from supertokens_python.utils import deprecated_warn from .interfaces import APIInterface, RecipeInterface @@ -28,19 +24,12 @@ from .provider import Provider from jwt import PyJWKClient, decode -from supertokens_python.ingredients.emaildelivery.types import ( - EmailDeliveryConfig, - EmailDeliveryConfigWithService, -) from supertokens_python.recipe.emailverification.utils import ( OverrideConfig as EmailVerificationOverrideConfig, ) -from supertokens_python.recipe.emailverification.utils import ( - ParentRecipeEmailVerificationConfig, -) from ..emailverification.types import User as EmailVerificationUser -from .types import EmailTemplateVars, User +from .types import User class SignInAndUpFeature: @@ -87,21 +76,6 @@ def __init__(self, providers: List[Provider]): self.providers = providers -class InputEmailVerificationConfig: - def __init__( - self, - # TODO: Marker: Removed get_email_verification_url - create_and_send_custom_email: Union[ - Callable[[User, str, Dict[str, Any]], Awaitable[None]], None - ] = None, - ): - self.create_and_send_custom_email = create_and_send_custom_email - if create_and_send_custom_email: - deprecated_warn( - "create_and_send_custom_email is depricated. Please use email delivery config instead" - ) - - def email_verification_create_and_send_custom_email( recipe: ThirdPartyRecipe, create_and_send_custom_email: Callable[ @@ -136,26 +110,6 @@ async def func(user: EmailVerificationUser, user_context: Dict[str, Any]): return func -def validate_and_normalise_email_verification_config( - recipe: ThirdPartyRecipe, - config: Union[InputEmailVerificationConfig, None], - override: InputOverrideConfig, -) -> ParentRecipeEmailVerificationConfig: - create_and_send_custom_email = None - if config is None: - config = InputEmailVerificationConfig() - if config.create_and_send_custom_email is not None: - create_and_send_custom_email = email_verification_create_and_send_custom_email( - recipe, config.create_and_send_custom_email - ) - - return ParentRecipeEmailVerificationConfig( - mode="OPTIONAL", # TODO: FIXME? - create_and_send_custom_email=create_and_send_custom_email, - override=override.email_verification_feature, - ) - - class InputOverrideConfig: def __init__( self, @@ -182,24 +136,15 @@ class ThirdPartyConfig: def __init__( self, sign_in_and_up_feature: SignInAndUpFeature, - email_verification_feature: ParentRecipeEmailVerificationConfig, override: OverrideConfig, - get_email_delivery_config: Callable[ - [RecipeInterface], EmailDeliveryConfigWithService[EmailTemplateVars] - ], ): self.sign_in_and_up_feature = sign_in_and_up_feature - self.email_verification_feature = email_verification_feature self.override = override - self.get_email_delivery_config = get_email_delivery_config def validate_and_normalise_user_input( - recipe: ThirdPartyRecipe, sign_in_and_up_feature: SignInAndUpFeature, - email_verification_feature: Union[InputEmailVerificationConfig, None] = None, override: Union[InputOverrideConfig, None] = None, - email_delivery_config: Union[EmailDeliveryConfig[EmailTemplateVars], None] = None, ) -> ThirdPartyConfig: if not isinstance(sign_in_and_up_feature, SignInAndUpFeature): # type: ignore raise ValueError( @@ -212,36 +157,9 @@ def validate_and_normalise_user_input( if override is None: override = InputOverrideConfig() - def get_email_delivery_config( - tp_recipe: RecipeInterface, - ) -> EmailDeliveryConfigWithService[EmailTemplateVars]: - if email_delivery_config and email_delivery_config.service: - return EmailDeliveryConfigWithService( - email_delivery_config.service, email_delivery_config.override - ) - - email_service = BackwardCompatibilityService( - recipe.app_info, - tp_recipe, - email_verification_feature, - ) - if ( - email_delivery_config is not None - and email_delivery_config.override is not None - ): - override = email_delivery_config.override - else: - override = None - - return EmailDeliveryConfigWithService(email_service, override=override) - return ThirdPartyConfig( sign_in_and_up_feature, - validate_and_normalise_email_verification_config( - recipe, email_verification_feature, override - ), OverrideConfig(functions=override.functions, apis=override.apis), - get_email_delivery_config, ) diff --git a/supertokens_python/recipe/thirdpartyemailpassword/recipe.py b/supertokens_python/recipe/thirdpartyemailpassword/recipe.py index 893016538..3baa7b215 100644 --- a/supertokens_python/recipe/thirdpartyemailpassword/recipe.py +++ b/supertokens_python/recipe/thirdpartyemailpassword/recipe.py @@ -26,9 +26,6 @@ VerificationEmailTemplateVars, ) from supertokens_python.recipe.thirdparty.provider import Provider -from supertokens_python.recipe.thirdparty.types import ( - EmailTemplateVars as ThirdPartyEmailTemplateVars, -) from supertokens_python.recipe.thirdparty.types import ThirdPartyIngredients from supertokens_python.recipe.thirdpartyemailpassword.types import ( EmailTemplateVars, @@ -196,22 +193,16 @@ def apis_override_third_party( return get_third_party_interface_impl(self.api_implementation) self.third_party_recipe: Union[ThirdPartyRecipe, None] = None - ingredient = cast( - EmailDeliveryIngredient[ThirdPartyEmailTemplateVars], - self.email_delivery, - ) - tp_ingredients = ThirdPartyIngredients(ingredient) + tp_ingredients = ThirdPartyIngredients() if len(self.config.providers) != 0: self.third_party_recipe = ThirdPartyRecipe( recipe_id, app_info, SignInAndUpFeature(self.config.providers), tp_ingredients, - None, TPOverrideConfig( func_override_third_party, apis_override_third_party ), - self.email_verification_recipe, ) def is_error_from_this_recipe_based_on_instance(self, err: Exception) -> bool: diff --git a/supertokens_python/recipe/thirdpartypasswordless/recipe.py b/supertokens_python/recipe/thirdpartypasswordless/recipe.py index 257bd5e9c..2e113453a 100644 --- a/supertokens_python/recipe/thirdpartypasswordless/recipe.py +++ b/supertokens_python/recipe/thirdpartypasswordless/recipe.py @@ -28,9 +28,6 @@ ) from supertokens_python.recipe.passwordless.types import PasswordlessIngredients from supertokens_python.recipe.thirdparty.provider import Provider -from supertokens_python.recipe.thirdparty.types import ( - EmailTemplateVars as ThirdPartyEmailTemplateVars, -) from supertokens_python.recipe.thirdparty.types import ThirdPartyIngredients from supertokens_python.recipe.thirdpartypasswordless.types import ( EmailTemplateVars, @@ -298,21 +295,15 @@ def apis_override_third_party( self.third_party_recipe: Union[ThirdPartyRecipe, None] = None if len(self.config.providers) != 0: - tp_email_delivery = cast( - EmailDeliveryIngredient[ThirdPartyEmailTemplateVars], - self.email_delivery, - ) - tp_ingredients = ThirdPartyIngredients(tp_email_delivery) + tp_ingredients = ThirdPartyIngredients() self.third_party_recipe = ThirdPartyRecipe( recipe_id, app_info, SignInAndUpFeature(self.config.providers), tp_ingredients, - None, TPOverrideConfig( func_override_third_party, apis_override_third_party ), - self.email_verification_recipe, ) def is_error_from_this_recipe_based_on_instance(self, err: Exception) -> bool: @@ -378,7 +369,7 @@ async def handle_error( and self.third_party_recipe.is_error_from_this_recipe_based_on_instance(err) ): return await self.third_party_recipe.handle_error(request, err, response) - return await self.email_verification_recipe.handle_error(request, err, response) + raise err def get_all_cors_headers(self) -> List[str]: cors_headers = ( diff --git a/supertokens_python/supertokens.py b/supertokens_python/supertokens.py index 2e48c7209..b5e0056fe 100644 --- a/supertokens_python/supertokens.py +++ b/supertokens_python/supertokens.py @@ -33,6 +33,7 @@ from .exceptions import SuperTokensError from .normalised_url_domain import NormalisedURLDomain from .normalised_url_path import NormalisedURLPath +from .post_init_callbacks import PostSTInitCallbacks from .querier import Querier from .recipe.session.cookie_and_header import ( attach_access_token_to_cookie, @@ -280,6 +281,7 @@ def init( Supertokens.__instance = Supertokens( app_info, framework, supertokens_config, recipe_list, mode, telemetry ) + PostSTInitCallbacks.run_post_init_callbacks() @staticmethod def reset(): diff --git a/tests/thirdparty/test_emaildelivery.py b/tests/thirdparty/test_emaildelivery.py index 9a5291728..211812820 100644 --- a/tests/thirdparty/test_emaildelivery.py +++ b/tests/thirdparty/test_emaildelivery.py @@ -31,27 +31,27 @@ SMTPSettings, SMTPSettingsFrom, ) -from supertokens_python.recipe import session, thirdparty +from supertokens_python.recipe import session, thirdparty, emailverification +from supertokens_python.recipe.emailverification.utils import ParentRecipeEmailVerificationConfig from supertokens_python.recipe.session import SessionRecipe from supertokens_python.recipe.session.recipe_implementation import ( RecipeImplementation as SessionRecipeImplementation, ) from supertokens_python.recipe.session.session_functions import create_new_session from supertokens_python.recipe.thirdparty.asyncio import sign_in_up -from supertokens_python.recipe.thirdparty.emaildelivery.services.smtp import SMTPService +from supertokens_python.recipe.emailverification.emaildelivery.services.smtp import SMTPService from supertokens_python.recipe.thirdparty.interfaces import SignInUpOkResult from supertokens_python.recipe.thirdparty.provider import Provider from supertokens_python.recipe.thirdparty.types import ( AccessTokenAPI, AuthorisationRedirectAPI, EmailTemplateVars, - User, UserInfo, UserInfoEmail, VerificationEmailTemplateVars, ) from tests.utils import clean_st, email_verify_token_request, reset, setup_st, start_st - +from supertokens_python.recipe.emailverification.types import User as EVUser respx_mock = respx.MockRouter @@ -257,7 +257,7 @@ async def test_email_verify_backward_compatibility(driver_config_client: TestCli email_verify_url = "" async def create_and_send_custom_email( - input_: User, email_verification_link: str, _: Dict[str, Any] + input_: EVUser, email_verification_link: str, _: Dict[str, Any] ): nonlocal email, email_verify_url email = input_.email @@ -273,13 +273,16 @@ async def create_and_send_custom_email( ), framework="fastapi", recipe_list=[ + emailverification.init( + ParentRecipeEmailVerificationConfig( + mode="OPTIONAL", + create_and_send_custom_email=create_and_send_custom_email, + ) + ), thirdparty.init( sign_in_and_up_feature=thirdparty.SignInAndUpFeature( providers=[CustomProvider("CUSTOM", True)] - ), - email_verification_feature=thirdparty.InputEmailVerificationConfig( - create_and_send_custom_email=create_and_send_custom_email, - ), + ) ), session.init(), ], @@ -342,14 +345,19 @@ async def send_email( ), framework="fastapi", recipe_list=[ + emailverification.init( + ParentRecipeEmailVerificationConfig( + mode="OPTIONAL", + email_delivery=EmailDeliveryConfig( + service=None, + override=email_delivery_override, + ), + ) + ), thirdparty.init( sign_in_and_up_feature=thirdparty.SignInAndUpFeature( providers=[CustomProvider("CUSTOM", True)] ), - email_delivery=EmailDeliveryConfig( - service=None, - override=email_delivery_override, - ), ), session.init(), ], @@ -476,14 +484,19 @@ async def send_email_override( ), framework="fastapi", recipe_list=[ + emailverification.init( + ParentRecipeEmailVerificationConfig( + mode="OPTIONAL", + email_delivery=EmailDeliveryConfig( + service=email_delivery_service, + override=email_delivery_override, + ) + ) + ), thirdparty.init( sign_in_and_up_feature=thirdparty.SignInAndUpFeature( providers=[CustomProvider("CUSTOM", True)] ), - email_delivery=EmailDeliveryConfig( - service=email_delivery_service, - override=email_delivery_override, - ), ), session.init(), ], From 69570009d23b6ff876c72f23df395690adb4e3ca Mon Sep 17 00:00:00 2001 From: KShivendu Date: Tue, 9 Aug 2022 14:34:02 +0530 Subject: [PATCH 05/18] refactor: Run black on tp recipe and use ev claim in pless recipe --- supertokens_python/post_init_callbacks.py | 1 + .../emailverification/api/implementation.py | 5 ++- .../emailverification/asyncio/__init__.py | 3 +- .../recipe/emailverification/ev_claim.py | 4 +- .../recipe/emailverification/utils.py | 2 + .../recipe/passwordless/api/implementation.py | 42 +++++++++++++------ .../recipe/passwordless/interfaces.py | 3 ++ .../recipe/passwordless/recipe.py | 39 ++++++++++++++--- .../recipe/thirdparty/__init__.py | 4 +- .../recipe/thirdparty/api/implementation.py | 10 +++-- .../recipe/thirdparty/asyncio/__init__.py | 1 + .../recipe/thirdparty/recipe.py | 4 +- tests/thirdparty/test_emaildelivery.py | 11 +++-- 13 files changed, 93 insertions(+), 36 deletions(-) diff --git a/supertokens_python/post_init_callbacks.py b/supertokens_python/post_init_callbacks.py index f2ac09e04..ec27fc2a1 100644 --- a/supertokens_python/post_init_callbacks.py +++ b/supertokens_python/post_init_callbacks.py @@ -3,6 +3,7 @@ class PostSTInitCallbacks: """Callbacks that are called after the SuperTokens instance is initialized.""" + callbacks: List[Callable[[], None]] = [] @staticmethod diff --git a/supertokens_python/recipe/emailverification/api/implementation.py b/supertokens_python/recipe/emailverification/api/implementation.py index ab9543f0f..a2cd3c705 100644 --- a/supertokens_python/recipe/emailverification/api/implementation.py +++ b/supertokens_python/recipe/emailverification/api/implementation.py @@ -16,7 +16,10 @@ from typing import TYPE_CHECKING, Any, Dict, Union, Optional from supertokens_python.logger import log_debug_message -from supertokens_python.recipe.emailverification import EmailVerificationRecipe, EmailVerificationClaim +from supertokens_python.recipe.emailverification import ( + EmailVerificationRecipe, + EmailVerificationClaim, +) from supertokens_python.recipe.emailverification.interfaces import ( APIInterface, CreateEmailVerificationTokenEmailAlreadyVerifiedError, diff --git a/supertokens_python/recipe/emailverification/asyncio/__init__.py b/supertokens_python/recipe/emailverification/asyncio/__init__.py index fbcf0b387..9e543d7ff 100644 --- a/supertokens_python/recipe/emailverification/asyncio/__init__.py +++ b/supertokens_python/recipe/emailverification/asyncio/__init__.py @@ -15,7 +15,8 @@ from supertokens_python.recipe.emailverification.interfaces import ( GetEmailForUserIdOkResult, EmailDoesnotExistError, - CreateEmailVerificationTokenEmailAlreadyVerifiedError, UnverifyEmailOkResult, + CreateEmailVerificationTokenEmailAlreadyVerifiedError, + UnverifyEmailOkResult, ) from supertokens_python.recipe.emailverification.types import EmailTemplateVars from supertokens_python.recipe.emailverification.recipe import EmailVerificationRecipe diff --git a/supertokens_python/recipe/emailverification/ev_claim.py b/supertokens_python/recipe/emailverification/ev_claim.py index a06a821a6..d57e724a0 100644 --- a/supertokens_python/recipe/emailverification/ev_claim.py +++ b/supertokens_python/recipe/emailverification/ev_claim.py @@ -75,9 +75,7 @@ def is_verified( class EmailVerificationClaimClass(BooleanClaim): def __init__(self): - async def fetch_value( - user_id: str, user_context: Dict[str, Any] - ) -> bool: + async def fetch_value(user_id: str, user_context: Dict[str, Any]) -> bool: recipe = EmailVerificationRecipe.get_instance() email_info = await recipe.get_email_for_user_id(user_id, user_context) diff --git a/supertokens_python/recipe/emailverification/utils.py b/supertokens_python/recipe/emailverification/utils.py index fa4ead1f5..774f3c575 100644 --- a/supertokens_python/recipe/emailverification/utils.py +++ b/supertokens_python/recipe/emailverification/utils.py @@ -43,8 +43,10 @@ def __init__( self.functions = functions self.apis = apis + MODE_TYPE = Literal["REQUIRED", "OPTIONAL"] + class ParentRecipeEmailVerificationConfig: # TODO: Now that this class will be used directly, we might want to rename this? def __init__( diff --git a/supertokens_python/recipe/passwordless/api/implementation.py b/supertokens_python/recipe/passwordless/api/implementation.py index 4ab1fcc9c..cb1966886 100644 --- a/supertokens_python/recipe/passwordless/api/implementation.py +++ b/supertokens_python/recipe/passwordless/api/implementation.py @@ -43,8 +43,8 @@ ) from supertokens_python.recipe.session.asyncio import create_new_session from supertokens_python.types import GeneralErrorResponse - -from ..utils import PhoneOrEmailInput +from ...emailverification import EmailVerificationRecipe +from ...emailverification.interfaces import CreateEmailVerificationTokenOkResult class APIImplementation(APIInterface): @@ -67,11 +67,11 @@ async def create_code_post( user_input_code = None flow_type = api_options.config.flow_type if flow_type in ("MAGIC_LINK", "USER_INPUT_CODE_AND_MAGIC_LINK"): - magic_link = await api_options.config.get_link_domain_and_path( - PhoneOrEmailInput(phone_number=phone_number, email=email), user_context - ) - magic_link += ( - "?rid=" + magic_link = ( + api_options.app_info.website_domain.get_as_string_dangerous() + + api_options.app_info.website_base_path.get_as_string_dangerous() + + "/verify" + + "?rid=" + api_options.recipe_id + "&preAuthSessionId=" + response.pre_auth_session_id @@ -172,12 +172,11 @@ async def resend_code_post( user_input_code = None flow_type = api_options.config.flow_type if flow_type in ("MAGIC_LINK", "USER_INPUT_CODE_AND_MAGIC_LINK"): - magic_link = await api_options.config.get_link_domain_and_path( - PhoneOrEmailInput(device_info.phone_number, device_info.email), - user_context, - ) - magic_link += ( - "?rid=" + magic_link = ( + api_options.app_info.website_domain.get_as_string_dangerous() + + api_options.app_info.website_base_path.get_as_string_dangerous() + + "/verify" + + "?rid=" + api_options.recipe_id + "&preAuthSessionId=" + response.pre_auth_session_id @@ -272,9 +271,26 @@ async def consume_code_post( return ConsumeCodePostRestartFlowError() user = response.user + + if user.email is not None: + ev_instance = EmailVerificationRecipe.get_instance() + if ev_instance: + token_response = await ev_instance.recipe_implementation.create_email_verification_token( + user.user_id, + user.email, + user_context, + ) + + if isinstance(token_response, CreateEmailVerificationTokenOkResult): + await ev_instance.recipe_implementation.verify_email_using_token( + token_response.token, + user_context, + ) + session = await create_new_session( api_options.request, user.user_id, {}, {}, user_context=user_context ) + return ConsumeCodePostOkResult( created_new_user=response.created_new_user, user=response.user, diff --git a/supertokens_python/recipe/passwordless/interfaces.py b/supertokens_python/recipe/passwordless/interfaces.py index 3200852fe..bfc8dd844 100644 --- a/supertokens_python/recipe/passwordless/interfaces.py +++ b/supertokens_python/recipe/passwordless/interfaces.py @@ -31,6 +31,7 @@ User, ) from .utils import PasswordlessConfig +from ...supertokens import AppInfo class CreateCodeOkResult: @@ -275,6 +276,7 @@ def __init__( recipe_id: str, config: PasswordlessConfig, recipe_implementation: RecipeInterface, + app_info: AppInfo, email_delivery: EmailDeliveryIngredient[PasswordlessLoginEmailTemplateVars], sms_delivery: SMSDeliveryIngredient[PasswordlessLoginSMSTemplateVars], ): @@ -283,6 +285,7 @@ def __init__( self.recipe_id = recipe_id self.config = config self.recipe_implementation = recipe_implementation + self.app_info = app_info self.email_delivery = email_delivery self.sms_delivery = sms_delivery diff --git a/supertokens_python/recipe/passwordless/recipe.py b/supertokens_python/recipe/passwordless/recipe.py index 421f88425..648e1b67e 100644 --- a/supertokens_python/recipe/passwordless/recipe.py +++ b/supertokens_python/recipe/passwordless/recipe.py @@ -55,6 +55,13 @@ PhoneOrEmailInput, validate_and_normalise_user_input, ) +from ..emailverification import EmailVerificationRecipe +from ..emailverification.interfaces import ( + GetEmailForUserIdOkResult, + EmailDoesnotExistError, + UnknownUserIdError, +) +from ...post_init_callbacks import PostSTInitCallbacks if TYPE_CHECKING: from supertokens_python.framework.request import BaseRequest @@ -137,6 +144,12 @@ def __init__( else sms_delivery_ingredient ) + def callback(): + ev_recipe = EmailVerificationRecipe.get_instance() + ev_recipe.add_get_email_for_user_id_func(self.get_email_for_user_id) + + PostSTInitCallbacks.add_post_init_callback(callback) + def get_apis_handled(self) -> List[APIHandled]: return [ APIHandled( @@ -187,6 +200,7 @@ async def handle_api_request( self.get_recipe_id(), self.config, self.recipe_implementation, + self.get_app_info(), self.email_delivery, self.sms_delivery, ) @@ -202,7 +216,7 @@ async def handle_api_request( async def handle_error( self, request: BaseRequest, err: SuperTokensError, response: BaseResponse - ) -> BaseResponse: # type: ignore + ) -> BaseResponse: # type: ignore raise err def get_all_cors_headers(self) -> List[str]: @@ -288,11 +302,14 @@ async def create_magic_link( user_input_code=user_input_code, user_context=user_context, ) - magic_link = await self.config.get_link_domain_and_path( - PhoneOrEmailInput(phone_number, email), user_context - ) - magic_link += ( - "?rid=" + + app_info = self.get_app_info() + + magic_link = ( + app_info.website_domain.get_as_string_dangerous() + + app_info.website_base_path.get_as_string_dangerous() + + "/verify" + + "?rid=" + self.get_recipe_id() + "&preAuthSessionId=" + code_info.pre_auth_session_id @@ -323,3 +340,13 @@ async def signinup( if isinstance(consume_code_result, ConsumeCodeOkResult): return consume_code_result raise Exception("Failed to create user. Please retry") + + async def get_email_for_user_id(self, user_id: str, user_context: Dict[str, Any]): + user_info = await self.recipe_implementation.get_user_by_id( + user_id, user_context + ) + if user_info is not None: + if user_info.email is not None: + return GetEmailForUserIdOkResult(user_info.email) + return EmailDoesnotExistError() + return UnknownUserIdError() diff --git a/supertokens_python/recipe/thirdparty/__init__.py b/supertokens_python/recipe/thirdparty/__init__.py index e13a1a592..44312ecee 100644 --- a/supertokens_python/recipe/thirdparty/__init__.py +++ b/supertokens_python/recipe/thirdparty/__init__.py @@ -41,6 +41,4 @@ def init( sign_in_and_up_feature: SignInAndUpFeature, override: Union[InputOverrideConfig, None] = None, ) -> Callable[[AppInfo], RecipeModule]: - return ThirdPartyRecipe.init( - sign_in_and_up_feature, override - ) + return ThirdPartyRecipe.init(sign_in_and_up_feature, override) diff --git a/supertokens_python/recipe/thirdparty/api/implementation.py b/supertokens_python/recipe/thirdparty/api/implementation.py index 38cd3fafb..21a307ef4 100644 --- a/supertokens_python/recipe/thirdparty/api/implementation.py +++ b/supertokens_python/recipe/thirdparty/api/implementation.py @@ -164,10 +164,12 @@ async def sign_in_up_post( if email_verified: ev_instance = EmailVerificationRecipe.get_instance() - token_response = await ev_instance.recipe_implementation.create_email_verification_token( - user_id=signinup_response.user.user_id, - email=signinup_response.user.email, - user_context=user_context, + token_response = ( + await ev_instance.recipe_implementation.create_email_verification_token( + user_id=signinup_response.user.user_id, + email=signinup_response.user.email, + user_context=user_context, + ) ) if isinstance(token_response, CreateEmailVerificationTokenOkResult): diff --git a/supertokens_python/recipe/thirdparty/asyncio/__init__.py b/supertokens_python/recipe/thirdparty/asyncio/__init__.py index 2f81723c5..a97691e9a 100644 --- a/supertokens_python/recipe/thirdparty/asyncio/__init__.py +++ b/supertokens_python/recipe/thirdparty/asyncio/__init__.py @@ -18,6 +18,7 @@ from ..types import EmailTemplateVars, User + async def get_user_by_id( user_id: str, user_context: Union[None, Dict[str, Any]] = None ) -> Union[User, None]: diff --git a/supertokens_python/recipe/thirdparty/recipe.py b/supertokens_python/recipe/thirdparty/recipe.py index da0ba778e..c24305122 100644 --- a/supertokens_python/recipe/thirdparty/recipe.py +++ b/supertokens_python/recipe/thirdparty/recipe.py @@ -138,11 +138,11 @@ async def handle_api_request( if request_id == APPLE_REDIRECT_HANDLER: return await handle_apple_redirect_api(self.api_implementation, api_options) - return None # TODO: Node PR returns False, but here signature is different. Verify if this is correct. + return None # TODO: Node PR returns False, but here signature is different. Verify if this is correct. async def handle_error( self, request: BaseRequest, err: SuperTokensError, response: BaseResponse - ) -> BaseResponse: # type: ignore + ) -> BaseResponse: # type: ignore if isinstance(err, SuperTokensThirdPartyError): raise err diff --git a/tests/thirdparty/test_emaildelivery.py b/tests/thirdparty/test_emaildelivery.py index 211812820..0ccc871f8 100644 --- a/tests/thirdparty/test_emaildelivery.py +++ b/tests/thirdparty/test_emaildelivery.py @@ -32,14 +32,18 @@ SMTPSettingsFrom, ) from supertokens_python.recipe import session, thirdparty, emailverification -from supertokens_python.recipe.emailverification.utils import ParentRecipeEmailVerificationConfig +from supertokens_python.recipe.emailverification.utils import ( + ParentRecipeEmailVerificationConfig, +) from supertokens_python.recipe.session import SessionRecipe from supertokens_python.recipe.session.recipe_implementation import ( RecipeImplementation as SessionRecipeImplementation, ) from supertokens_python.recipe.session.session_functions import create_new_session from supertokens_python.recipe.thirdparty.asyncio import sign_in_up -from supertokens_python.recipe.emailverification.emaildelivery.services.smtp import SMTPService +from supertokens_python.recipe.emailverification.emaildelivery.services.smtp import ( + SMTPService, +) from supertokens_python.recipe.thirdparty.interfaces import SignInUpOkResult from supertokens_python.recipe.thirdparty.provider import Provider from supertokens_python.recipe.thirdparty.types import ( @@ -52,6 +56,7 @@ ) from tests.utils import clean_st, email_verify_token_request, reset, setup_st, start_st from supertokens_python.recipe.emailverification.types import User as EVUser + respx_mock = respx.MockRouter @@ -490,7 +495,7 @@ async def send_email_override( email_delivery=EmailDeliveryConfig( service=email_delivery_service, override=email_delivery_override, - ) + ), ) ), thirdparty.init( From 44d0f3a7cd2b289cec1ae24dd66ada7a938424a6 Mon Sep 17 00:00:00 2001 From: KShivendu Date: Tue, 9 Aug 2022 14:59:00 +0530 Subject: [PATCH 06/18] refactor: Remove email verification features from thirdpartyemailpassword recipe in favour of EV claims --- .../thirdpartyemailpassword/__init__.py | 3 - .../asyncio/__init__.py | 63 ------------------- .../recipe/thirdpartyemailpassword/recipe.py | 39 +----------- .../syncio/__init__.py | 45 ------------- .../recipe/thirdpartyemailpassword/utils.py | 57 ----------------- .../input_validation/test_input_validation.py | 7 ++- .../test_email_delivery.py | 25 ++++---- 7 files changed, 20 insertions(+), 219 deletions(-) diff --git a/supertokens_python/recipe/thirdpartyemailpassword/__init__.py b/supertokens_python/recipe/thirdpartyemailpassword/__init__.py index b85bcf11c..50deec0e0 100644 --- a/supertokens_python/recipe/thirdpartyemailpassword/__init__.py +++ b/supertokens_python/recipe/thirdpartyemailpassword/__init__.py @@ -25,7 +25,6 @@ from .emaildelivery import services as emaildelivery_services from .recipe import ThirdPartyEmailPasswordRecipe -InputEmailVerificationConfig = utils.InputEmailVerificationConfig InputOverrideConfig = utils.InputOverrideConfig exceptions = ex InputResetPasswordUsingTokenFeature = emailpassword.InputResetPasswordUsingTokenFeature @@ -49,7 +48,6 @@ def init( reset_password_using_token_feature: Union[ InputResetPasswordUsingTokenFeature, None ] = None, - email_verification_feature: Union[InputEmailVerificationConfig, None] = None, override: Union[InputOverrideConfig, None] = None, providers: Union[List[Provider], None] = None, email_delivery: Union[EmailDeliveryConfig[EmailTemplateVars], None] = None, @@ -57,7 +55,6 @@ def init( return ThirdPartyEmailPasswordRecipe.init( sign_up_feature, reset_password_using_token_feature, - email_verification_feature, override, providers, email_delivery, diff --git a/supertokens_python/recipe/thirdpartyemailpassword/asyncio/__init__.py b/supertokens_python/recipe/thirdpartyemailpassword/asyncio/__init__.py index 752be2569..3e3b943d0 100644 --- a/supertokens_python/recipe/thirdpartyemailpassword/asyncio/__init__.py +++ b/supertokens_python/recipe/thirdpartyemailpassword/asyncio/__init__.py @@ -20,69 +20,6 @@ from ..types import EmailTemplateVars, User - -async def create_email_verification_token( - user_id: str, user_context: Union[None, Dict[str, Any]] = None -): - if user_context is None: - user_context = {} - email = await ThirdPartyEmailPasswordRecipe.get_instance().get_email_for_user_id( - user_id, user_context - ) - return await ThirdPartyEmailPasswordRecipe.get_instance().email_verification_recipe.recipe_implementation.create_email_verification_token( - user_id, email, user_context - ) - - -async def verify_email_using_token( - token: str, user_context: Union[None, Dict[str, Any]] = None -): - if user_context is None: - user_context = {} - return ThirdPartyEmailPasswordRecipe.get_instance().email_verification_recipe.recipe_implementation.verify_email_using_token( - token, user_context - ) - - -async def is_email_verified( - user_id: str, user_context: Union[None, Dict[str, Any]] = None -): - if user_context is None: - user_context = {} - email = await ThirdPartyEmailPasswordRecipe.get_instance().get_email_for_user_id( - user_id, user_context - ) - return await ThirdPartyEmailPasswordRecipe.get_instance().email_verification_recipe.recipe_implementation.is_email_verified( - user_id, email, user_context - ) - - -async def unverify_email( - user_id: str, user_context: Union[None, Dict[str, Any]] = None -): - if user_context is None: - user_context = {} - email = await ThirdPartyEmailPasswordRecipe.get_instance().get_email_for_user_id( - user_id, user_context - ) - return await ThirdPartyEmailPasswordRecipe.get_instance().email_verification_recipe.recipe_implementation.unverify_email( - user_id, email, user_context - ) - - -async def revoke_email_verification_tokens( - user_id: str, user_context: Union[None, Dict[str, Any]] = None -): - if user_context is None: - user_context = {} - email = await ThirdPartyEmailPasswordRecipe.get_instance().get_email_for_user_id( - user_id, user_context - ) - return await ThirdPartyEmailPasswordRecipe.get_instance().email_verification_recipe.recipe_implementation.revoke_email_verification_tokens( - user_id, email, user_context - ) - - async def get_user_by_id( user_id: str, user_context: Union[None, Dict[str, Any]] = None ) -> Union[None, User]: diff --git a/supertokens_python/recipe/thirdpartyemailpassword/recipe.py b/supertokens_python/recipe/thirdpartyemailpassword/recipe.py index 3baa7b215..35482ea6c 100644 --- a/supertokens_python/recipe/thirdpartyemailpassword/recipe.py +++ b/supertokens_python/recipe/thirdpartyemailpassword/recipe.py @@ -14,17 +14,13 @@ from __future__ import annotations from os import environ -from typing import TYPE_CHECKING, Any, Dict, List, Union, cast +from typing import TYPE_CHECKING, Any, Dict, List, Union from supertokens_python.framework.response import BaseResponse from supertokens_python.ingredients.emaildelivery.types import EmailDeliveryConfig from supertokens_python.normalised_url_path import NormalisedURLPath from supertokens_python.querier import Querier from supertokens_python.recipe.emailpassword.types import EmailPasswordIngredients -from supertokens_python.recipe.emailverification.types import ( - EmailVerificationIngredients, - VerificationEmailTemplateVars, -) from supertokens_python.recipe.thirdparty.provider import Provider from supertokens_python.recipe.thirdparty.types import ThirdPartyIngredients from supertokens_python.recipe.thirdpartyemailpassword.types import ( @@ -51,7 +47,6 @@ from .recipeimplementation.third_party_recipe_implementation import ( RecipeImplementation as ThirdPartyRecipeImplementation, ) -from .utils import InputEmailVerificationConfig if TYPE_CHECKING: from supertokens_python.framework.request import BaseRequest @@ -63,7 +58,6 @@ from supertokens_python.recipe.emailpassword.utils import ( InputOverrideConfig as EPOverrideConfig, ) -from supertokens_python.recipe.emailverification import EmailVerificationRecipe from supertokens_python.recipe.thirdparty import ThirdPartyRecipe from supertokens_python.recipe.thirdparty.utils import ( InputOverrideConfig as TPOverrideConfig, @@ -93,10 +87,8 @@ def __init__( reset_password_using_token_feature: Union[ InputResetPasswordUsingTokenFeature, None ] = None, - email_verification_feature: Union[InputEmailVerificationConfig, None] = None, override: Union[InputOverrideConfig, None] = None, providers: Union[List[Provider], None] = None, - email_verification_recipe: Union[EmailVerificationRecipe, None] = None, email_password_recipe: Union[EmailPasswordRecipe, None] = None, third_party_recipe: Union[ThirdPartyRecipe, None] = None, email_delivery: Union[EmailDeliveryConfig[EmailTemplateVars], None] = None, @@ -106,7 +98,6 @@ def __init__( self, sign_up_feature, reset_password_using_token_feature, - email_verification_feature, override, providers, email_delivery, @@ -140,21 +131,6 @@ def __init__( else: self.email_delivery = email_delivery_ingredient - if email_verification_recipe is not None: - self.email_verification_recipe = email_verification_recipe - else: - ev_email_delivery_ingredient = cast( - EmailDeliveryIngredient[VerificationEmailTemplateVars], - self.email_delivery, - ) - ev_ingredients = EmailVerificationIngredients(ev_email_delivery_ingredient) - self.email_verification_recipe = EmailVerificationRecipe( - recipe_id, - app_info, - self.config.email_verification_feature, - ev_ingredients, - ) - if email_password_recipe is not None: self.email_password_recipe = email_password_recipe else: @@ -208,9 +184,6 @@ def apis_override_third_party( def is_error_from_this_recipe_based_on_instance(self, err: Exception) -> bool: return isinstance(err, SuperTokensError) and ( isinstance(err, SupertokensThirdPartyEmailPasswordError) - or self.email_verification_recipe.is_error_from_this_recipe_based_on_instance( - err - ) or self.email_password_recipe.is_error_from_this_recipe_based_on_instance( err ) @@ -225,7 +198,6 @@ def is_error_from_this_recipe_based_on_instance(self, err: Exception) -> bool: def get_apis_handled(self) -> List[APIHandled]: apis_handled = ( self.email_password_recipe.get_apis_handled() - + self.email_verification_recipe.get_apis_handled() ) if self.third_party_recipe is not None: apis_handled = apis_handled + self.third_party_recipe.get_apis_handled() @@ -256,9 +228,7 @@ async def handle_api_request( return await self.third_party_recipe.handle_api_request( request_id, request, path, method, response ) - return await self.email_verification_recipe.handle_api_request( - request_id, request, path, method, response - ) + return None async def handle_error( self, request: BaseRequest, err: SuperTokensError, response: BaseResponse @@ -270,12 +240,11 @@ async def handle_error( and self.third_party_recipe.is_error_from_this_recipe_based_on_instance(err) ): return await self.third_party_recipe.handle_error(request, err, response) - return await self.email_verification_recipe.handle_error(request, err, response) + raise err def get_all_cors_headers(self) -> List[str]: cors_headers = ( self.email_password_recipe.get_all_cors_headers() - + self.email_verification_recipe.get_all_cors_headers() ) if self.third_party_recipe is not None: cors_headers = cors_headers + self.third_party_recipe.get_all_cors_headers() @@ -287,7 +256,6 @@ def init( reset_password_using_token_feature: Union[ InputResetPasswordUsingTokenFeature, None ] = None, - email_verification_feature: Union[InputEmailVerificationConfig, None] = None, override: Union[InputOverrideConfig, None] = None, providers: Union[List[Provider], None] = None, email_delivery: Union[EmailDeliveryConfig[EmailTemplateVars], None] = None, @@ -302,7 +270,6 @@ def func(app_info: AppInfo): ingredients, sign_up_feature, reset_password_using_token_feature, - email_verification_feature, override, providers, email_delivery=email_delivery, diff --git a/supertokens_python/recipe/thirdpartyemailpassword/syncio/__init__.py b/supertokens_python/recipe/thirdpartyemailpassword/syncio/__init__.py index 8e817f194..be3e2788b 100644 --- a/supertokens_python/recipe/thirdpartyemailpassword/syncio/__init__.py +++ b/supertokens_python/recipe/thirdpartyemailpassword/syncio/__init__.py @@ -22,41 +22,6 @@ ) from ..types import EmailTemplateVars, User - -def create_email_verification_token( - user_id: str, user_context: Union[None, Dict[str, Any]] = None -): - from supertokens_python.recipe.thirdpartyemailpassword.asyncio import ( - create_email_verification_token, - ) - - return sync(create_email_verification_token(user_id, user_context)) - - -def verify_email_using_token( - token: str, user_context: Union[None, Dict[str, Any]] = None -): - from supertokens_python.recipe.thirdpartyemailpassword.asyncio import ( - verify_email_using_token, - ) - - return sync(verify_email_using_token(token, user_context)) - - -def is_email_verified(user_id: str, user_context: Union[None, Dict[str, Any]] = None): - from supertokens_python.recipe.thirdpartyemailpassword.asyncio import ( - is_email_verified, - ) - - return sync(is_email_verified(user_id, user_context)) - - -def unverify_email(user_id: str, user_context: Union[None, Dict[str, Any]] = None): - from supertokens_python.recipe.thirdpartyemailpassword.asyncio import unverify_email - - return sync(unverify_email(user_id, user_context)) - - def get_user_by_id( user_id: str, user_context: Union[None, Dict[str, Any]] = None ) -> Union[None, User]: @@ -160,16 +125,6 @@ def get_users_by_email( return sync(get_users_by_email(email, user_context)) -def revoke_email_verification_tokens( - user_id: str, user_context: Union[None, Dict[str, Any]] = None -): - from supertokens_python.recipe.thirdpartyemailpassword.asyncio import ( - revoke_email_verification_tokens, - ) - - return sync(revoke_email_verification_tokens(user_id, user_context)) - - def send_email( input_: EmailTemplateVars, user_context: Union[None, Dict[str, Any]] = None ): diff --git a/supertokens_python/recipe/thirdpartyemailpassword/utils.py b/supertokens_python/recipe/thirdpartyemailpassword/utils.py index e931e4358..26c1ffbee 100644 --- a/supertokens_python/recipe/thirdpartyemailpassword/utils.py +++ b/supertokens_python/recipe/thirdpartyemailpassword/utils.py @@ -23,7 +23,6 @@ RecipeInterface as EPRecipeInterface, ) from supertokens_python.recipe.thirdparty.provider import Provider -from supertokens_python.utils import deprecated_warn from ..emailpassword.utils import ( InputResetPasswordUsingTokenFeature, @@ -37,29 +36,6 @@ if TYPE_CHECKING: from .recipe import ThirdPartyEmailPasswordRecipe -from supertokens_python.recipe.emailverification.utils import ( - OverrideConfig as EmailVerificationOverrideConfig, -) -from supertokens_python.recipe.emailverification.utils import ( - ParentRecipeEmailVerificationConfig, -) - - -class InputEmailVerificationConfig: - def __init__( - self, - # TODO: Marker: get_email_verification_url removed - create_and_send_custom_email: Union[ - Callable[[User, str, Any], Awaitable[None]], None - ] = None, - ): - self.create_and_send_custom_email = create_and_send_custom_email - if create_and_send_custom_email: - deprecated_warn( - "create_and_send_custom_email is depricated. Please use email delivery config instead" - ) - - def email_verification_create_and_send_custom_email( recipe: ThirdPartyEmailPasswordRecipe, create_and_send_custom_email: Callable[ @@ -94,36 +70,14 @@ async def func(user: EmailVerificationUser, user_context: Dict[str, Any]): return func -def validate_and_normalise_email_verification_config( - recipe: ThirdPartyEmailPasswordRecipe, - config: Union[InputEmailVerificationConfig, None], - override: InputOverrideConfig, -) -> ParentRecipeEmailVerificationConfig: - create_and_send_custom_email = None - if config is None: - config = InputEmailVerificationConfig() - if config.create_and_send_custom_email is not None: - create_and_send_custom_email = email_verification_create_and_send_custom_email( - recipe, config.create_and_send_custom_email - ) - - return ParentRecipeEmailVerificationConfig( - mode="OPTIONAL", # TODO: FIXME? - create_and_send_custom_email=create_and_send_custom_email, - override=override.email_verification_feature, - ) - - class InputOverrideConfig: def __init__( self, functions: Union[Callable[[RecipeInterface], RecipeInterface], None] = None, apis: Union[Callable[[APIInterface], APIInterface], None] = None, - email_verification_feature: Union[EmailVerificationOverrideConfig, None] = None, ): self.functions = functions self.apis = apis - self.email_verification_feature = email_verification_feature class OverrideConfig: @@ -140,7 +94,6 @@ class ThirdPartyEmailPasswordConfig: def __init__( self, providers: List[Provider], - email_verification_feature: ParentRecipeEmailVerificationConfig, sign_up_feature: Union[InputSignUpFeature, None], reset_password_using_token_feature: Union[ InputResetPasswordUsingTokenFeature, None @@ -152,7 +105,6 @@ def __init__( override: OverrideConfig, ): self.sign_up_feature = sign_up_feature - self.email_verification_feature = email_verification_feature self.providers = providers self.reset_password_using_token_feature = reset_password_using_token_feature self.get_email_delivery_config = get_email_delivery_config @@ -165,7 +117,6 @@ def validate_and_normalise_user_input( reset_password_using_token_feature: Union[ InputResetPasswordUsingTokenFeature, None ] = None, - email_verification_feature: Union[InputEmailVerificationConfig, None] = None, override: Union[InputOverrideConfig, None] = None, providers: Union[List[Provider], None] = None, email_delivery: Union[EmailDeliveryConfig[EmailTemplateVars], None] = None, @@ -178,11 +129,6 @@ def validate_and_normalise_user_input( "reset_password_using_token_feature must be of type InputResetPasswordUsingTokenFeature or None" ) - if email_verification_feature is not None and not isinstance(email_verification_feature, InputEmailVerificationConfig): # type: ignore - raise ValueError( - "email_verification_feature must be of type InputEmailVerificationConfig or None" - ) - if override is not None and not isinstance(override, InputOverrideConfig): # type: ignore raise ValueError("override must be of type InputOverrideConfig or None") @@ -220,9 +166,6 @@ def get_email_delivery_config( return ThirdPartyEmailPasswordConfig( providers, - validate_and_normalise_email_verification_config( - recipe, email_verification_feature, override - ), sign_up_feature, reset_password_using_token_feature, get_email_delivery_config, diff --git a/tests/input_validation/test_input_validation.py b/tests/input_validation/test_input_validation.py index 914f30b66..8f3fd6620 100644 --- a/tests/input_validation/test_input_validation.py +++ b/tests/input_validation/test_input_validation.py @@ -469,9 +469,10 @@ async def test_init_validation_thirdpartyemailpassword(): ), framework="fastapi", recipe_list=[ - thirdpartyemailpassword.init( - email_verification_feature="email verification" # type: ignore - ) + emailverification.init( + "email verification" # type: ignore + ), + thirdpartyemailpassword.init() ], ) assert ( diff --git a/tests/thirdpartyemailpassword/test_email_delivery.py b/tests/thirdpartyemailpassword/test_email_delivery.py index 393cd5ddb..486f81290 100644 --- a/tests/thirdpartyemailpassword/test_email_delivery.py +++ b/tests/thirdpartyemailpassword/test_email_delivery.py @@ -46,7 +46,6 @@ ) from supertokens_python.recipe.session.session_functions import create_new_session from supertokens_python.recipe.thirdpartyemailpassword import ( - InputEmailVerificationConfig, InputResetPasswordUsingTokenFeature, ) from supertokens_python.recipe.thirdpartyemailpassword.asyncio import ( @@ -62,7 +61,7 @@ from supertokens_python.recipe.emailverification.types import ( VerificationEmailTemplateVars, ) -from supertokens_python.recipe.thirdpartyemailpassword.types import User as TPEPUser +from supertokens_python.recipe.emailverification.types import User as EVUser from tests.utils import ( clean_st, email_verify_token_request, @@ -592,7 +591,7 @@ async def test_email_verification_backward_compatibility( email_verify_url = "" async def custom_create_and_send_custom_email( - user: TPEPUser, email_verification_link: str, _: Dict[str, Any] + user: EVUser, email_verification_link: str, _: Dict[str, Any] ): nonlocal email, email_verify_url email = user.email @@ -608,11 +607,11 @@ async def custom_create_and_send_custom_email( ), framework="fastapi", recipe_list=[ - thirdpartyemailpassword.init( - email_verification_feature=InputEmailVerificationConfig( - create_and_send_custom_email=custom_create_and_send_custom_email - ) - ), + emailverification.init(ParentRecipeEmailVerificationConfig( + mode="OPTIONAL", + create_and_send_custom_email=custom_create_and_send_custom_email + )), + thirdpartyemailpassword.init(), session.init(), ], ) @@ -913,7 +912,7 @@ async def test_email_verification_backward_compatibility_thirdparty_user( email_verify_url = "" async def custom_create_and_send_custom_email( - user: TPEPUser, email_verification_link: str, _: Dict[str, Any] + user: EVUser, email_verification_link: str, _: Dict[str, Any] ): nonlocal email, email_verify_url email = user.email @@ -929,11 +928,13 @@ async def custom_create_and_send_custom_email( ), framework="fastapi", recipe_list=[ - thirdpartyemailpassword.init( - email_verification_feature=InputEmailVerificationConfig( - create_and_send_custom_email=custom_create_and_send_custom_email + emailverification.init( + ParentRecipeEmailVerificationConfig( + mode="OPTIONAL", + create_and_send_custom_email=custom_create_and_send_custom_email, ) ), + thirdpartyemailpassword.init(), session.init(), ], ) From 720f593d99e9714025727fc29b5ad99d024d5eca Mon Sep 17 00:00:00 2001 From: KShivendu Date: Tue, 9 Aug 2022 15:27:00 +0530 Subject: [PATCH 07/18] refactor: Remove email verification features from thirdpartypasswordless recipe in favour of EV claims --- .../asyncio/__init__.py | 1 + .../recipe/thirdpartyemailpassword/recipe.py | 8 +- .../syncio/__init__.py | 1 + .../recipe/thirdpartyemailpassword/utils.py | 1 + .../recipe/thirdpartypasswordless/__init__.py | 2 - .../asyncio/__init__.py | 62 --------- .../backward_compatibility/__init__.py | 55 +------- .../emaildelivery/services/smtp/__init__.py | 17 --- .../smtp/service_implementation/__init__.py | 25 ---- .../email_verification_implementation.py | 46 ------- .../recipe/thirdpartypasswordless/recipe.py | 124 +----------------- .../thirdpartypasswordless/syncio/__init__.py | 36 ----- .../recipe/thirdpartypasswordless/types.py | 6 +- .../recipe/thirdpartypasswordless/utils.py | 45 +------ .../input_validation/test_input_validation.py | 12 +- .../test_email_delivery.py | 10 +- .../test_emaildelivery.py | 31 +++-- 17 files changed, 56 insertions(+), 426 deletions(-) delete mode 100644 supertokens_python/recipe/thirdpartypasswordless/emaildelivery/services/smtp/service_implementation/email_verification_implementation.py diff --git a/supertokens_python/recipe/thirdpartyemailpassword/asyncio/__init__.py b/supertokens_python/recipe/thirdpartyemailpassword/asyncio/__init__.py index 3e3b943d0..f54094a12 100644 --- a/supertokens_python/recipe/thirdpartyemailpassword/asyncio/__init__.py +++ b/supertokens_python/recipe/thirdpartyemailpassword/asyncio/__init__.py @@ -20,6 +20,7 @@ from ..types import EmailTemplateVars, User + async def get_user_by_id( user_id: str, user_context: Union[None, Dict[str, Any]] = None ) -> Union[None, User]: diff --git a/supertokens_python/recipe/thirdpartyemailpassword/recipe.py b/supertokens_python/recipe/thirdpartyemailpassword/recipe.py index 35482ea6c..98a80e148 100644 --- a/supertokens_python/recipe/thirdpartyemailpassword/recipe.py +++ b/supertokens_python/recipe/thirdpartyemailpassword/recipe.py @@ -196,9 +196,7 @@ def is_error_from_this_recipe_based_on_instance(self, err: Exception) -> bool: ) def get_apis_handled(self) -> List[APIHandled]: - apis_handled = ( - self.email_password_recipe.get_apis_handled() - ) + apis_handled = self.email_password_recipe.get_apis_handled() if self.third_party_recipe is not None: apis_handled = apis_handled + self.third_party_recipe.get_apis_handled() return apis_handled @@ -243,9 +241,7 @@ async def handle_error( raise err def get_all_cors_headers(self) -> List[str]: - cors_headers = ( - self.email_password_recipe.get_all_cors_headers() - ) + cors_headers = self.email_password_recipe.get_all_cors_headers() if self.third_party_recipe is not None: cors_headers = cors_headers + self.third_party_recipe.get_all_cors_headers() return cors_headers diff --git a/supertokens_python/recipe/thirdpartyemailpassword/syncio/__init__.py b/supertokens_python/recipe/thirdpartyemailpassword/syncio/__init__.py index be3e2788b..581c05456 100644 --- a/supertokens_python/recipe/thirdpartyemailpassword/syncio/__init__.py +++ b/supertokens_python/recipe/thirdpartyemailpassword/syncio/__init__.py @@ -22,6 +22,7 @@ ) from ..types import EmailTemplateVars, User + def get_user_by_id( user_id: str, user_context: Union[None, Dict[str, Any]] = None ) -> Union[None, User]: diff --git a/supertokens_python/recipe/thirdpartyemailpassword/utils.py b/supertokens_python/recipe/thirdpartyemailpassword/utils.py index 26c1ffbee..7810bef8f 100644 --- a/supertokens_python/recipe/thirdpartyemailpassword/utils.py +++ b/supertokens_python/recipe/thirdpartyemailpassword/utils.py @@ -36,6 +36,7 @@ if TYPE_CHECKING: from .recipe import ThirdPartyEmailPasswordRecipe + def email_verification_create_and_send_custom_email( recipe: ThirdPartyEmailPasswordRecipe, create_and_send_custom_email: Callable[ diff --git a/supertokens_python/recipe/thirdpartypasswordless/__init__.py b/supertokens_python/recipe/thirdpartypasswordless/__init__.py index 8cc07fdd1..358229fcf 100644 --- a/supertokens_python/recipe/thirdpartypasswordless/__init__.py +++ b/supertokens_python/recipe/thirdpartypasswordless/__init__.py @@ -63,7 +63,6 @@ def init( get_custom_user_input_code: Union[ Callable[[Dict[str, Any]], Awaitable[str]], None ] = None, - email_verification_feature: Union[InputEmailVerificationConfig, None] = None, email_delivery: Union[EmailDeliveryConfig[EmailTemplateVars], None] = None, sms_delivery: Union[SMSDeliveryConfig[SMSTemplateVars], None] = None, override: Union[InputOverrideConfig, None] = None, @@ -74,7 +73,6 @@ def init( flow_type, get_link_domain_and_path, get_custom_user_input_code, - email_verification_feature, email_delivery, sms_delivery, override, diff --git a/supertokens_python/recipe/thirdpartypasswordless/asyncio/__init__.py b/supertokens_python/recipe/thirdpartypasswordless/asyncio/__init__.py index cf7d2f651..d2b4d062e 100644 --- a/supertokens_python/recipe/thirdpartypasswordless/asyncio/__init__.py +++ b/supertokens_python/recipe/thirdpartypasswordless/asyncio/__init__.py @@ -24,68 +24,6 @@ from ..types import EmailTemplateVars, SMSTemplateVars, User -async def create_email_verification_token( - user_id: str, user_context: Union[None, Dict[str, Any]] = None -): - if user_context is None: - user_context = {} - email = await ThirdPartyPasswordlessRecipe.get_instance().get_email_for_user_id( - user_id, user_context - ) - return await ThirdPartyPasswordlessRecipe.get_instance().email_verification_recipe.recipe_implementation.create_email_verification_token( - user_id, email, user_context - ) - - -async def verify_email_using_token( - token: str, user_context: Union[None, Dict[str, Any]] = None -): - if user_context is None: - user_context = {} - return ThirdPartyPasswordlessRecipe.get_instance().email_verification_recipe.recipe_implementation.verify_email_using_token( - token, user_context - ) - - -async def is_email_verified( - user_id: str, user_context: Union[None, Dict[str, Any]] = None -): - if user_context is None: - user_context = {} - email = await ThirdPartyPasswordlessRecipe.get_instance().get_email_for_user_id( - user_id, user_context - ) - return await ThirdPartyPasswordlessRecipe.get_instance().email_verification_recipe.recipe_implementation.is_email_verified( - user_id, email, user_context - ) - - -async def unverify_email( - user_id: str, user_context: Union[None, Dict[str, Any]] = None -): - if user_context is None: - user_context = {} - email = await ThirdPartyPasswordlessRecipe.get_instance().get_email_for_user_id( - user_id, user_context - ) - return await ThirdPartyPasswordlessRecipe.get_instance().email_verification_recipe.recipe_implementation.unverify_email( - user_id, email, user_context - ) - - -async def revoke_email_verification_tokens( - user_id: str, user_context: Union[None, Dict[str, Any]] = None -): - if user_context is None: - user_context = {} - email = await ThirdPartyPasswordlessRecipe.get_instance().get_email_for_user_id( - user_id, user_context - ) - return await ThirdPartyPasswordlessRecipe.get_instance().email_verification_recipe.recipe_implementation.revoke_email_verification_tokens( - user_id, email, user_context - ) - - async def get_user_by_id( user_id: str, user_context: Union[None, Dict[str, Any]] = None ) -> Union[None, User]: diff --git a/supertokens_python/recipe/thirdpartypasswordless/emaildelivery/services/backward_compatibility/__init__.py b/supertokens_python/recipe/thirdpartypasswordless/emaildelivery/services/backward_compatibility/__init__.py index 2f7a20c64..76e99063d 100644 --- a/supertokens_python/recipe/thirdpartypasswordless/emaildelivery/services/backward_compatibility/__init__.py +++ b/supertokens_python/recipe/thirdpartypasswordless/emaildelivery/services/backward_compatibility/__init__.py @@ -14,74 +14,32 @@ from __future__ import annotations -from typing import TYPE_CHECKING, Any, Awaitable, Callable, Dict, Union +from typing import Any, Awaitable, Callable, Dict, Union from supertokens_python.ingredients.emaildelivery import EmailDeliveryInterface -from supertokens_python.recipe.emailverification.emaildelivery.services.backward_compatibility import ( - BackwardCompatibilityService as EVBackwardCompatibilityService, -) -from supertokens_python.recipe.emailverification.types import ( - VerificationEmailTemplateVars, -) -from supertokens_python.recipe.emailverification.types import User as EVUser from supertokens_python.recipe.passwordless.emaildelivery.services.backward_compatibility import ( BackwardCompatibilityService as PlessBackwardCompatibilityService, ) from supertokens_python.recipe.passwordless.types import ( CreateAndSendCustomEmailParameters, ) -from supertokens_python.recipe.thirdpartypasswordless.interfaces import RecipeInterface from supertokens_python.recipe.thirdpartypasswordless.types import EmailTemplateVars from supertokens_python.supertokens import AppInfo -if TYPE_CHECKING: - from supertokens_python.recipe.thirdpartypasswordless.utils import ( - InputEmailVerificationConfig, - ) - class BackwardCompatibilityService(EmailDeliveryInterface[EmailTemplateVars]): pless_backward_compatiblity_service: PlessBackwardCompatibilityService - ev_backward_compatiblity_service: EVBackwardCompatibilityService def __init__( self, app_info: AppInfo, - recipe_interface_impl: RecipeInterface, create_and_send_custom_email: Union[ Callable[ [CreateAndSendCustomEmailParameters, Dict[str, Any]], Awaitable[None] ], None, ] = None, - ev_feature: Union[InputEmailVerificationConfig, None] = None, ) -> None: - ev_create_and_send_custom_email = None - if ev_feature: - if ev_feature.create_and_send_custom_email is not None: - original_create_and_send_custom_email = ( - ev_feature.create_and_send_custom_email - ) - - async def create_and_send_custom_email_wrapper( - user: EVUser, link: str, user_context: Dict[str, Any] - ): - user_info = await recipe_interface_impl.get_user_by_id( - user.user_id, user_context - ) - if user_info is None: - raise Exception("Unknown User ID provided") - - return await original_create_and_send_custom_email( - user_info, link, user_context - ) - - ev_create_and_send_custom_email = create_and_send_custom_email_wrapper - - self.ev_backward_compatiblity_service = EVBackwardCompatibilityService( - app_info, ev_create_and_send_custom_email - ) - self.pless_backward_compatiblity_service = PlessBackwardCompatibilityService( app_info, create_and_send_custom_email ) @@ -89,11 +47,6 @@ async def create_and_send_custom_email_wrapper( async def send_email( self, template_vars: EmailTemplateVars, user_context: Dict[str, Any] ) -> None: - if isinstance(template_vars, VerificationEmailTemplateVars): - await self.ev_backward_compatiblity_service.send_email( - template_vars, user_context - ) - else: - await self.pless_backward_compatiblity_service.send_email( - template_vars, user_context - ) + await self.pless_backward_compatiblity_service.send_email( + template_vars, user_context + ) diff --git a/supertokens_python/recipe/thirdpartypasswordless/emaildelivery/services/smtp/__init__.py b/supertokens_python/recipe/thirdpartypasswordless/emaildelivery/services/smtp/__init__.py index deccfec33..76756d47c 100644 --- a/supertokens_python/recipe/thirdpartypasswordless/emaildelivery/services/smtp/__init__.py +++ b/supertokens_python/recipe/thirdpartypasswordless/emaildelivery/services/smtp/__init__.py @@ -19,12 +19,6 @@ EmailDeliveryInterface, SMTPSettings, ) -from supertokens_python.recipe.emailverification.emaildelivery.services.smtp import ( - SMTPService as EmailVerificationSMTPService, -) -from supertokens_python.recipe.emailverification.types import ( - VerificationEmailTemplateVars, -) from supertokens_python.recipe.passwordless.emaildelivery.services.smtp import ( SMTPService as PlessSMTPService, ) @@ -34,9 +28,6 @@ ) from .service_implementation import ServiceImplementation -from .service_implementation.email_verification_implementation import ( - ServiceImplementation as EmailVerificationServiceImpl, -) from .service_implementation.passwordless_implementation import ( ServiceImplementation as PlessServiceImpl, ) @@ -53,11 +44,6 @@ def __init__( oi = ServiceImplementation(self.transporter) service_implementation = oi if override is None else override(oi) - self.ev_smtp_service = EmailVerificationSMTPService( - smtp_settings=smtp_settings, - override=lambda _: EmailVerificationServiceImpl(service_implementation), - ) - self.pless_smtp_service = PlessSMTPService( smtp_settings=smtp_settings, override=lambda _: PlessServiceImpl(service_implementation), @@ -66,7 +52,4 @@ def __init__( async def send_email( self, template_vars: EmailTemplateVars, user_context: Dict[str, Any] ) -> None: - if isinstance(template_vars, VerificationEmailTemplateVars): - return await self.ev_smtp_service.send_email(template_vars, user_context) - return await self.pless_smtp_service.send_email(template_vars, user_context) diff --git a/supertokens_python/recipe/thirdpartypasswordless/emaildelivery/services/smtp/service_implementation/__init__.py b/supertokens_python/recipe/thirdpartypasswordless/emaildelivery/services/smtp/service_implementation/__init__.py index 345f904b5..493be068a 100644 --- a/supertokens_python/recipe/thirdpartypasswordless/emaildelivery/services/smtp/service_implementation/__init__.py +++ b/supertokens_python/recipe/thirdpartypasswordless/emaildelivery/services/smtp/service_implementation/__init__.py @@ -19,20 +19,11 @@ EmailContent, SMTPServiceInterface, ) -from supertokens_python.recipe.emailverification.emaildelivery.services.smtp.service_implementation import ( - ServiceImplementation as EVServiceImplementation, -) -from supertokens_python.recipe.emailverification.types import ( - VerificationEmailTemplateVars, -) from supertokens_python.recipe.passwordless.emaildelivery.services.smtp.service_implementation import ( ServiceImplementation as PlessServiceImplementation, ) from supertokens_python.recipe.thirdpartypasswordless.types import EmailTemplateVars -from .email_verification_implementation import ( - ServiceImplementation as DerivedEVServiceImplementation, -) from .passwordless_implementation import ( ServiceImplementation as DerivedPlessServiceImplementation, ) @@ -42,19 +33,6 @@ class ServiceImplementation(SMTPServiceInterface[EmailTemplateVars]): def __init__(self, transporter: Transporter) -> None: super().__init__(transporter) - # Email Verification: - email_verification_service_impl = EVServiceImplementation(transporter) - self.ev_send_raw_email = email_verification_service_impl.send_raw_email - self.ev_get_content = email_verification_service_impl.get_content - - derived_ev_service_implementation = DerivedEVServiceImplementation(self) - email_verification_service_impl.send_raw_email = ( - derived_ev_service_implementation.send_raw_email - ) - email_verification_service_impl.get_content = ( - derived_ev_service_implementation.get_content - ) - # Passwordless: pless_service_impl = PlessServiceImplementation(transporter) self.pless_send_raw_email = pless_service_impl.send_raw_email @@ -76,7 +54,4 @@ async def send_raw_email( async def get_content( self, template_vars: EmailTemplateVars, user_context: Dict[str, Any] ) -> EmailContent: - if isinstance(template_vars, VerificationEmailTemplateVars): - return await self.ev_get_content(template_vars, user_context) - return await self.pless_get_content(template_vars, user_context) diff --git a/supertokens_python/recipe/thirdpartypasswordless/emaildelivery/services/smtp/service_implementation/email_verification_implementation.py b/supertokens_python/recipe/thirdpartypasswordless/emaildelivery/services/smtp/service_implementation/email_verification_implementation.py deleted file mode 100644 index c00b66fba..000000000 --- a/supertokens_python/recipe/thirdpartypasswordless/emaildelivery/services/smtp/service_implementation/email_verification_implementation.py +++ /dev/null @@ -1,46 +0,0 @@ -# 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 typing import Any, Dict - -from supertokens_python.ingredients.emaildelivery.types import ( - EmailContent, - SMTPServiceInterface, -) -from supertokens_python.recipe.emailverification.types import ( - VerificationEmailTemplateVars, -) -from supertokens_python.recipe.thirdpartypasswordless.types import EmailTemplateVars - - -class ServiceImplementation(SMTPServiceInterface[VerificationEmailTemplateVars]): - def __init__( - self, tppless_service_implementation: SMTPServiceInterface[EmailTemplateVars] - ) -> None: - super().__init__(tppless_service_implementation.transporter) - self.tppless_service_implementation = tppless_service_implementation - - async def send_raw_email( - self, content: EmailContent, user_context: Dict[str, Any] - ) -> None: - return await self.tppless_service_implementation.send_raw_email( - content, user_context - ) - - async def get_content( - self, template_vars: VerificationEmailTemplateVars, user_context: Dict[str, Any] - ) -> EmailContent: - return await self.tppless_service_implementation.get_content( - template_vars, user_context - ) diff --git a/supertokens_python/recipe/thirdpartypasswordless/recipe.py b/supertokens_python/recipe/thirdpartypasswordless/recipe.py index 2e113453a..5cb88e411 100644 --- a/supertokens_python/recipe/thirdpartypasswordless/recipe.py +++ b/supertokens_python/recipe/thirdpartypasswordless/recipe.py @@ -14,23 +14,16 @@ from __future__ import annotations from os import environ -from typing import TYPE_CHECKING, Any, Dict, List, Union, cast +from typing import TYPE_CHECKING, Any, Dict, List, Union from supertokens_python.framework.response import BaseResponse from supertokens_python.ingredients.emaildelivery.types import EmailDeliveryConfig from supertokens_python.normalised_url_path import NormalisedURLPath from supertokens_python.querier import Querier -from supertokens_python.recipe.emailverification.types import ( - EmailTemplateVars as EmailVerificationEmailTemplateVars, -) -from supertokens_python.recipe.emailverification.types import ( - EmailVerificationIngredients, -) from supertokens_python.recipe.passwordless.types import PasswordlessIngredients from supertokens_python.recipe.thirdparty.provider import Provider from supertokens_python.recipe.thirdparty.types import ThirdPartyIngredients from supertokens_python.recipe.thirdpartypasswordless.types import ( - EmailTemplateVars, ThirdPartyPasswordlessIngredients, ) from supertokens_python.recipe_module import APIHandled, RecipeModule @@ -50,7 +43,6 @@ from .recipeimplementation.third_party_recipe_implementation import ( RecipeImplementation as ThirdPartyRecipeImplementation, ) -from .utils import InputEmailVerificationConfig if TYPE_CHECKING: from supertokens_python.framework.request import BaseRequest @@ -62,7 +54,6 @@ from supertokens_python.ingredients.emaildelivery import EmailDeliveryIngredient from supertokens_python.ingredients.smsdelivery import SMSDeliveryIngredient from supertokens_python.ingredients.smsdelivery.types import SMSDeliveryConfig -from supertokens_python.recipe.emailverification import EmailVerificationRecipe from supertokens_python.recipe.thirdparty import ThirdPartyRecipe from supertokens_python.recipe.thirdparty.utils import ( InputOverrideConfig as TPOverrideConfig, @@ -70,15 +61,8 @@ from supertokens_python.recipe.thirdparty.utils import SignInAndUpFeature from typing_extensions import Literal -from ..emailverification.interfaces import ( - CreateEmailVerificationTokenEmailAlreadyVerifiedError, - CreateEmailVerificationTokenOkResult, -) -from ..emailverification.interfaces import RecipeInterface as EVRecipeInterface -from ..emailverification.utils import OverrideConfig as EVOverrideConfig from ..passwordless import PasswordlessRecipe from ..passwordless.interfaces import APIInterface as PasswordlessAPIInterface -from ..passwordless.interfaces import PasswordlessLoginEmailTemplateVars from ..passwordless.interfaces import RecipeInterface as PasswordlessRecipeInterface from ..passwordless.utils import OverrideConfig as PlessOverrideConfig from ..thirdparty.interfaces import APIInterface as ThirdPartyAPIInterface @@ -110,10 +94,8 @@ def __init__( get_custom_user_input_code: Union[ Callable[[Dict[str, Any]], Awaitable[str]], None ] = None, - email_verification_feature: Union[InputEmailVerificationConfig, None] = None, override: Union[InputOverrideConfig, None] = None, providers: Union[List[Provider], None] = None, - email_verification_recipe: Union[EmailVerificationRecipe, None] = None, third_party_recipe: Union[ThirdPartyRecipe, None] = None, passwordless_recipe: Union[PasswordlessRecipe, None] = None, email_delivery: Union[EmailDeliveryConfig[EmailTemplateVars], None] = None, @@ -123,7 +105,6 @@ def __init__( self.config = validate_and_normalise_user_input( self, contact_config=contact_config, - email_verification_feature=email_verification_feature, flow_type=flow_type, get_custom_user_input_code=get_custom_user_input_code, get_link_domain_and_path=get_link_domain_and_path, @@ -153,7 +134,7 @@ def __init__( email_delivery_ingredient = ingredients.email_delivery if email_delivery_ingredient is None: self.email_delivery = EmailDeliveryIngredient( - self.config.get_email_delivery_config(recipe_implementation) + self.config.get_email_delivery_config() ) else: self.email_delivery = email_delivery_ingredient @@ -166,83 +147,6 @@ def __init__( else: self.sms_delivery = sms_delivery_ingredient - if email_verification_recipe is not None: - self.email_verification_recipe = email_verification_recipe - else: - userProvidedFunctionOverride: Union[ - None, Callable[[EVRecipeInterface], EVRecipeInterface] - ] = None - if self.config.email_verification_feature.override is not None: - userProvidedFunctionOverride = ( - self.config.email_verification_feature.override.functions - ) - - def email_verification_override( - original_impl: EVRecipeInterface, - ) -> EVRecipeInterface: - og_create_email_verification_token = ( - original_impl.create_email_verification_token - ) - og_is_email_verified = original_impl.is_email_verified - - async def create_email_verification_token( - user_id: str, email: str, user_context: Dict[str, Any] - ) -> Union[ - CreateEmailVerificationTokenOkResult, - CreateEmailVerificationTokenEmailAlreadyVerifiedError, - ]: - user = await self.recipe_implementation.get_user_by_id( - user_id, user_context - ) - if user is None or user.third_party_info is not None: - return await og_create_email_verification_token( - user_id, email, user_context - ) - return CreateEmailVerificationTokenEmailAlreadyVerifiedError() - - async def is_email_verified( - user_id: str, email: str, user_context: Dict[str, Any] - ) -> bool: - user = await self.recipe_implementation.get_user_by_id( - user_id, user_context - ) - if user is None or user.third_party_info is not None: - return await og_is_email_verified(user_id, email, user_context) - - # this is a passwordless user, so we always want - # to return that their info / email is verified - return True - - original_impl.create_email_verification_token = ( - create_email_verification_token - ) - original_impl.is_email_verified = is_email_verified - - if userProvidedFunctionOverride is None: - return original_impl - return userProvidedFunctionOverride(original_impl) - - if self.config.email_verification_feature.override is None: - self.config.email_verification_feature.override = EVOverrideConfig( - email_verification_override - ) - else: - self.config.email_verification_feature.override.functions = ( - email_verification_override - ) - - ev_email_delivery = cast( - EmailDeliveryIngredient[EmailVerificationEmailTemplateVars], - self.email_delivery, - ) - ev_ingredients = EmailVerificationIngredients(ev_email_delivery) - self.email_verification_recipe = EmailVerificationRecipe( - recipe_id, - app_info, - self.config.email_verification_feature, - ev_ingredients, - ) - if passwordless_recipe is not None: self.passwordless_recipe = passwordless_recipe else: @@ -257,10 +161,7 @@ def apis_override_passwordless( ) -> PasswordlessAPIInterface: return get_passwordless_interface_impl(self.api_implementation) - pless_email_delivery = cast( - EmailDeliveryIngredient[PasswordlessLoginEmailTemplateVars], - self.email_delivery, - ) + pless_email_delivery = self.email_delivery pless_sms_delivery = self.sms_delivery pless_ingredients = PasswordlessIngredients( pless_email_delivery, pless_sms_delivery @@ -309,9 +210,6 @@ def apis_override_third_party( def is_error_from_this_recipe_based_on_instance(self, err: Exception) -> bool: return isinstance(err, SuperTokensError) and ( isinstance(err, SupertokensThirdPartyPasswordlessError) - or self.email_verification_recipe.is_error_from_this_recipe_based_on_instance( - err - ) or self.passwordless_recipe.is_error_from_this_recipe_based_on_instance(err) or ( self.third_party_recipe is not None @@ -322,10 +220,7 @@ def is_error_from_this_recipe_based_on_instance(self, err: Exception) -> bool: ) def get_apis_handled(self) -> List[APIHandled]: - apis_handled = ( - self.passwordless_recipe.get_apis_handled() - + self.email_verification_recipe.get_apis_handled() - ) + apis_handled = self.passwordless_recipe.get_apis_handled() if self.third_party_recipe is not None: apis_handled = apis_handled + self.third_party_recipe.get_apis_handled() return apis_handled @@ -355,9 +250,7 @@ async def handle_api_request( return await self.third_party_recipe.handle_api_request( request_id, request, path, method, response ) - return await self.email_verification_recipe.handle_api_request( - request_id, request, path, method, response - ) + return None async def handle_error( self, request: BaseRequest, err: SuperTokensError, response: BaseResponse @@ -372,10 +265,7 @@ async def handle_error( raise err def get_all_cors_headers(self) -> List[str]: - cors_headers = ( - self.passwordless_recipe.get_all_cors_headers() - + self.email_verification_recipe.get_all_cors_headers() - ) + cors_headers = self.passwordless_recipe.get_all_cors_headers() if self.third_party_recipe is not None: cors_headers = cors_headers + self.third_party_recipe.get_all_cors_headers() return cors_headers @@ -392,7 +282,6 @@ def init( get_custom_user_input_code: Union[ Callable[[Dict[str, Any]], Awaitable[str]], None ] = None, - email_verification_feature: Union[InputEmailVerificationConfig, None] = None, email_delivery: Union[EmailDeliveryConfig[EmailTemplateVars], None] = None, sms_delivery: Union[SMSDeliveryConfig[SMSTemplateVars], None] = None, override: Union[InputOverrideConfig, None] = None, @@ -409,7 +298,6 @@ def func(app_info: AppInfo): ingredients, get_link_domain_and_path, get_custom_user_input_code, - email_verification_feature, override, providers, email_delivery=email_delivery, diff --git a/supertokens_python/recipe/thirdpartypasswordless/syncio/__init__.py b/supertokens_python/recipe/thirdpartypasswordless/syncio/__init__.py index 38b3939a4..79f694080 100644 --- a/supertokens_python/recipe/thirdpartypasswordless/syncio/__init__.py +++ b/supertokens_python/recipe/thirdpartypasswordless/syncio/__init__.py @@ -24,42 +24,6 @@ from ..types import EmailTemplateVars, SMSTemplateVars, User -def create_email_verification_token( - user_id: str, user_context: Union[None, Dict[str, Any]] = None -): - from ..asyncio import create_email_verification_token - - return sync(create_email_verification_token(user_id, user_context)) - - -def verify_email_using_token( - token: str, user_context: Union[None, Dict[str, Any]] = None -): - from ..asyncio import verify_email_using_token - - return sync(verify_email_using_token(token, user_context)) - - -def revoke_email_verification_tokens( - user_id: str, user_context: Union[None, Dict[str, Any]] = None -): - from ..asyncio import revoke_email_verification_tokens - - return sync(revoke_email_verification_tokens(user_id, user_context)) - - -def is_email_verified(user_id: str, user_context: Union[None, Dict[str, Any]] = None): - from ..asyncio import is_email_verified - - return sync(is_email_verified(user_id, user_context)) - - -def unverify_email(user_id: str, user_context: Union[None, Dict[str, Any]] = None): - from ..asyncio import unverify_email - - return sync(unverify_email(user_id, user_context)) - - def get_user_by_id( user_id: str, user_context: Union[None, Dict[str, Any]] = None ) -> Union[None, User]: diff --git a/supertokens_python/recipe/thirdpartypasswordless/types.py b/supertokens_python/recipe/thirdpartypasswordless/types.py index 987257aef..20a41c85f 100644 --- a/supertokens_python/recipe/thirdpartypasswordless/types.py +++ b/supertokens_python/recipe/thirdpartypasswordless/types.py @@ -48,9 +48,11 @@ def __init__( _T = TypeVar("_T") # Export: -EmailTemplateVars = Union[tp_types.EmailTemplateVars, pless_types.EmailTemplateVars] +EmailTemplateVars = pless_types.EmailTemplateVars SMSTemplateVars = pless_types.SMSTemplateVars -VerificationEmailTemplateVars = tp_types.VerificationEmailTemplateVars +VerificationEmailTemplateVars = ( + tp_types.VerificationEmailTemplateVars +) # TODO: Remove this. PasswordlessLoginEmailTemplateVars = pless_types.PasswordlessLoginEmailTemplateVars PasswordlessLoginSMSTemplateVars = pless_types.PasswordlessLoginSMSTemplateVars diff --git a/supertokens_python/recipe/thirdpartypasswordless/utils.py b/supertokens_python/recipe/thirdpartypasswordless/utils.py index b310c0ef9..bce3cf84c 100644 --- a/supertokens_python/recipe/thirdpartypasswordless/utils.py +++ b/supertokens_python/recipe/thirdpartypasswordless/utils.py @@ -54,9 +54,6 @@ from supertokens_python.recipe.emailverification.utils import ( OverrideConfig as EmailVerificationOverrideConfig, ) -from supertokens_python.recipe.emailverification.utils import ( - ParentRecipeEmailVerificationConfig, -) from .smsdelivery.services.backward_compatibility import ( BackwardCompatibilityService as SMSBackwardCompatibilityService, @@ -115,26 +112,6 @@ async def func(user: EmailVerificationUser, user_context: Dict[str, Any]): return func -def validate_and_normalise_email_verification_config( - recipe: ThirdPartyPasswordlessRecipe, - config: Union[InputEmailVerificationConfig, None], - override: InputOverrideConfig, -) -> ParentRecipeEmailVerificationConfig: - create_and_send_custom_email = None - if config is None: - config = InputEmailVerificationConfig() - if config.create_and_send_custom_email is not None: - create_and_send_custom_email = email_verification_create_and_send_custom_email( - recipe, config.create_and_send_custom_email - ) - - return ParentRecipeEmailVerificationConfig( - mode="OPTIONAL", # TODO: FIXME? - create_and_send_custom_email=create_and_send_custom_email, - override=override.email_verification_feature, - ) - - class InputOverrideConfig: def __init__( self, @@ -162,7 +139,6 @@ def __init__( self, override: OverrideConfig, providers: List[Provider], - email_verification_feature: ParentRecipeEmailVerificationConfig, contact_config: ContactConfig, flow_type: Literal[ "USER_INPUT_CODE", "MAGIC_LINK", "USER_INPUT_CODE_AND_MAGIC_LINK" @@ -171,7 +147,7 @@ def __init__( [PhoneOrEmailInput, Dict[str, Any]], Awaitable[str] ], get_email_delivery_config: Callable[ - [RecipeInterface], EmailDeliveryConfigWithService[EmailTemplateVars] + [], EmailDeliveryConfigWithService[EmailTemplateVars] ], get_sms_delivery_config: Callable[ [], SMSDeliveryConfigWithService[SMSTemplateVars] @@ -180,7 +156,6 @@ def __init__( Callable[[Dict[str, Any]], Awaitable[str]], None ] = None, ): - self.email_verification_feature = email_verification_feature self.providers = providers self.contact_config = contact_config self.flow_type: Literal[ @@ -205,7 +180,6 @@ def validate_and_normalise_user_input( get_custom_user_input_code: Union[ Callable[[Dict[str, Any]], Awaitable[str]], None ] = None, - email_verification_feature: Union[InputEmailVerificationConfig, None] = None, override: Union[InputOverrideConfig, None] = None, providers: Union[List[Provider], None] = None, email_delivery: Union[EmailDeliveryConfig[EmailTemplateVars], None] = None, @@ -219,11 +193,6 @@ def validate_and_normalise_user_input( "flow_type must be one of USER_INPUT_CODE, MAGIC_LINK, USER_INPUT_CODE_AND_MAGIC_LINK" ) - if email_verification_feature is not None and not isinstance(email_verification_feature, InputEmailVerificationConfig): # type: ignore - raise ValueError( - "email_verification_feature must be an instance of InputEmailVerificationConfig or None" - ) - if override is not None and not isinstance(override, InputOverrideConfig): # type: ignore raise ValueError("override must be an instance of InputOverrideConfig or None") @@ -242,9 +211,9 @@ def validate_and_normalise_user_input( if get_link_domain_and_path is None: get_link_domain_and_path = default_get_link_domain_and_path(recipe.app_info) - def get_email_delivery_config( - tppless_recipe: RecipeInterface, - ) -> EmailDeliveryConfigWithService[EmailTemplateVars]: + def get_email_delivery_config() -> EmailDeliveryConfigWithService[ + EmailTemplateVars + ]: email_service = email_delivery.service if email_delivery is not None else None if isinstance( contact_config, (ContactEmailOnlyConfig, ContactEmailOrPhoneConfig) @@ -254,12 +223,9 @@ def get_email_delivery_config( create_and_send_custom_email = None if email_service is None: - ev_feature = email_verification_feature email_service = BackwardCompatibilityService( recipe.app_info, - tppless_recipe, create_and_send_custom_email, - ev_feature, ) if email_delivery is not None and email_delivery.override is not None: @@ -303,9 +269,6 @@ def get_sms_delivery_config() -> SMSDeliveryConfigWithService[SMSTemplateVars]: flow_type=flow_type, get_link_domain_and_path=get_link_domain_and_path, get_custom_user_input_code=get_custom_user_input_code, - email_verification_feature=validate_and_normalise_email_verification_config( - recipe, email_verification_feature, override - ), get_email_delivery_config=get_email_delivery_config, get_sms_delivery_config=get_sms_delivery_config, ) diff --git a/tests/input_validation/test_input_validation.py b/tests/input_validation/test_input_validation.py index 8f3fd6620..47c15babd 100644 --- a/tests/input_validation/test_input_validation.py +++ b/tests/input_validation/test_input_validation.py @@ -469,10 +469,8 @@ async def test_init_validation_thirdpartyemailpassword(): ), framework="fastapi", recipe_list=[ - emailverification.init( - "email verification" # type: ignore - ), - thirdpartyemailpassword.init() + emailverification.init("email verification"), # type: ignore + thirdpartyemailpassword.init(), ], ) assert ( @@ -598,14 +596,16 @@ async def test_init_validation_thirdpartypasswordless(): ), framework="fastapi", recipe_list=[ + emailverification.init( + "email verify", # type: ignore + ), thirdpartypasswordless.init( contact_config=ContactEmailOrPhoneConfig( create_and_send_custom_text_message=save_code_text, create_and_send_custom_email=save_code_email, ), flow_type="USER_INPUT_CODE_AND_MAGIC_LINK", - email_verification_feature="email verify", # type: ignore - ) + ), ], ) assert ( diff --git a/tests/thirdpartyemailpassword/test_email_delivery.py b/tests/thirdpartyemailpassword/test_email_delivery.py index 486f81290..ace47c2b6 100644 --- a/tests/thirdpartyemailpassword/test_email_delivery.py +++ b/tests/thirdpartyemailpassword/test_email_delivery.py @@ -607,10 +607,12 @@ async def custom_create_and_send_custom_email( ), framework="fastapi", recipe_list=[ - emailverification.init(ParentRecipeEmailVerificationConfig( - mode="OPTIONAL", - create_and_send_custom_email=custom_create_and_send_custom_email - )), + emailverification.init( + ParentRecipeEmailVerificationConfig( + mode="OPTIONAL", + create_and_send_custom_email=custom_create_and_send_custom_email, + ) + ), thirdpartyemailpassword.init(), session.init(), ], diff --git a/tests/thirdpartypasswordless/test_emaildelivery.py b/tests/thirdpartypasswordless/test_emaildelivery.py index 12dfc6308..7a88bba14 100644 --- a/tests/thirdpartypasswordless/test_emaildelivery.py +++ b/tests/thirdpartypasswordless/test_emaildelivery.py @@ -32,7 +32,15 @@ SMTPSettingsFrom, ) from supertokens_python.querier import Querier -from supertokens_python.recipe import passwordless, session, thirdpartypasswordless +from supertokens_python.recipe import ( + passwordless, + session, + thirdpartypasswordless, + emailverification, +) +from supertokens_python.recipe.emailverification import ( + ParentRecipeEmailVerificationConfig, +) from supertokens_python.recipe.emailverification.interfaces import ( CreateEmailVerificationTokenEmailAlreadyVerifiedError, ) @@ -45,8 +53,10 @@ RecipeImplementation as SessionRecipeImplementation, ) from supertokens_python.recipe.session.session_functions import create_new_session -from supertokens_python.recipe.thirdpartypasswordless.asyncio import ( +from supertokens_python.recipe.emailverification.asyncio import ( create_email_verification_token, +) +from supertokens_python.recipe.thirdpartypasswordless.asyncio import ( passwordlessSigninup, thirdparty_sign_in_up, ) @@ -58,9 +68,9 @@ ) from supertokens_python.recipe.thirdpartypasswordless.types import ( EmailTemplateVars, - User, VerificationEmailTemplateVars, ) +from supertokens_python.recipe.emailverification.types import User as EVUser from supertokens_python.utils import is_version_gte from tests.utils import ( clean_st, @@ -179,7 +189,7 @@ async def test_email_verify_backward_compatibility(driver_config_client: TestCli email_verify_url = "" async def create_and_send_custom_email( - input_: User, email_verification_link: str, _: Dict[str, Any] + input_: EVUser, email_verification_link: str, _: Dict[str, Any] ): nonlocal email, email_verify_url email = input_.email @@ -195,13 +205,16 @@ async def create_and_send_custom_email( ), framework="fastapi", recipe_list=[ + emailverification.init( + ParentRecipeEmailVerificationConfig( + mode="OPTIONAL", + create_and_send_custom_email=create_and_send_custom_email, + ) + ), thirdpartypasswordless.init( contact_config=ContactEmailOnlyConfig(), flow_type="USER_INPUT_CODE_AND_MAGIC_LINK", providers=[], - email_verification_feature=thirdpartypasswordless.InputEmailVerificationConfig( - create_and_send_custom_email=create_and_send_custom_email, - ), ), session.init(), ], @@ -535,9 +548,7 @@ async def send_email_override( return pless_response = await passwordlessSigninup("test@example.com", None, {}) - create_token = await create_email_verification_token( - pless_response.user.user_id, {} - ) + create_token = await create_email_verification_token(pless_response.user.user_id) assert isinstance( create_token, CreateEmailVerificationTokenEmailAlreadyVerifiedError From e31b60d6ebb323c6ae00e307bef270d29aeb25e0 Mon Sep 17 00:00:00 2001 From: KShivendu Date: Wed, 10 Aug 2022 12:28:19 +0530 Subject: [PATCH 08/18] refactor: Clean up usages of email verification in other recipes in favour of ev claims --- .../recipe/emailpassword/__init__.py | 1 - .../recipe/emailpassword/interfaces.py | 48 ----------------- .../recipe/emailpassword/utils.py | 25 --------- .../recipe/emailverification/interfaces.py | 3 +- .../recipe/passwordless/api/implementation.py | 15 +++--- .../recipe/thirdparty/asyncio/__init__.py | 12 +---- .../recipe/thirdparty/recipe.py | 4 +- .../recipe/thirdparty/syncio/__init__.py | 10 +--- supertokens_python/recipe/thirdparty/types.py | 14 ----- supertokens_python/recipe/thirdparty/utils.py | 45 +--------------- .../recipe/thirdpartyemailpassword/types.py | 1 - .../recipe/thirdpartypasswordless/__init__.py | 1 - .../recipe/thirdpartypasswordless/types.py | 4 -- .../recipe/thirdpartypasswordless/utils.py | 19 ------- tests/thirdparty/test_emaildelivery.py | 23 +++++---- .../test_emaildelivery.py | 51 ++++++++++++------- 16 files changed, 60 insertions(+), 216 deletions(-) diff --git a/supertokens_python/recipe/emailpassword/__init__.py b/supertokens_python/recipe/emailpassword/__init__.py index d116b1b08..d91415aa5 100644 --- a/supertokens_python/recipe/emailpassword/__init__.py +++ b/supertokens_python/recipe/emailpassword/__init__.py @@ -26,7 +26,6 @@ exceptions = ex InputOverrideConfig = utils.InputOverrideConfig InputResetPasswordUsingTokenFeature = utils.InputResetPasswordUsingTokenFeature -# InputEmailVerificationConfig = utils.InputEmailVerificationConfig InputSignUpFeature = utils.InputSignUpFeature InputFormField = utils.InputFormField SMTPService = emaildelivery_services.SMTPService diff --git a/supertokens_python/recipe/emailpassword/interfaces.py b/supertokens_python/recipe/emailpassword/interfaces.py index 38f0b3281..4a99c2f2a 100644 --- a/supertokens_python/recipe/emailpassword/interfaces.py +++ b/supertokens_python/recipe/emailpassword/interfaces.py @@ -144,7 +144,6 @@ def __init__( config: EmailPasswordConfig, recipe_implementation: RecipeInterface, app_info: AppInfo, - # email_verification_recipe_implementation: EmailVerificationRecipeInterface, email_delivery: EmailDeliveryIngredient[EmailTemplateVars], ): self.request: BaseRequest = request @@ -153,56 +152,9 @@ def __init__( self.config: EmailPasswordConfig = config self.recipe_implementation: RecipeInterface = recipe_implementation self.app_info = app_info - # self.email_verification_recipe_implementation: EmailVerificationRecipeInterface = ( - # email_verification_recipe_implementation - # ) self.email_delivery = email_delivery -class EmailVerifyPostOkResult(APIResponse): - status: str = "OK" - - def __init__(self, user: User): - self.user = user - - def to_json(self) -> Dict[str, Any]: - return { - "status": self.status, - "user": {"id": self.user.user_id, "email": self.user.email}, - } - - -class EmailVerifyPostInvalidTokenError(APIResponse): - status: str = "EMAIL_VERIFICATION_INVALID_TOKEN_ERROR" - - def to_json(self) -> Dict[str, Any]: - return {"status": self.status} - - -class IsEmailVerifiedGetOkResult(APIResponse): - status: str = "OK" - - def __init__(self, is_verified: bool): - self.is_verified = is_verified - - def to_json(self) -> Dict[str, Any]: - return {"status": self.status, "isVerified": self.is_verified} - - -class GenerateEmailVerifyTokenPostOkResult(APIResponse): - status: str = "OK" - - def to_json(self) -> Dict[str, Any]: - return {"status": self.status} - - -class GenerateEmailVerifyTokenPostEmailAlreadyVerifiedError(APIResponse): - status: str = "EMAIL_ALREADY_VERIFIED_ERROR" - - def to_json(self) -> Dict[str, Any]: - return {"status": self.status} - - class EmailExistsGetOkResult(APIResponse): status: str = "OK" diff --git a/supertokens_python/recipe/emailpassword/utils.py b/supertokens_python/recipe/emailpassword/utils.py index ae4546f92..fc95b2a1a 100644 --- a/supertokens_python/recipe/emailpassword/utils.py +++ b/supertokens_python/recipe/emailpassword/utils.py @@ -287,31 +287,6 @@ async def func(user: EmailVerificationUser, user_context: Dict[str, Any]): return func -# def validate_and_normalise_email_verification_config( -# recipe: EmailPasswordRecipe, -# config: Union[InputEmailVerificationConfig, None], -# override: InputOverrideConfig, -# ) -> ParentRecipeEmailVerificationConfig: -# create_and_send_custom_email = None -# get_email_verification_url = None -# if config is None: -# config = InputEmailVerificationConfig() -# if config.create_and_send_custom_email is not None: -# create_and_send_custom_email = email_verification_create_and_send_custom_email( -# recipe, config.create_and_send_custom_email -# ) -# if config.get_email_verification_url is not None: -# get_email_verification_url = email_verification_get_email_verification_url( -# recipe, config.get_email_verification_url -# ) -# return ParentRecipeEmailVerificationConfig( -# get_email_for_user_id=recipe.get_email_for_user_id, -# create_and_send_custom_email=create_and_send_custom_email, -# get_email_verification_url=get_email_verification_url, -# override=override.email_verification_feature, -# ) - - class InputOverrideConfig: def __init__( self, diff --git a/supertokens_python/recipe/emailverification/interfaces.py b/supertokens_python/recipe/emailverification/interfaces.py index c1f705a4b..a852192b9 100644 --- a/supertokens_python/recipe/emailverification/interfaces.py +++ b/supertokens_python/recipe/emailverification/interfaces.py @@ -125,8 +125,7 @@ def to_json(self) -> Dict[str, Any]: class EmailVerifyPostInvalidTokenError(APIResponse): - def __init__(self): - self.status = "EMAIL_VERIFICATION_INVALID_TOKEN_ERROR" + status = "EMAIL_VERIFICATION_INVALID_TOKEN_ERROR" def to_json(self) -> Dict[str, Any]: return {"status": self.status} diff --git a/supertokens_python/recipe/passwordless/api/implementation.py b/supertokens_python/recipe/passwordless/api/implementation.py index cb1966886..c1833faa6 100644 --- a/supertokens_python/recipe/passwordless/api/implementation.py +++ b/supertokens_python/recipe/passwordless/api/implementation.py @@ -274,18 +274,19 @@ async def consume_code_post( if user.email is not None: ev_instance = EmailVerificationRecipe.get_instance() - if ev_instance: - token_response = await ev_instance.recipe_implementation.create_email_verification_token( + token_response = ( + await ev_instance.recipe_implementation.create_email_verification_token( user.user_id, user.email, user_context, ) + ) - if isinstance(token_response, CreateEmailVerificationTokenOkResult): - await ev_instance.recipe_implementation.verify_email_using_token( - token_response.token, - user_context, - ) + if isinstance(token_response, CreateEmailVerificationTokenOkResult): + await ev_instance.recipe_implementation.verify_email_using_token( + token_response.token, + user_context, + ) session = await create_new_session( api_options.request, user.user_id, {}, {}, user_context=user_context diff --git a/supertokens_python/recipe/thirdparty/asyncio/__init__.py b/supertokens_python/recipe/thirdparty/asyncio/__init__.py index a97691e9a..ff8a1f06c 100644 --- a/supertokens_python/recipe/thirdparty/asyncio/__init__.py +++ b/supertokens_python/recipe/thirdparty/asyncio/__init__.py @@ -16,7 +16,7 @@ from supertokens_python.recipe.thirdparty.recipe import ThirdPartyRecipe -from ..types import EmailTemplateVars, User +from ..types import User async def get_user_by_id( @@ -65,13 +65,3 @@ async def sign_in_up( return await ThirdPartyRecipe.get_instance().recipe_implementation.sign_in_up( third_party_id, third_party_user_id, email, email_verified, user_context ) - - -async def send_email( - input_: EmailTemplateVars, user_context: Union[None, Dict[str, Any]] = None -): - if user_context is None: - user_context = {} - return await ThirdPartyRecipe.get_instance().email_delivery.ingredient_interface_impl.send_email( - input_, user_context - ) diff --git a/supertokens_python/recipe/thirdparty/recipe.py b/supertokens_python/recipe/thirdparty/recipe.py index c24305122..51faf072e 100644 --- a/supertokens_python/recipe/thirdparty/recipe.py +++ b/supertokens_python/recipe/thirdparty/recipe.py @@ -32,7 +32,6 @@ from .utils import SignInAndUpFeature, InputOverrideConfig from supertokens_python.exceptions import SuperTokensError, raise_general_exception -from supertokens_python.ingredients.emaildelivery import EmailDeliveryIngredient from supertokens_python.recipe.emailverification import EmailVerificationRecipe from .api import ( @@ -42,14 +41,13 @@ ) from .constants import APPLE_REDIRECT_HANDLER, AUTHORISATIONURL, SIGNINUP from .exceptions import SuperTokensThirdPartyError -from .types import ThirdPartyIngredients, EmailTemplateVars +from .types import ThirdPartyIngredients from .utils import validate_and_normalise_user_input class ThirdPartyRecipe(RecipeModule): recipe_id = "thirdparty" __instance = None - email_delivery: EmailDeliveryIngredient[EmailTemplateVars] def __init__( self, diff --git a/supertokens_python/recipe/thirdparty/syncio/__init__.py b/supertokens_python/recipe/thirdparty/syncio/__init__.py index dbfeec566..e6374e927 100644 --- a/supertokens_python/recipe/thirdparty/syncio/__init__.py +++ b/supertokens_python/recipe/thirdparty/syncio/__init__.py @@ -15,7 +15,7 @@ from supertokens_python.async_to_sync_wrapper import sync -from ..types import EmailTemplateVars, User +from ..types import User def get_user_by_id( @@ -62,11 +62,3 @@ def sign_in_up( third_party_id, third_party_user_id, email, email_verified, user_context ) ) - - -def send_email( - input_: EmailTemplateVars, user_context: Union[None, Dict[str, Any]] = None -): - from supertokens_python.recipe.thirdparty.asyncio import send_email - - return sync(send_email(input_, user_context)) diff --git a/supertokens_python/recipe/thirdparty/types.py b/supertokens_python/recipe/thirdparty/types.py index f5692a00b..bd892dd85 100644 --- a/supertokens_python/recipe/thirdparty/types.py +++ b/supertokens_python/recipe/thirdparty/types.py @@ -14,11 +14,6 @@ from typing import Callable, Dict, List, Union from supertokens_python.framework.request import BaseRequest -from supertokens_python.ingredients.emaildelivery.types import ( - EmailDeliveryInterface, - SMTPServiceInterface, -) -from supertokens_python.recipe.emailverification import types as ev_types class ThirdPartyInfo: @@ -79,14 +74,5 @@ def __init__(self, users: List[User], next_pagination_token: Union[str, None]): self.next_pagination_token = next_pagination_token -# Export: -EmailTemplateVars = ev_types.VerificationEmailTemplateVars -VerificationEmailTemplateVars = ev_types.VerificationEmailTemplateVars - -SMTPOverrideInput = SMTPServiceInterface[EmailTemplateVars] - -EmailDeliveryOverrideInput = EmailDeliveryInterface[EmailTemplateVars] - - class ThirdPartyIngredients: pass diff --git a/supertokens_python/recipe/thirdparty/utils.py b/supertokens_python/recipe/thirdparty/utils.py index 8b3730139..e5d0f943e 100644 --- a/supertokens_python/recipe/thirdparty/utils.py +++ b/supertokens_python/recipe/thirdparty/utils.py @@ -13,23 +13,16 @@ # under the License. from __future__ import annotations -from typing import TYPE_CHECKING, Any, Awaitable, Callable, Dict, List, Set, Union +from typing import TYPE_CHECKING, Any, Callable, Dict, List, Set, Union from supertokens_python.exceptions import raise_bad_input_exception from .interfaces import APIInterface, RecipeInterface if TYPE_CHECKING: - from .recipe import ThirdPartyRecipe from .provider import Provider from jwt import PyJWKClient, decode -from supertokens_python.recipe.emailverification.utils import ( - OverrideConfig as EmailVerificationOverrideConfig, -) - -from ..emailverification.types import User as EmailVerificationUser -from .types import User class SignInAndUpFeature: @@ -76,50 +69,14 @@ def __init__(self, providers: List[Provider]): self.providers = providers -def email_verification_create_and_send_custom_email( - recipe: ThirdPartyRecipe, - create_and_send_custom_email: Callable[ - [User, str, Dict[str, Any]], Awaitable[None] - ], -) -> Callable[[EmailVerificationUser, str, Dict[str, Any]], Awaitable[None]]: - async def func( - user: EmailVerificationUser, link: str, user_context: Dict[str, Any] - ): - user_info = await recipe.recipe_implementation.get_user_by_id( - user.user_id, user_context - ) - if user_info is None: - raise Exception("Unknown User ID provided") - return await create_and_send_custom_email(user_info, link, user_context) - - return func - - -def email_verification_get_email_verification_url( - recipe: ThirdPartyRecipe, - get_email_verification_url: Callable[[User, Dict[str, Any]], Awaitable[str]], -) -> Callable[[EmailVerificationUser, Any], Awaitable[str]]: - async def func(user: EmailVerificationUser, user_context: Dict[str, Any]): - user_info = await recipe.recipe_implementation.get_user_by_id( - user.user_id, user_context - ) - if user_info is None: - raise Exception("Unknown User ID provided") - return await get_email_verification_url(user_info, user_context) - - return func - - class InputOverrideConfig: def __init__( self, functions: Union[Callable[[RecipeInterface], RecipeInterface], None] = None, apis: Union[Callable[[APIInterface], APIInterface], None] = None, - email_verification_feature: Union[EmailVerificationOverrideConfig, None] = None, ): self.functions = functions self.apis = apis - self.email_verification_feature = email_verification_feature class OverrideConfig: diff --git a/supertokens_python/recipe/thirdpartyemailpassword/types.py b/supertokens_python/recipe/thirdpartyemailpassword/types.py index 8b74b0b7a..37d659eda 100644 --- a/supertokens_python/recipe/thirdpartyemailpassword/types.py +++ b/supertokens_python/recipe/thirdpartyemailpassword/types.py @@ -40,7 +40,6 @@ def __init__( # Export: EmailTemplateVars = ep_types.EmailTemplateVars -# VerificationEmailTemplateVars = ep_types.VerificationEmailTemplateVars PasswordResetEmailTemplateVars = ep_types.PasswordResetEmailTemplateVars SMTPOverrideInput = SMTPServiceInterface[EmailTemplateVars] diff --git a/supertokens_python/recipe/thirdpartypasswordless/__init__.py b/supertokens_python/recipe/thirdpartypasswordless/__init__.py index 358229fcf..29854fd67 100644 --- a/supertokens_python/recipe/thirdpartypasswordless/__init__.py +++ b/supertokens_python/recipe/thirdpartypasswordless/__init__.py @@ -28,7 +28,6 @@ from .smsdelivery import services as smsdelivery_services from .types import EmailTemplateVars, SMSTemplateVars -InputEmailVerificationConfig = utils.InputEmailVerificationConfig InputOverrideConfig = utils.InputOverrideConfig exceptions = ex ContactConfig = passwordless.ContactConfig diff --git a/supertokens_python/recipe/thirdpartypasswordless/types.py b/supertokens_python/recipe/thirdpartypasswordless/types.py index 20a41c85f..91aa3956d 100644 --- a/supertokens_python/recipe/thirdpartypasswordless/types.py +++ b/supertokens_python/recipe/thirdpartypasswordless/types.py @@ -25,7 +25,6 @@ TwilioServiceInterface, ) from ..passwordless import types as pless_types -from ..thirdparty import types as tp_types from ..thirdparty.types import ThirdPartyInfo @@ -50,9 +49,6 @@ def __init__( # Export: EmailTemplateVars = pless_types.EmailTemplateVars SMSTemplateVars = pless_types.SMSTemplateVars -VerificationEmailTemplateVars = ( - tp_types.VerificationEmailTemplateVars -) # TODO: Remove this. PasswordlessLoginEmailTemplateVars = pless_types.PasswordlessLoginEmailTemplateVars PasswordlessLoginSMSTemplateVars = pless_types.PasswordlessLoginSMSTemplateVars diff --git a/supertokens_python/recipe/thirdpartypasswordless/utils.py b/supertokens_python/recipe/thirdpartypasswordless/utils.py index bce3cf84c..d8cf49a38 100644 --- a/supertokens_python/recipe/thirdpartypasswordless/utils.py +++ b/supertokens_python/recipe/thirdpartypasswordless/utils.py @@ -34,7 +34,6 @@ BackwardCompatibilityService, ) from supertokens_python.recipe.thirdpartypasswordless.types import SMSTemplateVars -from supertokens_python.utils import deprecated_warn from typing_extensions import Literal from ..emailverification.types import User as EmailVerificationUser @@ -60,24 +59,6 @@ ) -class InputEmailVerificationConfig: - def __init__( - self, - get_email_verification_url: Union[ - Callable[[User, Any], Awaitable[str]], None - ] = None, - create_and_send_custom_email: Union[ - Callable[[User, str, Any], Awaitable[None]], None - ] = None, - ): - self.get_email_verification_url = get_email_verification_url - self.create_and_send_custom_email = create_and_send_custom_email - if create_and_send_custom_email: - deprecated_warn( - "create_and_send_custom_email is deprecated. Please use email delivery config instead" - ) - - def email_verification_create_and_send_custom_email( recipe: ThirdPartyPasswordlessRecipe, create_and_send_custom_email: Callable[ diff --git a/tests/thirdparty/test_emaildelivery.py b/tests/thirdparty/test_emaildelivery.py index 0ccc871f8..326931569 100644 --- a/tests/thirdparty/test_emaildelivery.py +++ b/tests/thirdparty/test_emaildelivery.py @@ -49,13 +49,14 @@ from supertokens_python.recipe.thirdparty.types import ( AccessTokenAPI, AuthorisationRedirectAPI, - EmailTemplateVars, UserInfo, UserInfoEmail, - VerificationEmailTemplateVars, ) from tests.utils import clean_st, email_verify_token_request, reset, setup_st, start_st -from supertokens_python.recipe.emailverification.types import User as EVUser +from supertokens_python.recipe.emailverification.types import ( + User as EVUser, + VerificationEmailTemplateVars, +) respx_mock = respx.MockRouter @@ -325,11 +326,13 @@ async def test_email_verify_custom_override(driver_config_client: TestClient): email = "" email_verify_url = "" - def email_delivery_override(oi: EmailDeliveryInterface[EmailTemplateVars]): + def email_delivery_override( + oi: EmailDeliveryInterface[VerificationEmailTemplateVars], + ): oi_send_email = oi.send_email async def send_email( - template_vars: EmailTemplateVars, user_context: Dict[str, Any] + template_vars: VerificationEmailTemplateVars, user_context: Dict[str, Any] ): nonlocal email, email_verify_url assert isinstance(template_vars, VerificationEmailTemplateVars) @@ -419,7 +422,7 @@ async def test_email_verify_smtp_service(driver_config_client: TestClient): False, ) - def smtp_service_override(oi: SMTPServiceInterface[EmailTemplateVars]): + def smtp_service_override(oi: SMTPServiceInterface[VerificationEmailTemplateVars]): async def send_raw_email_override( content: EmailContent, _user_context: Dict[str, Any] ): @@ -433,7 +436,7 @@ async def send_raw_email_override( # Note that we aren't calling oi.send_raw_email. So Transporter won't be used. async def get_content_override( - template_vars: EmailTemplateVars, _user_context: Dict[str, Any] + template_vars: VerificationEmailTemplateVars, _user_context: Dict[str, Any] ) -> EmailContent: nonlocal get_content_called, email_verify_url get_content_called = True @@ -465,12 +468,12 @@ async def get_content_override( ) def email_delivery_override( - oi: EmailDeliveryInterface[EmailTemplateVars], - ) -> EmailDeliveryInterface[EmailTemplateVars]: + oi: EmailDeliveryInterface[VerificationEmailTemplateVars], + ) -> EmailDeliveryInterface[VerificationEmailTemplateVars]: oi_send_email = oi.send_email async def send_email_override( - template_vars: EmailTemplateVars, user_context: Dict[str, Any] + template_vars: VerificationEmailTemplateVars, user_context: Dict[str, Any] ): nonlocal outer_override_called outer_override_called = True diff --git a/tests/thirdpartypasswordless/test_emaildelivery.py b/tests/thirdpartypasswordless/test_emaildelivery.py index 7a88bba14..3c6a94119 100644 --- a/tests/thirdpartypasswordless/test_emaildelivery.py +++ b/tests/thirdpartypasswordless/test_emaildelivery.py @@ -63,14 +63,19 @@ from supertokens_python.recipe.thirdpartypasswordless.emaildelivery.services.smtp import ( SMTPService, ) +from supertokens_python.recipe.emailverification.emaildelivery.services.smtp import ( + SMTPService as EVSMTPService, +) from supertokens_python.recipe.thirdpartypasswordless.interfaces import ( ThirdPartySignInUpOkResult, ) from supertokens_python.recipe.thirdpartypasswordless.types import ( EmailTemplateVars, +) +from supertokens_python.recipe.emailverification.types import ( + User as EVUser, VerificationEmailTemplateVars, ) -from supertokens_python.recipe.emailverification.types import User as EVUser from supertokens_python.utils import is_version_gte from tests.utils import ( clean_st, @@ -254,11 +259,13 @@ async def test_email_verify_custom_override(driver_config_client: TestClient): email = "" email_verify_url = "" - def email_delivery_override(oi: EmailDeliveryInterface[EmailTemplateVars]): + def email_delivery_override( + oi: EmailDeliveryInterface[VerificationEmailTemplateVars], + ): oi_send_email = oi.send_email async def send_email( - template_vars: EmailTemplateVars, user_context: Dict[str, Any] + template_vars: VerificationEmailTemplateVars, user_context: Dict[str, Any] ): nonlocal email, email_verify_url assert isinstance(template_vars, VerificationEmailTemplateVars) @@ -279,14 +286,19 @@ async def send_email( ), framework="fastapi", recipe_list=[ + emailverification.init( + ParentRecipeEmailVerificationConfig( + mode="OPTIONAL", + email_delivery=EmailDeliveryConfig( + service=None, + override=email_delivery_override, + ), + ) + ), thirdpartypasswordless.init( contact_config=ContactEmailOnlyConfig(), flow_type="USER_INPUT_CODE_AND_MAGIC_LINK", providers=[], - email_delivery=EmailDeliveryConfig( - service=None, - override=email_delivery_override, - ), ), session.init(), ], @@ -345,7 +357,7 @@ async def test_email_verify_smtp_service(driver_config_client: TestClient): False, ) - def smtp_service_override(oi: SMTPServiceInterface[EmailTemplateVars]): + def smtp_service_override(oi: SMTPServiceInterface[VerificationEmailTemplateVars]): async def send_raw_email_override( content: EmailContent, _user_context: Dict[str, Any] ): @@ -359,7 +371,7 @@ async def send_raw_email_override( # Note that we aren't calling oi.send_raw_email. So Transporter won't be used. async def get_content_override( - template_vars: EmailTemplateVars, _user_context: Dict[str, Any] + template_vars: VerificationEmailTemplateVars, _user_context: Dict[str, Any] ) -> EmailContent: nonlocal get_content_called, email_verify_url get_content_called = True @@ -379,7 +391,7 @@ async def get_content_override( return oi - email_delivery_service = SMTPService( + email_delivery_service = EVSMTPService( smtp_settings=SMTPSettings( host="", from_=SMTPSettingsFrom("", ""), @@ -391,12 +403,12 @@ async def get_content_override( ) def email_delivery_override( - oi: EmailDeliveryInterface[EmailTemplateVars], - ) -> EmailDeliveryInterface[EmailTemplateVars]: + oi: EmailDeliveryInterface[VerificationEmailTemplateVars], + ) -> EmailDeliveryInterface[VerificationEmailTemplateVars]: oi_send_email = oi.send_email async def send_email_override( - template_vars: EmailTemplateVars, user_context: Dict[str, Any] + template_vars: VerificationEmailTemplateVars, user_context: Dict[str, Any] ): nonlocal outer_override_called outer_override_called = True @@ -415,14 +427,19 @@ async def send_email_override( ), framework="fastapi", recipe_list=[ + emailverification.init( + ParentRecipeEmailVerificationConfig( + mode="OPTIONAL", + email_delivery=EmailDeliveryConfig( + service=email_delivery_service, + override=email_delivery_override, + ), + ) + ), thirdpartypasswordless.init( contact_config=ContactEmailOnlyConfig(), flow_type="USER_INPUT_CODE_AND_MAGIC_LINK", providers=[], - email_delivery=EmailDeliveryConfig( - service=email_delivery_service, - override=email_delivery_override, - ), ), session.init(), ], From 0834fe513d035f69995d4380c9a8882cfb22617f Mon Sep 17 00:00:00 2001 From: KShivendu Date: Wed, 10 Aug 2022 15:29:55 +0530 Subject: [PATCH 09/18] refactor: Fix circular dependency error in emailverification recipe and fix tests failures --- .../recipe/emailpassword/recipe.py | 9 +- .../recipe/emailverification/__init__.py | 6 +- .../emailverification/api/implementation.py | 134 +--------------- .../recipe/emailverification/ev_claim.py | 30 ---- .../recipe/emailverification/recipe.py | 150 +++++++++++++++++- .../recipe/passwordless/recipe.py | 5 +- supertokens_python/recipe/session/recipe.py | 3 + .../recipe/thirdparty/api/implementation.py | 1 + .../recipe/thirdparty/recipe.py | 5 +- tests/emailpassword/test_emaildelivery.py | 53 ++++--- 10 files changed, 200 insertions(+), 196 deletions(-) diff --git a/supertokens_python/recipe/emailpassword/recipe.py b/supertokens_python/recipe/emailpassword/recipe.py index 1c5bde2f4..08ccd17f2 100644 --- a/supertokens_python/recipe/emailpassword/recipe.py +++ b/supertokens_python/recipe/emailpassword/recipe.py @@ -115,10 +115,11 @@ def __init__( ) def callback(): - email_veriifcation_recipe = EmailVerificationRecipe.get_instance() - email_veriifcation_recipe.add_get_email_for_user_id_func( - self.get_email_for_user_id - ) + ev_recipe = EmailVerificationRecipe.get_instance_optional() + if ev_recipe: + ev_recipe.add_get_email_for_user_id_func( + self.get_email_for_user_id + ) PostSTInitCallbacks.add_post_init_callback(callback) diff --git a/supertokens_python/recipe/emailverification/__init__.py b/supertokens_python/recipe/emailverification/__init__.py index 9221fc07c..b533a42da 100644 --- a/supertokens_python/recipe/emailverification/__init__.py +++ b/supertokens_python/recipe/emailverification/__init__.py @@ -15,17 +15,17 @@ from typing import TYPE_CHECKING, Callable -from supertokens_python.recipe.emailverification import ev_claim from . import exceptions as ex from . import utils from .emaildelivery import services as emaildelivery_services -from .recipe import EmailVerificationRecipe +from . import recipe InputOverrideConfig = utils.OverrideConfig ParentRecipeEmailVerificationConfig = utils.ParentRecipeEmailVerificationConfig exception = ex SMTPService = emaildelivery_services.SMTPService -EmailVerificationClaim = ev_claim.EmailVerificationClaim +EmailVerificationClaim = recipe.EmailVerificationClaim +EmailVerificationRecipe = recipe.EmailVerificationRecipe if TYPE_CHECKING: diff --git a/supertokens_python/recipe/emailverification/api/implementation.py b/supertokens_python/recipe/emailverification/api/implementation.py index a2cd3c705..400856fa3 100644 --- a/supertokens_python/recipe/emailverification/api/implementation.py +++ b/supertokens_python/recipe/emailverification/api/implementation.py @@ -13,137 +13,7 @@ # under the License. from __future__ import annotations -from typing import TYPE_CHECKING, Any, Dict, Union, Optional - -from supertokens_python.logger import log_debug_message -from supertokens_python.recipe.emailverification import ( - EmailVerificationRecipe, - EmailVerificationClaim, -) -from supertokens_python.recipe.emailverification.interfaces import ( - APIInterface, - CreateEmailVerificationTokenEmailAlreadyVerifiedError, - EmailVerifyPostInvalidTokenError, - EmailVerifyPostOkResult, - GenerateEmailVerifyTokenPostEmailAlreadyVerifiedError, - GenerateEmailVerifyTokenPostOkResult, - IsEmailVerifiedGetOkResult, - VerifyEmailUsingTokenOkResult, - EmailDoesnotExistError, - GetEmailForUserIdOkResult, -) -from supertokens_python.recipe.session.interfaces import SessionContainer +from typing import TYPE_CHECKING if TYPE_CHECKING: - from supertokens_python.recipe.emailverification.interfaces import APIOptions - -from supertokens_python.recipe.emailverification.types import ( - VerificationEmailTemplateVarsUser, - VerificationEmailTemplateVars, -) - - -class APIImplementation(APIInterface): - async def email_verify_post( - self, - token: str, - api_options: APIOptions, - user_context: Dict[str, Any], - session: Optional[SessionContainer] = None, - ) -> Union[EmailVerifyPostOkResult, EmailVerifyPostInvalidTokenError]: - response = await api_options.recipe_implementation.verify_email_using_token( - token, user_context - ) - if isinstance(response, VerifyEmailUsingTokenOkResult): - if session is not None: - await session.fetch_and_set_claim(EmailVerificationClaim, user_context) - - return EmailVerifyPostOkResult(response.user) - return EmailVerifyPostInvalidTokenError() - - async def is_email_verified_get( - self, - api_options: APIOptions, - user_context: Dict[str, Any], - session: Optional[SessionContainer] = None, - ) -> IsEmailVerifiedGetOkResult: - if session is None: - raise Exception("Session is undefined. Should not come here.") - await session.fetch_and_set_claim(EmailVerificationClaim, user_context) - is_verified = await session.get_claim_value( - EmailVerificationClaim, user_context - ) - # TODO: Type of is_verified should be bool. It's any for now. - - if is_verified is None: - raise Exception( - "Should never come here: EmailVerificationClaim failed to set value" - ) - - return IsEmailVerifiedGetOkResult(is_verified) - - async def generate_email_verify_token_post( - self, - api_options: APIOptions, - user_context: Dict[str, Any], - session: SessionContainer, - ) -> Union[ - GenerateEmailVerifyTokenPostOkResult, - GenerateEmailVerifyTokenPostEmailAlreadyVerifiedError, - ]: - if session is None: - raise Exception("Session is undefined. Should not come here.") - - user_id = session.get_user_id(user_context) - email_info = await EmailVerificationRecipe.get_instance().get_email_for_user_id( - user_id, user_context - ) - - if isinstance(email_info, EmailDoesnotExistError): - log_debug_message( - "Email verification email not sent to user %s because it doesn't have an email address.", - user_id, - ) - return GenerateEmailVerifyTokenPostEmailAlreadyVerifiedError() - if isinstance(email_info, GetEmailForUserIdOkResult): - response = ( - await api_options.recipe_implementation.create_email_verification_token( - user_id, - email_info.email, - user_context, - ) - ) - - if isinstance( - response, CreateEmailVerificationTokenEmailAlreadyVerifiedError - ): - log_debug_message( - "Email verification email not sent to %s because it is already verified.", - email_info.email, - ) - return GenerateEmailVerifyTokenPostEmailAlreadyVerifiedError() - - email_verify_link = ( - api_options.app_info.website_domain.get_as_string_dangerous() - + api_options.app_info.website_base_path.get_as_string_dangerous() - + "/verify-email/" - + "?token=" - + response.token - + "&rid=" - + api_options.recipe_id - ) - - log_debug_message("Sending email verification email to %s", email_info) - email_verification_email_delivery_input = VerificationEmailTemplateVars( - user=VerificationEmailTemplateVarsUser(user_id, email_info.email), - email_verify_link=email_verify_link, - user_context=user_context, - ) - await api_options.email_delivery.ingredient_interface_impl.send_email( - email_verification_email_delivery_input, user_context - ) - return GenerateEmailVerifyTokenPostOkResult() - - raise Exception( - "Should never come here: UNKNOWN_USER_ID or invalid result from get_email_for_user_id" - ) + pass diff --git a/supertokens_python/recipe/emailverification/ev_claim.py b/supertokens_python/recipe/emailverification/ev_claim.py index d57e724a0..ed65af615 100644 --- a/supertokens_python/recipe/emailverification/ev_claim.py +++ b/supertokens_python/recipe/emailverification/ev_claim.py @@ -14,11 +14,6 @@ from __future__ import annotations from typing import Dict, Any -from supertokens_python.recipe.emailverification import EmailVerificationRecipe -from supertokens_python.recipe.emailverification.interfaces import ( - GetEmailForUserIdOkResult, - EmailDoesnotExistError, -) from supertokens_python.recipe.session.claim_base_classes.boolean_claim import ( BooleanClaim, BooleanClaimValidators, @@ -71,28 +66,3 @@ def is_verified( return IsVerifiedSCV( self.claim, has_value_res, refetch_time_on_false_in_seconds ) - - -class EmailVerificationClaimClass(BooleanClaim): - def __init__(self): - async def fetch_value(user_id: str, user_context: Dict[str, Any]) -> bool: - recipe = EmailVerificationRecipe.get_instance() - email_info = await recipe.get_email_for_user_id(user_id, user_context) - - if isinstance(email_info, GetEmailForUserIdOkResult): - return await recipe.recipe_implementation.is_email_verified( - user_id, email_info.email, user_context - ) - if isinstance(email_info, EmailDoesnotExistError): - # we consider people without email addresses as validated - return True - raise Exception( - "Should never come here: UNKNOWN_USER_ID or invalid result from get_email_for_user" - ) - - super().__init__("st-ev", fetch_value) - - self.validators = EmailVerificationClaimValidators(claim=self) - - -EmailVerificationClaim = EmailVerificationClaimClass() diff --git a/supertokens_python/recipe/emailverification/recipe.py b/supertokens_python/recipe/emailverification/recipe.py index d029e7f7b..b06954b42 100644 --- a/supertokens_python/recipe/emailverification/recipe.py +++ b/supertokens_python/recipe/emailverification/recipe.py @@ -14,7 +14,7 @@ from __future__ import annotations from os import environ -from typing import TYPE_CHECKING, List, Union, Any, Dict, Callable +from typing import TYPE_CHECKING, List, Union, Any, Dict, Callable, Optional from supertokens_python.exceptions import SuperTokensError, raise_general_exception from supertokens_python.ingredients.emaildelivery import EmailDeliveryIngredient @@ -23,21 +23,25 @@ ) from supertokens_python.recipe.emailverification.types import ( EmailVerificationIngredients, - VerificationEmailTemplateVars, + VerificationEmailTemplateVars, VerificationEmailTemplateVarsUser, ) from supertokens_python.recipe_module import APIHandled, RecipeModule +from .ev_claim import EmailVerificationClaimValidators -from .api.implementation import APIImplementation -from .ev_claim import EmailVerificationClaim from .interfaces import ( APIOptions, UnknownUserIdError, TypeGetEmailForUserIdFunction, GetEmailForUserIdOkResult, - EmailDoesnotExistError, + EmailDoesnotExistError, APIInterface, EmailVerifyPostOkResult, EmailVerifyPostInvalidTokenError, + VerifyEmailUsingTokenOkResult, IsEmailVerifiedGetOkResult, GenerateEmailVerifyTokenPostOkResult, + GenerateEmailVerifyTokenPostEmailAlreadyVerifiedError, CreateEmailVerificationTokenEmailAlreadyVerifiedError, ) from .recipe_implementation import RecipeImplementation from ..session import SessionRecipe +from ..session.claim_base_classes.boolean_claim import BooleanClaim +from ..session.interfaces import SessionContainer +from ...logger import log_debug_message from ...post_init_callbacks import PostSTInitCallbacks if TYPE_CHECKING: @@ -205,6 +209,10 @@ def get_instance() -> EmailVerificationRecipe: "Initialisation not done. Did you forget to call the SuperTokens.init function?" ) + @staticmethod + def get_instance_optional() -> Optional[EmailVerificationRecipe]: + return EmailVerificationRecipe.__instance + @staticmethod def reset(): if ("SUPERTOKENS_ENV" not in environ) or ( @@ -230,3 +238,135 @@ async def get_email_for_user_id( def add_get_email_for_user_id_func(self, f: Callable[[str, Dict[str, Any]], Any]): self.get_email_for_user_id_funcs_from_other_recipes.append(f) + + +class EmailVerificationClaimClass(BooleanClaim): + def __init__(self): + async def fetch_value(user_id: str, user_context: Dict[str, Any]) -> bool: + recipe = EmailVerificationRecipe.get_instance() + email_info = await recipe.get_email_for_user_id(user_id, user_context) + + if isinstance(email_info, GetEmailForUserIdOkResult): + return await recipe.recipe_implementation.is_email_verified( + user_id, email_info.email, user_context + ) + if isinstance(email_info, EmailDoesnotExistError): + # we consider people without email addresses as validated + return True + raise Exception( + "Should never come here: UNKNOWN_USER_ID or invalid result from get_email_for_user" + ) + + super().__init__("st-ev", fetch_value) + + self.validators = EmailVerificationClaimValidators(claim=self) + + +EmailVerificationClaim = EmailVerificationClaimClass() + + +class APIImplementation(APIInterface): + async def email_verify_post( + self, + token: str, + api_options: APIOptions, + user_context: Dict[str, Any], + session: Optional[SessionContainer] = None, + ) -> Union[EmailVerifyPostOkResult, EmailVerifyPostInvalidTokenError]: + + response = await api_options.recipe_implementation.verify_email_using_token( + token, user_context + ) + if isinstance(response, VerifyEmailUsingTokenOkResult): + if session is not None: + await session.fetch_and_set_claim(EmailVerificationClaim, user_context) + + return EmailVerifyPostOkResult(response.user) + return EmailVerifyPostInvalidTokenError() + + async def is_email_verified_get( + self, + api_options: APIOptions, + user_context: Dict[str, Any], + session: Optional[SessionContainer] = None, + ) -> IsEmailVerifiedGetOkResult: + if session is None: + raise Exception("Session is undefined. Should not come here.") + await session.fetch_and_set_claim(EmailVerificationClaim, user_context) + is_verified = await session.get_claim_value( + EmailVerificationClaim, user_context + ) + # TODO: Type of is_verified should be bool. It's any for now. + + if is_verified is None: + raise Exception( + "Should never come here: EmailVerificationClaim failed to set value" + ) + + return IsEmailVerifiedGetOkResult(is_verified) + + async def generate_email_verify_token_post( + self, + api_options: APIOptions, + user_context: Dict[str, Any], + session: SessionContainer, + ) -> Union[ + GenerateEmailVerifyTokenPostOkResult, + GenerateEmailVerifyTokenPostEmailAlreadyVerifiedError, + ]: + if session is None: + raise Exception("Session is undefined. Should not come here.") + + user_id = session.get_user_id(user_context) + email_info = await EmailVerificationRecipe.get_instance().get_email_for_user_id( + user_id, user_context + ) + + if isinstance(email_info, EmailDoesnotExistError): + log_debug_message( + "Email verification email not sent to user %s because it doesn't have an email address.", + user_id, + ) + return GenerateEmailVerifyTokenPostEmailAlreadyVerifiedError() + if isinstance(email_info, GetEmailForUserIdOkResult): + response = ( + await api_options.recipe_implementation.create_email_verification_token( + user_id, + email_info.email, + user_context, + ) + ) + + if isinstance( + response, CreateEmailVerificationTokenEmailAlreadyVerifiedError + ): + log_debug_message( + "Email verification email not sent to %s because it is already verified.", + email_info.email, + ) + return GenerateEmailVerifyTokenPostEmailAlreadyVerifiedError() + + email_verify_link = ( + api_options.app_info.website_domain.get_as_string_dangerous() + + api_options.app_info.website_base_path.get_as_string_dangerous() + + "/verify-email/" + + "?token=" + + response.token + + "&rid=" + + api_options.recipe_id + ) + + log_debug_message("Sending email verification email to %s", email_info) + email_verification_email_delivery_input = VerificationEmailTemplateVars( + user=VerificationEmailTemplateVarsUser(user_id, email_info.email), + email_verify_link=email_verify_link, + user_context=user_context, + ) + await api_options.email_delivery.ingredient_interface_impl.send_email( + email_verification_email_delivery_input, user_context + ) + return GenerateEmailVerifyTokenPostOkResult() + + raise Exception( + "Should never come here: UNKNOWN_USER_ID or invalid result from get_email_for_user_id" + ) diff --git a/supertokens_python/recipe/passwordless/recipe.py b/supertokens_python/recipe/passwordless/recipe.py index 648e1b67e..e707ab66a 100644 --- a/supertokens_python/recipe/passwordless/recipe.py +++ b/supertokens_python/recipe/passwordless/recipe.py @@ -145,8 +145,9 @@ def __init__( ) def callback(): - ev_recipe = EmailVerificationRecipe.get_instance() - ev_recipe.add_get_email_for_user_id_func(self.get_email_for_user_id) + ev_recipe = EmailVerificationRecipe.get_instance_optional() + if ev_recipe: + ev_recipe.add_get_email_for_user_id_func(self.get_email_for_user_id) PostSTInitCallbacks.add_post_init_callback(callback) diff --git a/supertokens_python/recipe/session/recipe.py b/supertokens_python/recipe/session/recipe.py index 94a07f71b..a5a851bf1 100644 --- a/supertokens_python/recipe/session/recipe.py +++ b/supertokens_python/recipe/session/recipe.py @@ -302,6 +302,9 @@ def reset(): ): raise_general_exception("calling testing function in non testing env") SessionRecipe.__instance = None + # FIXME: Discovered its requirement while running tests. Confirm if this is correct: + SessionRecipe.claims_added_by_other_recipes = [] + SessionRecipe.claim_validators_added_by_other_recipes = [] @staticmethod def add_claim_from_other_recipe(claim: SessionClaim[Any]): diff --git a/supertokens_python/recipe/thirdparty/api/implementation.py b/supertokens_python/recipe/thirdparty/api/implementation.py index 21a307ef4..bdb124405 100644 --- a/supertokens_python/recipe/thirdparty/api/implementation.py +++ b/supertokens_python/recipe/thirdparty/api/implementation.py @@ -164,6 +164,7 @@ async def sign_in_up_post( if email_verified: ev_instance = EmailVerificationRecipe.get_instance() + assert ev_instance is not None token_response = ( await ev_instance.recipe_implementation.create_email_verification_token( user_id=signinup_response.user.user_id, diff --git a/supertokens_python/recipe/thirdparty/recipe.py b/supertokens_python/recipe/thirdparty/recipe.py index 51faf072e..53507bb47 100644 --- a/supertokens_python/recipe/thirdparty/recipe.py +++ b/supertokens_python/recipe/thirdparty/recipe.py @@ -77,8 +77,9 @@ def __init__( ) def callback(): - ev_recipe = EmailVerificationRecipe.get_instance() - ev_recipe.add_get_email_for_user_id_func(self.get_email_for_user_id) + ev_recipe = EmailVerificationRecipe.get_instance_optional() + if ev_recipe: + ev_recipe.add_get_email_for_user_id_func(self.get_email_for_user_id) PostSTInitCallbacks.add_post_init_callback(callback) diff --git a/tests/emailpassword/test_emaildelivery.py b/tests/emailpassword/test_emaildelivery.py index 7f14d4bbe..adae58bc2 100644 --- a/tests/emailpassword/test_emaildelivery.py +++ b/tests/emailpassword/test_emaildelivery.py @@ -36,6 +36,7 @@ InputResetPasswordUsingTokenFeature, ) from supertokens_python.recipe.emailpassword.emaildelivery.services import SMTPService +from supertokens_python.recipe.emailverification.emaildelivery.services import SMTPService as EVSMTPService from supertokens_python.recipe.emailpassword.types import ( EmailTemplateVars, PasswordResetEmailTemplateVars, @@ -526,7 +527,11 @@ async def test_email_verification_default_backward_compatibility( api_base_path="/auth", ), framework="fastapi", - recipe_list=[emailpassword.init(), session.init()], + recipe_list=[ + emailverification.init(ParentRecipeEmailVerificationConfig(mode="OPTIONAL")), + emailpassword.init(), + session.init() + ], ) start_st() @@ -587,7 +592,11 @@ async def test_email_verification_default_backward_compatibility_suppress_error( api_base_path="/auth", ), framework="fastapi", - recipe_list=[emailpassword.init(), session.init()], + recipe_list=[ + emailverification.init(ParentRecipeEmailVerificationConfig(mode="OPTIONAL")), + emailpassword.init(), + session.init() + ], ) start_st() @@ -698,11 +707,11 @@ async def test_email_verification_custom_override(driver_config_client: TestClie email = "" email_verify_url = "" - def email_delivery_override(oi: EmailDeliveryInterface[EmailTemplateVars]): + def email_delivery_override(oi: EmailDeliveryInterface[VerificationEmailTemplateVars]): oi_send_email = oi.send_email async def send_email( - template_vars: EmailTemplateVars, user_context: Dict[str, Any] + template_vars: VerificationEmailTemplateVars, user_context: Dict[str, Any] ): nonlocal email, email_verify_url email = template_vars.user.email @@ -723,12 +732,16 @@ async def send_email( ), framework="fastapi", recipe_list=[ - emailpassword.init( - email_delivery=EmailDeliveryConfig( - service=None, - override=email_delivery_override, + emailverification.init( + ParentRecipeEmailVerificationConfig( + mode="OPTIONAL", + email_delivery=EmailDeliveryConfig( + service=None, + override=email_delivery_override, + ) ) ), + emailpassword.init(), session.init(), ], ) @@ -782,7 +795,7 @@ async def test_email_verification_smtp_service(driver_config_client: TestClient) False, ) - def smtp_service_override(oi: SMTPServiceInterface[EmailTemplateVars]): + def smtp_service_override(oi: SMTPServiceInterface[VerificationEmailTemplateVars]): async def send_raw_email_override( content: EmailContent, _user_context: Dict[str, Any] ): @@ -796,7 +809,7 @@ async def send_raw_email_override( # Note that we aren't calling oi.send_raw_email. So Transporter won't be used. async def get_content_override( - template_vars: EmailTemplateVars, _user_context: Dict[str, Any] + template_vars: VerificationEmailTemplateVars, _user_context: Dict[str, Any] ) -> EmailContent: nonlocal get_content_called, email_verify_url get_content_called = True @@ -816,7 +829,7 @@ async def get_content_override( return oi - email_delivery_service = SMTPService( + email_delivery_service = EVSMTPService( smtp_settings=SMTPSettings( host="", from_=SMTPSettingsFrom("", ""), @@ -828,12 +841,12 @@ async def get_content_override( ) def email_delivery_override( - oi: EmailDeliveryInterface[EmailTemplateVars], - ) -> EmailDeliveryInterface[EmailTemplateVars]: + oi: EmailDeliveryInterface[VerificationEmailTemplateVars], + ) -> EmailDeliveryInterface[VerificationEmailTemplateVars]: oi_send_email = oi.send_email async def send_email_override( - template_vars: EmailTemplateVars, user_context: Dict[str, Any] + template_vars: VerificationEmailTemplateVars, user_context: Dict[str, Any] ): nonlocal outer_override_called outer_override_called = True @@ -852,12 +865,16 @@ async def send_email_override( ), framework="fastapi", recipe_list=[ - emailpassword.init( - email_delivery=EmailDeliveryConfig( - service=email_delivery_service, - override=email_delivery_override, + emailverification.init( + ParentRecipeEmailVerificationConfig( + mode="OPTIONAL", + email_delivery=EmailDeliveryConfig( + service=email_delivery_service, + override=email_delivery_override, + ) ) ), + emailpassword.init(), session.init(), ], ) From 34e492ae9361cf95dea958397718841f35b3a044 Mon Sep 17 00:00:00 2001 From: KShivendu Date: Wed, 10 Aug 2022 16:23:50 +0530 Subject: [PATCH 10/18] test: Fix test failures related to tpep recipe --- .../recipe/emailpassword/recipe.py | 4 +- .../recipe/emailverification/recipe.py | 18 ++++-- .../recipe/thirdparty/recipe.py | 12 ++-- .../recipe/thirdpartyemailpassword/recipe.py | 12 ++-- tests/emailpassword/test_emaildelivery.py | 24 +++++--- .../test_email_delivery.py | 58 ++++++++++++++----- 6 files changed, 87 insertions(+), 41 deletions(-) diff --git a/supertokens_python/recipe/emailpassword/recipe.py b/supertokens_python/recipe/emailpassword/recipe.py index 08ccd17f2..8b00699ea 100644 --- a/supertokens_python/recipe/emailpassword/recipe.py +++ b/supertokens_python/recipe/emailpassword/recipe.py @@ -117,9 +117,7 @@ def __init__( def callback(): ev_recipe = EmailVerificationRecipe.get_instance_optional() if ev_recipe: - ev_recipe.add_get_email_for_user_id_func( - self.get_email_for_user_id - ) + ev_recipe.add_get_email_for_user_id_func(self.get_email_for_user_id) PostSTInitCallbacks.add_post_init_callback(callback) diff --git a/supertokens_python/recipe/emailverification/recipe.py b/supertokens_python/recipe/emailverification/recipe.py index b06954b42..2f34b67dd 100644 --- a/supertokens_python/recipe/emailverification/recipe.py +++ b/supertokens_python/recipe/emailverification/recipe.py @@ -23,7 +23,8 @@ ) from supertokens_python.recipe.emailverification.types import ( EmailVerificationIngredients, - VerificationEmailTemplateVars, VerificationEmailTemplateVarsUser, + VerificationEmailTemplateVars, + VerificationEmailTemplateVarsUser, ) from supertokens_python.recipe_module import APIHandled, RecipeModule from .ev_claim import EmailVerificationClaimValidators @@ -33,9 +34,15 @@ UnknownUserIdError, TypeGetEmailForUserIdFunction, GetEmailForUserIdOkResult, - EmailDoesnotExistError, APIInterface, EmailVerifyPostOkResult, EmailVerifyPostInvalidTokenError, - VerifyEmailUsingTokenOkResult, IsEmailVerifiedGetOkResult, GenerateEmailVerifyTokenPostOkResult, - GenerateEmailVerifyTokenPostEmailAlreadyVerifiedError, CreateEmailVerificationTokenEmailAlreadyVerifiedError, + EmailDoesnotExistError, + APIInterface, + EmailVerifyPostOkResult, + EmailVerifyPostInvalidTokenError, + VerifyEmailUsingTokenOkResult, + IsEmailVerifiedGetOkResult, + GenerateEmailVerifyTokenPostOkResult, + GenerateEmailVerifyTokenPostEmailAlreadyVerifiedError, + CreateEmailVerificationTokenEmailAlreadyVerifiedError, ) from .recipe_implementation import RecipeImplementation from ..session import SessionRecipe @@ -367,6 +374,9 @@ async def generate_email_verify_token_post( ) return GenerateEmailVerifyTokenPostOkResult() + if isinstance(email_info, UnknownUserIdError): + pass + raise Exception( "Should never come here: UNKNOWN_USER_ID or invalid result from get_email_for_user_id" ) diff --git a/supertokens_python/recipe/thirdparty/recipe.py b/supertokens_python/recipe/thirdparty/recipe.py index 53507bb47..73062e6ad 100644 --- a/supertokens_python/recipe/thirdparty/recipe.py +++ b/supertokens_python/recipe/thirdparty/recipe.py @@ -23,6 +23,7 @@ from .api.implementation import APIImplementation from .interfaces import APIInterface, APIOptions, RecipeInterface from .recipe_implementation import RecipeImplementation +from ..emailverification.interfaces import GetEmailForUserIdOkResult, UnknownUserIdError from ...post_init_callbacks import PostSTInitCallbacks if TYPE_CHECKING: @@ -188,12 +189,11 @@ def reset(): # instance functions below............... - async def get_email_for_user_id( - self, user_id: str, user_context: Dict[str, Any] - ) -> str: + async def get_email_for_user_id(self, user_id: str, user_context: Dict[str, Any]): user_info = await self.recipe_implementation.get_user_by_id( user_id, user_context ) - if user_info is None: - raise Exception("Unknown User ID provided") - return user_info.email + if user_info is not None: + return GetEmailForUserIdOkResult(user_info.email) + + return UnknownUserIdError() diff --git a/supertokens_python/recipe/thirdpartyemailpassword/recipe.py b/supertokens_python/recipe/thirdpartyemailpassword/recipe.py index 98a80e148..35a213203 100644 --- a/supertokens_python/recipe/thirdpartyemailpassword/recipe.py +++ b/supertokens_python/recipe/thirdpartyemailpassword/recipe.py @@ -47,6 +47,7 @@ from .recipeimplementation.third_party_recipe_implementation import ( RecipeImplementation as ThirdPartyRecipeImplementation, ) +from ..emailverification.interfaces import GetEmailForUserIdOkResult, UnknownUserIdError if TYPE_CHECKING: from supertokens_python.framework.request import BaseRequest @@ -296,12 +297,11 @@ def reset(): raise Exception(None, "calling testing function in non testing env") ThirdPartyEmailPasswordRecipe.__instance = None - async def get_email_for_user_id( - self, user_id: str, user_context: Dict[str, Any] - ) -> str: + async def get_email_for_user_id(self, user_id: str, user_context: Dict[str, Any]): user_info = await self.recipe_implementation.get_user_by_id( user_id, user_context ) - if user_info is None: - raise Exception("Unknown User ID provided") - return user_info.email + if user_info is not None: + return GetEmailForUserIdOkResult(user_info.email) + + return UnknownUserIdError() diff --git a/tests/emailpassword/test_emaildelivery.py b/tests/emailpassword/test_emaildelivery.py index adae58bc2..f0995faa6 100644 --- a/tests/emailpassword/test_emaildelivery.py +++ b/tests/emailpassword/test_emaildelivery.py @@ -36,7 +36,9 @@ InputResetPasswordUsingTokenFeature, ) from supertokens_python.recipe.emailpassword.emaildelivery.services import SMTPService -from supertokens_python.recipe.emailverification.emaildelivery.services import SMTPService as EVSMTPService +from supertokens_python.recipe.emailverification.emaildelivery.services import ( + SMTPService as EVSMTPService, +) from supertokens_python.recipe.emailpassword.types import ( EmailTemplateVars, PasswordResetEmailTemplateVars, @@ -528,9 +530,11 @@ async def test_email_verification_default_backward_compatibility( ), framework="fastapi", recipe_list=[ - emailverification.init(ParentRecipeEmailVerificationConfig(mode="OPTIONAL")), + emailverification.init( + ParentRecipeEmailVerificationConfig(mode="OPTIONAL") + ), emailpassword.init(), - session.init() + session.init(), ], ) start_st() @@ -593,9 +597,11 @@ async def test_email_verification_default_backward_compatibility_suppress_error( ), framework="fastapi", recipe_list=[ - emailverification.init(ParentRecipeEmailVerificationConfig(mode="OPTIONAL")), + emailverification.init( + ParentRecipeEmailVerificationConfig(mode="OPTIONAL") + ), emailpassword.init(), - session.init() + session.init(), ], ) start_st() @@ -707,7 +713,9 @@ async def test_email_verification_custom_override(driver_config_client: TestClie email = "" email_verify_url = "" - def email_delivery_override(oi: EmailDeliveryInterface[VerificationEmailTemplateVars]): + def email_delivery_override( + oi: EmailDeliveryInterface[VerificationEmailTemplateVars], + ): oi_send_email = oi.send_email async def send_email( @@ -738,7 +746,7 @@ async def send_email( email_delivery=EmailDeliveryConfig( service=None, override=email_delivery_override, - ) + ), ) ), emailpassword.init(), @@ -871,7 +879,7 @@ async def send_email_override( email_delivery=EmailDeliveryConfig( service=email_delivery_service, override=email_delivery_override, - ) + ), ) ), emailpassword.init(), diff --git a/tests/thirdpartyemailpassword/test_email_delivery.py b/tests/thirdpartyemailpassword/test_email_delivery.py index ace47c2b6..c858276b2 100644 --- a/tests/thirdpartyemailpassword/test_email_delivery.py +++ b/tests/thirdpartyemailpassword/test_email_delivery.py @@ -47,6 +47,7 @@ from supertokens_python.recipe.session.session_functions import create_new_session from supertokens_python.recipe.thirdpartyemailpassword import ( InputResetPasswordUsingTokenFeature, + Github, ) from supertokens_python.recipe.thirdpartyemailpassword.asyncio import ( thirdparty_sign_in_up, @@ -54,6 +55,9 @@ from supertokens_python.recipe.thirdpartyemailpassword.emaildelivery.services import ( SMTPService, ) +from supertokens_python.recipe.emailverification.emaildelivery.services import ( + SMTPService as EVSMTPService, +) from supertokens_python.recipe.thirdpartyemailpassword.types import ( EmailTemplateVars, PasswordResetEmailTemplateVars, @@ -477,7 +481,13 @@ async def test_email_verification_default_backward_compatibility( api_base_path="/auth", ), framework="fastapi", - recipe_list=[thirdpartyemailpassword.init(), session.init()], + recipe_list=[ + emailverification.init( + ParentRecipeEmailVerificationConfig(mode="OPTIONAL") + ), + thirdpartyemailpassword.init(), + session.init(), + ], ) start_st() @@ -538,7 +548,13 @@ async def test_email_verification_default_backward_compatibility_suppress_error( api_base_path="/auth", ), framework="fastapi", - recipe_list=[thirdpartyemailpassword.init(), session.init()], + recipe_list=[ + emailverification.init( + ParentRecipeEmailVerificationConfig(mode="OPTIONAL") + ), + thirdpartyemailpassword.init(), + session.init(), + ], ) start_st() @@ -739,7 +755,7 @@ async def test_email_verification_smtp_service(driver_config_client: TestClient) False, ) - def smtp_service_override(oi: SMTPServiceInterface[EmailTemplateVars]): + def smtp_service_override(oi: SMTPServiceInterface[VerificationEmailTemplateVars]): async def send_raw_email_override( content: EmailContent, _user_context: Dict[str, Any] ): @@ -753,7 +769,7 @@ async def send_raw_email_override( # Note that we aren't calling oi.send_raw_email. So Transporter won't be used. async def get_content_override( - template_vars: EmailTemplateVars, _user_context: Dict[str, Any] + template_vars: VerificationEmailTemplateVars, _user_context: Dict[str, Any] ) -> EmailContent: nonlocal get_content_called, email_verify_url get_content_called = True @@ -773,7 +789,7 @@ async def get_content_override( return oi - email_delivery_service = SMTPService( + email_delivery_service = EVSMTPService( smtp_settings=SMTPSettings( host="", from_=SMTPSettingsFrom("", ""), @@ -785,12 +801,12 @@ async def get_content_override( ) def email_delivery_override( - oi: EmailDeliveryInterface[EmailTemplateVars], - ) -> EmailDeliveryInterface[EmailTemplateVars]: + oi: EmailDeliveryInterface[VerificationEmailTemplateVars], + ) -> EmailDeliveryInterface[VerificationEmailTemplateVars]: oi_send_email = oi.send_email async def send_email_override( - template_vars: EmailTemplateVars, user_context: Dict[str, Any] + template_vars: VerificationEmailTemplateVars, user_context: Dict[str, Any] ): nonlocal outer_override_called outer_override_called = True @@ -809,12 +825,16 @@ async def send_email_override( ), framework="fastapi", recipe_list=[ - thirdpartyemailpassword.init( - email_delivery=EmailDeliveryConfig( - service=email_delivery_service, - override=email_delivery_override, + emailverification.init( + ParentRecipeEmailVerificationConfig( + mode="OPTIONAL", + email_delivery=EmailDeliveryConfig( + service=email_delivery_service, + override=email_delivery_override, + ), ) ), + thirdpartyemailpassword.init(), session.init(), ], ) @@ -870,10 +890,16 @@ async def custom_create_and_send_custom_email( ), framework="fastapi", recipe_list=[ + emailverification.init( + ParentRecipeEmailVerificationConfig(mode="OPTIONAL") + ), thirdpartyemailpassword.init( + providers=[ + Github(client_id="", client_secret="") + ], # Note: Provider must be passed to init TP recipe reset_password_using_token_feature=InputResetPasswordUsingTokenFeature( create_and_send_custom_email=custom_create_and_send_custom_email, - ) + ), ), session.init(), ], @@ -936,7 +962,11 @@ async def custom_create_and_send_custom_email( create_and_send_custom_email=custom_create_and_send_custom_email, ) ), - thirdpartyemailpassword.init(), + thirdpartyemailpassword.init( + providers=[ + Github(client_id="", client_secret="") + ], # Note: Provider must be passed to init TP recipe + ), session.init(), ], ) From 15862511fcc40a49a251c22b3a151907c8fa5b33 Mon Sep 17 00:00:00 2001 From: KShivendu Date: Wed, 10 Aug 2022 16:45:13 +0530 Subject: [PATCH 11/18] refactor: Allow emailverification init without directly using ParentRecipeEmailVerificationConfig --- .../recipe/emailverification/__init__.py | 23 ++++++-- .../recipe/emailverification/recipe.py | 27 ++++++++-- .../recipe/emailverification/utils.py | 4 +- tests/auth-react/django3x/mysite/utils.py | 9 ++-- tests/auth-react/fastapi-server/app.py | 11 ++-- tests/auth-react/flask-server/app.py | 11 ++-- tests/emailpassword/test_emaildelivery.py | 41 +++++--------- tests/emailpassword/test_emailverify.py | 53 ++++++------------- .../input_validation/test_input_validation.py | 8 ++- tests/thirdparty/test_emaildelivery.py | 35 +++++------- .../test_email_delivery.py | 51 ++++++------------ .../test_emaildelivery.py | 33 +++++------- 12 files changed, 130 insertions(+), 176 deletions(-) diff --git a/supertokens_python/recipe/emailverification/__init__.py b/supertokens_python/recipe/emailverification/__init__.py index b533a42da..314abed70 100644 --- a/supertokens_python/recipe/emailverification/__init__.py +++ b/supertokens_python/recipe/emailverification/__init__.py @@ -13,12 +13,15 @@ # under the License. from __future__ import annotations -from typing import TYPE_CHECKING, Callable +from typing import TYPE_CHECKING, Callable, Union, Optional, Any, Awaitable, Dict from . import exceptions as ex from . import utils from .emaildelivery import services as emaildelivery_services from . import recipe +from .interfaces import TypeGetEmailForUserIdFunction +from .types import EmailTemplateVars, User +from ...ingredients.emaildelivery.types import EmailDeliveryConfig InputOverrideConfig = utils.OverrideConfig ParentRecipeEmailVerificationConfig = utils.ParentRecipeEmailVerificationConfig @@ -33,8 +36,22 @@ from ...recipe_module import RecipeModule +from .utils import MODE_TYPE, OverrideConfig + def init( - config: ParentRecipeEmailVerificationConfig, + mode: MODE_TYPE = "OPTIONAL", + email_delivery: Union[EmailDeliveryConfig[EmailTemplateVars], None] = None, + get_email_for_user_id: Optional[TypeGetEmailForUserIdFunction] = None, + create_and_send_custom_email: Union[ + Callable[[User, str, Dict[str, Any]], Awaitable[None]], None + ] = None, + override: Union[OverrideConfig, None] = None, ) -> Callable[[AppInfo], RecipeModule]: - return EmailVerificationRecipe.init(config) + return EmailVerificationRecipe.init( + mode, + email_delivery, + get_email_for_user_id, + create_and_send_custom_email, + override, + ) diff --git a/supertokens_python/recipe/emailverification/recipe.py b/supertokens_python/recipe/emailverification/recipe.py index 2f34b67dd..1f8d8c397 100644 --- a/supertokens_python/recipe/emailverification/recipe.py +++ b/supertokens_python/recipe/emailverification/recipe.py @@ -14,7 +14,7 @@ from __future__ import annotations from os import environ -from typing import TYPE_CHECKING, List, Union, Any, Dict, Callable, Optional +from typing import TYPE_CHECKING, List, Union, Any, Dict, Callable, Optional, Awaitable from supertokens_python.exceptions import SuperTokensError, raise_general_exception from supertokens_python.ingredients.emaildelivery import EmailDeliveryIngredient @@ -25,6 +25,8 @@ EmailVerificationIngredients, VerificationEmailTemplateVars, VerificationEmailTemplateVarsUser, + EmailTemplateVars, + User, ) from supertokens_python.recipe_module import APIHandled, RecipeModule from .ev_claim import EmailVerificationClaimValidators @@ -48,6 +50,7 @@ from ..session import SessionRecipe from ..session.claim_base_classes.boolean_claim import BooleanClaim from ..session.interfaces import SessionContainer +from ...ingredients.emaildelivery.types import EmailDeliveryConfig from ...logger import log_debug_message from ...post_init_callbacks import PostSTInitCallbacks @@ -65,6 +68,8 @@ from .utils import ( ParentRecipeEmailVerificationConfig, validate_and_normalise_user_input, + MODE_TYPE, + OverrideConfig, ) @@ -179,10 +184,25 @@ def get_all_cors_headers(self) -> List[str]: return [] @staticmethod - def init(config: ParentRecipeEmailVerificationConfig): + def init( + mode: MODE_TYPE = "OPTIONAL", + email_delivery: Union[EmailDeliveryConfig[EmailTemplateVars], None] = None, + get_email_for_user_id: Optional[TypeGetEmailForUserIdFunction] = None, + create_and_send_custom_email: Union[ + Callable[[User, str, Dict[str, Any]], Awaitable[None]], None + ] = None, + override: Union[OverrideConfig, None] = None, + ): def func(app_info: AppInfo): if EmailVerificationRecipe.__instance is None: ingredients = EmailVerificationIngredients(email_delivery=None) + config = ParentRecipeEmailVerificationConfig( + mode, + email_delivery, + get_email_for_user_id, + create_and_send_custom_email, + override, + ) EmailVerificationRecipe.__instance = EmailVerificationRecipe( EmailVerificationRecipe.recipe_id, app_info, @@ -374,9 +394,6 @@ async def generate_email_verify_token_post( ) return GenerateEmailVerifyTokenPostOkResult() - if isinstance(email_info, UnknownUserIdError): - pass - raise Exception( "Should never come here: UNKNOWN_USER_ID or invalid result from get_email_for_user_id" ) diff --git a/supertokens_python/recipe/emailverification/utils.py b/supertokens_python/recipe/emailverification/utils.py index 774f3c575..689c22bb3 100644 --- a/supertokens_python/recipe/emailverification/utils.py +++ b/supertokens_python/recipe/emailverification/utils.py @@ -48,10 +48,10 @@ def __init__( class ParentRecipeEmailVerificationConfig: - # TODO: Now that this class will be used directly, we might want to rename this? + # TODO: Rename this? ^ def __init__( self, - mode: MODE_TYPE, + mode: MODE_TYPE = "OPTIONAL", email_delivery: Union[EmailDeliveryConfig[EmailTemplateVars], None] = None, get_email_for_user_id: Optional[TypeGetEmailForUserIdFunction] = None, create_and_send_custom_email: Union[ diff --git a/tests/auth-react/django3x/mysite/utils.py b/tests/auth-react/django3x/mysite/utils.py index ab8d03c0d..ce98d2b6e 100644 --- a/tests/auth-react/django3x/mysite/utils.py +++ b/tests/auth-react/django3x/mysite/utils.py @@ -28,7 +28,6 @@ from supertokens_python.recipe.emailverification.types import User as EVUser from supertokens_python.recipe.emailverification import ( EmailVerificationRecipe, - ParentRecipeEmailVerificationConfig, ) from supertokens_python.recipe.emailverification import ( InputOverrideConfig as EVInputOverrideConfig, @@ -847,11 +846,9 @@ async def authorisation_url_get( recipe_list = [ session.init(override=session.InputOverrideConfig(apis=override_session_apis)), emailverification.init( - ParentRecipeEmailVerificationConfig( - mode="REQUIRED", - create_and_send_custom_email=ev_create_and_send_custom_email, # TODO: Is it correct to create a seperate func for this? - override=EVInputOverrideConfig(apis=override_email_verification_apis), - ) + mode="REQUIRED", + create_and_send_custom_email=ev_create_and_send_custom_email, # TODO: Is it correct to create a seperate func for this? + override=EVInputOverrideConfig(apis=override_email_verification_apis), ), emailpassword.init( sign_up_feature=emailpassword.InputSignUpFeature(form_fields), diff --git a/tests/auth-react/fastapi-server/app.py b/tests/auth-react/fastapi-server/app.py index 94b6867ab..dad7f9858 100644 --- a/tests/auth-react/fastapi-server/app.py +++ b/tests/auth-react/fastapi-server/app.py @@ -65,9 +65,6 @@ from supertokens_python.recipe.emailverification.interfaces import ( APIOptions as EVAPIOptions, ) -from supertokens_python.recipe.emailverification.utils import ( - ParentRecipeEmailVerificationConfig, -) from supertokens_python.recipe.jwt import JWTRecipe from supertokens_python.recipe.passwordless import ( ContactEmailOnlyConfig, @@ -904,11 +901,9 @@ async def authorisation_url_get( recipe_list = [ session.init(override=session.InputOverrideConfig(apis=override_session_apis)), emailverification.init( - ParentRecipeEmailVerificationConfig( - mode="REQUIRED", - create_and_send_custom_email=ev_create_and_send_custom_email, # TODO: Is this correct? - override=EVInputOverrideConfig(apis=override_email_verification_apis), - ) + mode="REQUIRED", + create_and_send_custom_email=ev_create_and_send_custom_email, # TODO: Is this correct? + override=EVInputOverrideConfig(apis=override_email_verification_apis), ), emailpassword.init( sign_up_feature=emailpassword.InputSignUpFeature(form_fields), diff --git a/tests/auth-react/flask-server/app.py b/tests/auth-react/flask-server/app.py index 135cb06ef..b5d0ea15e 100644 --- a/tests/auth-react/flask-server/app.py +++ b/tests/auth-react/flask-server/app.py @@ -57,9 +57,6 @@ from supertokens_python.recipe.emailverification.interfaces import ( APIOptions as EVAPIOptions, ) -from supertokens_python.recipe.emailverification.utils import ( - ParentRecipeEmailVerificationConfig, -) from supertokens_python.recipe.jwt import JWTRecipe from supertokens_python.recipe.passwordless import ( ContactEmailOnlyConfig, @@ -891,11 +888,9 @@ async def authorisation_url_get( recipe_list = [ session.init(override=session.InputOverrideConfig(apis=override_session_apis)), emailverification.init( - ParentRecipeEmailVerificationConfig( - mode="REQUIRED", - create_and_send_custom_email=ev_create_and_send_custom_email, - override=EVInputOverrideConfig(apis=override_email_verification_apis), - ) + mode="REQUIRED", + create_and_send_custom_email=ev_create_and_send_custom_email, + override=EVInputOverrideConfig(apis=override_email_verification_apis), ), emailpassword.init( sign_up_feature=emailpassword.InputSignUpFeature(form_fields), diff --git a/tests/emailpassword/test_emaildelivery.py b/tests/emailpassword/test_emaildelivery.py index f0995faa6..4decd7675 100644 --- a/tests/emailpassword/test_emaildelivery.py +++ b/tests/emailpassword/test_emaildelivery.py @@ -48,9 +48,6 @@ VerificationEmailTemplateVars, ) from supertokens_python.recipe.emailverification.types import User as EVUser -from supertokens_python.recipe.emailverification.utils import ( - ParentRecipeEmailVerificationConfig, -) from supertokens_python.recipe.session import SessionRecipe from supertokens_python.recipe.session.recipe_implementation import ( RecipeImplementation as SessionRecipeImplementation, @@ -530,9 +527,7 @@ async def test_email_verification_default_backward_compatibility( ), framework="fastapi", recipe_list=[ - emailverification.init( - ParentRecipeEmailVerificationConfig(mode="OPTIONAL") - ), + emailverification.init(), emailpassword.init(), session.init(), ], @@ -597,9 +592,7 @@ async def test_email_verification_default_backward_compatibility_suppress_error( ), framework="fastapi", recipe_list=[ - emailverification.init( - ParentRecipeEmailVerificationConfig(mode="OPTIONAL") - ), + emailverification.init(), emailpassword.init(), session.init(), ], @@ -672,10 +665,8 @@ async def custom_create_and_send_custom_email( framework="fastapi", recipe_list=[ emailverification.init( - ParentRecipeEmailVerificationConfig( - mode="REQUIRED", - create_and_send_custom_email=custom_create_and_send_custom_email, - ) + mode="REQUIRED", + create_and_send_custom_email=custom_create_and_send_custom_email, ), emailpassword.init(), session.init(), @@ -741,13 +732,11 @@ async def send_email( framework="fastapi", recipe_list=[ emailverification.init( - ParentRecipeEmailVerificationConfig( - mode="OPTIONAL", - email_delivery=EmailDeliveryConfig( - service=None, - override=email_delivery_override, - ), - ) + mode="OPTIONAL", + email_delivery=EmailDeliveryConfig( + service=None, + override=email_delivery_override, + ), ), emailpassword.init(), session.init(), @@ -874,13 +863,11 @@ async def send_email_override( framework="fastapi", recipe_list=[ emailverification.init( - ParentRecipeEmailVerificationConfig( - mode="OPTIONAL", - email_delivery=EmailDeliveryConfig( - service=email_delivery_service, - override=email_delivery_override, - ), - ) + mode="OPTIONAL", + email_delivery=EmailDeliveryConfig( + service=email_delivery_service, + override=email_delivery_override, + ), ), emailpassword.init(), session.init(), diff --git a/tests/emailpassword/test_emailverify.py b/tests/emailpassword/test_emailverify.py index 305216776..8cccfa772 100644 --- a/tests/emailpassword/test_emailverify.py +++ b/tests/emailpassword/test_emailverify.py @@ -41,7 +41,6 @@ from supertokens_python.recipe.emailverification.types import User as EVUser from supertokens_python.recipe.emailverification.utils import ( OverrideConfig, - ParentRecipeEmailVerificationConfig, ) from supertokens_python.recipe.session import SessionContainer from supertokens_python.recipe.session.asyncio import ( @@ -320,9 +319,7 @@ async def custom_f( recipe_list=[ session.init(anti_csrf="VIA_TOKEN"), emailverification.init( - ParentRecipeEmailVerificationConfig( - mode="REQUIRED", create_and_send_custom_email=custom_f - ) + mode="REQUIRED", create_and_send_custom_email=custom_f ), emailpassword.init(), ], @@ -378,9 +375,7 @@ async def custom_f( recipe_list=[ session.init(anti_csrf="VIA_TOKEN"), emailverification.init( - ParentRecipeEmailVerificationConfig( - mode="REQUIRED", create_and_send_custom_email=custom_f - ) + mode="REQUIRED", create_and_send_custom_email=custom_f ), emailpassword.init(), ], @@ -451,9 +446,7 @@ async def custom_f( recipe_list=[ session.init(anti_csrf="VIA_TOKEN"), emailverification.init( - ParentRecipeEmailVerificationConfig( - mode="REQUIRED", create_and_send_custom_email=custom_f - ) + mode="REQUIRED", create_and_send_custom_email=custom_f ), emailpassword.init(), ], @@ -524,9 +517,7 @@ async def custom_f( recipe_list=[ session.init(anti_csrf="VIA_TOKEN"), emailverification.init( - ParentRecipeEmailVerificationConfig( - mode="REQUIRED", create_and_send_custom_email=custom_f - ) + mode="REQUIRED", create_and_send_custom_email=custom_f ), emailpassword.init(), ], @@ -620,11 +611,9 @@ async def email_verify_post( recipe_list=[ session.init(anti_csrf="VIA_TOKEN"), emailverification.init( - ParentRecipeEmailVerificationConfig( - mode="REQUIRED", - create_and_send_custom_email=custom_f, - override=OverrideConfig(apis=apis_override_email_password), - ) + mode="REQUIRED", + create_and_send_custom_email=custom_f, + override=OverrideConfig(apis=apis_override_email_password), ), emailpassword.init(), ], @@ -702,10 +691,8 @@ async def custom_f( recipe_list=[ session.init(anti_csrf="VIA_TOKEN"), emailverification.init( - ParentRecipeEmailVerificationConfig( - mode="REQUIRED", - create_and_send_custom_email=custom_f, - ) + mode="REQUIRED", + create_and_send_custom_email=custom_f, ), emailpassword.init(), ], @@ -830,11 +817,9 @@ async def email_verify_post( recipe_list=[ session.init(anti_csrf="VIA_TOKEN"), emailverification.init( - ParentRecipeEmailVerificationConfig( - mode="REQUIRED", - create_and_send_custom_email=custom_f, - override=OverrideConfig(apis=apis_override_email_password), - ) + mode="REQUIRED", + create_and_send_custom_email=custom_f, + override=OverrideConfig(apis=apis_override_email_password), ), emailpassword.init(), ], @@ -926,12 +911,10 @@ async def email_verify_post( recipe_list=[ session.init(anti_csrf="VIA_TOKEN"), emailverification.init( - ParentRecipeEmailVerificationConfig( - mode="REQUIRED", - create_and_send_custom_email=custom_f, - override=emailverification.InputOverrideConfig( - apis=apis_override_email_password - ), + mode="REQUIRED", + create_and_send_custom_email=custom_f, + override=emailverification.InputOverrideConfig( + apis=apis_override_email_password ), ), emailpassword.init(), @@ -1031,9 +1014,7 @@ async def test_the_generate_token_api_with_valid_input_verify_and_then_unverify_ framework="fastapi", recipe_list=[ session.init(anti_csrf="VIA_TOKEN"), - emailverification.init( - ParentRecipeEmailVerificationConfig(mode="OPTIONAL") - ), + emailverification.init(), emailpassword.init(), ], ) diff --git a/tests/input_validation/test_input_validation.py b/tests/input_validation/test_input_validation.py index 47c15babd..0f7948666 100644 --- a/tests/input_validation/test_input_validation.py +++ b/tests/input_validation/test_input_validation.py @@ -143,11 +143,9 @@ async def test_init_validation_emailverification(): framework="fastapi", recipe_list=[ emailverification.init( - emailverification.ParentRecipeEmailVerificationConfig( - mode="OPTIONAL", - get_email_for_user_id=get_email_for_user_id, - override="override", # type: ignore - ) + mode="OPTIONAL", + get_email_for_user_id=get_email_for_user_id, + override="override", # type: ignore ) ], ) diff --git a/tests/thirdparty/test_emaildelivery.py b/tests/thirdparty/test_emaildelivery.py index 326931569..b51ee665d 100644 --- a/tests/thirdparty/test_emaildelivery.py +++ b/tests/thirdparty/test_emaildelivery.py @@ -32,9 +32,6 @@ SMTPSettingsFrom, ) from supertokens_python.recipe import session, thirdparty, emailverification -from supertokens_python.recipe.emailverification.utils import ( - ParentRecipeEmailVerificationConfig, -) from supertokens_python.recipe.session import SessionRecipe from supertokens_python.recipe.session.recipe_implementation import ( RecipeImplementation as SessionRecipeImplementation, @@ -134,6 +131,7 @@ async def test_email_verify_default_backward_compatibility( ), framework="fastapi", recipe_list=[ + emailverification.init(), thirdparty.init( sign_in_and_up_feature=thirdparty.SignInAndUpFeature( providers=[CustomProvider("CUSTOM", True)] @@ -204,6 +202,7 @@ async def test_email_verify_default_backward_compatibility_supress_error( ), framework="fastapi", recipe_list=[ + emailverification.init(), thirdparty.init( sign_in_and_up_feature=thirdparty.SignInAndUpFeature( providers=[CustomProvider("CUSTOM", True)] @@ -280,10 +279,8 @@ async def create_and_send_custom_email( framework="fastapi", recipe_list=[ emailverification.init( - ParentRecipeEmailVerificationConfig( - mode="OPTIONAL", - create_and_send_custom_email=create_and_send_custom_email, - ) + mode="OPTIONAL", + create_and_send_custom_email=create_and_send_custom_email, ), thirdparty.init( sign_in_and_up_feature=thirdparty.SignInAndUpFeature( @@ -354,13 +351,11 @@ async def send_email( framework="fastapi", recipe_list=[ emailverification.init( - ParentRecipeEmailVerificationConfig( - mode="OPTIONAL", - email_delivery=EmailDeliveryConfig( - service=None, - override=email_delivery_override, - ), - ) + mode="OPTIONAL", + email_delivery=EmailDeliveryConfig( + service=None, + override=email_delivery_override, + ), ), thirdparty.init( sign_in_and_up_feature=thirdparty.SignInAndUpFeature( @@ -493,13 +488,11 @@ async def send_email_override( framework="fastapi", recipe_list=[ emailverification.init( - ParentRecipeEmailVerificationConfig( - mode="OPTIONAL", - email_delivery=EmailDeliveryConfig( - service=email_delivery_service, - override=email_delivery_override, - ), - ) + mode="OPTIONAL", + email_delivery=EmailDeliveryConfig( + service=email_delivery_service, + override=email_delivery_override, + ), ), thirdparty.init( sign_in_and_up_feature=thirdparty.SignInAndUpFeature( diff --git a/tests/thirdpartyemailpassword/test_email_delivery.py b/tests/thirdpartyemailpassword/test_email_delivery.py index c858276b2..94c1cab83 100644 --- a/tests/thirdpartyemailpassword/test_email_delivery.py +++ b/tests/thirdpartyemailpassword/test_email_delivery.py @@ -37,9 +37,6 @@ emailverification, ) from supertokens_python.recipe.emailpassword.types import User as EPUser -from supertokens_python.recipe.emailverification.utils import ( - ParentRecipeEmailVerificationConfig, -) from supertokens_python.recipe.session import SessionRecipe from supertokens_python.recipe.session.recipe_implementation import ( RecipeImplementation as SessionRecipeImplementation, @@ -482,9 +479,7 @@ async def test_email_verification_default_backward_compatibility( ), framework="fastapi", recipe_list=[ - emailverification.init( - ParentRecipeEmailVerificationConfig(mode="OPTIONAL") - ), + emailverification.init(), thirdpartyemailpassword.init(), session.init(), ], @@ -549,9 +544,7 @@ async def test_email_verification_default_backward_compatibility_suppress_error( ), framework="fastapi", recipe_list=[ - emailverification.init( - ParentRecipeEmailVerificationConfig(mode="OPTIONAL") - ), + emailverification.init(), thirdpartyemailpassword.init(), session.init(), ], @@ -624,10 +617,8 @@ async def custom_create_and_send_custom_email( framework="fastapi", recipe_list=[ emailverification.init( - ParentRecipeEmailVerificationConfig( - mode="OPTIONAL", - create_and_send_custom_email=custom_create_and_send_custom_email, - ) + mode="OPTIONAL", + create_and_send_custom_email=custom_create_and_send_custom_email, ), thirdpartyemailpassword.init(), session.init(), @@ -693,13 +684,11 @@ async def send_email( framework="fastapi", recipe_list=[ emailverification.init( - ParentRecipeEmailVerificationConfig( - mode="REQUIRED", - email_delivery=EmailDeliveryConfig( - service=None, - override=email_delivery_override, - ), - ) + mode="REQUIRED", + email_delivery=EmailDeliveryConfig( + service=None, + override=email_delivery_override, + ), ), thirdpartyemailpassword.init(), session.init(), @@ -826,13 +815,11 @@ async def send_email_override( framework="fastapi", recipe_list=[ emailverification.init( - ParentRecipeEmailVerificationConfig( - mode="OPTIONAL", - email_delivery=EmailDeliveryConfig( - service=email_delivery_service, - override=email_delivery_override, - ), - ) + mode="OPTIONAL", + email_delivery=EmailDeliveryConfig( + service=email_delivery_service, + override=email_delivery_override, + ), ), thirdpartyemailpassword.init(), session.init(), @@ -890,9 +877,7 @@ async def custom_create_and_send_custom_email( ), framework="fastapi", recipe_list=[ - emailverification.init( - ParentRecipeEmailVerificationConfig(mode="OPTIONAL") - ), + emailverification.init(), thirdpartyemailpassword.init( providers=[ Github(client_id="", client_secret="") @@ -957,10 +942,8 @@ async def custom_create_and_send_custom_email( framework="fastapi", recipe_list=[ emailverification.init( - ParentRecipeEmailVerificationConfig( - mode="OPTIONAL", - create_and_send_custom_email=custom_create_and_send_custom_email, - ) + mode="OPTIONAL", + create_and_send_custom_email=custom_create_and_send_custom_email, ), thirdpartyemailpassword.init( providers=[ diff --git a/tests/thirdpartypasswordless/test_emaildelivery.py b/tests/thirdpartypasswordless/test_emaildelivery.py index 3c6a94119..d79f3324e 100644 --- a/tests/thirdpartypasswordless/test_emaildelivery.py +++ b/tests/thirdpartypasswordless/test_emaildelivery.py @@ -38,9 +38,6 @@ thirdpartypasswordless, emailverification, ) -from supertokens_python.recipe.emailverification import ( - ParentRecipeEmailVerificationConfig, -) from supertokens_python.recipe.emailverification.interfaces import ( CreateEmailVerificationTokenEmailAlreadyVerifiedError, ) @@ -211,10 +208,8 @@ async def create_and_send_custom_email( framework="fastapi", recipe_list=[ emailverification.init( - ParentRecipeEmailVerificationConfig( - mode="OPTIONAL", - create_and_send_custom_email=create_and_send_custom_email, - ) + mode="OPTIONAL", + create_and_send_custom_email=create_and_send_custom_email, ), thirdpartypasswordless.init( contact_config=ContactEmailOnlyConfig(), @@ -287,13 +282,11 @@ async def send_email( framework="fastapi", recipe_list=[ emailverification.init( - ParentRecipeEmailVerificationConfig( - mode="OPTIONAL", - email_delivery=EmailDeliveryConfig( - service=None, - override=email_delivery_override, - ), - ) + mode="OPTIONAL", + email_delivery=EmailDeliveryConfig( + service=None, + override=email_delivery_override, + ), ), thirdpartypasswordless.init( contact_config=ContactEmailOnlyConfig(), @@ -428,13 +421,11 @@ async def send_email_override( framework="fastapi", recipe_list=[ emailverification.init( - ParentRecipeEmailVerificationConfig( - mode="OPTIONAL", - email_delivery=EmailDeliveryConfig( - service=email_delivery_service, - override=email_delivery_override, - ), - ) + mode="OPTIONAL", + email_delivery=EmailDeliveryConfig( + service=email_delivery_service, + override=email_delivery_override, + ), ), thirdpartypasswordless.init( contact_config=ContactEmailOnlyConfig(), From 10395c48f6f10984c85c93f36ea4bc9c0ed617fa Mon Sep 17 00:00:00 2001 From: KShivendu Date: Wed, 10 Aug 2022 17:13:52 +0530 Subject: [PATCH 12/18] tests: Fix test failures in tppless email delivery --- .../recipe/emailverification/asyncio/__init__.py | 4 ++-- .../thirdpartypasswordless/test_emaildelivery.py | 16 ++++++++++------ 2 files changed, 12 insertions(+), 8 deletions(-) diff --git a/supertokens_python/recipe/emailverification/asyncio/__init__.py b/supertokens_python/recipe/emailverification/asyncio/__init__.py index 9e543d7ff..9528b84a3 100644 --- a/supertokens_python/recipe/emailverification/asyncio/__init__.py +++ b/supertokens_python/recipe/emailverification/asyncio/__init__.py @@ -16,7 +16,7 @@ GetEmailForUserIdOkResult, EmailDoesnotExistError, CreateEmailVerificationTokenEmailAlreadyVerifiedError, - UnverifyEmailOkResult, + UnverifyEmailOkResult, CreateEmailVerificationTokenOkResult, ) from supertokens_python.recipe.emailverification.types import EmailTemplateVars from supertokens_python.recipe.emailverification.recipe import EmailVerificationRecipe @@ -26,7 +26,7 @@ async def create_email_verification_token( user_id: str, email: Optional[str] = None, user_context: Union[None, Dict[str, Any]] = None, -): +) -> Union[CreateEmailVerificationTokenOkResult, CreateEmailVerificationTokenEmailAlreadyVerifiedError]: if user_context is None: user_context = {} recipe = EmailVerificationRecipe.get_instance() diff --git a/tests/thirdpartypasswordless/test_emaildelivery.py b/tests/thirdpartypasswordless/test_emaildelivery.py index d79f3324e..470c13cd9 100644 --- a/tests/thirdpartypasswordless/test_emaildelivery.py +++ b/tests/thirdpartypasswordless/test_emaildelivery.py @@ -39,7 +39,7 @@ emailverification, ) from supertokens_python.recipe.emailverification.interfaces import ( - CreateEmailVerificationTokenEmailAlreadyVerifiedError, + CreateEmailVerificationTokenEmailAlreadyVerifiedError, CreateEmailVerificationTokenOkResult, ) from supertokens_python.recipe.passwordless import ContactEmailOnlyConfig from supertokens_python.recipe.passwordless.types import ( @@ -53,6 +53,7 @@ from supertokens_python.recipe.emailverification.asyncio import ( create_email_verification_token, ) +from supertokens_python.recipe.thirdparty.providers import Github from supertokens_python.recipe.thirdpartypasswordless.asyncio import ( passwordlessSigninup, thirdparty_sign_in_up, @@ -131,10 +132,11 @@ async def test_email_verify_default_backward_compatibility( ), framework="fastapi", recipe_list=[ + emailverification.init(), thirdpartypasswordless.init( contact_config=ContactEmailOnlyConfig(), flow_type="USER_INPUT_CODE_AND_MAGIC_LINK", - providers=[], + providers=[Github(client_id="", client_secret="")], # Note: providers must be set to init tp recipe ), session.init(), ], @@ -214,7 +216,7 @@ async def create_and_send_custom_email( thirdpartypasswordless.init( contact_config=ContactEmailOnlyConfig(), flow_type="USER_INPUT_CODE_AND_MAGIC_LINK", - providers=[], + providers=[Github(client_id="", client_secret="")], # Note: providers must be set to init tp recipe ), session.init(), ], @@ -291,7 +293,7 @@ async def send_email( thirdpartypasswordless.init( contact_config=ContactEmailOnlyConfig(), flow_type="USER_INPUT_CODE_AND_MAGIC_LINK", - providers=[], + providers=[Github(client_id="", client_secret="")], # Note: providers must be set to init tp recipe ), session.init(), ], @@ -430,7 +432,7 @@ async def send_email_override( thirdpartypasswordless.init( contact_config=ContactEmailOnlyConfig(), flow_type="USER_INPUT_CODE_AND_MAGIC_LINK", - providers=[], + providers=[Github(client_id="", client_secret="")], # Note: providers must be set to init tp recipe ), session.init(), ], @@ -537,6 +539,7 @@ async def send_email_override( ), framework="fastapi", recipe_list=[ + emailverification.init(), thirdpartypasswordless.init( contact_config=ContactEmailOnlyConfig(), flow_type="USER_INPUT_CODE_AND_MAGIC_LINK", @@ -559,8 +562,9 @@ async def send_email_override( create_token = await create_email_verification_token(pless_response.user.user_id) assert isinstance( - create_token, CreateEmailVerificationTokenEmailAlreadyVerifiedError + create_token, CreateEmailVerificationTokenOkResult ) + # TODO: Replaced CreateEmailVerificationTokenEmailAlreadyVerifiedError. Confirm if this is correct. assert ( all([outer_override_called, get_content_called, send_raw_email_called]) is False From 27282d45772b8100de84c963091301cdf56beb5b Mon Sep 17 00:00:00 2001 From: KShivendu Date: Wed, 17 Aug 2022 15:27:53 +0530 Subject: [PATCH 13/18] changes based on feedback --- .../emailpassword/api/implementation.py | 2 +- .../smtp/service_implementation/__init__.py | 5 --- .../recipe/emailpassword/recipe.py | 4 +-- .../recipe/emailpassword/utils.py | 31 ------------------- .../emailverification/asyncio/__init__.py | 18 ++++++----- .../recipe/emailverification/interfaces.py | 4 +-- .../recipe/emailverification/recipe.py | 8 ++--- .../recipe/passwordless/api/implementation.py | 3 +- .../recipe/passwordless/recipe.py | 4 +-- .../recipe/thirdparty/api/implementation.py | 2 +- .../recipe/thirdpartyemailpassword/recipe.py | 12 +------ .../test_emaildelivery.py | 22 ++++++++----- 12 files changed, 40 insertions(+), 75 deletions(-) diff --git a/supertokens_python/recipe/emailpassword/api/implementation.py b/supertokens_python/recipe/emailpassword/api/implementation.py index ca84eee3b..d12dce126 100644 --- a/supertokens_python/recipe/emailpassword/api/implementation.py +++ b/supertokens_python/recipe/emailpassword/api/implementation.py @@ -94,7 +94,7 @@ async def generate_password_reset_token_post( password_reset_link = ( api_options.app_info.website_domain.get_as_string_dangerous() + api_options.app_info.website_base_path.get_as_string_dangerous() - + "?token=" + + "/reset-password?token=" + token + "&rid=" + api_options.recipe_id diff --git a/supertokens_python/recipe/emailpassword/emaildelivery/services/smtp/service_implementation/__init__.py b/supertokens_python/recipe/emailpassword/emaildelivery/services/smtp/service_implementation/__init__.py index c4fada039..e6ea9481b 100644 --- a/supertokens_python/recipe/emailpassword/emaildelivery/services/smtp/service_implementation/__init__.py +++ b/supertokens_python/recipe/emailpassword/emaildelivery/services/smtp/service_implementation/__init__.py @@ -26,9 +26,6 @@ from supertokens_python.recipe.emailverification.emaildelivery.services.smtp.service_implementation import ( ServiceImplementation as EVServiceImplementation, ) -from supertokens_python.recipe.emailverification.types import ( - VerificationEmailTemplateVars, -) class ServiceImplementation(SMTPServiceInterface[EmailTemplateVars]): @@ -49,6 +46,4 @@ async def send_raw_email( async def get_content( self, template_vars: EmailTemplateVars, user_context: Dict[str, Any] ) -> EmailContent: - if isinstance(template_vars, VerificationEmailTemplateVars): - return await self.ev_get_content(template_vars, user_context) return get_password_reset_email_content(template_vars) diff --git a/supertokens_python/recipe/emailpassword/recipe.py b/supertokens_python/recipe/emailpassword/recipe.py index 8b00699ea..cbf32e78b 100644 --- a/supertokens_python/recipe/emailpassword/recipe.py +++ b/supertokens_python/recipe/emailpassword/recipe.py @@ -27,7 +27,7 @@ from ..emailverification.interfaces import ( UnknownUserIdError, GetEmailForUserIdOkResult, - EmailDoesnotExistError, + EmailDoesNotExistError, ) from .api.implementation import APIImplementation @@ -255,7 +255,7 @@ def reset(): async def get_email_for_user_id( self, user_id: str, user_context: Dict[str, Any] - ) -> Union[UnknownUserIdError, GetEmailForUserIdOkResult, EmailDoesnotExistError]: + ) -> Union[UnknownUserIdError, GetEmailForUserIdOkResult, EmailDoesNotExistError]: user_info = await self.recipe_implementation.get_user_by_id( user_id, user_context ) diff --git a/supertokens_python/recipe/emailpassword/utils.py b/supertokens_python/recipe/emailpassword/utils.py index fc95b2a1a..0cd7d4d09 100644 --- a/supertokens_python/recipe/emailpassword/utils.py +++ b/supertokens_python/recipe/emailpassword/utils.py @@ -207,26 +207,6 @@ def __init__( self.form_fields_for_generate_token_form = form_fields_for_generate_token_form -# -# class InputEmailVerificationConfig: -# def __init__( -# self, -# get_email_verification_url: Union[ -# Callable[[User, Dict[str, Any]], Awaitable[str]], None -# ] = None, -# create_and_send_custom_email: Union[ -# Callable[[User, str, Dict[str, Any]], Awaitable[None]], None -# ] = None, -# ): -# self.get_email_verification_url = get_email_verification_url -# self.create_and_send_custom_email = create_and_send_custom_email -# -# if create_and_send_custom_email: -# deprecated_warn( -# "create_and_send_custom_email is deprecated. Please use email delivery config instead" -# ) - - def validate_and_normalise_reset_password_using_token_config( sign_up_config: InputSignUpFeature, ) -> ResetPasswordUsingTokenFeature: @@ -313,7 +293,6 @@ def __init__( sign_up_feature: SignUpFeature, sign_in_feature: SignInFeature, reset_password_using_token_feature: ResetPasswordUsingTokenFeature, - # email_verification_feature: ParentRecipeEmailVerificationConfig, override: OverrideConfig, get_email_delivery_config: Callable[ [RecipeInterface], EmailDeliveryConfigWithService[EmailTemplateVars] @@ -322,7 +301,6 @@ def __init__( 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.email_verification_feature = email_verification_feature self.override = override self.get_email_delivery_config = get_email_delivery_config @@ -333,7 +311,6 @@ def validate_and_normalise_user_input( reset_password_using_token_feature: Union[ InputResetPasswordUsingTokenFeature, None ] = None, - # email_verification_feature: Union[InputEmailVerificationConfig, None] = None, override: Union[InputOverrideConfig, None] = None, email_delivery: Union[EmailDeliveryConfig[EmailTemplateVars], None] = None, ) -> EmailPasswordConfig: @@ -346,11 +323,6 @@ def validate_and_normalise_user_input( "reset_password_using_token_feature must be of type InputResetPasswordUsingTokenFeature or None" ) - # if email_verification_feature is not None and not isinstance(email_verification_feature, InputEmailVerificationConfig): # type: ignore - # raise ValueError( - # "email_verification_feature must be of type InputEmailVerificationConfig or None" - # ) - if override is not None and not isinstance(override, InputOverrideConfig): # type: ignore raise ValueError("override must be of type InputOverrideConfig or None") @@ -386,9 +358,6 @@ def get_email_delivery_config( 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), - # validate_and_normalise_email_verification_config( - # recipe, email_verification_feature, override - # ), OverrideConfig(functions=override.functions, apis=override.apis), get_email_delivery_config=get_email_delivery_config, ) diff --git a/supertokens_python/recipe/emailverification/asyncio/__init__.py b/supertokens_python/recipe/emailverification/asyncio/__init__.py index 9528b84a3..27896eee4 100644 --- a/supertokens_python/recipe/emailverification/asyncio/__init__.py +++ b/supertokens_python/recipe/emailverification/asyncio/__init__.py @@ -14,9 +14,10 @@ from supertokens_python.recipe.emailverification.interfaces import ( GetEmailForUserIdOkResult, - EmailDoesnotExistError, + EmailDoesNotExistError, CreateEmailVerificationTokenEmailAlreadyVerifiedError, - UnverifyEmailOkResult, CreateEmailVerificationTokenOkResult, + UnverifyEmailOkResult, + CreateEmailVerificationTokenOkResult, ) from supertokens_python.recipe.emailverification.types import EmailTemplateVars from supertokens_python.recipe.emailverification.recipe import EmailVerificationRecipe @@ -26,7 +27,10 @@ async def create_email_verification_token( user_id: str, email: Optional[str] = None, user_context: Union[None, Dict[str, Any]] = None, -) -> Union[CreateEmailVerificationTokenOkResult, CreateEmailVerificationTokenEmailAlreadyVerifiedError]: +) -> Union[ + CreateEmailVerificationTokenOkResult, + CreateEmailVerificationTokenEmailAlreadyVerifiedError, +]: if user_context is None: user_context = {} recipe = EmailVerificationRecipe.get_instance() @@ -34,7 +38,7 @@ async def create_email_verification_token( email_info = await recipe.get_email_for_user_id(user_id, user_context) if isinstance(email_info, GetEmailForUserIdOkResult): email = email_info.email - elif isinstance(email_info, EmailDoesnotExistError): + elif isinstance(email_info, EmailDoesNotExistError): return CreateEmailVerificationTokenEmailAlreadyVerifiedError() else: raise Exception("Unknown User ID provided without email") @@ -67,7 +71,7 @@ async def is_email_verified( email_info = await recipe.get_email_for_user_id(user_id, user_context) if isinstance(email_info, GetEmailForUserIdOkResult): email = email_info.email - elif isinstance(email_info, EmailDoesnotExistError): + elif isinstance(email_info, EmailDoesNotExistError): return True else: raise Exception("Unknown User ID provided without email") @@ -90,7 +94,7 @@ async def revoke_email_verification_token( email_info = await recipe.get_email_for_user_id(user_id, user_context) if isinstance(email_info, GetEmailForUserIdOkResult): email = email_info.email - elif isinstance(email_info, EmailDoesnotExistError): + elif isinstance(email_info, EmailDoesNotExistError): # Here we are returning OK since that's how it used to work, but a later call # to is_verified will still return true return CreateEmailVerificationTokenEmailAlreadyVerifiedError() @@ -115,7 +119,7 @@ async def unverify_email( email_info = await recipe.get_email_for_user_id(user_id, user_context) if isinstance(email_info, GetEmailForUserIdOkResult): email = email_info.email - elif isinstance(email_info, EmailDoesnotExistError): + elif isinstance(email_info, EmailDoesNotExistError): # Here we are returning OK since that's how it used to work, but a later call # to is_verified will still return true return UnverifyEmailOkResult diff --git a/supertokens_python/recipe/emailverification/interfaces.py b/supertokens_python/recipe/emailverification/interfaces.py index a852192b9..db9b38764 100644 --- a/supertokens_python/recipe/emailverification/interfaces.py +++ b/supertokens_python/recipe/emailverification/interfaces.py @@ -202,7 +202,7 @@ def __init__(self, email: str): self.email = email -class EmailDoesnotExistError(Exception): +class EmailDoesNotExistError(Exception): pass @@ -213,6 +213,6 @@ class UnknownUserIdError(Exception): TypeGetEmailForUserIdFunction = Callable[ [str, Dict[str, Any]], Awaitable[ - Union[GetEmailForUserIdOkResult, EmailDoesnotExistError, UnknownUserIdError] + Union[GetEmailForUserIdOkResult, EmailDoesNotExistError, UnknownUserIdError] ], ] diff --git a/supertokens_python/recipe/emailverification/recipe.py b/supertokens_python/recipe/emailverification/recipe.py index 1f8d8c397..cf7e49c4c 100644 --- a/supertokens_python/recipe/emailverification/recipe.py +++ b/supertokens_python/recipe/emailverification/recipe.py @@ -36,7 +36,7 @@ UnknownUserIdError, TypeGetEmailForUserIdFunction, GetEmailForUserIdOkResult, - EmailDoesnotExistError, + EmailDoesNotExistError, APIInterface, EmailVerifyPostOkResult, EmailVerifyPostInvalidTokenError, @@ -250,7 +250,7 @@ def reset(): async def get_email_for_user_id( self, user_id: str, user_context: Dict[str, Any] - ) -> Union[GetEmailForUserIdOkResult, EmailDoesnotExistError, UnknownUserIdError]: + ) -> Union[GetEmailForUserIdOkResult, EmailDoesNotExistError, UnknownUserIdError]: if self.config.get_email_for_user_id is not None: res = await self.config.get_email_for_user_id(user_id, user_context) if not isinstance(res, UnknownUserIdError): @@ -277,7 +277,7 @@ async def fetch_value(user_id: str, user_context: Dict[str, Any]) -> bool: return await recipe.recipe_implementation.is_email_verified( user_id, email_info.email, user_context ) - if isinstance(email_info, EmailDoesnotExistError): + if isinstance(email_info, EmailDoesNotExistError): # we consider people without email addresses as validated return True raise Exception( @@ -349,7 +349,7 @@ async def generate_email_verify_token_post( user_id, user_context ) - if isinstance(email_info, EmailDoesnotExistError): + if isinstance(email_info, EmailDoesNotExistError): log_debug_message( "Email verification email not sent to user %s because it doesn't have an email address.", user_id, diff --git a/supertokens_python/recipe/passwordless/api/implementation.py b/supertokens_python/recipe/passwordless/api/implementation.py index c1833faa6..f875c8a08 100644 --- a/supertokens_python/recipe/passwordless/api/implementation.py +++ b/supertokens_python/recipe/passwordless/api/implementation.py @@ -273,7 +273,8 @@ async def consume_code_post( user = response.user if user.email is not None: - ev_instance = EmailVerificationRecipe.get_instance() + ev_instance = EmailVerificationRecipe.get_instance_optional() + assert ev_instance is not None token_response = ( await ev_instance.recipe_implementation.create_email_verification_token( user.user_id, diff --git a/supertokens_python/recipe/passwordless/recipe.py b/supertokens_python/recipe/passwordless/recipe.py index e707ab66a..ed0a821ac 100644 --- a/supertokens_python/recipe/passwordless/recipe.py +++ b/supertokens_python/recipe/passwordless/recipe.py @@ -58,7 +58,7 @@ from ..emailverification import EmailVerificationRecipe from ..emailverification.interfaces import ( GetEmailForUserIdOkResult, - EmailDoesnotExistError, + EmailDoesNotExistError, UnknownUserIdError, ) from ...post_init_callbacks import PostSTInitCallbacks @@ -349,5 +349,5 @@ async def get_email_for_user_id(self, user_id: str, user_context: Dict[str, Any] if user_info is not None: if user_info.email is not None: return GetEmailForUserIdOkResult(user_info.email) - return EmailDoesnotExistError() + return EmailDoesNotExistError() return UnknownUserIdError() diff --git a/supertokens_python/recipe/thirdparty/api/implementation.py b/supertokens_python/recipe/thirdparty/api/implementation.py index bdb124405..449db40b8 100644 --- a/supertokens_python/recipe/thirdparty/api/implementation.py +++ b/supertokens_python/recipe/thirdparty/api/implementation.py @@ -163,7 +163,7 @@ async def sign_in_up_post( ) if email_verified: - ev_instance = EmailVerificationRecipe.get_instance() + ev_instance = EmailVerificationRecipe.get_instance_optional() assert ev_instance is not None token_response = ( await ev_instance.recipe_implementation.create_email_verification_token( diff --git a/supertokens_python/recipe/thirdpartyemailpassword/recipe.py b/supertokens_python/recipe/thirdpartyemailpassword/recipe.py index 35a213203..84971b326 100644 --- a/supertokens_python/recipe/thirdpartyemailpassword/recipe.py +++ b/supertokens_python/recipe/thirdpartyemailpassword/recipe.py @@ -14,7 +14,7 @@ from __future__ import annotations from os import environ -from typing import TYPE_CHECKING, Any, Dict, List, Union +from typing import TYPE_CHECKING, List, Union from supertokens_python.framework.response import BaseResponse from supertokens_python.ingredients.emaildelivery.types import EmailDeliveryConfig @@ -47,7 +47,6 @@ from .recipeimplementation.third_party_recipe_implementation import ( RecipeImplementation as ThirdPartyRecipeImplementation, ) -from ..emailverification.interfaces import GetEmailForUserIdOkResult, UnknownUserIdError if TYPE_CHECKING: from supertokens_python.framework.request import BaseRequest @@ -296,12 +295,3 @@ def reset(): ): raise Exception(None, "calling testing function in non testing env") ThirdPartyEmailPasswordRecipe.__instance = None - - async def get_email_for_user_id(self, user_id: str, user_context: Dict[str, Any]): - user_info = await self.recipe_implementation.get_user_by_id( - user_id, user_context - ) - if user_info is not None: - return GetEmailForUserIdOkResult(user_info.email) - - return UnknownUserIdError() diff --git a/tests/thirdpartypasswordless/test_emaildelivery.py b/tests/thirdpartypasswordless/test_emaildelivery.py index 470c13cd9..f8f661f9e 100644 --- a/tests/thirdpartypasswordless/test_emaildelivery.py +++ b/tests/thirdpartypasswordless/test_emaildelivery.py @@ -39,7 +39,7 @@ emailverification, ) from supertokens_python.recipe.emailverification.interfaces import ( - CreateEmailVerificationTokenEmailAlreadyVerifiedError, CreateEmailVerificationTokenOkResult, + CreateEmailVerificationTokenOkResult, ) from supertokens_python.recipe.passwordless import ContactEmailOnlyConfig from supertokens_python.recipe.passwordless.types import ( @@ -136,7 +136,9 @@ async def test_email_verify_default_backward_compatibility( thirdpartypasswordless.init( contact_config=ContactEmailOnlyConfig(), flow_type="USER_INPUT_CODE_AND_MAGIC_LINK", - providers=[Github(client_id="", client_secret="")], # Note: providers must be set to init tp recipe + providers=[ + Github(client_id="", client_secret="") + ], # Note: providers must be set to init tp recipe ), session.init(), ], @@ -216,7 +218,9 @@ async def create_and_send_custom_email( thirdpartypasswordless.init( contact_config=ContactEmailOnlyConfig(), flow_type="USER_INPUT_CODE_AND_MAGIC_LINK", - providers=[Github(client_id="", client_secret="")], # Note: providers must be set to init tp recipe + providers=[ + Github(client_id="", client_secret="") + ], # Note: providers must be set to init tp recipe ), session.init(), ], @@ -293,7 +297,9 @@ async def send_email( thirdpartypasswordless.init( contact_config=ContactEmailOnlyConfig(), flow_type="USER_INPUT_CODE_AND_MAGIC_LINK", - providers=[Github(client_id="", client_secret="")], # Note: providers must be set to init tp recipe + providers=[ + Github(client_id="", client_secret="") + ], # Note: providers must be set to init tp recipe ), session.init(), ], @@ -432,7 +438,9 @@ async def send_email_override( thirdpartypasswordless.init( contact_config=ContactEmailOnlyConfig(), flow_type="USER_INPUT_CODE_AND_MAGIC_LINK", - providers=[Github(client_id="", client_secret="")], # Note: providers must be set to init tp recipe + providers=[ + Github(client_id="", client_secret="") + ], # Note: providers must be set to init tp recipe ), session.init(), ], @@ -561,9 +569,7 @@ async def send_email_override( pless_response = await passwordlessSigninup("test@example.com", None, {}) create_token = await create_email_verification_token(pless_response.user.user_id) - assert isinstance( - create_token, CreateEmailVerificationTokenOkResult - ) + assert isinstance(create_token, CreateEmailVerificationTokenOkResult) # TODO: Replaced CreateEmailVerificationTokenEmailAlreadyVerifiedError. Confirm if this is correct. assert ( From 3df9f161ae6c54d8c3828a4ddf8f78a308a9149c Mon Sep 17 00:00:00 2001 From: KShivendu Date: Wed, 17 Aug 2022 16:00:10 +0530 Subject: [PATCH 14/18] Remove get_link_domain_and_path function and ev logic from other recipes --- .../recipe/emailpassword/utils.py | 37 ------------ .../recipe/passwordless/__init__.py | 4 -- .../recipe/passwordless/recipe.py | 9 --- .../recipe/passwordless/utils.py | 22 -------- .../recipe/thirdpartyemailpassword/utils.py | 39 +------------ .../recipe/thirdpartypasswordless/__init__.py | 4 -- .../recipe/thirdpartypasswordless/recipe.py | 11 +--- .../recipe/thirdpartypasswordless/utils.py | 56 +------------------ 8 files changed, 4 insertions(+), 178 deletions(-) diff --git a/supertokens_python/recipe/emailpassword/utils.py b/supertokens_python/recipe/emailpassword/utils.py index 0cd7d4d09..7108c21d4 100644 --- a/supertokens_python/recipe/emailpassword/utils.py +++ b/supertokens_python/recipe/emailpassword/utils.py @@ -24,12 +24,10 @@ BackwardCompatibilityService, ) -from ..emailverification.types import User as EmailVerificationUser from .interfaces import APIInterface, RecipeInterface from .types import InputFormField, NormalisedFormField, EmailTemplateVars, User if TYPE_CHECKING: - from .recipe import EmailPasswordRecipe from supertokens_python.supertokens import AppInfo from typing import Dict @@ -233,40 +231,6 @@ def validate_and_normalise_reset_password_using_token_config( ) -def email_verification_create_and_send_custom_email( - recipe: EmailPasswordRecipe, - create_and_send_custom_email: Callable[ - [User, str, Dict[str, Any]], Awaitable[None] - ], -) -> Callable[[EmailVerificationUser, str, Dict[str, Any]], Awaitable[None]]: - async def func( - user: EmailVerificationUser, link: str, user_context: Dict[str, Any] - ): - user_info = await recipe.recipe_implementation.get_user_by_id( - user.user_id, user_context - ) - if user_info is None: - raise Exception("Unknown User ID provided") - return await create_and_send_custom_email(user_info, link, user_context) - - return func - - -def email_verification_get_email_verification_url( - recipe: EmailPasswordRecipe, - get_email_verification_url: Callable[[User, Any], Awaitable[str]], -) -> Callable[[EmailVerificationUser, Any], Awaitable[str]]: - async def func(user: EmailVerificationUser, user_context: Dict[str, Any]): - user_info = await recipe.recipe_implementation.get_user_by_id( - user.user_id, user_context - ) - if user_info is None: - raise Exception("Unknown User ID provided") - return await get_email_verification_url(user_info, user_context) - - return func - - class InputOverrideConfig: def __init__( self, @@ -346,7 +310,6 @@ def get_email_delivery_config( app_info=app_info, recipe_interface_impl=ep_recipe, reset_password_using_token_feature=reset_password_using_token_feature, - # email_verification_feature=email_verification_feature, ) if email_delivery is not None and email_delivery.override is not None: override = email_delivery.override diff --git a/supertokens_python/recipe/passwordless/__init__.py b/supertokens_python/recipe/passwordless/__init__.py index c05086fcf..375fc62a1 100644 --- a/supertokens_python/recipe/passwordless/__init__.py +++ b/supertokens_python/recipe/passwordless/__init__.py @@ -55,9 +55,6 @@ def init( "USER_INPUT_CODE", "MAGIC_LINK", "USER_INPUT_CODE_AND_MAGIC_LINK" ], override: Union[InputOverrideConfig, None] = None, - get_link_domain_and_path: Union[ - Callable[[PhoneOrEmailInput, Dict[str, Any]], Awaitable[str]], None - ] = None, get_custom_user_input_code: Union[ Callable[[Dict[str, Any]], Awaitable[str]], None ] = None, @@ -68,7 +65,6 @@ def init( contact_config, flow_type, override, - get_link_domain_and_path, get_custom_user_input_code, email_delivery, sms_delivery, diff --git a/supertokens_python/recipe/passwordless/recipe.py b/supertokens_python/recipe/passwordless/recipe.py index ed0a821ac..d52623d0a 100644 --- a/supertokens_python/recipe/passwordless/recipe.py +++ b/supertokens_python/recipe/passwordless/recipe.py @@ -52,7 +52,6 @@ from .utils import ( ContactConfig, OverrideConfig, - PhoneOrEmailInput, validate_and_normalise_user_input, ) from ..emailverification import EmailVerificationRecipe @@ -90,9 +89,6 @@ def __init__( ], ingredients: PasswordlessIngredients, override: Union[OverrideConfig, None] = None, - get_link_domain_and_path: Union[ - Callable[[PhoneOrEmailInput, Dict[str, Any]], Awaitable[str]], None - ] = None, get_custom_user_input_code: Union[ Callable[[Dict[str, Any]], Awaitable[str]], None ] = None, @@ -109,7 +105,6 @@ def __init__( contact_config, flow_type, override, - get_link_domain_and_path, get_custom_user_input_code, email_delivery, sms_delivery, @@ -235,9 +230,6 @@ def init( "USER_INPUT_CODE", "MAGIC_LINK", "USER_INPUT_CODE_AND_MAGIC_LINK" ], override: Union[OverrideConfig, None] = None, - get_link_domain_and_path: Union[ - Callable[[PhoneOrEmailInput, Dict[str, Any]], Awaitable[str]], None - ] = None, get_custom_user_input_code: Union[ Callable[[Dict[str, Any]], Awaitable[str]], None ] = None, @@ -258,7 +250,6 @@ def func(app_info: AppInfo): flow_type, ingredients, override, - get_link_domain_and_path, get_custom_user_input_code, email_delivery, sms_delivery, diff --git a/supertokens_python/recipe/passwordless/utils.py b/supertokens_python/recipe/passwordless/utils.py index ceb543c43..36270d7c5 100644 --- a/supertokens_python/recipe/passwordless/utils.py +++ b/supertokens_python/recipe/passwordless/utils.py @@ -62,17 +62,6 @@ async def default_validate_phone_number(value: str): return "Phone number is invalid" -def default_get_link_domain_and_path(app_info: AppInfo): - async def get_link_domain_and_path(_: PhoneOrEmailInput, __: Dict[str, Any]) -> str: - return ( - app_info.website_domain.get_as_string_dangerous() - + app_info.website_base_path.get_as_string_dangerous() - + "/verify" - ) - - return get_link_domain_and_path - - async def default_validate_email(value: str): pattern = r"^(([^<>()\[\]\\.,;:\s@\"]+(\.[^<>()\[\]\\.,;:\s@\"]+)*)|(\".+\"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$" if fullmatch(pattern, value) is None: @@ -211,9 +200,6 @@ def __init__( flow_type: Literal[ "USER_INPUT_CODE", "MAGIC_LINK", "USER_INPUT_CODE_AND_MAGIC_LINK" ], - get_link_domain_and_path: Callable[ - [PhoneOrEmailInput, Dict[str, Any]], Awaitable[str] - ], get_email_delivery_config: Callable[ [], EmailDeliveryConfigWithService[PasswordlessLoginEmailTemplateVars] ], @@ -230,7 +216,6 @@ def __init__( "USER_INPUT_CODE", "MAGIC_LINK", "USER_INPUT_CODE_AND_MAGIC_LINK" ] = flow_type self.get_custom_user_input_code = get_custom_user_input_code - self.get_link_domain_and_path = get_link_domain_and_path self.get_email_delivery_config = get_email_delivery_config self.get_sms_delivery_config = get_sms_delivery_config @@ -242,9 +227,6 @@ def validate_and_normalise_user_input( "USER_INPUT_CODE", "MAGIC_LINK", "USER_INPUT_CODE_AND_MAGIC_LINK" ], override: Union[OverrideConfig, None] = None, - get_link_domain_and_path: Union[ - Callable[[PhoneOrEmailInput, Dict[str, Any]], Awaitable[str]], None - ] = None, get_custom_user_input_code: Union[ Callable[[Dict[str, Any]], Awaitable[str]], None ] = None, @@ -259,9 +241,6 @@ def validate_and_normalise_user_input( if override is None: override = OverrideConfig() - if get_link_domain_and_path is None: - get_link_domain_and_path = default_get_link_domain_and_path(app_info) - def get_email_delivery_config() -> EmailDeliveryConfigWithService[ PasswordlessLoginEmailTemplateVars ]: @@ -330,7 +309,6 @@ def get_sms_delivery_config() -> SMSDeliveryConfigWithService[ contact_config=contact_config, override=OverrideConfig(functions=override.functions, apis=override.apis), flow_type=flow_type, - get_link_domain_and_path=get_link_domain_and_path, get_email_delivery_config=get_email_delivery_config, get_sms_delivery_config=get_sms_delivery_config, get_custom_user_input_code=get_custom_user_input_code, diff --git a/supertokens_python/recipe/thirdpartyemailpassword/utils.py b/supertokens_python/recipe/thirdpartyemailpassword/utils.py index 7810bef8f..20d397f12 100644 --- a/supertokens_python/recipe/thirdpartyemailpassword/utils.py +++ b/supertokens_python/recipe/thirdpartyemailpassword/utils.py @@ -13,7 +13,7 @@ # under the License. from __future__ import annotations -from typing import TYPE_CHECKING, Any, Awaitable, Callable, Dict, List, Union +from typing import TYPE_CHECKING, Callable, List, Union from supertokens_python.ingredients.emaildelivery.types import ( EmailDeliveryConfig, @@ -28,49 +28,14 @@ InputResetPasswordUsingTokenFeature, InputSignUpFeature, ) -from ..emailverification.types import User as EmailVerificationUser from .emaildelivery.services.backward_compatibility import BackwardCompatibilityService from .interfaces import APIInterface, RecipeInterface -from .types import EmailTemplateVars, User +from .types import EmailTemplateVars if TYPE_CHECKING: from .recipe import ThirdPartyEmailPasswordRecipe -def email_verification_create_and_send_custom_email( - recipe: ThirdPartyEmailPasswordRecipe, - create_and_send_custom_email: Callable[ - [User, str, Dict[str, Any]], Awaitable[None] - ], -) -> Callable[[EmailVerificationUser, str, Dict[str, Any]], Awaitable[None]]: - async def func( - user: EmailVerificationUser, link: str, user_context: Dict[str, Any] - ): - user_info = await recipe.recipe_implementation.get_user_by_id( - user.user_id, user_context - ) - if user_info is None: - raise Exception("Unknown User ID provided") - return await create_and_send_custom_email(user_info, link, user_context) - - return func - - -def email_verification_get_email_verification_url( - recipe: ThirdPartyEmailPasswordRecipe, - get_email_verification_url: Callable[[User, Any], Awaitable[str]], -) -> Callable[[EmailVerificationUser, Any], Awaitable[str]]: - async def func(user: EmailVerificationUser, user_context: Dict[str, Any]): - user_info = await recipe.recipe_implementation.get_user_by_id( - user.user_id, user_context - ) - if user_info is None: - raise Exception("Unknown User ID provided") - return await get_email_verification_url(user_info, user_context) - - return func - - class InputOverrideConfig: def __init__( self, diff --git a/supertokens_python/recipe/thirdpartypasswordless/__init__.py b/supertokens_python/recipe/thirdpartypasswordless/__init__.py index 29854fd67..ad7d2bb14 100644 --- a/supertokens_python/recipe/thirdpartypasswordless/__init__.py +++ b/supertokens_python/recipe/thirdpartypasswordless/__init__.py @@ -56,9 +56,6 @@ def init( flow_type: Literal[ "USER_INPUT_CODE", "MAGIC_LINK", "USER_INPUT_CODE_AND_MAGIC_LINK" ], - get_link_domain_and_path: Union[ - Callable[[PhoneOrEmailInput, Dict[str, Any]], Awaitable[str]], None - ] = None, get_custom_user_input_code: Union[ Callable[[Dict[str, Any]], Awaitable[str]], None ] = None, @@ -70,7 +67,6 @@ def init( return ThirdPartyPasswordlessRecipe.init( contact_config, flow_type, - get_link_domain_and_path, get_custom_user_input_code, email_delivery, sms_delivery, diff --git a/supertokens_python/recipe/thirdpartypasswordless/recipe.py b/supertokens_python/recipe/thirdpartypasswordless/recipe.py index 5cb88e411..6f1ff6fa6 100644 --- a/supertokens_python/recipe/thirdpartypasswordless/recipe.py +++ b/supertokens_python/recipe/thirdpartypasswordless/recipe.py @@ -28,7 +28,7 @@ ) from supertokens_python.recipe_module import APIHandled, RecipeModule -from ..passwordless.utils import ContactConfig, PhoneOrEmailInput +from ..passwordless.utils import ContactConfig from .api.implementation import APIImplementation from .api.passwordless_api_impementation import ( get_interface_impl as get_passwordless_interface_impl, @@ -88,9 +88,6 @@ def __init__( "USER_INPUT_CODE", "MAGIC_LINK", "USER_INPUT_CODE_AND_MAGIC_LINK" ], ingredients: ThirdPartyPasswordlessIngredients, - get_link_domain_and_path: Union[ - Callable[[PhoneOrEmailInput, Dict[str, Any]], Awaitable[str]], None - ] = None, get_custom_user_input_code: Union[ Callable[[Dict[str, Any]], Awaitable[str]], None ] = None, @@ -107,7 +104,6 @@ def __init__( contact_config=contact_config, flow_type=flow_type, get_custom_user_input_code=get_custom_user_input_code, - get_link_domain_and_path=get_link_domain_and_path, override=override, providers=providers, email_delivery=email_delivery, @@ -175,7 +171,6 @@ def apis_override_passwordless( PlessOverrideConfig( func_override_passwordless, apis_override_passwordless ), - self.config.get_link_domain_and_path, self.config.get_custom_user_input_code, ) @@ -276,9 +271,6 @@ def init( flow_type: Literal[ "USER_INPUT_CODE", "MAGIC_LINK", "USER_INPUT_CODE_AND_MAGIC_LINK" ], - get_link_domain_and_path: Union[ - Callable[[PhoneOrEmailInput, Dict[str, Any]], Awaitable[str]], None - ] = None, get_custom_user_input_code: Union[ Callable[[Dict[str, Any]], Awaitable[str]], None ] = None, @@ -296,7 +288,6 @@ def func(app_info: AppInfo): contact_config, flow_type, ingredients, - get_link_domain_and_path, get_custom_user_input_code, override, providers, diff --git a/supertokens_python/recipe/thirdpartypasswordless/utils.py b/supertokens_python/recipe/thirdpartypasswordless/utils.py index d8cf49a38..d4bac7941 100644 --- a/supertokens_python/recipe/thirdpartypasswordless/utils.py +++ b/supertokens_python/recipe/thirdpartypasswordless/utils.py @@ -36,73 +36,30 @@ from supertokens_python.recipe.thirdpartypasswordless.types import SMSTemplateVars from typing_extensions import Literal -from ..emailverification.types import User as EmailVerificationUser from ..passwordless.utils import ( ContactConfig, ContactEmailOnlyConfig, ContactEmailOrPhoneConfig, - PhoneOrEmailInput, - default_get_link_domain_and_path, ) if TYPE_CHECKING: from .recipe import ThirdPartyPasswordlessRecipe from .interfaces import APIInterface, RecipeInterface - from .types import EmailTemplateVars, User - -from supertokens_python.recipe.emailverification.utils import ( - OverrideConfig as EmailVerificationOverrideConfig, -) + from .types import EmailTemplateVars from .smsdelivery.services.backward_compatibility import ( BackwardCompatibilityService as SMSBackwardCompatibilityService, ) -def email_verification_create_and_send_custom_email( - recipe: ThirdPartyPasswordlessRecipe, - create_and_send_custom_email: Callable[ - [User, str, Dict[str, Any]], Awaitable[None] - ], -) -> Callable[[EmailVerificationUser, str, Dict[str, Any]], Awaitable[None]]: - async def func( - user: EmailVerificationUser, link: str, user_context: Dict[str, Any] - ): - user_info = await recipe.recipe_implementation.get_user_by_id( - user.user_id, user_context - ) - if user_info is None: - raise Exception("Unknown User ID provided") - return await create_and_send_custom_email(user_info, link, user_context) - - return func - - -def email_verification_get_email_verification_url( - recipe: ThirdPartyPasswordlessRecipe, - get_email_verification_url: Callable[[User, Any], Awaitable[str]], -) -> Callable[[EmailVerificationUser, Any], Awaitable[str]]: - async def func(user: EmailVerificationUser, user_context: Dict[str, Any]): - user_info = await recipe.recipe_implementation.get_user_by_id( - user.user_id, user_context - ) - if user_info is None: - raise Exception("Unknown User ID provided") - return await get_email_verification_url(user_info, user_context) - - return func - - class InputOverrideConfig: def __init__( self, functions: Union[Callable[[RecipeInterface], RecipeInterface], None] = None, apis: Union[Callable[[APIInterface], APIInterface], None] = None, - email_verification_feature: Union[EmailVerificationOverrideConfig, None] = None, ): self.functions = functions self.apis = apis - self.email_verification_feature = email_verification_feature class OverrideConfig: @@ -124,9 +81,6 @@ def __init__( flow_type: Literal[ "USER_INPUT_CODE", "MAGIC_LINK", "USER_INPUT_CODE_AND_MAGIC_LINK" ], - get_link_domain_and_path: Callable[ - [PhoneOrEmailInput, Dict[str, Any]], Awaitable[str] - ], get_email_delivery_config: Callable[ [], EmailDeliveryConfigWithService[EmailTemplateVars] ], @@ -142,7 +96,6 @@ def __init__( self.flow_type: Literal[ "USER_INPUT_CODE", "MAGIC_LINK", "USER_INPUT_CODE_AND_MAGIC_LINK" ] = flow_type - self.get_link_domain_and_path = get_link_domain_and_path self.get_custom_user_input_code = get_custom_user_input_code self.get_email_delivery_config = get_email_delivery_config self.get_sms_delivery_config = get_sms_delivery_config @@ -155,9 +108,6 @@ def validate_and_normalise_user_input( flow_type: Literal[ "USER_INPUT_CODE", "MAGIC_LINK", "USER_INPUT_CODE_AND_MAGIC_LINK" ], - get_link_domain_and_path: Union[ - Callable[[PhoneOrEmailInput, Dict[str, Any]], Awaitable[str]], None - ] = None, get_custom_user_input_code: Union[ Callable[[Dict[str, Any]], Awaitable[str]], None ] = None, @@ -189,9 +139,6 @@ def validate_and_normalise_user_input( if override is None: override = InputOverrideConfig() - if get_link_domain_and_path is None: - get_link_domain_and_path = default_get_link_domain_and_path(recipe.app_info) - def get_email_delivery_config() -> EmailDeliveryConfigWithService[ EmailTemplateVars ]: @@ -248,7 +195,6 @@ def get_sms_delivery_config() -> SMSDeliveryConfigWithService[SMSTemplateVars]: providers=providers, contact_config=contact_config, flow_type=flow_type, - get_link_domain_and_path=get_link_domain_and_path, get_custom_user_input_code=get_custom_user_input_code, get_email_delivery_config=get_email_delivery_config, get_sms_delivery_config=get_sms_delivery_config, From 619ac257ef0529646df882d9694df7a12f989320 Mon Sep 17 00:00:00 2001 From: KShivendu Date: Thu, 1 Sep 2022 13:59:54 +0530 Subject: [PATCH 15/18] feat: Changes suggested in feedback --- .../recipe/emailverification/__init__.py | 2 +- .../emailverification/api/email_verify.py | 4 ++-- .../api/generate_email_verify_token.py | 2 +- .../recipe/emailverification/interfaces.py | 9 +++++---- .../recipe/emailverification/recipe.py | 10 +++++----- .../recipe/emailverification/utils.py | 8 ++++++-- .../recipe/passwordless/api/implementation.py | 16 +++++++-------- .../recipe/passwordless/recipe.py | 4 +++- .../recipe/session/asyncio/__init__.py | 4 ---- .../claim_base_classes/primitive_claim.py | 4 ---- .../recipe/thirdparty/api/implementation.py | 14 ++++++------- .../recipe/thirdpartypasswordless/recipe.py | 20 ------------------- tests/auth-react/django3x/mysite/utils.py | 11 ++++++---- tests/auth-react/fastapi-server/app.py | 8 ++++---- tests/auth-react/flask-server/app.py | 11 ++++++---- tests/emailpassword/test_emaildelivery.py | 4 ++-- tests/emailpassword/test_emailverify.py | 14 ++++++------- tests/thirdparty/test_emaildelivery.py | 4 ++-- .../test_email_delivery.py | 6 +++--- .../test_emaildelivery.py | 4 ++-- 20 files changed, 70 insertions(+), 89 deletions(-) diff --git a/supertokens_python/recipe/emailverification/__init__.py b/supertokens_python/recipe/emailverification/__init__.py index 314abed70..120c0c6f5 100644 --- a/supertokens_python/recipe/emailverification/__init__.py +++ b/supertokens_python/recipe/emailverification/__init__.py @@ -40,7 +40,7 @@ def init( - mode: MODE_TYPE = "OPTIONAL", + mode: MODE_TYPE, email_delivery: Union[EmailDeliveryConfig[EmailTemplateVars], None] = None, get_email_for_user_id: Optional[TypeGetEmailForUserIdFunction] = None, create_and_send_custom_email: Union[ diff --git a/supertokens_python/recipe/emailverification/api/email_verify.py b/supertokens_python/recipe/emailverification/api/email_verify.py index a458dae5c..7d35ed530 100644 --- a/supertokens_python/recipe/emailverification/api/email_verify.py +++ b/supertokens_python/recipe/emailverification/api/email_verify.py @@ -52,7 +52,7 @@ async def handle_email_verify_api( ) result = await api_implementation.email_verify_post( - token, api_options, user_context, session + token, api_options, session, user_context ) else: if api_implementation.disable_is_email_verified_get: @@ -65,7 +65,7 @@ async def handle_email_verify_api( ) result = await api_implementation.is_email_verified_get( - api_options, user_context, session + api_options, session, user_context ) return send_200_response(result.to_json(), api_options.response) diff --git a/supertokens_python/recipe/emailverification/api/generate_email_verify_token.py b/supertokens_python/recipe/emailverification/api/generate_email_verify_token.py index 33844094f..e14bdf887 100644 --- a/supertokens_python/recipe/emailverification/api/generate_email_verify_token.py +++ b/supertokens_python/recipe/emailverification/api/generate_email_verify_token.py @@ -35,6 +35,6 @@ async def handle_generate_email_verify_token_api( assert session is not None result = await api_implementation.generate_email_verify_token_post( - api_options, user_context, session + api_options, session, user_context ) return send_200_response(result.to_json(), api_options.response) diff --git a/supertokens_python/recipe/emailverification/interfaces.py b/supertokens_python/recipe/emailverification/interfaces.py index db9b38764..31027edfb 100644 --- a/supertokens_python/recipe/emailverification/interfaces.py +++ b/supertokens_python/recipe/emailverification/interfaces.py @@ -125,7 +125,8 @@ def to_json(self) -> Dict[str, Any]: class EmailVerifyPostInvalidTokenError(APIResponse): - status = "EMAIL_VERIFICATION_INVALID_TOKEN_ERROR" + def __init__(self): + self.status = "EMAIL_VERIFICATION_INVALID_TOKEN_ERROR" def to_json(self) -> Dict[str, Any]: return {"status": self.status} @@ -167,8 +168,8 @@ async def email_verify_post( self, token: str, api_options: APIOptions, + session: Optional[SessionContainer], user_context: Dict[str, Any], - session: Optional[SessionContainer] = None, ) -> Union[ EmailVerifyPostOkResult, EmailVerifyPostInvalidTokenError, GeneralErrorResponse ]: @@ -178,8 +179,8 @@ async def email_verify_post( async def is_email_verified_get( self, api_options: APIOptions, + session: Optional[SessionContainer], user_context: Dict[str, Any], - session: Optional[SessionContainer] = None, ) -> Union[IsEmailVerifiedGetOkResult, GeneralErrorResponse]: pass @@ -187,8 +188,8 @@ async def is_email_verified_get( async def generate_email_verify_token_post( self, api_options: APIOptions, - user_context: Dict[str, Any], session: SessionContainer, + user_context: Dict[str, Any], ) -> Union[ GenerateEmailVerifyTokenPostOkResult, GenerateEmailVerifyTokenPostEmailAlreadyVerifiedError, diff --git a/supertokens_python/recipe/emailverification/recipe.py b/supertokens_python/recipe/emailverification/recipe.py index cf7e49c4c..e966ec5f0 100644 --- a/supertokens_python/recipe/emailverification/recipe.py +++ b/supertokens_python/recipe/emailverification/recipe.py @@ -185,7 +185,7 @@ def get_all_cors_headers(self) -> List[str]: @staticmethod def init( - mode: MODE_TYPE = "OPTIONAL", + mode: MODE_TYPE, email_delivery: Union[EmailDeliveryConfig[EmailTemplateVars], None] = None, get_email_for_user_id: Optional[TypeGetEmailForUserIdFunction] = None, create_and_send_custom_email: Union[ @@ -263,7 +263,7 @@ async def get_email_for_user_id( return UnknownUserIdError() - def add_get_email_for_user_id_func(self, f: Callable[[str, Dict[str, Any]], Any]): + def add_get_email_for_user_id_func(self, f: TypeGetEmailForUserIdFunction): self.get_email_for_user_id_funcs_from_other_recipes.append(f) @@ -297,8 +297,8 @@ async def email_verify_post( self, token: str, api_options: APIOptions, + session: Optional[SessionContainer], user_context: Dict[str, Any], - session: Optional[SessionContainer] = None, ) -> Union[EmailVerifyPostOkResult, EmailVerifyPostInvalidTokenError]: response = await api_options.recipe_implementation.verify_email_using_token( @@ -314,8 +314,8 @@ async def email_verify_post( async def is_email_verified_get( self, api_options: APIOptions, + session: Optional[SessionContainer], user_context: Dict[str, Any], - session: Optional[SessionContainer] = None, ) -> IsEmailVerifiedGetOkResult: if session is None: raise Exception("Session is undefined. Should not come here.") @@ -335,8 +335,8 @@ async def is_email_verified_get( async def generate_email_verify_token_post( self, api_options: APIOptions, - user_context: Dict[str, Any], session: SessionContainer, + user_context: Dict[str, Any], ) -> Union[ GenerateEmailVerifyTokenPostOkResult, GenerateEmailVerifyTokenPostEmailAlreadyVerifiedError, diff --git a/supertokens_python/recipe/emailverification/utils.py b/supertokens_python/recipe/emailverification/utils.py index 689c22bb3..0b3386487 100644 --- a/supertokens_python/recipe/emailverification/utils.py +++ b/supertokens_python/recipe/emailverification/utils.py @@ -48,10 +48,9 @@ def __init__( class ParentRecipeEmailVerificationConfig: - # TODO: Rename this? ^ def __init__( self, - mode: MODE_TYPE = "OPTIONAL", + mode: MODE_TYPE, email_delivery: Union[EmailDeliveryConfig[EmailTemplateVars], None] = None, get_email_for_user_id: Optional[TypeGetEmailForUserIdFunction] = None, create_and_send_custom_email: Union[ @@ -96,6 +95,11 @@ def validate_and_normalise_user_input( "config must be an instance of ParentRecipeEmailVerificationConfig" ) + if config.mode not in ["REQUIRED", "OPTIONAL"]: + raise ValueError( + "Email Verification recipe mode must be one of 'REQUIRED' or 'OPTIONAL'" + ) + def get_email_delivery_config() -> EmailDeliveryConfigWithService[ VerificationEmailTemplateVars ]: diff --git a/supertokens_python/recipe/passwordless/api/implementation.py b/supertokens_python/recipe/passwordless/api/implementation.py index f875c8a08..13591c8b2 100644 --- a/supertokens_python/recipe/passwordless/api/implementation.py +++ b/supertokens_python/recipe/passwordless/api/implementation.py @@ -274,20 +274,18 @@ async def consume_code_post( if user.email is not None: ev_instance = EmailVerificationRecipe.get_instance_optional() - assert ev_instance is not None - token_response = ( - await ev_instance.recipe_implementation.create_email_verification_token( + if ev_instance is not None: + token_response = await ev_instance.recipe_implementation.create_email_verification_token( user.user_id, user.email, user_context, ) - ) - if isinstance(token_response, CreateEmailVerificationTokenOkResult): - await ev_instance.recipe_implementation.verify_email_using_token( - token_response.token, - user_context, - ) + if isinstance(token_response, CreateEmailVerificationTokenOkResult): + await ev_instance.recipe_implementation.verify_email_using_token( + token_response.token, + user_context, + ) session = await create_new_session( api_options.request, user.user_id, {}, {}, user_context=user_context diff --git a/supertokens_python/recipe/passwordless/recipe.py b/supertokens_python/recipe/passwordless/recipe.py index d52623d0a..f79751054 100644 --- a/supertokens_python/recipe/passwordless/recipe.py +++ b/supertokens_python/recipe/passwordless/recipe.py @@ -333,7 +333,9 @@ async def signinup( return consume_code_result raise Exception("Failed to create user. Please retry") - async def get_email_for_user_id(self, user_id: str, user_context: Dict[str, Any]): + async def get_email_for_user_id( + self, user_id: str, user_context: Dict[str, Any] + ) -> Union[GetEmailForUserIdOkResult, EmailDoesNotExistError, UnknownUserIdError]: user_info = await self.recipe_implementation.get_user_by_id( user_id, user_context ) diff --git a/supertokens_python/recipe/session/asyncio/__init__.py b/supertokens_python/recipe/session/asyncio/__init__.py index bfb1ea2b5..45ee435ca 100644 --- a/supertokens_python/recipe/session/asyncio/__init__.py +++ b/supertokens_python/recipe/session/asyncio/__init__.py @@ -13,8 +13,6 @@ # under the License. from typing import Any, Dict, List, Union, TypeVar, Callable, Optional -# TODO: Finalize how to import Optional - from supertokens_python.recipe.openid.interfaces import ( GetOpenIdDiscoveryConfigurationResult, ) @@ -39,8 +37,6 @@ GetJWKSResult, ) -# TODO: https://github.com/supertokens/supertokens-python/pull/209#discussion_r932049999 -# There have been changes to this function as well. See this: _T = TypeVar("_T") diff --git a/supertokens_python/recipe/session/claim_base_classes/primitive_claim.py b/supertokens_python/recipe/session/claim_base_classes/primitive_claim.py index 9be76a0e6..0fb9d0e83 100644 --- a/supertokens_python/recipe/session/claim_base_classes/primitive_claim.py +++ b/supertokens_python/recipe/session/claim_base_classes/primitive_claim.py @@ -128,7 +128,6 @@ async def validate( class PrimitiveClaimValidators(Generic[_T]): - # TODO: We should discuss how someone would override existing validators def __init__(self, claim: SessionClaim[_T]) -> None: self.claim = claim @@ -174,9 +173,6 @@ def add_to_payload_( def remove_from_payload_by_merge_( self, payload: JSONObject, user_context: Dict[str, Any] ) -> JSONObject: - # TODO: https://github.com/supertokens/supertokens-python/pull/209#discussion_r931922854 - # Not sure if this will actually work in python -> cause there is no diff between "null" and - # "undefined" in python (both are None). So setting this to None is as good as deleting it from the map? payload[self.key] = None return payload diff --git a/supertokens_python/recipe/thirdparty/api/implementation.py b/supertokens_python/recipe/thirdparty/api/implementation.py index 449db40b8..af136249b 100644 --- a/supertokens_python/recipe/thirdparty/api/implementation.py +++ b/supertokens_python/recipe/thirdparty/api/implementation.py @@ -164,19 +164,17 @@ async def sign_in_up_post( if email_verified: ev_instance = EmailVerificationRecipe.get_instance_optional() - assert ev_instance is not None - token_response = ( - await ev_instance.recipe_implementation.create_email_verification_token( + if ev_instance is not None: + token_response = await ev_instance.recipe_implementation.create_email_verification_token( user_id=signinup_response.user.user_id, email=signinup_response.user.email, user_context=user_context, ) - ) - if isinstance(token_response, CreateEmailVerificationTokenOkResult): - await ev_instance.recipe_implementation.verify_email_using_token( - token=token_response.token, user_context=user_context - ) + if isinstance(token_response, CreateEmailVerificationTokenOkResult): + await ev_instance.recipe_implementation.verify_email_using_token( + token=token_response.token, user_context=user_context + ) user = signinup_response.user session = await create_new_session( diff --git a/supertokens_python/recipe/thirdpartypasswordless/recipe.py b/supertokens_python/recipe/thirdpartypasswordless/recipe.py index 6f1ff6fa6..624bdaec2 100644 --- a/supertokens_python/recipe/thirdpartypasswordless/recipe.py +++ b/supertokens_python/recipe/thirdpartypasswordless/recipe.py @@ -318,23 +318,3 @@ def reset(): ): raise Exception(None, "calling testing function in non testing env") ThirdPartyPasswordlessRecipe.__instance = None - - async def get_email_for_user_id( - self, user_id: str, user_context: Dict[str, Any] - ) -> str: - user_info = await self.recipe_implementation.get_user_by_id( - user_id, user_context - ) - if user_info is None: - raise Exception("Unknown User ID provided") - if user_info.third_party_info is None: - if user_info.email is not None: - return user_info.email - # this is a passwordless user with only a phone number. - # returning an empty string here is not a problem since - # we override the email verification functions above to - # send that the email is already verified for passwordless users. - return "" - if user_info.email is None: - raise Exception("Should never come here") - return user_info.email diff --git a/tests/auth-react/django3x/mysite/utils.py b/tests/auth-react/django3x/mysite/utils.py index ce98d2b6e..9151abbd8 100644 --- a/tests/auth-react/django3x/mysite/utils.py +++ b/tests/auth-react/django3x/mysite/utils.py @@ -265,8 +265,8 @@ def override_email_verification_apis( async def email_verify_post( token: str, api_options: EVAPIOptions, + session: Optional[SessionContainer], user_context: Dict[str, Any], - session: Optional[SessionContainer] = None, ): is_general_error = await check_for_general_error( "body", api_options.request @@ -274,13 +274,16 @@ async def email_verify_post( if is_general_error: return GeneralErrorResponse("general error from API email verify") return await original_email_verify_post( - token, api_options, user_context, session + token, + api_options, + session, + user_context, ) async def generate_email_verify_token_post( api_options: EVAPIOptions, - user_context: Dict[str, Any], session: SessionContainer, + user_context: Dict[str, Any], ): is_general_error = await check_for_general_error( "body", api_options.request @@ -290,7 +293,7 @@ async def generate_email_verify_token_post( "general error from API email verification code" ) return await original_generate_email_verify_token_post( - api_options, user_context, session + api_options, session, user_context ) original_implementation_email_verification.email_verify_post = email_verify_post diff --git a/tests/auth-react/fastapi-server/app.py b/tests/auth-react/fastapi-server/app.py index dad7f9858..9cc438c60 100644 --- a/tests/auth-react/fastapi-server/app.py +++ b/tests/auth-react/fastapi-server/app.py @@ -318,8 +318,8 @@ def override_email_verification_apis( async def email_verify_post( token: str, api_options: EVAPIOptions, + session: Optional[SessionContainer], user_context: Dict[str, Any], - session: Optional[SessionContainer] = None, ): is_general_error = await check_for_general_error( "body", api_options.request @@ -327,13 +327,13 @@ async def email_verify_post( if is_general_error: return GeneralErrorResponse("general error from API email verify") return await original_email_verify_post( - token, api_options, user_context, session + token, api_options, session, user_context ) async def generate_email_verify_token_post( api_options: EVAPIOptions, - user_context: Dict[str, Any], session: SessionContainer, + user_context: Dict[str, Any], ): is_general_error = await check_for_general_error( "body", api_options.request @@ -344,8 +344,8 @@ async def generate_email_verify_token_post( ) return await original_generate_email_verify_token_post( api_options, - user_context, session, + user_context, ) original_implementation_email_verification.email_verify_post = email_verify_post diff --git a/tests/auth-react/flask-server/app.py b/tests/auth-react/flask-server/app.py index b5d0ea15e..91f01a32d 100644 --- a/tests/auth-react/flask-server/app.py +++ b/tests/auth-react/flask-server/app.py @@ -287,8 +287,8 @@ def override_email_verification_apis( async def email_verify_post( token: str, api_options: EVAPIOptions, + session: Optional[SessionContainer], user_context: Dict[str, Any], - session: Optional[SessionContainer] = None, ): is_general_error = await check_for_general_error( "body", api_options.request @@ -296,13 +296,16 @@ async def email_verify_post( if is_general_error: return GeneralErrorResponse("general error from API email verify") return await original_email_verify_post( - token, api_options, user_context, session + token, + api_options, + session, + user_context, ) async def generate_email_verify_token_post( api_options: EVAPIOptions, - user_context: Dict[str, Any], session: SessionContainer, + user_context: Dict[str, Any], ): is_general_error = await check_for_general_error( "body", api_options.request @@ -312,7 +315,7 @@ async def generate_email_verify_token_post( "general error from API email verification code" ) return await original_generate_email_verify_token_post( - api_options, user_context, session + api_options, session, user_context ) original_implementation_email_verification.email_verify_post = email_verify_post diff --git a/tests/emailpassword/test_emaildelivery.py b/tests/emailpassword/test_emaildelivery.py index 4decd7675..de7a855dc 100644 --- a/tests/emailpassword/test_emaildelivery.py +++ b/tests/emailpassword/test_emaildelivery.py @@ -527,7 +527,7 @@ async def test_email_verification_default_backward_compatibility( ), framework="fastapi", recipe_list=[ - emailverification.init(), + emailverification.init(mode="OPTIONAL"), emailpassword.init(), session.init(), ], @@ -592,7 +592,7 @@ async def test_email_verification_default_backward_compatibility_suppress_error( ), framework="fastapi", recipe_list=[ - emailverification.init(), + emailverification.init(mode="OPTIONAL"), emailpassword.init(), session.init(), ], diff --git a/tests/emailpassword/test_emailverify.py b/tests/emailpassword/test_emailverify.py index 8cccfa772..ee15d5e29 100644 --- a/tests/emailpassword/test_emailverify.py +++ b/tests/emailpassword/test_emailverify.py @@ -584,12 +584,12 @@ def apis_override_email_password(param: APIInterface): async def email_verify_post( token: str, api_options: APIOptions, + session: Optional[SessionContainer], user_context: Dict[str, Any], - session: Optional[SessionContainer] = None, ): nonlocal user_info_from_callback - response = await temp(token, api_options, user_context, session) + response = await temp(token, api_options, session, user_context) if isinstance(response, EmailVerifyPostOkResult): user_info_from_callback = response.user @@ -790,12 +790,12 @@ def apis_override_email_password(param: APIInterface): async def email_verify_post( token: str, api_options: APIOptions, + session: Optional[SessionContainer], user_context: Dict[str, Any], - session: Optional[SessionContainer] = None, ): nonlocal user_info_from_callback - response = await temp(token, api_options, user_context, session) + response = await temp(token, api_options, session, user_context) if isinstance(response, EmailVerifyPostOkResult): user_info_from_callback = response.user @@ -884,12 +884,12 @@ def apis_override_email_password(param: APIInterface): async def email_verify_post( token: str, api_options: APIOptions, + session: Optional[SessionContainer], user_context: Dict[str, Any], - session: Optional[SessionContainer] = None, ): nonlocal user_info_from_callback - response = await temp(token, api_options, user_context, session) + response = await temp(token, api_options, session, user_context) if isinstance(response, EmailVerifyPostOkResult): user_info_from_callback = response.user @@ -1014,7 +1014,7 @@ async def test_the_generate_token_api_with_valid_input_verify_and_then_unverify_ framework="fastapi", recipe_list=[ session.init(anti_csrf="VIA_TOKEN"), - emailverification.init(), + emailverification.init(mode="OPTIONAL"), emailpassword.init(), ], ) diff --git a/tests/thirdparty/test_emaildelivery.py b/tests/thirdparty/test_emaildelivery.py index b51ee665d..61ea97776 100644 --- a/tests/thirdparty/test_emaildelivery.py +++ b/tests/thirdparty/test_emaildelivery.py @@ -131,7 +131,7 @@ async def test_email_verify_default_backward_compatibility( ), framework="fastapi", recipe_list=[ - emailverification.init(), + emailverification.init(mode="OPTIONAL"), thirdparty.init( sign_in_and_up_feature=thirdparty.SignInAndUpFeature( providers=[CustomProvider("CUSTOM", True)] @@ -202,7 +202,7 @@ async def test_email_verify_default_backward_compatibility_supress_error( ), framework="fastapi", recipe_list=[ - emailverification.init(), + emailverification.init(mode="OPTIONAL"), thirdparty.init( sign_in_and_up_feature=thirdparty.SignInAndUpFeature( providers=[CustomProvider("CUSTOM", True)] diff --git a/tests/thirdpartyemailpassword/test_email_delivery.py b/tests/thirdpartyemailpassword/test_email_delivery.py index 94c1cab83..407d91175 100644 --- a/tests/thirdpartyemailpassword/test_email_delivery.py +++ b/tests/thirdpartyemailpassword/test_email_delivery.py @@ -479,7 +479,7 @@ async def test_email_verification_default_backward_compatibility( ), framework="fastapi", recipe_list=[ - emailverification.init(), + emailverification.init(mode="OPTIONAL"), thirdpartyemailpassword.init(), session.init(), ], @@ -544,7 +544,7 @@ async def test_email_verification_default_backward_compatibility_suppress_error( ), framework="fastapi", recipe_list=[ - emailverification.init(), + emailverification.init(mode="OPTIONAL"), thirdpartyemailpassword.init(), session.init(), ], @@ -877,7 +877,7 @@ async def custom_create_and_send_custom_email( ), framework="fastapi", recipe_list=[ - emailverification.init(), + emailverification.init(mode="OPTIONAL"), thirdpartyemailpassword.init( providers=[ Github(client_id="", client_secret="") diff --git a/tests/thirdpartypasswordless/test_emaildelivery.py b/tests/thirdpartypasswordless/test_emaildelivery.py index f8f661f9e..2e58561f2 100644 --- a/tests/thirdpartypasswordless/test_emaildelivery.py +++ b/tests/thirdpartypasswordless/test_emaildelivery.py @@ -132,7 +132,7 @@ async def test_email_verify_default_backward_compatibility( ), framework="fastapi", recipe_list=[ - emailverification.init(), + emailverification.init(mode="OPTIONAL"), thirdpartypasswordless.init( contact_config=ContactEmailOnlyConfig(), flow_type="USER_INPUT_CODE_AND_MAGIC_LINK", @@ -547,7 +547,7 @@ async def send_email_override( ), framework="fastapi", recipe_list=[ - emailverification.init(), + emailverification.init(mode="OPTIONAL"), thirdpartypasswordless.init( contact_config=ContactEmailOnlyConfig(), flow_type="USER_INPUT_CODE_AND_MAGIC_LINK", From 5b36b168cc9c60bc51eb86f15bc111f9507d885d Mon Sep 17 00:00:00 2001 From: KShivendu Date: Thu, 1 Sep 2022 14:40:06 +0530 Subject: [PATCH 16/18] refactor: Remove ParentRecipeEmailVerificationConfig --- .../recipe/emailverification/__init__.py | 1 - .../recipe/emailverification/recipe.py | 31 ++++++---- .../recipe/emailverification/utils.py | 60 ++++++------------- .../input_validation/test_input_validation.py | 5 +- 4 files changed, 41 insertions(+), 56 deletions(-) diff --git a/supertokens_python/recipe/emailverification/__init__.py b/supertokens_python/recipe/emailverification/__init__.py index 120c0c6f5..ee37a884e 100644 --- a/supertokens_python/recipe/emailverification/__init__.py +++ b/supertokens_python/recipe/emailverification/__init__.py @@ -24,7 +24,6 @@ from ...ingredients.emaildelivery.types import EmailDeliveryConfig InputOverrideConfig = utils.OverrideConfig -ParentRecipeEmailVerificationConfig = utils.ParentRecipeEmailVerificationConfig exception = ex SMTPService = emaildelivery_services.SMTPService EmailVerificationClaim = recipe.EmailVerificationClaim diff --git a/supertokens_python/recipe/emailverification/recipe.py b/supertokens_python/recipe/emailverification/recipe.py index e966ec5f0..cb8cf0265 100644 --- a/supertokens_python/recipe/emailverification/recipe.py +++ b/supertokens_python/recipe/emailverification/recipe.py @@ -66,7 +66,6 @@ from .constants import USER_EMAIL_VERIFY, USER_EMAIL_VERIFY_TOKEN from .exceptions import SuperTokensEmailVerificationError from .utils import ( - ParentRecipeEmailVerificationConfig, validate_and_normalise_user_input, MODE_TYPE, OverrideConfig, @@ -82,11 +81,24 @@ def __init__( self, recipe_id: str, app_info: AppInfo, - config: ParentRecipeEmailVerificationConfig, ingredients: EmailVerificationIngredients, + mode: MODE_TYPE, + email_delivery: Union[EmailDeliveryConfig[EmailTemplateVars], None] = None, + get_email_for_user_id: Optional[TypeGetEmailForUserIdFunction] = None, + create_and_send_custom_email: Union[ + Callable[[User, str, Dict[str, Any]], Awaitable[None]], None + ] = None, + override: Union[OverrideConfig, None] = None, ) -> None: super().__init__(recipe_id, app_info) - self.config = validate_and_normalise_user_input(app_info, config) + self.config = validate_and_normalise_user_input( + app_info, + mode, + email_delivery, + get_email_for_user_id, + create_and_send_custom_email, + override, + ) recipe_implementation = RecipeImplementation( Querier.get_instance(recipe_id), self.config @@ -196,25 +208,22 @@ def init( def func(app_info: AppInfo): if EmailVerificationRecipe.__instance is None: ingredients = EmailVerificationIngredients(email_delivery=None) - config = ParentRecipeEmailVerificationConfig( + EmailVerificationRecipe.__instance = EmailVerificationRecipe( + EmailVerificationRecipe.recipe_id, + app_info, + ingredients, mode, email_delivery, get_email_for_user_id, create_and_send_custom_email, override, ) - EmailVerificationRecipe.__instance = EmailVerificationRecipe( - EmailVerificationRecipe.recipe_id, - app_info, - config, - ingredients=ingredients, - ) def callback(): SessionRecipe.get_instance().add_claim_from_other_recipe( EmailVerificationClaim ) - if str(config.mode) == "REQUIRED": + if mode == "REQUIRED": SessionRecipe.get_instance().add_claim_validator_from_other_recipe( EmailVerificationClaim.validators.is_verified() ) diff --git a/supertokens_python/recipe/emailverification/utils.py b/supertokens_python/recipe/emailverification/utils.py index 0b3386487..cb8e44b5d 100644 --- a/supertokens_python/recipe/emailverification/utils.py +++ b/supertokens_python/recipe/emailverification/utils.py @@ -47,30 +47,6 @@ def __init__( MODE_TYPE = Literal["REQUIRED", "OPTIONAL"] -class ParentRecipeEmailVerificationConfig: - def __init__( - self, - mode: MODE_TYPE, - email_delivery: Union[EmailDeliveryConfig[EmailTemplateVars], None] = None, - get_email_for_user_id: Optional[TypeGetEmailForUserIdFunction] = None, - create_and_send_custom_email: Union[ - Callable[[User, str, Dict[str, Any]], Awaitable[None]], None - ] = None, - override: Union[OverrideConfig, None] = None, - ): - self.mode: MODE_TYPE = mode - self.email_delivery = email_delivery - self.get_email_for_user_id = get_email_for_user_id - self.create_and_send_custom_email = create_and_send_custom_email - self.override = override - - if create_and_send_custom_email: - # Note: This will appear twice because `InputEmailVerificationConfig` will also produce same warning. - deprecated_warn( - "create_and_send_custom_email is deprecated. Please use email delivery config instead" - ) - - class EmailVerificationConfig: def __init__( self, @@ -88,14 +64,21 @@ def __init__( def validate_and_normalise_user_input( - app_info: AppInfo, config: ParentRecipeEmailVerificationConfig + app_info: AppInfo, + mode: MODE_TYPE, + email_delivery: Union[EmailDeliveryConfig[EmailTemplateVars], None] = None, + get_email_for_user_id: Optional[TypeGetEmailForUserIdFunction] = None, + create_and_send_custom_email: Union[ + Callable[[User, str, Dict[str, Any]], Awaitable[None]], None + ] = None, + override: Union[OverrideConfig, None] = None, ) -> EmailVerificationConfig: - if not isinstance(config, ParentRecipeEmailVerificationConfig): # type: ignore - raise ValueError( - "config must be an instance of ParentRecipeEmailVerificationConfig" + if create_and_send_custom_email: + deprecated_warn( + "create_and_send_custom_email is deprecated. Please use email delivery config instead" ) - if config.mode not in ["REQUIRED", "OPTIONAL"]: + if mode not in ["REQUIRED", "OPTIONAL"]: raise ValueError( "Email Verification recipe mode must be one of 'REQUIRED' or 'OPTIONAL'" ) @@ -103,25 +86,18 @@ def validate_and_normalise_user_input( def get_email_delivery_config() -> EmailDeliveryConfigWithService[ VerificationEmailTemplateVars ]: - email_service = ( - config.email_delivery.service if config.email_delivery is not None else None - ) + email_service = email_delivery.service if email_delivery is not None else None if email_service is None: email_service = BackwardCompatibilityService( - app_info, config.create_and_send_custom_email + app_info, create_and_send_custom_email ) - if ( - config.email_delivery is not None - and config.email_delivery.override is not None - ): - override = config.email_delivery.override + 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) - override = config.override - if override is not None and not isinstance(override, OverrideConfig): # type: ignore raise ValueError("override must be of type OverrideConfig or None") @@ -129,8 +105,8 @@ def get_email_delivery_config() -> EmailDeliveryConfigWithService[ override = OverrideConfig() return EmailVerificationConfig( - config.mode, + mode, get_email_delivery_config, - config.get_email_for_user_id, + get_email_for_user_id, override, ) diff --git a/tests/input_validation/test_input_validation.py b/tests/input_validation/test_input_validation.py index 0f7948666..81caf5f2a 100644 --- a/tests/input_validation/test_input_validation.py +++ b/tests/input_validation/test_input_validation.py @@ -127,8 +127,9 @@ async def test_init_validation_emailverification(): framework="fastapi", recipe_list=[emailverification.init("config")], # type: ignore ) - assert "config must be an instance of ParentRecipeEmailVerificationConfig" == str( - ex.value + assert ( + "Email Verification recipe mode must be one of 'REQUIRED' or 'OPTIONAL'" + == str(ex.value) ) with pytest.raises(ValueError) as ex: From 01c2609b45be1e18c8feb665f03d49481f1d96af Mon Sep 17 00:00:00 2001 From: KShivendu Date: Thu, 1 Sep 2022 16:48:33 +0530 Subject: [PATCH 17/18] test: Fix failing tests --- supertokens_python/recipe/session/recipe.py | 13 +++-- tests/emailpassword/test_emailverify.py | 49 +++++++++++++++---- .../input_validation/test_input_validation.py | 6 +-- 3 files changed, 52 insertions(+), 16 deletions(-) diff --git a/supertokens_python/recipe/session/recipe.py b/supertokens_python/recipe/session/recipe.py index 3a1ab03d6..70d5df661 100644 --- a/supertokens_python/recipe/session/recipe.py +++ b/supertokens_python/recipe/session/recipe.py @@ -26,6 +26,7 @@ TokenTheftError, UnauthorisedError, InvalidClaimsError, + TryRefreshTokenError, ) from ...types import MaybeAwaitable @@ -239,10 +240,14 @@ async def handle_error( return await self.config.error_handlers.on_invalid_claim( self, request, err.payload, response ) - log_debug_message("errorHandler: returning TRY_REFRESH_TOKEN") - return await self.config.error_handlers.on_try_refresh_token( - request, str(err), response - ) + if isinstance(err, TryRefreshTokenError): + log_debug_message("errorHandler: returning TRY_REFRESH_TOKEN") + return await self.config.error_handlers.on_try_refresh_token( + request, str(err), response + ) + + # TODO: Is raising err okay? + raise err def get_all_cors_headers(self) -> List[str]: cors_headers = get_cors_allowed_headers() diff --git a/tests/emailpassword/test_emailverify.py b/tests/emailpassword/test_emailverify.py index ee15d5e29..6e1523685 100644 --- a/tests/emailpassword/test_emailverify.py +++ b/tests/emailpassword/test_emailverify.py @@ -48,6 +48,7 @@ get_session, refresh_session, ) +from supertokens_python.recipe.session.constants import ANTI_CSRF_HEADER_KEY from supertokens_python.utils import is_version_gte from tests.utils import ( TEST_ACCESS_TOKEN_MAX_AGE_CONFIG_KEY, @@ -133,7 +134,11 @@ async def test_the_generate_token_api_with_valid_input_email_not_verified( api_base_path="/auth", ), framework="fastapi", - recipe_list=[session.init(anti_csrf="VIA_TOKEN"), emailpassword.init()], + recipe_list=[ + session.init(anti_csrf="VIA_TOKEN"), + emailverification.init("OPTIONAL"), + emailpassword.init(), + ], ) start_st() @@ -170,7 +175,11 @@ async def test_the_generate_token_api_with_valid_input_email_verified_and_test_e api_base_path="/auth", ), framework="fastapi", - recipe_list=[session.init(anti_csrf="VIA_TOKEN"), emailpassword.init()], + recipe_list=[ + session.init(anti_csrf="VIA_TOKEN"), + emailverification.init("OPTIONAL"), + emailpassword.init(), + ], ) start_st() @@ -213,7 +222,11 @@ async def test_the_generate_token_api_with_valid_input_no_session_and_check_outp api_base_path="/auth", ), framework="fastapi", - recipe_list=[session.init(anti_csrf="VIA_TOKEN"), emailpassword.init()], + recipe_list=[ + session.init(anti_csrf="VIA_TOKEN"), + emailverification.init("OPTIONAL"), + emailpassword.init(), + ], ) start_st() @@ -238,7 +251,11 @@ async def test_the_generate_token_api_with_an_expired_access_token_and_see_that_ api_base_path="/auth", ), framework="fastapi", - recipe_list=[session.init(anti_csrf="VIA_TOKEN"), emailpassword.init()], + recipe_list=[ + session.init(anti_csrf="VIA_TOKEN"), + emailverification.init("OPTIONAL"), + emailpassword.init(), + ], ) start_st() @@ -723,7 +740,9 @@ async def custom_f( assert token is not None response_3 = driver_config_client.post( - url="/auth/user/email/verify", json={"method": "token", "token": token} + url="/auth/user/email/verify", + json={"method": "token", "token": token}, + headers={ANTI_CSRF_HEADER_KEY: response_1.headers.get("anti-csrf")}, ) dict_response = json.loads(response_3.text) @@ -760,7 +779,11 @@ async def test_the_email_verify_with_no_session_using_the_get_method( api_base_path="/auth", ), framework="fastapi", - recipe_list=[session.init(anti_csrf="VIA_TOKEN"), emailpassword.init()], + recipe_list=[ + session.init(anti_csrf="VIA_TOKEN"), + emailverification.init("OPTIONAL"), + emailpassword.init(), + ], ) start_st() @@ -850,7 +873,9 @@ async def email_verify_post( assert token is not None response_3 = driver_config_client.post( - url="/auth/user/email/verify", json={"method": "token", "token": token} + url="/auth/user/email/verify", + json={"method": "token", "token": token}, + headers={ANTI_CSRF_HEADER_KEY: response_1.headers.get("anti-csrf")}, ) dict_response = json.loads(response_3.text) @@ -946,7 +971,9 @@ async def email_verify_post( assert token is not None response_3 = driver_config_client.post( - url="/auth/user/email/verify", json={"method": "token", "token": token} + url="/auth/user/email/verify", + json={"method": "token", "token": token}, + headers={ANTI_CSRF_HEADER_KEY: response_1.headers.get("anti-csrf")}, ) dict_response = json.loads(response_3.text) @@ -974,7 +1001,11 @@ async def test_the_generate_token_api_with_valid_input_and_then_remove_token( api_base_path="/auth", ), framework="fastapi", - recipe_list=[session.init(anti_csrf="VIA_TOKEN"), emailpassword.init()], + recipe_list=[ + session.init(anti_csrf="VIA_TOKEN"), + emailverification.init("OPTIONAL"), + emailpassword.init(), + ], ) start_st() diff --git a/tests/input_validation/test_input_validation.py b/tests/input_validation/test_input_validation.py index 81caf5f2a..d63eb63cf 100644 --- a/tests/input_validation/test_input_validation.py +++ b/tests/input_validation/test_input_validation.py @@ -88,7 +88,7 @@ async def test_init_validation_emailpassword(): ], ) assert ( - "email_verification_feature must be of type InputEmailVerificationConfig or None" + "Email Verification recipe mode must be one of 'REQUIRED' or 'OPTIONAL'" == str(ex.value) ) @@ -473,7 +473,7 @@ async def test_init_validation_thirdpartyemailpassword(): ], ) assert ( - "email_verification_feature must be of type InputEmailVerificationConfig or None" + "Email Verification recipe mode must be one of 'REQUIRED' or 'OPTIONAL'" == str(ex.value) ) @@ -608,7 +608,7 @@ async def test_init_validation_thirdpartypasswordless(): ], ) assert ( - "email_verification_feature must be an instance of InputEmailVerificationConfig or None" + "Email Verification recipe mode must be one of 'REQUIRED' or 'OPTIONAL'" == str(ex.value) ) From 6fc90c608094a66e7844b4b752b4a4ae46a4e644 Mon Sep 17 00:00:00 2001 From: KShivendu Date: Thu, 1 Sep 2022 17:37:40 +0530 Subject: [PATCH 18/18] refactor: Remove irrelevant comment --- .../recipe/emailverification/recipe.py | 45 +++++++++---------- tests/sessions/claims/utils.py | 5 ++- 2 files changed, 24 insertions(+), 26 deletions(-) diff --git a/supertokens_python/recipe/emailverification/recipe.py b/supertokens_python/recipe/emailverification/recipe.py index cb8cf0265..0e1b03a6c 100644 --- a/supertokens_python/recipe/emailverification/recipe.py +++ b/supertokens_python/recipe/emailverification/recipe.py @@ -14,7 +14,7 @@ from __future__ import annotations from os import environ -from typing import TYPE_CHECKING, List, Union, Any, Dict, Callable, Optional, Awaitable +from typing import TYPE_CHECKING, Any, Awaitable, Callable, Dict, List, Optional, Union from supertokens_python.exceptions import SuperTokensError, raise_general_exception from supertokens_python.ingredients.emaildelivery import EmailDeliveryIngredient @@ -22,37 +22,37 @@ EmailVerificationInvalidTokenError, ) from supertokens_python.recipe.emailverification.types import ( + EmailTemplateVars, EmailVerificationIngredients, + User, VerificationEmailTemplateVars, VerificationEmailTemplateVarsUser, - EmailTemplateVars, - User, ) from supertokens_python.recipe_module import APIHandled, RecipeModule -from .ev_claim import EmailVerificationClaimValidators +from ...ingredients.emaildelivery.types import EmailDeliveryConfig +from ...logger import log_debug_message +from ...post_init_callbacks import PostSTInitCallbacks +from ..session import SessionRecipe +from ..session.claim_base_classes.boolean_claim import BooleanClaim +from ..session.interfaces import SessionContainer +from .ev_claim import EmailVerificationClaimValidators from .interfaces import ( + APIInterface, APIOptions, - UnknownUserIdError, - TypeGetEmailForUserIdFunction, - GetEmailForUserIdOkResult, + CreateEmailVerificationTokenEmailAlreadyVerifiedError, EmailDoesNotExistError, - APIInterface, - EmailVerifyPostOkResult, EmailVerifyPostInvalidTokenError, - VerifyEmailUsingTokenOkResult, - IsEmailVerifiedGetOkResult, - GenerateEmailVerifyTokenPostOkResult, + EmailVerifyPostOkResult, GenerateEmailVerifyTokenPostEmailAlreadyVerifiedError, - CreateEmailVerificationTokenEmailAlreadyVerifiedError, + GenerateEmailVerifyTokenPostOkResult, + GetEmailForUserIdOkResult, + IsEmailVerifiedGetOkResult, + TypeGetEmailForUserIdFunction, + UnknownUserIdError, + VerifyEmailUsingTokenOkResult, ) from .recipe_implementation import RecipeImplementation -from ..session import SessionRecipe -from ..session.claim_base_classes.boolean_claim import BooleanClaim -from ..session.interfaces import SessionContainer -from ...ingredients.emaildelivery.types import EmailDeliveryConfig -from ...logger import log_debug_message -from ...post_init_callbacks import PostSTInitCallbacks if TYPE_CHECKING: from supertokens_python.framework.request import BaseRequest @@ -65,11 +65,7 @@ from .api import handle_email_verify_api, handle_generate_email_verify_token_api from .constants import USER_EMAIL_VERIFY, USER_EMAIL_VERIFY_TOKEN from .exceptions import SuperTokensEmailVerificationError -from .utils import ( - validate_and_normalise_user_input, - MODE_TYPE, - OverrideConfig, -) +from .utils import MODE_TYPE, OverrideConfig, validate_and_normalise_user_input class EmailVerificationRecipe(RecipeModule): @@ -332,7 +328,6 @@ async def is_email_verified_get( is_verified = await session.get_claim_value( EmailVerificationClaim, user_context ) - # TODO: Type of is_verified should be bool. It's any for now. if is_verified is None: raise Exception( diff --git a/tests/sessions/claims/utils.py b/tests/sessions/claims/utils.py index 203f78463..1393778c2 100644 --- a/tests/sessions/claims/utils.py +++ b/tests/sessions/claims/utils.py @@ -4,7 +4,10 @@ from supertokens_python.framework.request import BaseRequest from supertokens_python.recipe import session from supertokens_python.recipe.session import JWTConfig -from supertokens_python.recipe.session.claims import BooleanClaim, SessionClaim +from supertokens_python.recipe.session.claims import ( + BooleanClaim, + SessionClaim, +) from supertokens_python.recipe.session.interfaces import RecipeInterface TrueClaim = BooleanClaim("st-true", fetch_value=lambda _, __: True) # type: ignore