From db28082116f41e7f8fd1db35b8532f364f2d6421 Mon Sep 17 00:00:00 2001 From: rishabhpoddar Date: Mon, 14 Oct 2024 16:52:32 +0530 Subject: [PATCH 01/34] updates python dependency version --- v2/src/plugins/codeTypeChecking/pythonEnv/requirements.txt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/v2/src/plugins/codeTypeChecking/pythonEnv/requirements.txt b/v2/src/plugins/codeTypeChecking/pythonEnv/requirements.txt index c4fa3e4c4..fd1fd9584 100644 --- a/v2/src/plugins/codeTypeChecking/pythonEnv/requirements.txt +++ b/v2/src/plugins/codeTypeChecking/pythonEnv/requirements.txt @@ -72,7 +72,7 @@ six==1.16.0 sniffio==1.3.0 sqlparse==0.4.2 starlette==0.14.2 -supertokens-python==0.24.3 +supertokens_python @ git+https://github.com/supertokens/supertokens-python.git@5236c14f807a99ac88692e03ead769416722859f tldextract==3.1.0 toml==0.10.2 tomli==2.0.1 @@ -85,4 +85,4 @@ urllib3==2.0.4 uvicorn==0.18.2 Werkzeug==2.0.3 wrapt==1.13.3 -zipp==3.7.0 \ No newline at end of file +zipp==3.7.0 From 0167a2046ecbf6c91f5a9b3539763944d95a75ec Mon Sep 17 00:00:00 2001 From: rishabhpoddar Date: Wed, 16 Oct 2024 13:58:05 +0530 Subject: [PATCH 02/34] fixes some snippets --- v2/attackprotectionsuite/backend-setup.mdx | 23 +++++----- .../custom-response/general-error.mdx | 45 ++++++++++++++----- .../custom-response/throwing-error.mdx | 32 ++++++++----- .../apis-override/usage.mdx | 37 ++++++++++----- .../backend-functions-override/usage.mdx | 44 ++++++++++++------ .../custom-response/throwing-error.mdx | 32 ++++++++----- .../custom-response/throwing-error.mdx | 32 ++++++++----- .../custom-response/throwing-error.mdx | 32 ++++++++----- .../custom-response/general-error.mdx | 45 ++++++++++++++----- .../custom-response/throwing-error.mdx | 32 ++++++++----- .../apis-override/usage.mdx | 37 ++++++++++----- .../custom-response/throwing-error.mdx | 32 ++++++++----- 12 files changed, 284 insertions(+), 139 deletions(-) diff --git a/v2/attackprotectionsuite/backend-setup.mdx b/v2/attackprotectionsuite/backend-setup.mdx index b42329dc2..fd1e36a0e 100644 --- a/v2/attackprotectionsuite/backend-setup.mdx +++ b/v2/attackprotectionsuite/backend-setup.mdx @@ -1038,13 +1038,14 @@ from supertokens_python.recipe.emailpassword.interfaces import APIInterface, API from supertokens_python.recipe.emailpassword.types import FormField from supertokens_python.framework import BaseRequest from supertokens_python.types import GeneralErrorResponse +from supertokens_python.recipe.session import SessionContainer SECRET_API_KEY = ""; # Your secret API key that you received from the SuperTokens team # The full URL with the correct region will be provided by the SuperTokens team ANOMALY_DETECTION_API_URL = "https://security-.aws.supertokens.io/v1/security" async def handle_security_checks(request_id: Union[str, None], password: Union[str, None], brute_force_config: Union[List[Dict[str, Any]], None], email: Union[str, None], phone_number: Union[str, None], action_type: Union[str, None]) -> Union[GeneralErrorResponse, None]: - request_body = {} + request_body: Dict[str, Any] = {} if request_id is not None: request_body['requestId'] = request_id @@ -1117,7 +1118,8 @@ def get_brute_force_config(user_identifier: Union[str, None], ip: str, prefix: U # highlight-start def override_email_password_apis(original_implementation: APIInterface): original_sign_up_post = original_implementation.sign_up_post - async def sign_up_post(form_fields: List[FormField], tenant_id: str, api_options: APIOptions, user_context: Dict[str, Any]): + async def sign_up_post(form_fields: List[FormField], tenant_id: str, session: Union[SessionContainer, None], + should_try_linking_with_session_user: Union[bool, None], api_options: APIOptions, user_context: Dict[str, Any]): request_body = await api_options.request.json() if not request_body: return GeneralErrorResponse(message="The request body is required") @@ -1150,13 +1152,13 @@ def override_email_password_apis(original_implementation: APIInterface): return security_check_response # We need to call the original implementation of sign_up_post. - response = await original_sign_up_post(form_fields, tenant_id, api_options, user_context) + response = await original_sign_up_post(form_fields, tenant_id, session, should_try_linking_with_session_user, api_options, user_context) return response original_implementation.sign_up_post = sign_up_post original_sign_in_post = original_implementation.sign_in_post - async def sign_in_post(form_fields: List[FormField], tenant_id: str, api_options: APIOptions, user_context: Dict[str, Any]): + async def sign_in_post(form_fields: List[FormField], tenant_id: str, session: Union[SessionContainer, None], should_try_linking_with_session_user: Union[bool, None], api_options: APIOptions, user_context: Dict[str, Any]): request_body = await api_options.request.json() if not request_body: return GeneralErrorResponse(message="The request body is required") @@ -1186,7 +1188,7 @@ def override_email_password_apis(original_implementation: APIInterface): return security_check_response # We need to call the original implementation of sign_in_post. - response = await original_sign_in_post(form_fields, tenant_id, api_options, user_context) + response = await original_sign_in_post(form_fields, tenant_id, session, should_try_linking_with_session_user, api_options, user_context) return response original_implementation.sign_in_post = sign_in_post @@ -1710,13 +1712,14 @@ from supertokens_python.recipe.passwordless.interfaces import APIInterface, APIO from supertokens_python.recipe.passwordless.asyncio import list_codes_by_device_id from supertokens_python.framework import BaseRequest from supertokens_python.types import GeneralErrorResponse +from supertokens_python.recipe.session import SessionContainer SECRET_API_KEY = "" # Your secret API key that you received from the SuperTokens team # The full URL with the correct region will be provided by the SuperTokens team ANOMALY_DETECTION_API_URL = "https://security-.aws.supertokens.io/v1/security" async def handle_security_checks(request_id: Union[str, None], password: Union[str, None], brute_force_config: Union[List[Dict[str, Any]], None], email: Union[str, None], phone_number: Union[str, None], action_type: Union[str, None]) -> Union[GeneralErrorResponse, None]: - request_body = {} + request_body: Dict[str, Any] = {} request_body['bruteForce'] = brute_force_config request_body['email'] = email @@ -1767,7 +1770,7 @@ def get_brute_force_config(user_identifier: Union[str, None], ip: str, prefix: U # highlight-start def override_passwordless_apis(original_implementation: APIInterface): original_create_code_post = original_implementation.create_code_post - async def create_code_post(email: Union[str, None], phone_number: Union[str, None], tenant_id: str, api_options: APIOptions, user_context: Dict[str, Any]): + async def create_code_post(email: Union[str, None], phone_number: Union[str, None], session: Union[SessionContainer, None], should_try_linking_with_session_user: Union[bool, None], tenant_id: str, api_options: APIOptions, user_context: Dict[str, Any]): action_type = 'passwordless-send-sms' ip = get_ip_from_request(api_options.request) identifier = None @@ -1790,13 +1793,13 @@ def override_passwordless_apis(original_implementation: APIInterface): return security_check_response # We need to call the original implementation of create_code_post. - response = await original_create_code_post(email, phone_number, tenant_id, api_options, user_context) + response = await original_create_code_post(email, phone_number, session, should_try_linking_with_session_user,tenant_id, api_options, user_context) return response original_implementation.create_code_post = create_code_post original_resend_code_post = original_implementation.resend_code_post - async def resend_code_post(device_id: str, pre_auth_session_id: str, tenant_id: str, api_options: APIOptions, user_context: Dict[str, Any]): + async def resend_code_post(device_id: str, pre_auth_session_id: str, session: Union[SessionContainer, None], should_try_linking_with_session_user: Union[bool, None], tenant_id: str, api_options: APIOptions, user_context: Dict[str, Any]): action_type = 'passwordless-send-sms' ip = get_ip_from_request(api_options.request) email = None @@ -1825,7 +1828,7 @@ def override_passwordless_apis(original_implementation: APIInterface): return security_check_response # We need to call the original implementation of resend_code_post. - response = await original_resend_code_post(device_id, pre_auth_session_id, tenant_id, api_options, user_context) + response = await original_resend_code_post(device_id, pre_auth_session_id, session, should_try_linking_with_session_user, tenant_id, api_options, user_context) return response original_implementation.resend_code_post = resend_code_post diff --git a/v2/emailpassword/advanced-customizations/apis-override/custom-response/general-error.mdx b/v2/emailpassword/advanced-customizations/apis-override/custom-response/general-error.mdx index 7ed597bd6..1d9752d82 100644 --- a/v2/emailpassword/advanced-customizations/apis-override/custom-response/general-error.mdx +++ b/v2/emailpassword/advanced-customizations/apis-override/custom-response/general-error.mdx @@ -120,10 +120,17 @@ func emailNotAllowed(email string) bool { ```python from supertokens_python.recipe import emailpassword -from supertokens_python.recipe.emailpassword.interfaces import APIInterface, SignUpPostOkResult, SignUpPostEmailAlreadyExistsError, APIOptions +from supertokens_python.recipe.emailpassword.interfaces import ( + APIInterface, + SignUpPostOkResult, + EmailAlreadyExistsError, + SignUpPostNotAllowedResponse, + APIOptions, +) from supertokens_python.recipe.emailpassword.types import FormField from typing import Any, Dict, Union, List from supertokens_python.types import GeneralErrorResponse +from supertokens_python.recipe.session import SessionContainer def override_apis(original_implementation: APIInterface): @@ -131,19 +138,39 @@ def override_apis(original_implementation: APIInterface): # first we copy the original implementation original_sign_up = original_implementation.sign_up_post - async def sign_up(form_fields: List[FormField], tenant_id: str, - api_options: APIOptions, user_context: Dict[str, Any]) -> Union[SignUpPostOkResult, SignUpPostEmailAlreadyExistsError, GeneralErrorResponse]: + async def sign_up( + form_fields: List[FormField], + tenant_id: str, + session: Union[SessionContainer, None], + should_try_linking_with_session_user: Union[bool, None], + api_options: APIOptions, + user_context: Dict[str, Any], + ) -> Union[ + SignUpPostOkResult, + EmailAlreadyExistsError, + SignUpPostNotAllowedResponse, + GeneralErrorResponse, + ]: email = "" for i in range(len(form_fields)): if form_fields[i].id == "email": email = form_fields[i].value - if (is_not_allowed(email)): + if is_not_allowed(email): # highlight-start - return GeneralErrorResponse(message="You are not allowed to sign up. Please contact the app's admin to get permission") + return GeneralErrorResponse( + message="You are not allowed to sign up. Please contact the app's admin to get permission" + ) # highlight-end - return await original_sign_up(form_fields, tenant_id, api_options, user_context) + return await original_sign_up( + form_fields, + tenant_id, + session, + should_try_linking_with_session_user, + api_options, + user_context, + ) original_implementation.sign_up_post = sign_up @@ -155,11 +182,7 @@ def is_not_allowed(email: str): return True -emailpassword.init( - override=emailpassword.InputOverrideConfig( - apis=override_apis - ) -) +emailpassword.init(override=emailpassword.InputOverrideConfig(apis=override_apis)) ``` diff --git a/v2/emailpassword/advanced-customizations/apis-override/custom-response/throwing-error.mdx b/v2/emailpassword/advanced-customizations/apis-override/custom-response/throwing-error.mdx index 9b4162b20..fd453819d 100644 --- a/v2/emailpassword/advanced-customizations/apis-override/custom-response/throwing-error.mdx +++ b/v2/emailpassword/advanced-customizations/apis-override/custom-response/throwing-error.mdx @@ -107,6 +107,7 @@ from supertokens_python.recipe.session.asyncio import get_all_session_handles_fo from supertokens_python.recipe import session from supertokens_python.recipe.session.interfaces import RecipeInterface from typing import Any, Dict, Optional +from supertokens_python.types import RecipeUserId def override_session_functions(original_implementation: RecipeInterface): @@ -114,12 +115,15 @@ def override_session_functions(original_implementation: RecipeInterface): # first we copy the original implementation original_create_new_session = original_implementation.create_new_session - async def create_new_session(user_id: str, - access_token_payload: Optional[Dict[str, Any]], - session_data_in_database: Optional[Dict[str, Any]], - disable_anti_csrf: Optional[bool], - tenant_id: str, - user_context: Dict[str, Any]): + async def create_new_session( + user_id: str, + recipe_user_id: RecipeUserId, + access_token_payload: Optional[Dict[str, Any]], + session_data_in_database: Optional[Dict[str, Any]], + disable_anti_csrf: Optional[bool], + tenant_id: str, + user_context: Dict[str, Any], + ): # highlight-start existing_sessions = await get_all_session_handles_for_user(user_id) @@ -128,7 +132,15 @@ def override_session_functions(original_implementation: RecipeInterface): raise Exception("Session already exists on another device") # no other session exists, and so we can continue with logging in this user - return await original_create_new_session(user_id, access_token_payload, session_data_in_database, disable_anti_csrf, tenant_id, user_context) + return await original_create_new_session( + user_id, + recipe_user_id, + access_token_payload, + session_data_in_database, + disable_anti_csrf, + tenant_id, + user_context, + ) # highlight-end original_implementation.create_new_session = create_new_session @@ -136,11 +148,7 @@ def override_session_functions(original_implementation: RecipeInterface): return original_implementation -session.init( - override=session.InputOverrideConfig( - functions=override_session_functions - ) -) +session.init(override=session.InputOverrideConfig(functions=override_session_functions)) ``` diff --git a/v2/emailpassword/advanced-customizations/apis-override/usage.mdx b/v2/emailpassword/advanced-customizations/apis-override/usage.mdx index 8c6dcb36e..aaf524bea 100644 --- a/v2/emailpassword/advanced-customizations/apis-override/usage.mdx +++ b/v2/emailpassword/advanced-customizations/apis-override/usage.mdx @@ -125,21 +125,38 @@ See all the [functions that can be overrided here](https://supertokens.com/docs/ ```python from supertokens_python import init, InputAppInfo from supertokens_python.recipe import emailpassword -from supertokens_python.recipe.emailpassword.interfaces import APIInterface as EmailPasswordAPIInterface, APIOptions as EPAPIOptions +from supertokens_python.recipe.emailpassword.interfaces import ( + APIInterface as EmailPasswordAPIInterface, + APIOptions, +) from supertokens_python.recipe.emailpassword.types import FormField -from typing import List, Dict, Any +from typing import List, Dict, Any, Union +from supertokens_python.recipe.session import SessionContainer + # highlight-start def override_email_password_apis(original_implementation: EmailPasswordAPIInterface): original_sign_up_post = original_implementation.sign_up_post - async def sign_up_post(form_fields: List[FormField], tenant_id: str, - api_options: EPAPIOptions, - user_context: Dict[str, Any]): + async def sign_up_post( + form_fields: List[FormField], + tenant_id: str, + session: Union[SessionContainer, None], + should_try_linking_with_session_user: Union[bool, None], + api_options: APIOptions, + user_context: Dict[str, Any], + ): # TODO: custom logic # or call the default behaviour as show below - return await original_sign_up_post(form_fields, tenant_id, api_options, user_context) + return await original_sign_up_post( + form_fields, + tenant_id, + session, + should_try_linking_with_session_user, + api_options, + user_context, + ) original_implementation.sign_up_post = sign_up_post return original_implementation @@ -147,10 +164,8 @@ def override_email_password_apis(original_implementation: EmailPasswordAPIInterf init( - app_info=InputAppInfo( - api_domain="...", app_name="...", website_domain="..."), - - framework='...', # type: ignore + app_info=InputAppInfo(api_domain="...", app_name="...", website_domain="..."), + framework="...", # type: ignore recipe_list=[ emailpassword.init( # highlight-start @@ -159,7 +174,7 @@ init( ) # highlight-end ) - ] + ], ) ``` diff --git a/v2/emailpassword/advanced-customizations/backend-functions-override/usage.mdx b/v2/emailpassword/advanced-customizations/backend-functions-override/usage.mdx index 9ee5c25b8..d81852abc 100644 --- a/v2/emailpassword/advanced-customizations/backend-functions-override/usage.mdx +++ b/v2/emailpassword/advanced-customizations/backend-functions-override/usage.mdx @@ -120,27 +120,45 @@ See all the [functions that can be overrided here](https://supertokens.com/docs/ ```python from supertokens_python import init, InputAppInfo from supertokens_python.recipe import emailpassword -from supertokens_python.recipe.emailpassword.interfaces import RecipeInterface as EPInterface -from typing import Dict, Any +from supertokens_python.recipe.emailpassword.interfaces import ( + RecipeInterface as EPInterface, +) +from typing import Dict, Any, Union +from supertokens_python.recipe.session import SessionContainer + # highlight-start def override_email_password_functions(original_implementation: EPInterface): - original_sign_up = original_implementation.sign_up - - async def sign_up(email: str, password: str, tenant_id: str, user_context: Dict[str, Any]): - # TODO: custom logic + original_sign_up = original_implementation.sign_up + + async def sign_up( + email: str, + password: str, + tenant_id: str, + session: Union[SessionContainer, None], + should_try_linking_with_session_user: Union[bool, None], + user_context: Dict[str, Any], + ): + # TODO: custom logic + + # or call the default behaviour as show below + return await original_sign_up( + email, + password, + tenant_id, + session, + should_try_linking_with_session_user, + user_context, + ) - # or call the default behaviour as show below - return await original_sign_up(email, password, tenant_id, user_context) - - original_implementation.sign_up = sign_up - return original_implementation + original_implementation.sign_up = sign_up + return original_implementation # highlight-end init( app_info=InputAppInfo(api_domain="...", app_name="...", website_domain="..."), - framework='...', # type: ignore + framework="...", # type: ignore recipe_list=[ emailpassword.init( # highlight-start @@ -149,7 +167,7 @@ init( ) # highlight-end ) - ] + ], ) ``` diff --git a/v2/passwordless/advanced-customizations/apis-override/custom-response/throwing-error.mdx b/v2/passwordless/advanced-customizations/apis-override/custom-response/throwing-error.mdx index 9b4162b20..fd453819d 100644 --- a/v2/passwordless/advanced-customizations/apis-override/custom-response/throwing-error.mdx +++ b/v2/passwordless/advanced-customizations/apis-override/custom-response/throwing-error.mdx @@ -107,6 +107,7 @@ from supertokens_python.recipe.session.asyncio import get_all_session_handles_fo from supertokens_python.recipe import session from supertokens_python.recipe.session.interfaces import RecipeInterface from typing import Any, Dict, Optional +from supertokens_python.types import RecipeUserId def override_session_functions(original_implementation: RecipeInterface): @@ -114,12 +115,15 @@ def override_session_functions(original_implementation: RecipeInterface): # first we copy the original implementation original_create_new_session = original_implementation.create_new_session - async def create_new_session(user_id: str, - access_token_payload: Optional[Dict[str, Any]], - session_data_in_database: Optional[Dict[str, Any]], - disable_anti_csrf: Optional[bool], - tenant_id: str, - user_context: Dict[str, Any]): + async def create_new_session( + user_id: str, + recipe_user_id: RecipeUserId, + access_token_payload: Optional[Dict[str, Any]], + session_data_in_database: Optional[Dict[str, Any]], + disable_anti_csrf: Optional[bool], + tenant_id: str, + user_context: Dict[str, Any], + ): # highlight-start existing_sessions = await get_all_session_handles_for_user(user_id) @@ -128,7 +132,15 @@ def override_session_functions(original_implementation: RecipeInterface): raise Exception("Session already exists on another device") # no other session exists, and so we can continue with logging in this user - return await original_create_new_session(user_id, access_token_payload, session_data_in_database, disable_anti_csrf, tenant_id, user_context) + return await original_create_new_session( + user_id, + recipe_user_id, + access_token_payload, + session_data_in_database, + disable_anti_csrf, + tenant_id, + user_context, + ) # highlight-end original_implementation.create_new_session = create_new_session @@ -136,11 +148,7 @@ def override_session_functions(original_implementation: RecipeInterface): return original_implementation -session.init( - override=session.InputOverrideConfig( - functions=override_session_functions - ) -) +session.init(override=session.InputOverrideConfig(functions=override_session_functions)) ``` diff --git a/v2/session/advanced-customizations/apis-override/custom-response/throwing-error.mdx b/v2/session/advanced-customizations/apis-override/custom-response/throwing-error.mdx index 9b4162b20..fd453819d 100644 --- a/v2/session/advanced-customizations/apis-override/custom-response/throwing-error.mdx +++ b/v2/session/advanced-customizations/apis-override/custom-response/throwing-error.mdx @@ -107,6 +107,7 @@ from supertokens_python.recipe.session.asyncio import get_all_session_handles_fo from supertokens_python.recipe import session from supertokens_python.recipe.session.interfaces import RecipeInterface from typing import Any, Dict, Optional +from supertokens_python.types import RecipeUserId def override_session_functions(original_implementation: RecipeInterface): @@ -114,12 +115,15 @@ def override_session_functions(original_implementation: RecipeInterface): # first we copy the original implementation original_create_new_session = original_implementation.create_new_session - async def create_new_session(user_id: str, - access_token_payload: Optional[Dict[str, Any]], - session_data_in_database: Optional[Dict[str, Any]], - disable_anti_csrf: Optional[bool], - tenant_id: str, - user_context: Dict[str, Any]): + async def create_new_session( + user_id: str, + recipe_user_id: RecipeUserId, + access_token_payload: Optional[Dict[str, Any]], + session_data_in_database: Optional[Dict[str, Any]], + disable_anti_csrf: Optional[bool], + tenant_id: str, + user_context: Dict[str, Any], + ): # highlight-start existing_sessions = await get_all_session_handles_for_user(user_id) @@ -128,7 +132,15 @@ def override_session_functions(original_implementation: RecipeInterface): raise Exception("Session already exists on another device") # no other session exists, and so we can continue with logging in this user - return await original_create_new_session(user_id, access_token_payload, session_data_in_database, disable_anti_csrf, tenant_id, user_context) + return await original_create_new_session( + user_id, + recipe_user_id, + access_token_payload, + session_data_in_database, + disable_anti_csrf, + tenant_id, + user_context, + ) # highlight-end original_implementation.create_new_session = create_new_session @@ -136,11 +148,7 @@ def override_session_functions(original_implementation: RecipeInterface): return original_implementation -session.init( - override=session.InputOverrideConfig( - functions=override_session_functions - ) -) +session.init(override=session.InputOverrideConfig(functions=override_session_functions)) ``` diff --git a/v2/thirdparty/advanced-customizations/apis-override/custom-response/throwing-error.mdx b/v2/thirdparty/advanced-customizations/apis-override/custom-response/throwing-error.mdx index 9b4162b20..fd453819d 100644 --- a/v2/thirdparty/advanced-customizations/apis-override/custom-response/throwing-error.mdx +++ b/v2/thirdparty/advanced-customizations/apis-override/custom-response/throwing-error.mdx @@ -107,6 +107,7 @@ from supertokens_python.recipe.session.asyncio import get_all_session_handles_fo from supertokens_python.recipe import session from supertokens_python.recipe.session.interfaces import RecipeInterface from typing import Any, Dict, Optional +from supertokens_python.types import RecipeUserId def override_session_functions(original_implementation: RecipeInterface): @@ -114,12 +115,15 @@ def override_session_functions(original_implementation: RecipeInterface): # first we copy the original implementation original_create_new_session = original_implementation.create_new_session - async def create_new_session(user_id: str, - access_token_payload: Optional[Dict[str, Any]], - session_data_in_database: Optional[Dict[str, Any]], - disable_anti_csrf: Optional[bool], - tenant_id: str, - user_context: Dict[str, Any]): + async def create_new_session( + user_id: str, + recipe_user_id: RecipeUserId, + access_token_payload: Optional[Dict[str, Any]], + session_data_in_database: Optional[Dict[str, Any]], + disable_anti_csrf: Optional[bool], + tenant_id: str, + user_context: Dict[str, Any], + ): # highlight-start existing_sessions = await get_all_session_handles_for_user(user_id) @@ -128,7 +132,15 @@ def override_session_functions(original_implementation: RecipeInterface): raise Exception("Session already exists on another device") # no other session exists, and so we can continue with logging in this user - return await original_create_new_session(user_id, access_token_payload, session_data_in_database, disable_anti_csrf, tenant_id, user_context) + return await original_create_new_session( + user_id, + recipe_user_id, + access_token_payload, + session_data_in_database, + disable_anti_csrf, + tenant_id, + user_context, + ) # highlight-end original_implementation.create_new_session = create_new_session @@ -136,11 +148,7 @@ def override_session_functions(original_implementation: RecipeInterface): return original_implementation -session.init( - override=session.InputOverrideConfig( - functions=override_session_functions - ) -) +session.init(override=session.InputOverrideConfig(functions=override_session_functions)) ``` diff --git a/v2/thirdpartyemailpassword/advanced-customizations/apis-override/custom-response/general-error.mdx b/v2/thirdpartyemailpassword/advanced-customizations/apis-override/custom-response/general-error.mdx index 6233bcf6a..940ec4686 100644 --- a/v2/thirdpartyemailpassword/advanced-customizations/apis-override/custom-response/general-error.mdx +++ b/v2/thirdpartyemailpassword/advanced-customizations/apis-override/custom-response/general-error.mdx @@ -120,10 +120,17 @@ func emailNotAllowed(email string) bool { ```python from supertokens_python.recipe import emailpassword -from supertokens_python.recipe.emailpassword.interfaces import APIInterface, SignUpPostOkResult, SignUpPostEmailAlreadyExistsError, APIOptions +from supertokens_python.recipe.emailpassword.interfaces import ( + APIInterface, + SignUpPostOkResult, + EmailAlreadyExistsError, + SignUpPostNotAllowedResponse, + APIOptions, +) from supertokens_python.recipe.emailpassword.types import FormField from typing import Any, Dict, Union, List from supertokens_python.types import GeneralErrorResponse +from supertokens_python.recipe.session import SessionContainer def override_apis(original_implementation: APIInterface): @@ -131,19 +138,39 @@ def override_apis(original_implementation: APIInterface): # first we copy the original implementation original_sign_up = original_implementation.sign_up_post - async def sign_up(form_fields: List[FormField], tenant_id: str, - api_options: APIOptions, user_context: Dict[str, Any]) -> Union[SignUpPostOkResult, SignUpPostEmailAlreadyExistsError, GeneralErrorResponse]: + async def sign_up( + form_fields: List[FormField], + tenant_id: str, + session: Union[SessionContainer, None], + should_try_linking_with_session_user: Union[bool, None], + api_options: APIOptions, + user_context: Dict[str, Any], + ) -> Union[ + SignUpPostOkResult, + EmailAlreadyExistsError, + SignUpPostNotAllowedResponse, + GeneralErrorResponse, + ]: email = "" for i in range(len(form_fields)): if form_fields[i].id == "email": email = form_fields[i].value - if (is_not_allowed(email)): + if is_not_allowed(email): # highlight-start - return GeneralErrorResponse(message="You are not allowed to sign up. Please contact the app's admin to get permission") + return GeneralErrorResponse( + message="You are not allowed to sign up. Please contact the app's admin to get permission" + ) # highlight-end - return await original_sign_up(form_fields, tenant_id, api_options, user_context) + return await original_sign_up( + form_fields, + tenant_id, + session, + should_try_linking_with_session_user, + api_options, + user_context, + ) original_implementation.sign_up_post = sign_up @@ -155,11 +182,7 @@ def is_not_allowed(email: str): return True -emailpassword.init( - override=emailpassword.InputOverrideConfig( - apis=override_apis - ) -) +emailpassword.init(override=emailpassword.InputOverrideConfig(apis=override_apis)) ``` diff --git a/v2/thirdpartyemailpassword/advanced-customizations/apis-override/custom-response/throwing-error.mdx b/v2/thirdpartyemailpassword/advanced-customizations/apis-override/custom-response/throwing-error.mdx index a9f90d9eb..7eef7d4c3 100644 --- a/v2/thirdpartyemailpassword/advanced-customizations/apis-override/custom-response/throwing-error.mdx +++ b/v2/thirdpartyemailpassword/advanced-customizations/apis-override/custom-response/throwing-error.mdx @@ -107,6 +107,7 @@ from supertokens_python.recipe.session.asyncio import get_all_session_handles_fo from supertokens_python.recipe import session from supertokens_python.recipe.session.interfaces import RecipeInterface from typing import Any, Dict, Optional +from supertokens_python.types import RecipeUserId def override_session_functions(original_implementation: RecipeInterface): @@ -114,12 +115,15 @@ def override_session_functions(original_implementation: RecipeInterface): # first we copy the original implementation original_create_new_session = original_implementation.create_new_session - async def create_new_session(user_id: str, - access_token_payload: Optional[Dict[str, Any]], - session_data_in_database: Optional[Dict[str, Any]], - disable_anti_csrf: Optional[bool], - tenant_id: str, - user_context: Dict[str, Any]): + async def create_new_session( + user_id: str, + recipe_user_id: RecipeUserId, + access_token_payload: Optional[Dict[str, Any]], + session_data_in_database: Optional[Dict[str, Any]], + disable_anti_csrf: Optional[bool], + tenant_id: str, + user_context: Dict[str, Any], + ): # highlight-start existing_sessions = await get_all_session_handles_for_user(user_id) @@ -128,7 +132,15 @@ def override_session_functions(original_implementation: RecipeInterface): raise Exception("Session already exists on another device") # no other session exists, and so we can continue with logging in this user - return await original_create_new_session(user_id, access_token_payload, session_data_in_database, disable_anti_csrf, tenant_id, user_context) + return await original_create_new_session( + user_id, + recipe_user_id, + access_token_payload, + session_data_in_database, + disable_anti_csrf, + tenant_id, + user_context, + ) # highlight-end original_implementation.create_new_session = create_new_session @@ -136,11 +148,7 @@ def override_session_functions(original_implementation: RecipeInterface): return original_implementation -session.init( - override=session.InputOverrideConfig( - functions=override_session_functions - ) -) +session.init(override=session.InputOverrideConfig(functions=override_session_functions)) ``` diff --git a/v2/thirdpartyemailpassword/advanced-customizations/apis-override/usage.mdx b/v2/thirdpartyemailpassword/advanced-customizations/apis-override/usage.mdx index 25fbf47f6..4e0ccb610 100644 --- a/v2/thirdpartyemailpassword/advanced-customizations/apis-override/usage.mdx +++ b/v2/thirdpartyemailpassword/advanced-customizations/apis-override/usage.mdx @@ -125,21 +125,38 @@ See all the [functions that can be overrided here](https://supertokens.com/docs/ ```python from supertokens_python import init, InputAppInfo from supertokens_python.recipe import emailpassword -from supertokens_python.recipe.emailpassword.interfaces import APIInterface as EmailPasswordAPIInterface, APIOptions as EPAPIOptions +from supertokens_python.recipe.emailpassword.interfaces import ( + APIInterface as EmailPasswordAPIInterface, + APIOptions, +) from supertokens_python.recipe.emailpassword.types import FormField -from typing import List, Dict, Any +from typing import List, Dict, Any, Union +from supertokens_python.recipe.session import SessionContainer + # highlight-start def override_email_password_apis(original_implementation: EmailPasswordAPIInterface): original_sign_up_post = original_implementation.sign_up_post - async def sign_up_post(form_fields: List[FormField], tenant_id: str, - api_options: EPAPIOptions, - user_context: Dict[str, Any]): + async def sign_up_post( + form_fields: List[FormField], + tenant_id: str, + session: Union[SessionContainer, None], + should_try_linking_with_session_user: Union[bool, None], + api_options: APIOptions, + user_context: Dict[str, Any], + ): # TODO: custom logic # or call the default behaviour as show below - return await original_sign_up_post(form_fields, tenant_id, api_options, user_context) + return await original_sign_up_post( + form_fields, + tenant_id, + session, + should_try_linking_with_session_user, + api_options, + user_context, + ) original_implementation.sign_up_post = sign_up_post return original_implementation @@ -147,10 +164,8 @@ def override_email_password_apis(original_implementation: EmailPasswordAPIInterf init( - app_info=InputAppInfo( - api_domain="...", app_name="...", website_domain="..."), - - framework='...', # type: ignore + app_info=InputAppInfo(api_domain="...", app_name="...", website_domain="..."), + framework="...", # type: ignore recipe_list=[ emailpassword.init( # highlight-start @@ -159,7 +174,7 @@ init( ) # highlight-end ) - ] + ], ) ``` diff --git a/v2/thirdpartypasswordless/advanced-customizations/apis-override/custom-response/throwing-error.mdx b/v2/thirdpartypasswordless/advanced-customizations/apis-override/custom-response/throwing-error.mdx index 9b4162b20..fd453819d 100644 --- a/v2/thirdpartypasswordless/advanced-customizations/apis-override/custom-response/throwing-error.mdx +++ b/v2/thirdpartypasswordless/advanced-customizations/apis-override/custom-response/throwing-error.mdx @@ -107,6 +107,7 @@ from supertokens_python.recipe.session.asyncio import get_all_session_handles_fo from supertokens_python.recipe import session from supertokens_python.recipe.session.interfaces import RecipeInterface from typing import Any, Dict, Optional +from supertokens_python.types import RecipeUserId def override_session_functions(original_implementation: RecipeInterface): @@ -114,12 +115,15 @@ def override_session_functions(original_implementation: RecipeInterface): # first we copy the original implementation original_create_new_session = original_implementation.create_new_session - async def create_new_session(user_id: str, - access_token_payload: Optional[Dict[str, Any]], - session_data_in_database: Optional[Dict[str, Any]], - disable_anti_csrf: Optional[bool], - tenant_id: str, - user_context: Dict[str, Any]): + async def create_new_session( + user_id: str, + recipe_user_id: RecipeUserId, + access_token_payload: Optional[Dict[str, Any]], + session_data_in_database: Optional[Dict[str, Any]], + disable_anti_csrf: Optional[bool], + tenant_id: str, + user_context: Dict[str, Any], + ): # highlight-start existing_sessions = await get_all_session_handles_for_user(user_id) @@ -128,7 +132,15 @@ def override_session_functions(original_implementation: RecipeInterface): raise Exception("Session already exists on another device") # no other session exists, and so we can continue with logging in this user - return await original_create_new_session(user_id, access_token_payload, session_data_in_database, disable_anti_csrf, tenant_id, user_context) + return await original_create_new_session( + user_id, + recipe_user_id, + access_token_payload, + session_data_in_database, + disable_anti_csrf, + tenant_id, + user_context, + ) # highlight-end original_implementation.create_new_session = create_new_session @@ -136,11 +148,7 @@ def override_session_functions(original_implementation: RecipeInterface): return original_implementation -session.init( - override=session.InputOverrideConfig( - functions=override_session_functions - ) -) +session.init(override=session.InputOverrideConfig(functions=override_session_functions)) ``` From b202555d9c47364cc473d3a98315fee3d672318c Mon Sep 17 00:00:00 2001 From: rishabhpoddar Date: Wed, 16 Oct 2024 14:41:22 +0530 Subject: [PATCH 03/34] more changes --- .../advanced-customizations/user-context.mdx | 140 +++++++++++++----- .../custom-request-properties.mdx | 4 +- .../custom-request-properties.mdx | 4 +- .../custom-request-properties.mdx | 4 +- .../custom-request-properties.mdx | 4 +- .../custom-request-properties.mdx | 4 +- .../custom-request-properties.mdx | 4 +- 7 files changed, 113 insertions(+), 51 deletions(-) diff --git a/v2/emailpassword/advanced-customizations/user-context.mdx b/v2/emailpassword/advanced-customizations/user-context.mdx index b7d637fb1..4218da9dc 100644 --- a/v2/emailpassword/advanced-customizations/user-context.mdx +++ b/v2/emailpassword/advanced-customizations/user-context.mdx @@ -120,16 +120,22 @@ from supertokens_python import init, InputAppInfo from supertokens_python.recipe import emailpassword from supertokens_python.recipe.emailpassword.interfaces import APIInterface, APIOptions from supertokens_python.recipe.emailpassword.types import FormField -from typing import List, Dict, Any - -# highlight-start +from typing import List, Dict, Any, Union +from supertokens_python.recipe.session import SessionContainer +# highlight-start def override_emailpassword_apis(original_implementation: APIInterface): original_sign_up_post = original_implementation.sign_up_post - async def sign_up_post(form_fields: List[FormField], tenant_id: str, - api_options: APIOptions, user_context: Dict[str, Any]): + async def sign_up_post( + form_fields: List[FormField], + tenant_id: str, + session: Union[SessionContainer, None], + should_try_linking_with_session_user: Union[bool, None], + api_options: APIOptions, + user_context: Dict[str, Any], + ): # by default, the userContext Dict is {}, # we change it to {isSignUp: true}, since this is the @@ -137,7 +143,14 @@ def override_emailpassword_apis(original_implementation: APIInterface): # (being called inside original_sign_up_post) # to not create a new session in case userContext["isSignUp"] is True user_context["isSignUp"] = True - return await original_sign_up_post(form_fields, tenant_id, api_options, user_context) + return await original_sign_up_post( + form_fields, + tenant_id, + session, + should_try_linking_with_session_user, + api_options, + user_context, + ) original_implementation.sign_up_post = sign_up_post return original_implementation @@ -145,16 +158,13 @@ def override_emailpassword_apis(original_implementation: APIInterface): init( - app_info=InputAppInfo( - api_domain="...", app_name="...", website_domain="..."), - framework='...', # type: ignore + app_info=InputAppInfo(api_domain="...", app_name="...", website_domain="..."), + framework="...", # type: ignore recipe_list=[ emailpassword.init( - override=emailpassword.InputOverrideConfig( - apis=override_emailpassword_apis - ) + override=emailpassword.InputOverrideConfig(apis=override_emailpassword_apis) ) - ] + ], ) ``` @@ -326,24 +336,34 @@ func main() { from supertokens_python import init, InputAppInfo from supertokens_python.framework import BaseRequest from supertokens_python.recipe import emailpassword -from supertokens_python.recipe.session.interfaces import GetSessionTokensDangerouslyDict, RecipeInterface, SessionClaimValidator, SessionClaim +from supertokens_python.recipe.session.interfaces import ( + GetSessionTokensDangerouslyDict, + RecipeInterface, + SessionClaimValidator, + SessionClaim, +) from supertokens_python.recipe.session.recipe_implementation import RecipeImplementation from typing import Dict, Any, Union, List, TypeVar, Optional from supertokens_python.recipe import session from supertokens_python.recipe.session.utils import TokenTransferMethod +from supertokens_python.types import RecipeUserId _T = TypeVar("_T") + # highlight-start def override_session_functions(original_implementation: RecipeInterface): original_create_new_session = original_implementation.create_new_session - async def create_new_session(user_id: str, - access_token_payload: Optional[Dict[str, Any]], - session_data_in_database: Optional[Dict[str, Any]], - disable_anti_csrf: Optional[bool], - tenant_id: str, - user_context: Dict[str, Any]): + async def create_new_session( + user_id: str, + recipe_user_id: RecipeUserId, + access_token_payload: Optional[Dict[str, Any]], + session_data_in_database: Optional[Dict[str, Any]], + disable_anti_csrf: Optional[bool], + tenant_id: str, + user_context: Dict[str, Any], + ): if user_context["isSignUp"] is True: # The execution will come here only in case # a sign up API is calling this function. This is because @@ -351,49 +371,82 @@ def override_session_functions(original_implementation: RecipeInterface): # (see above code). return EmptySession(original_implementation) - return await original_create_new_session(user_id, access_token_payload, session_data_in_database, disable_anti_csrf, tenant_id, user_context) + return await original_create_new_session( + user_id, + recipe_user_id, + access_token_payload, + session_data_in_database, + disable_anti_csrf, + tenant_id, + user_context, + ) + original_implementation.create_new_session = create_new_session return original_implementation + + # highlight-end init( - app_info=InputAppInfo( - api_domain="...", app_name="...", website_domain="..."), - framework='...', # type: ignore + app_info=InputAppInfo(api_domain="...", app_name="...", website_domain="..."), + framework="...", # type: ignore recipe_list=[ emailpassword.init( # see previous step... ), session.init( - override=session.InputOverrideConfig( - functions=override_session_functions - ) - ) - ] + override=session.InputOverrideConfig(functions=override_session_functions) + ), + ], ) class EmptySession(session.SessionContainer): def __init__(self, recipe_implementation: RecipeInterface): assert isinstance(recipe_implementation, RecipeImplementation) - super().__init__(recipe_implementation, - recipe_implementation.config, "", "", None, "", "", "", {}, None, False, "") + super().__init__( + recipe_implementation, + recipe_implementation.config, + "", + "", + None, + "", + "", + "", + RecipeUserId(""), + {}, + None, + False, + "", + ) async def revoke_session(self, user_context: Union[Any, None] = None) -> None: pass - async def get_session_data_from_database(self, user_context: Union[Dict[str, Any], None] = None) -> Dict[str, Any]: + async def get_session_data_from_database( + self, user_context: Union[Dict[str, Any], None] = None + ) -> Dict[str, Any]: return {} - async def update_session_data_in_database(self, new_session_data: Dict[str, Any], user_context: Union[Dict[str, Any], None] = None) -> None: + async def update_session_data_in_database( + self, + new_session_data: Dict[str, Any], + user_context: Union[Dict[str, Any], None] = None, + ) -> None: pass def get_user_id(self, user_context: Union[Dict[str, Any], None] = None) -> str: return "" + def get_recipe_user_id( + self, user_context: Optional[Dict[str, Any]] = None + ) -> RecipeUserId: + return RecipeUserId("") + def get_access_token_payload( - self, user_context: Union[Dict[str, Any], None] = None) -> Dict[str, Any]: + self, user_context: Union[Dict[str, Any], None] = None + ) -> Dict[str, Any]: return {} def get_handle(self, user_context: Union[Dict[str, Any], None] = None) -> str: @@ -402,13 +455,20 @@ class EmptySession(session.SessionContainer): def get_access_token(self, user_context: Union[Dict[str, Any], None] = None) -> str: return "" - async def get_time_created(self, user_context: Union[Dict[str, Any], None] = None) -> int: + async def get_time_created( + self, user_context: Union[Dict[str, Any], None] = None + ) -> int: return -1 async def get_expiry(self, user_context: Union[Dict[str, Any], None] = None) -> int: return -1 - async def attach_to_request_response(self, request: BaseRequest, transfer_method: TokenTransferMethod, user_context: Union[Dict[str, Any], None] = None): + async def attach_to_request_response( + self, + request: BaseRequest, + transfer_method: TokenTransferMethod, + user_context: Union[Dict[str, Any], None] = None, + ): pass async def assert_claims( @@ -444,7 +504,9 @@ class EmptySession(session.SessionContainer): pass async def merge_into_access_token_payload( - self, access_token_payload_update: Dict[str, Any], user_context: Union[Dict[str, Any], None] = None + self, + access_token_payload_update: Dict[str, Any], + user_context: Union[Dict[str, Any], None] = None, ) -> None: pass @@ -454,9 +516,9 @@ class EmptySession(session.SessionContainer): "accessToken": "", "antiCsrfToken": None, "frontToken": "", - "refreshToken": None + "refreshToken": None, } - + def get_tenant_id(self, user_context: Union[Dict[str, Any], None] = None) -> str: return "" ``` diff --git a/v2/emailpassword/advanced-customizations/user-context/custom-request-properties.mdx b/v2/emailpassword/advanced-customizations/user-context/custom-request-properties.mdx index fd5f26797..2f9e0ee89 100644 --- a/v2/emailpassword/advanced-customizations/user-context/custom-request-properties.mdx +++ b/v2/emailpassword/advanced-customizations/user-context/custom-request-properties.mdx @@ -224,7 +224,7 @@ We use the `get_request_from_user_context` function provided by the SDK to get t from supertokens_python import get_request_from_user_context from supertokens_python.recipe import session from supertokens_python.recipe.session.interfaces import RecipeInterface, APIInterface, APIOptions -from typing import Any, Dict, Optional +from typing import Any, Dict def override_session_functions(original_implementation: RecipeInterface): original_revoke_session = original_implementation.revoke_session @@ -256,7 +256,7 @@ def override_session_functions(original_implementation: RecipeInterface): def override_session_apis(original_implementation: APIInterface): original_signout_post = original_implementation.signout_post - async def signout_post(session: Optional[session.SessionContainer], api_options: APIOptions, user_context: Dict[str, Any]): + async def signout_post(session: session.SessionContainer, api_options: APIOptions, user_context: Dict[str, Any]): # highlight-start request=get_request_from_user_context(user_context) customHeaderValue="" diff --git a/v2/passwordless/advanced-customizations/user-context/custom-request-properties.mdx b/v2/passwordless/advanced-customizations/user-context/custom-request-properties.mdx index d72e523b4..1b918d432 100644 --- a/v2/passwordless/advanced-customizations/user-context/custom-request-properties.mdx +++ b/v2/passwordless/advanced-customizations/user-context/custom-request-properties.mdx @@ -224,7 +224,7 @@ We use the `get_request_from_user_context` function provided by the SDK to get t from supertokens_python import get_request_from_user_context from supertokens_python.recipe import session from supertokens_python.recipe.session.interfaces import RecipeInterface, APIInterface, APIOptions -from typing import Any, Dict, Optional +from typing import Any, Dict def override_session_functions(original_implementation: RecipeInterface): original_revoke_session = original_implementation.revoke_session @@ -256,7 +256,7 @@ def override_session_functions(original_implementation: RecipeInterface): def override_session_apis(original_implementation: APIInterface): original_signout_post = original_implementation.signout_post - async def signout_post(session: Optional[session.SessionContainer], api_options: APIOptions, user_context: Dict[str, Any]): + async def signout_post(session: session.SessionContainer, api_options: APIOptions, user_context: Dict[str, Any]): # highlight-start request=get_request_from_user_context(user_context) customHeaderValue="" diff --git a/v2/session/advanced-customizations/user-context/custom-request-properties.mdx b/v2/session/advanced-customizations/user-context/custom-request-properties.mdx index d72e523b4..1b918d432 100644 --- a/v2/session/advanced-customizations/user-context/custom-request-properties.mdx +++ b/v2/session/advanced-customizations/user-context/custom-request-properties.mdx @@ -224,7 +224,7 @@ We use the `get_request_from_user_context` function provided by the SDK to get t from supertokens_python import get_request_from_user_context from supertokens_python.recipe import session from supertokens_python.recipe.session.interfaces import RecipeInterface, APIInterface, APIOptions -from typing import Any, Dict, Optional +from typing import Any, Dict def override_session_functions(original_implementation: RecipeInterface): original_revoke_session = original_implementation.revoke_session @@ -256,7 +256,7 @@ def override_session_functions(original_implementation: RecipeInterface): def override_session_apis(original_implementation: APIInterface): original_signout_post = original_implementation.signout_post - async def signout_post(session: Optional[session.SessionContainer], api_options: APIOptions, user_context: Dict[str, Any]): + async def signout_post(session: session.SessionContainer, api_options: APIOptions, user_context: Dict[str, Any]): # highlight-start request=get_request_from_user_context(user_context) customHeaderValue="" diff --git a/v2/thirdparty/advanced-customizations/user-context/custom-request-properties.mdx b/v2/thirdparty/advanced-customizations/user-context/custom-request-properties.mdx index d72e523b4..1b918d432 100644 --- a/v2/thirdparty/advanced-customizations/user-context/custom-request-properties.mdx +++ b/v2/thirdparty/advanced-customizations/user-context/custom-request-properties.mdx @@ -224,7 +224,7 @@ We use the `get_request_from_user_context` function provided by the SDK to get t from supertokens_python import get_request_from_user_context from supertokens_python.recipe import session from supertokens_python.recipe.session.interfaces import RecipeInterface, APIInterface, APIOptions -from typing import Any, Dict, Optional +from typing import Any, Dict def override_session_functions(original_implementation: RecipeInterface): original_revoke_session = original_implementation.revoke_session @@ -256,7 +256,7 @@ def override_session_functions(original_implementation: RecipeInterface): def override_session_apis(original_implementation: APIInterface): original_signout_post = original_implementation.signout_post - async def signout_post(session: Optional[session.SessionContainer], api_options: APIOptions, user_context: Dict[str, Any]): + async def signout_post(session: session.SessionContainer, api_options: APIOptions, user_context: Dict[str, Any]): # highlight-start request=get_request_from_user_context(user_context) customHeaderValue="" diff --git a/v2/thirdpartyemailpassword/advanced-customizations/user-context/custom-request-properties.mdx b/v2/thirdpartyemailpassword/advanced-customizations/user-context/custom-request-properties.mdx index d72e523b4..1b918d432 100644 --- a/v2/thirdpartyemailpassword/advanced-customizations/user-context/custom-request-properties.mdx +++ b/v2/thirdpartyemailpassword/advanced-customizations/user-context/custom-request-properties.mdx @@ -224,7 +224,7 @@ We use the `get_request_from_user_context` function provided by the SDK to get t from supertokens_python import get_request_from_user_context from supertokens_python.recipe import session from supertokens_python.recipe.session.interfaces import RecipeInterface, APIInterface, APIOptions -from typing import Any, Dict, Optional +from typing import Any, Dict def override_session_functions(original_implementation: RecipeInterface): original_revoke_session = original_implementation.revoke_session @@ -256,7 +256,7 @@ def override_session_functions(original_implementation: RecipeInterface): def override_session_apis(original_implementation: APIInterface): original_signout_post = original_implementation.signout_post - async def signout_post(session: Optional[session.SessionContainer], api_options: APIOptions, user_context: Dict[str, Any]): + async def signout_post(session: session.SessionContainer, api_options: APIOptions, user_context: Dict[str, Any]): # highlight-start request=get_request_from_user_context(user_context) customHeaderValue="" diff --git a/v2/thirdpartypasswordless/advanced-customizations/user-context/custom-request-properties.mdx b/v2/thirdpartypasswordless/advanced-customizations/user-context/custom-request-properties.mdx index d72e523b4..1b918d432 100644 --- a/v2/thirdpartypasswordless/advanced-customizations/user-context/custom-request-properties.mdx +++ b/v2/thirdpartypasswordless/advanced-customizations/user-context/custom-request-properties.mdx @@ -224,7 +224,7 @@ We use the `get_request_from_user_context` function provided by the SDK to get t from supertokens_python import get_request_from_user_context from supertokens_python.recipe import session from supertokens_python.recipe.session.interfaces import RecipeInterface, APIInterface, APIOptions -from typing import Any, Dict, Optional +from typing import Any, Dict def override_session_functions(original_implementation: RecipeInterface): original_revoke_session = original_implementation.revoke_session @@ -256,7 +256,7 @@ def override_session_functions(original_implementation: RecipeInterface): def override_session_apis(original_implementation: APIInterface): original_signout_post = original_implementation.signout_post - async def signout_post(session: Optional[session.SessionContainer], api_options: APIOptions, user_context: Dict[str, Any]): + async def signout_post(session: session.SessionContainer, api_options: APIOptions, user_context: Dict[str, Any]): # highlight-start request=get_request_from_user_context(user_context) customHeaderValue="" From c97b871577a52ef74a4e7a622875084345f230d8 Mon Sep 17 00:00:00 2001 From: rishabhpoddar Date: Wed, 16 Oct 2024 15:05:10 +0530 Subject: [PATCH 04/34] more changes --- .../advanced-customizations/user-context.mdx | 47 +++++++++++-------- 1 file changed, 28 insertions(+), 19 deletions(-) diff --git a/v2/emailpassword/advanced-customizations/user-context.mdx b/v2/emailpassword/advanced-customizations/user-context.mdx index 4218da9dc..22db58e41 100644 --- a/v2/emailpassword/advanced-customizations/user-context.mdx +++ b/v2/emailpassword/advanced-customizations/user-context.mdx @@ -118,41 +118,48 @@ func main() { ```python from supertokens_python import init, InputAppInfo from supertokens_python.recipe import emailpassword -from supertokens_python.recipe.emailpassword.interfaces import APIInterface, APIOptions -from supertokens_python.recipe.emailpassword.types import FormField -from typing import List, Dict, Any, Union +from supertokens_python.recipe.emailpassword.interfaces import ( + RecipeInterface, + SignUpOkResult, +) +from typing import Dict, Any, Union from supertokens_python.recipe.session import SessionContainer # highlight-start -def override_emailpassword_apis(original_implementation: APIInterface): - original_sign_up_post = original_implementation.sign_up_post +def override_emailpassword_functions(original_implementation: RecipeInterface): + original_sign_up = original_implementation.sign_up - async def sign_up_post( - form_fields: List[FormField], + async def sign_up( + email: str, + password: str, tenant_id: str, session: Union[SessionContainer, None], should_try_linking_with_session_user: Union[bool, None], - api_options: APIOptions, user_context: Dict[str, Any], ): - - # by default, the userContext Dict is {}, - # we change it to {isSignUp: true}, since this is the - # sign up API, and this will tell the create_new_session function - # (being called inside original_sign_up_post) - # to not create a new session in case userContext["isSignUp"] is True user_context["isSignUp"] = True - return await original_sign_up_post( - form_fields, + resp = await original_sign_up( + email, + password, tenant_id, session, should_try_linking_with_session_user, - api_options, user_context, ) - original_implementation.sign_up_post = sign_up_post + if isinstance(resp, SignUpOkResult) and len(resp.user.login_methods) == 1: + # This is called during the sign up API for email password login, + # but before calling the createNewSession function. + # We override the recipe function as shown here, + # and then set the relevant context only if it's a new user. + # The user_context["isSignUp"] = True is set earlier in the function, + # so we don't need to set it again here. + user_context["isSignUp"] = True + + return resp + + original_implementation.sign_up = sign_up return original_implementation # highlight-end @@ -162,7 +169,9 @@ init( framework="...", # type: ignore recipe_list=[ emailpassword.init( - override=emailpassword.InputOverrideConfig(apis=override_emailpassword_apis) + override=emailpassword.InputOverrideConfig( + functions=override_emailpassword_functions + ) ) ], ) From a34b6af27487c6a96d9cb1faa5d655ea8b945b26 Mon Sep 17 00:00:00 2001 From: rishabhpoddar Date: Wed, 16 Oct 2024 16:36:48 +0530 Subject: [PATCH 05/34] more changes --- .../change-email-post-login.mdx | 148 +++++++++++------- .../common-customizations/change-password.mdx | 56 +++++-- .../change-email-post-login.mdx | 148 +++++++++++------- .../common-customizations/change-password.mdx | 56 +++++-- 4 files changed, 266 insertions(+), 142 deletions(-) diff --git a/v2/emailpassword/common-customizations/change-email-post-login.mdx b/v2/emailpassword/common-customizations/change-email-post-login.mdx index e9de06dd0..f640648de 100644 --- a/v2/emailpassword/common-customizations/change-email-post-login.mdx +++ b/v2/emailpassword/common-customizations/change-email-post-login.mdx @@ -245,13 +245,18 @@ func isValidEmail(email string) bool { from supertokens_python.recipe.session.framework.flask import verify_session from supertokens_python.recipe.session import SessionContainer from supertokens_python.recipe.emailpassword.syncio import update_email_or_password -from supertokens_python.recipe.emailpassword.interfaces import UpdateEmailOrPasswordOkResult, UpdateEmailOrPasswordEmailAlreadyExistsError +from supertokens_python.recipe.emailpassword.interfaces import ( + UpdateEmailOrPasswordOkResult, + UpdateEmailOrPasswordEmailChangeNotAllowedError, + EmailAlreadyExistsError, +) from flask import g, request, Flask from re import fullmatch app = Flask(__name__) -@app.route('/change-email', methods=['POST']) # type: ignore + +@app.route("/change-email", methods=["POST"]) # type: ignore @verify_session() def change_email(): # highlight-start @@ -259,7 +264,7 @@ def change_email(): request_body = request.get_json() - email = str(request_body["email"]) # type: ignore + email = str(request_body["email"]) # type: ignore if request_body is None: # handle invalid request body error return @@ -270,22 +275,30 @@ def change_email(): return # update the users email - user_id = session.get_user_id() - update_response = update_email_or_password(user_id, email=email) + update_response = update_email_or_password( + session.get_recipe_user_id(), email=email + ) if isinstance(update_response, UpdateEmailOrPasswordOkResult): # TODO send successful email update response return - if isinstance(update_response, UpdateEmailOrPasswordEmailAlreadyExistsError): + if isinstance(update_response, EmailAlreadyExistsError): # TODO handle error, email already exists return + if isinstance(update_response, UpdateEmailOrPasswordEmailChangeNotAllowedError): + # This is possible if you have enabled account linking. + # See our docs for account linking to know more about this. + # TODO: tell the user to contact support. + return + raise Exception("Should never reach here") # highlight-end -async def is_valid_email(value: str) -> bool: + +def is_valid_email(value: str) -> bool: return ( fullmatch( r'^(([^<>()\[\]\\.,;:\s@"]+(\.[^<>()\[\]\\.,;:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,' @@ -294,7 +307,6 @@ async def is_valid_email(value: str) -> bool: ) is not None ) - ``` @@ -615,31 +627,32 @@ func isValidEmail(email string) bool { ```python from supertokens_python.recipe.session.framework.flask import verify_session from supertokens_python.recipe.session import SessionContainer -from supertokens_python.recipe.emailpassword.syncio import ( +from supertokens_python.recipe.emailpassword.syncio import ( update_email_or_password, - get_user_by_id, - get_user_by_email, ) from supertokens_python.recipe.emailpassword.interfaces import ( UpdateEmailOrPasswordOkResult, - UpdateEmailOrPasswordEmailAlreadyExistsError, + EmailAlreadyExistsError, ) - +from supertokens_python.syncio import get_user, list_users_by_account_info +from supertokens_python.types import AccountInfo from supertokens_python.recipe.emailverification.syncio import ( is_email_verified, - send_email_verification_email, + send_email_verification_email, ) from flask import g, request, Flask from re import fullmatch +from supertokens_python.recipe.accountlinking.syncio import is_email_change_allowed app = Flask(__name__) -@app.route('/change-email', methods=['POST']) # type: ignore + +@app.route("/change-email", methods=["POST"]) # type: ignore @verify_session() def change_email(): # highlight-start - session: SessionContainer = g.supertokens # type: ignore + session: SessionContainer = g.supertokens # type: ignore request_body = request.get_json() @@ -657,35 +670,51 @@ def change_email(): # Then, we check if the email is verified for this user ID or not. # It is important to understand that SuperTokens stores email verification # status based on the user ID AND the email, and not just the email. - is_verified = is_email_verified(user_id, request_body["email"]) - + is_verified = is_email_verified(session.get_recipe_user_id(), request_body["email"]) + if not is_verified: + if not is_email_change_allowed( + session.get_recipe_user_id(), request_body["email"], False + ): + # Email change is not allowed, send a 400 error + return {"error": "Email change not allowed"}, 400 # Before sending a verification email, we check if the email is already # being used by another user. If it is, we throw an error. - user = get_user_by_id(user_id) + user = get_user(user_id) if user is not None: for tenant_id in user.tenant_ids: - user_with_email = get_user_by_email(tenant_id, request_body["email"]) - - if user_with_email is not None and user_with_email.user_id != user_id: - # TODO handle error, email already exists with another user. - return + users_with_same_email = list_users_by_account_info( + tenant_id, AccountInfo(email=request_body["email"]) + ) + for curr_user in users_with_same_email: + # Since one user can be shared across many tenants, we need to check if + # the email already exists in any of the tenants that belongs to this user. + if curr_user.id != user_id: + # TODO handle error, email already exists with another user. + return # Create and send the email verification link to the user for the new email. - send_email_verification_email(session.get_tenant_id(), user_id, request_body["email"]) - + send_email_verification_email( + session.get_tenant_id(), + user_id, + session.get_recipe_user_id(), + request_body["email"], + ) + # TODO send successful email verification response return - + # update the users email - update_response = update_email_or_password(user_id, email=request_body["email"]) + update_response = update_email_or_password( + session.get_recipe_user_id(), email=request_body["email"] + ) if isinstance(update_response, UpdateEmailOrPasswordOkResult): # TODO send successful email update response return - if isinstance(update_response, UpdateEmailOrPasswordEmailAlreadyExistsError): + if isinstance(update_response, EmailAlreadyExistsError): # TODO handle error, email already exists return @@ -694,7 +723,7 @@ def change_email(): raise Exception("Should never reach here") -async def is_valid_email(value: str) -> bool: +def is_valid_email(value: str) -> bool: return ( fullmatch( r'^(([^<>()\[\]\\.,;:\s@"]+(\.[^<>()\[\]\\.,;:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,' @@ -834,25 +863,20 @@ func test() { ```python - from supertokens_python import init, InputAppInfo, SupertokensConfig from supertokens_python.recipe import emailpassword, emailverification from supertokens_python.recipe.emailverification.interfaces import ( APIInterface, - APIOptions + APIOptions, ) -from supertokens_python.recipe.emailpassword.asyncio import ( - update_email_or_password -) +from supertokens_python.recipe.emailpassword.asyncio import update_email_or_password from supertokens_python.recipe.emailverification.interfaces import ( - EmailVerifyPostOkResult + EmailVerifyPostOkResult, ) -from supertokens_python.recipe.session.interfaces import ( - SessionContainer -) +from supertokens_python.recipe.session.interfaces import SessionContainer from supertokens_python import ( InputAppInfo, SupertokensConfig, @@ -862,41 +886,51 @@ from typing import Optional, Dict, Any def override_email_verification_apis(original_implementation: APIInterface): - original_email_verification_verify_email_post = original_implementation.email_verify_post - - async def email_verify_post(token: str, session: Optional[SessionContainer], tenant_id: str, api_options: APIOptions, user_context: Dict[str, Any] ): + original_email_verification_verify_email_post = ( + original_implementation.email_verify_post + ) + + async def email_verify_post( + token: str, + session: Optional[SessionContainer], + tenant_id: str, + api_options: APIOptions, + user_context: Dict[str, Any], + ): # highlight-start - verification_response = await original_email_verification_verify_email_post(token, session, tenant_id, api_options, user_context) - + verification_response = await original_email_verification_verify_email_post( + token, session, tenant_id, api_options, user_context + ) + if isinstance(verification_response, EmailVerifyPostOkResult): - await update_email_or_password(verification_response.user.user_id, verification_response.user.email) - + await update_email_or_password( + verification_response.user.recipe_user_id, + verification_response.user.email, + ) + return verification_response # highlight-end - + original_implementation.email_verify_post = email_verify_post return original_implementation init( - supertokens_config=SupertokensConfig( - connection_uri="..." - ), + supertokens_config=SupertokensConfig(connection_uri="..."), app_info=InputAppInfo(api_domain="...", app_name="...", website_domain="..."), framework="flask", recipe_list=[ emailpassword.init(), emailverification.init( - "REQUIRED", - override=emailverification.InputOverrideConfig( - # highlight-start - apis=override_email_verification_apis - # highlight-end + "REQUIRED", + override=emailverification.InputOverrideConfig( + # highlight-start + apis=override_email_verification_apis + # highlight-end + ), ), - ), ], ) - ``` diff --git a/v2/emailpassword/common-customizations/change-password.mdx b/v2/emailpassword/common-customizations/change-password.mdx index c6c3afd01..6bf94c82f 100644 --- a/v2/emailpassword/common-customizations/change-password.mdx +++ b/v2/emailpassword/common-customizations/change-password.mdx @@ -251,29 +251,51 @@ func changePasswordAPI(w http.ResponseWriter, r *http.Request) { ```python from supertokens_python.recipe.session.framework.flask import verify_session from supertokens_python.recipe.session import SessionContainer -from supertokens_python.recipe.emailpassword.syncio import get_user_by_id, sign_in, update_email_or_password -from supertokens_python.recipe.emailpassword.interfaces import SignInWrongCredentialsError, UpdateEmailOrPasswordPasswordPolicyViolationError +from supertokens_python.recipe.emailpassword.syncio import ( + verify_credentials, + update_email_or_password, +) +from supertokens_python.syncio import get_user +from supertokens_python.recipe.emailpassword.interfaces import ( + WrongCredentialsError, + PasswordPolicyViolationError, +) from flask import g, request -@app.route('/change-password', methods=['POST']) # type: ignore + +@app.route("/change-password", methods=["POST"]) # type: ignore @verify_session() def change_password(): # highlight-start session: SessionContainer = g.supertokens # type: ignore - # get the userId from the session object - user_id = session.get_user_id() # get the signed in user's email from the getUserById function - users_info = get_user_by_id(user_id) - - oldPassword = str(request.json["oldPassword"]) # type: ignore - newPassword = str(request.json["newPassword"]) # type: ignore + users_info = get_user(session.get_user_id()) if users_info is None: raise Exception("TODO: Handle error. User not found.") - + + # Find the login method for the current user + login_method = next( + ( + lm + for lm in users_info.login_methods + if lm.recipe_user_id.get_as_string() == session.get_recipe_user_id() + and lm.recipe_id == "emailpassword" + ), + None, + ) + + if login_method is None: + raise Exception("Should never come here") + + email = login_method.email + + if email is None: + raise Exception("Email not found for the user") + request_body = request.get_json() if request_body is None: @@ -281,16 +303,22 @@ def change_password(): return # call signin to check that the input password is correct - isPasswordValid = sign_in("public", users_info.email, password=request_body["oldPassword"]) + isPasswordValid = verify_credentials( + "public", email, password=request_body["oldPassword"] + ) - if isinstance(isPasswordValid, SignInWrongCredentialsError): + if isinstance(isPasswordValid, WrongCredentialsError): # TODO: handle incorrect password error return # update the users password - update_response = update_email_or_password(user_id, password=request_body["newPassword"], tenant_id_for_password_policy=session.get_tenant_id()) + update_response = update_email_or_password( + session.get_recipe_user_id(), + password=request_body["newPassword"], + tenant_id_for_password_policy=session.get_tenant_id(), + ) - if isinstance(update_response, UpdateEmailOrPasswordPasswordPolicyViolationError): + if isinstance(update_response, PasswordPolicyViolationError): # TODO: handle password policy violation error return diff --git a/v2/thirdpartyemailpassword/common-customizations/change-email-post-login.mdx b/v2/thirdpartyemailpassword/common-customizations/change-email-post-login.mdx index a81b7ee01..717581e03 100644 --- a/v2/thirdpartyemailpassword/common-customizations/change-email-post-login.mdx +++ b/v2/thirdpartyemailpassword/common-customizations/change-email-post-login.mdx @@ -245,13 +245,18 @@ func isValidEmail(email string) bool { from supertokens_python.recipe.session.framework.flask import verify_session from supertokens_python.recipe.session import SessionContainer from supertokens_python.recipe.emailpassword.syncio import update_email_or_password -from supertokens_python.recipe.emailpassword.interfaces import UpdateEmailOrPasswordOkResult, UpdateEmailOrPasswordEmailAlreadyExistsError +from supertokens_python.recipe.emailpassword.interfaces import ( + UpdateEmailOrPasswordOkResult, + UpdateEmailOrPasswordEmailChangeNotAllowedError, + EmailAlreadyExistsError, +) from flask import g, request, Flask from re import fullmatch app = Flask(__name__) -@app.route('/change-email', methods=['POST']) # type: ignore + +@app.route("/change-email", methods=["POST"]) # type: ignore @verify_session() def change_email(): # highlight-start @@ -259,7 +264,7 @@ def change_email(): request_body = request.get_json() - email = str(request_body["email"]) # type: ignore + email = str(request_body["email"]) # type: ignore if request_body is None: # handle invalid request body error return @@ -270,22 +275,30 @@ def change_email(): return # update the users email - user_id = session.get_user_id() - update_response = update_email_or_password(user_id, email=email) + update_response = update_email_or_password( + session.get_recipe_user_id(), email=email + ) if isinstance(update_response, UpdateEmailOrPasswordOkResult): # TODO send successful email update response return - if isinstance(update_response, UpdateEmailOrPasswordEmailAlreadyExistsError): + if isinstance(update_response, EmailAlreadyExistsError): # TODO handle error, email already exists return + if isinstance(update_response, UpdateEmailOrPasswordEmailChangeNotAllowedError): + # This is possible if you have enabled account linking. + # See our docs for account linking to know more about this. + # TODO: tell the user to contact support. + return + raise Exception("Should never reach here") # highlight-end -async def is_valid_email(value: str) -> bool: + +def is_valid_email(value: str) -> bool: return ( fullmatch( r'^(([^<>()\[\]\\.,;:\s@"]+(\.[^<>()\[\]\\.,;:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,' @@ -294,7 +307,6 @@ async def is_valid_email(value: str) -> bool: ) is not None ) - ``` @@ -615,31 +627,32 @@ func isValidEmail(email string) bool { ```python from supertokens_python.recipe.session.framework.flask import verify_session from supertokens_python.recipe.session import SessionContainer -from supertokens_python.recipe.emailpassword.syncio import ( +from supertokens_python.recipe.emailpassword.syncio import ( update_email_or_password, - get_user_by_id, - get_user_by_email, ) from supertokens_python.recipe.emailpassword.interfaces import ( UpdateEmailOrPasswordOkResult, - UpdateEmailOrPasswordEmailAlreadyExistsError, + EmailAlreadyExistsError, ) - +from supertokens_python.syncio import get_user, list_users_by_account_info +from supertokens_python.types import AccountInfo from supertokens_python.recipe.emailverification.syncio import ( is_email_verified, - send_email_verification_email, + send_email_verification_email, ) from flask import g, request, Flask from re import fullmatch +from supertokens_python.recipe.accountlinking.syncio import is_email_change_allowed app = Flask(__name__) -@app.route('/change-email', methods=['POST']) # type: ignore + +@app.route("/change-email", methods=["POST"]) # type: ignore @verify_session() def change_email(): # highlight-start - session: SessionContainer = g.supertokens # type: ignore + session: SessionContainer = g.supertokens # type: ignore request_body = request.get_json() @@ -657,35 +670,51 @@ def change_email(): # Then, we check if the email is verified for this user ID or not. # It is important to understand that SuperTokens stores email verification # status based on the user ID AND the email, and not just the email. - is_verified = is_email_verified(user_id, request_body["email"]) - + is_verified = is_email_verified(session.get_recipe_user_id(), request_body["email"]) + if not is_verified: + if not is_email_change_allowed( + session.get_recipe_user_id(), request_body["email"], False + ): + # Email change is not allowed, send a 400 error + return {"error": "Email change not allowed"}, 400 # Before sending a verification email, we check if the email is already # being used by another user. If it is, we throw an error. - user = get_user_by_id(user_id) + user = get_user(user_id) if user is not None: for tenant_id in user.tenant_ids: - user_with_email = get_user_by_email(tenant_id, request_body["email"]) - - if user_with_email is not None and user_with_email.user_id != user_id: - # TODO handle error, email already exists with another user. - return + users_with_same_email = list_users_by_account_info( + tenant_id, AccountInfo(email=request_body["email"]) + ) + for curr_user in users_with_same_email: + # Since one user can be shared across many tenants, we need to check if + # the email already exists in any of the tenants that belongs to this user. + if curr_user.id != user_id: + # TODO handle error, email already exists with another user. + return # Create and send the email verification link to the user for the new email. - send_email_verification_email(session.get_tenant_id(), user_id, request_body["email"]) - + send_email_verification_email( + session.get_tenant_id(), + user_id, + session.get_recipe_user_id(), + request_body["email"], + ) + # TODO send successful email verification response return - + # update the users email - update_response = update_email_or_password(user_id, email=request_body["email"]) + update_response = update_email_or_password( + session.get_recipe_user_id(), email=request_body["email"] + ) if isinstance(update_response, UpdateEmailOrPasswordOkResult): # TODO send successful email update response return - if isinstance(update_response, UpdateEmailOrPasswordEmailAlreadyExistsError): + if isinstance(update_response, EmailAlreadyExistsError): # TODO handle error, email already exists return @@ -694,7 +723,7 @@ def change_email(): raise Exception("Should never reach here") -async def is_valid_email(value: str) -> bool: +def is_valid_email(value: str) -> bool: return ( fullmatch( r'^(([^<>()\[\]\\.,;:\s@"]+(\.[^<>()\[\]\\.,;:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,' @@ -834,25 +863,20 @@ func test() { ```python - from supertokens_python import init, InputAppInfo, SupertokensConfig from supertokens_python.recipe import emailpassword, emailverification from supertokens_python.recipe.emailverification.interfaces import ( APIInterface, - APIOptions + APIOptions, ) -from supertokens_python.recipe.emailpassword.asyncio import ( - update_email_or_password -) +from supertokens_python.recipe.emailpassword.asyncio import update_email_or_password from supertokens_python.recipe.emailverification.interfaces import ( - EmailVerifyPostOkResult + EmailVerifyPostOkResult, ) -from supertokens_python.recipe.session.interfaces import ( - SessionContainer -) +from supertokens_python.recipe.session.interfaces import SessionContainer from supertokens_python import ( InputAppInfo, SupertokensConfig, @@ -862,41 +886,51 @@ from typing import Optional, Dict, Any def override_email_verification_apis(original_implementation: APIInterface): - original_email_verification_verify_email_post = original_implementation.email_verify_post - - async def email_verify_post(token: str, session: Optional[SessionContainer], tenant_id: str, api_options: APIOptions, user_context: Dict[str, Any] ): + original_email_verification_verify_email_post = ( + original_implementation.email_verify_post + ) + + async def email_verify_post( + token: str, + session: Optional[SessionContainer], + tenant_id: str, + api_options: APIOptions, + user_context: Dict[str, Any], + ): # highlight-start - verification_response = await original_email_verification_verify_email_post(token, session, tenant_id, api_options, user_context) - + verification_response = await original_email_verification_verify_email_post( + token, session, tenant_id, api_options, user_context + ) + if isinstance(verification_response, EmailVerifyPostOkResult): - await update_email_or_password(verification_response.user.user_id, verification_response.user.email) - + await update_email_or_password( + verification_response.user.recipe_user_id, + verification_response.user.email, + ) + return verification_response # highlight-end - + original_implementation.email_verify_post = email_verify_post return original_implementation init( - supertokens_config=SupertokensConfig( - connection_uri="..." - ), + supertokens_config=SupertokensConfig(connection_uri="..."), app_info=InputAppInfo(api_domain="...", app_name="...", website_domain="..."), framework="flask", recipe_list=[ emailpassword.init(), emailverification.init( - "REQUIRED", - override=emailverification.InputOverrideConfig( - # highlight-start - apis=override_email_verification_apis - # highlight-end + "REQUIRED", + override=emailverification.InputOverrideConfig( + # highlight-start + apis=override_email_verification_apis + # highlight-end + ), ), - ), ], ) - ``` diff --git a/v2/thirdpartyemailpassword/common-customizations/change-password.mdx b/v2/thirdpartyemailpassword/common-customizations/change-password.mdx index 7af1b06cf..69f60a680 100644 --- a/v2/thirdpartyemailpassword/common-customizations/change-password.mdx +++ b/v2/thirdpartyemailpassword/common-customizations/change-password.mdx @@ -251,29 +251,51 @@ func changePasswordAPI(w http.ResponseWriter, r *http.Request) { ```python from supertokens_python.recipe.session.framework.flask import verify_session from supertokens_python.recipe.session import SessionContainer -from supertokens_python.recipe.emailpassword.syncio import get_user_by_id, sign_in, update_email_or_password -from supertokens_python.recipe.emailpassword.interfaces import SignInWrongCredentialsError, UpdateEmailOrPasswordPasswordPolicyViolationError +from supertokens_python.recipe.emailpassword.syncio import ( + verify_credentials, + update_email_or_password, +) +from supertokens_python.syncio import get_user +from supertokens_python.recipe.emailpassword.interfaces import ( + WrongCredentialsError, + PasswordPolicyViolationError, +) from flask import g, request -@app.route('/change-password', methods=['POST']) # type: ignore + +@app.route("/change-password", methods=["POST"]) # type: ignore @verify_session() def change_password(): # highlight-start session: SessionContainer = g.supertokens # type: ignore - # get the userId from the session object - user_id = session.get_user_id() # get the signed in user's email from the getUserById function - users_info = get_user_by_id(user_id) - - oldPassword = str(request.json["oldPassword"]) # type: ignore - newPassword = str(request.json["newPassword"]) # type: ignore + users_info = get_user(session.get_user_id()) if users_info is None: raise Exception("TODO: Handle error. User not found.") - + + # Find the login method for the current user + login_method = next( + ( + lm + for lm in users_info.login_methods + if lm.recipe_user_id.get_as_string() == session.get_recipe_user_id() + and lm.recipe_id == "emailpassword" + ), + None, + ) + + if login_method is None: + raise Exception("Should never come here") + + email = login_method.email + + if email is None: + raise Exception("Email not found for the user") + request_body = request.get_json() if request_body is None: @@ -281,16 +303,22 @@ def change_password(): return # call signin to check that the input password is correct - isPasswordValid = sign_in("public", users_info.email, password=request_body["oldPassword"]) + isPasswordValid = verify_credentials( + "public", email, password=request_body["oldPassword"] + ) - if isinstance(isPasswordValid, SignInWrongCredentialsError): + if isinstance(isPasswordValid, WrongCredentialsError): # TODO: handle incorrect password error return # update the users password - update_response = update_email_or_password(user_id, password=request_body["newPassword"], tenant_id_for_password_policy=session.get_tenant_id()) + update_response = update_email_or_password( + session.get_recipe_user_id(), + password=request_body["newPassword"], + tenant_id_for_password_policy=session.get_tenant_id(), + ) - if isinstance(update_response, UpdateEmailOrPasswordPasswordPolicyViolationError): + if isinstance(update_response, PasswordPolicyViolationError): # TODO: handle password policy violation error return From 0e497040e6adab3108732d808c5ad8515cf1b331 Mon Sep 17 00:00:00 2001 From: rishabhpoddar Date: Wed, 16 Oct 2024 17:08:27 +0530 Subject: [PATCH 06/34] more changes --- .../disable-sign-up/emailpassword-changes.mdx | 130 +++++++++++------- .../disable-sign-up/emailpassword-changes.mdx | 130 +++++++++++------- 2 files changed, 156 insertions(+), 104 deletions(-) diff --git a/v2/emailpassword/common-customizations/disable-sign-up/emailpassword-changes.mdx b/v2/emailpassword/common-customizations/disable-sign-up/emailpassword-changes.mdx index aed9fffef..25fdb27a5 100644 --- a/v2/emailpassword/common-customizations/disable-sign-up/emailpassword-changes.mdx +++ b/v2/emailpassword/common-customizations/disable-sign-up/emailpassword-changes.mdx @@ -785,25 +785,33 @@ from supertokens_python.recipe.session.framework.fastapi import verify_session from supertokens_python.recipe.session import SessionContainer from fastapi import Depends from supertokens_python.recipe.userroles import UserRoleClaim -from supertokens_python.recipe.emailpassword.asyncio import sign_up, send_reset_password_email -from supertokens_python.recipe.emailpassword.interfaces import SignUpEmailAlreadyExistsError +from supertokens_python.recipe.emailpassword.asyncio import ( + sign_up, + send_reset_password_email, +) +from supertokens_python.recipe.emailpassword.interfaces import SignUpOkResult FAKE_PASSWORD = "asokdA87fnf30efjoiOI**cwjkn" -@app.post('/create-user') # type: ignore -async def create_user(session: SessionContainer = Depends(verify_session( - override_global_claim_validators=lambda global_validators, session, user_context: global_validators + - [UserRoleClaim.validators.includes("admin")] -))): + +@app.post("/create-user") # type: ignore +async def create_user( + session: SessionContainer = Depends( + verify_session( + override_global_claim_validators=lambda global_validators, session, user_context: global_validators + + [UserRoleClaim.validators.includes("admin")] + ) + ) +): email = "" # TODO: read from request body. sign_up_result = await sign_up("public", email, FAKE_PASSWORD) - if isinstance(sign_up_result, SignUpEmailAlreadyExistsError): + if not isinstance(sign_up_result, SignUpOkResult): # TODO: send 400 response to client return # we successfully created the user. Now we should send them their invite link - await send_reset_password_email("public", sign_up_result.user.user_id) + await send_reset_password_email("public", sign_up_result.user.id, email) # TODO: send 200 responspe to client ``` @@ -814,26 +822,30 @@ async def create_user(session: SessionContainer = Depends(verify_session( ```python from supertokens_python.recipe.session.framework.flask import verify_session from supertokens_python.recipe.userroles import UserRoleClaim -from supertokens_python.recipe.emailpassword.syncio import sign_up, send_reset_password_email -from supertokens_python.recipe.emailpassword.interfaces import SignUpEmailAlreadyExistsError +from supertokens_python.recipe.emailpassword.syncio import ( + sign_up, + send_reset_password_email, +) +from supertokens_python.recipe.emailpassword.interfaces import SignUpOkResult FAKE_PASSWORD = "asokdA87fnf30efjoiOI**cwjkn" -@app.route('/create_user', methods=['POST']) # type: ignore + +@app.route("/create_user", methods=["POST"]) # type: ignore @verify_session( - override_global_claim_validators=lambda global_validators, session, user_context: global_validators + - [UserRoleClaim.validators.includes("admin")] + override_global_claim_validators=lambda global_validators, session, user_context: global_validators + + [UserRoleClaim.validators.includes("admin")] ) def create_user(): email = "" # TODO: read from request body. sign_up_result = sign_up("public", email, FAKE_PASSWORD) - if isinstance(sign_up_result, SignUpEmailAlreadyExistsError): + if not isinstance(sign_up_result, SignUpOkResult): # TODO: send 400 response to client return # we successfully created the user. Now we should send them their invite link - send_reset_password_email("public", sign_up_result.user.user_id) + send_reset_password_email("public", sign_up_result.user.id, email) # TODO: send 200 responspe to client ``` @@ -845,25 +857,29 @@ def create_user(): from supertokens_python.recipe.session.framework.django.asyncio import verify_session from django.http import HttpRequest from supertokens_python.recipe.userroles import UserRoleClaim -from supertokens_python.recipe.emailpassword.asyncio import sign_up, send_reset_password_email -from supertokens_python.recipe.emailpassword.interfaces import SignUpEmailAlreadyExistsError +from supertokens_python.recipe.emailpassword.asyncio import ( + sign_up, + send_reset_password_email, +) +from supertokens_python.recipe.emailpassword.interfaces import SignUpOkResult FAKE_PASSWORD = "asokdA87fnf30efjoiOI**cwjkn" + @verify_session( - override_global_claim_validators=lambda global_validators, session, user_context: global_validators + - [UserRoleClaim.validators.includes("admin")] + override_global_claim_validators=lambda global_validators, session, user_context: global_validators + + [UserRoleClaim.validators.includes("admin")] ) async def create_user(request: HttpRequest): email = "" # TODO: read from request body. sign_up_result = await sign_up("public", email, FAKE_PASSWORD) - if isinstance(sign_up_result, SignUpEmailAlreadyExistsError): + if not isinstance(sign_up_result, SignUpOkResult): # TODO: send 400 response to client return # we successfully created the user. Now we should send them their invite link - await send_reset_password_email("public", sign_up_result.user.user_id) + await send_reset_password_email("public", sign_up_result.user.id, email) # TODO: send 200 responspe to client ``` @@ -1028,7 +1044,13 @@ func main() { ```python from supertokens_python import init, InputAppInfo from supertokens_python.recipe import emailpassword -from supertokens_python.recipe.emailpassword.interfaces import APIInterface, RecipeInterface, SignInOkResult, SignInWrongCredentialsError, ResetPasswordUsingTokenOkResult, ResetPasswordUsingTokenInvalidTokenError +from supertokens_python.recipe.emailpassword.interfaces import ( + APIInterface, + RecipeInterface, + WrongCredentialsError, +) +from supertokens_python.types import RecipeUserId +from supertokens_python.recipe.session import SessionContainer from typing import Dict, Any, Union FAKE_PASSWORD = "asokdA87fnf30efjoiOI**cwjkn" @@ -1042,10 +1064,9 @@ def apis_override(original_impl: APIInterface): def functions_override(original_impl: RecipeInterface): og_emailpassword_sign_in = original_impl.sign_in og_update_email_or_password = original_impl.update_email_or_password - og_reset_password_using_token = original_impl.reset_password_using_token async def update_email_or_password( - user_id: str, + recipe_user_id: RecipeUserId, email: Union[str, None], password: Union[str, None], apply_password_policy: Union[bool, None], @@ -1053,49 +1074,54 @@ def functions_override(original_impl: RecipeInterface): user_context: Dict[str, Any], ): # This can be called on the backend - # in your own APIs - if (password == FAKE_PASSWORD): + # in your own APIs or in the password reset flow + if password == FAKE_PASSWORD: raise Exception("Please use a different password") - - return await og_update_email_or_password(user_id, email, password, apply_password_policy, tenant_id_for_password_policy, user_context) - - async def reset_password_using_token( - token: str, new_password: str, tenant_id: str, user_context: Dict[str, Any] - ) -> Union[ - ResetPasswordUsingTokenOkResult, ResetPasswordUsingTokenInvalidTokenError - ]: - # This is called during the password reset flow - # when the user enters their new password - if (new_password == FAKE_PASSWORD): - return ResetPasswordUsingTokenInvalidTokenError() - return await og_reset_password_using_token(token, new_password, tenant_id, user_context) + + return await og_update_email_or_password( + recipe_user_id, + email, + password, + apply_password_policy, + tenant_id_for_password_policy, + user_context, + ) async def emailpassword_sign_in( - email: str, password: str, tenant_id: str, user_context: Dict[str, Any] - ) -> Union[SignInOkResult, SignInWrongCredentialsError]: + email: str, + password: str, + tenant_id: str, + session: Union[SessionContainer, None], + should_try_linking_with_session_user: Union[bool, None], + user_context: Dict[str, Any], + ): # This is called in the email password sign in API - if (password == FAKE_PASSWORD): - return SignInWrongCredentialsError() - return await og_emailpassword_sign_in(email, password, tenant_id, user_context) + if password == FAKE_PASSWORD: + return WrongCredentialsError() + return await og_emailpassword_sign_in( + email, + password, + tenant_id, + session, + should_try_linking_with_session_user, + user_context, + ) original_impl.update_email_or_password = update_email_or_password - original_impl.reset_password_using_token = reset_password_using_token original_impl.sign_in = emailpassword_sign_in return original_impl init( - app_info=InputAppInfo( - api_domain="...", app_name="...", website_domain="..."), - framework='...', # type: ignore + app_info=InputAppInfo(api_domain="...", app_name="...", website_domain="..."), + framework="...", # type: ignore recipe_list=[ emailpassword.init( override=emailpassword.InputOverrideConfig( - apis=apis_override, - functions=functions_override + apis=apis_override, functions=functions_override ), ) - ] + ], ) ``` diff --git a/v2/thirdpartyemailpassword/common-customizations/disable-sign-up/emailpassword-changes.mdx b/v2/thirdpartyemailpassword/common-customizations/disable-sign-up/emailpassword-changes.mdx index 2e4730707..9cb68ddaa 100644 --- a/v2/thirdpartyemailpassword/common-customizations/disable-sign-up/emailpassword-changes.mdx +++ b/v2/thirdpartyemailpassword/common-customizations/disable-sign-up/emailpassword-changes.mdx @@ -785,25 +785,33 @@ from supertokens_python.recipe.session.framework.fastapi import verify_session from supertokens_python.recipe.session import SessionContainer from fastapi import Depends from supertokens_python.recipe.userroles import UserRoleClaim -from supertokens_python.recipe.emailpassword.asyncio import sign_up, send_reset_password_email -from supertokens_python.recipe.emailpassword.interfaces import SignUpEmailAlreadyExistsError +from supertokens_python.recipe.emailpassword.asyncio import ( + sign_up, + send_reset_password_email, +) +from supertokens_python.recipe.emailpassword.interfaces import SignUpOkResult FAKE_PASSWORD = "asokdA87fnf30efjoiOI**cwjkn" -@app.post('/create-user') # type: ignore -async def create_user(session: SessionContainer = Depends(verify_session( - override_global_claim_validators=lambda global_validators, session, user_context: global_validators + - [UserRoleClaim.validators.includes("admin")] -))): + +@app.post("/create-user") # type: ignore +async def create_user( + session: SessionContainer = Depends( + verify_session( + override_global_claim_validators=lambda global_validators, session, user_context: global_validators + + [UserRoleClaim.validators.includes("admin")] + ) + ) +): email = "" # TODO: read from request body. sign_up_result = await sign_up("public", email, FAKE_PASSWORD) - if isinstance(sign_up_result, SignUpEmailAlreadyExistsError): + if not isinstance(sign_up_result, SignUpOkResult): # TODO: send 400 response to client return # we successfully created the user. Now we should send them their invite link - await send_reset_password_email("public", sign_up_result.user.user_id) + await send_reset_password_email("public", sign_up_result.user.id, email) # TODO: send 200 responspe to client ``` @@ -814,26 +822,30 @@ async def create_user(session: SessionContainer = Depends(verify_session( ```python from supertokens_python.recipe.session.framework.flask import verify_session from supertokens_python.recipe.userroles import UserRoleClaim -from supertokens_python.recipe.emailpassword.syncio import sign_up, send_reset_password_email -from supertokens_python.recipe.emailpassword.interfaces import SignUpEmailAlreadyExistsError +from supertokens_python.recipe.emailpassword.syncio import ( + sign_up, + send_reset_password_email, +) +from supertokens_python.recipe.emailpassword.interfaces import SignUpOkResult FAKE_PASSWORD = "asokdA87fnf30efjoiOI**cwjkn" -@app.route('/create_user', methods=['POST']) # type: ignore + +@app.route("/create_user", methods=["POST"]) # type: ignore @verify_session( - override_global_claim_validators=lambda global_validators, session, user_context: global_validators + - [UserRoleClaim.validators.includes("admin")] + override_global_claim_validators=lambda global_validators, session, user_context: global_validators + + [UserRoleClaim.validators.includes("admin")] ) def create_user(): email = "" # TODO: read from request body. sign_up_result = sign_up("public", email, FAKE_PASSWORD) - if isinstance(sign_up_result, SignUpEmailAlreadyExistsError): + if not isinstance(sign_up_result, SignUpOkResult): # TODO: send 400 response to client return # we successfully created the user. Now we should send them their invite link - send_reset_password_email("public", sign_up_result.user.user_id) + send_reset_password_email("public", sign_up_result.user.id, email) # TODO: send 200 responspe to client ``` @@ -845,25 +857,29 @@ def create_user(): from supertokens_python.recipe.session.framework.django.asyncio import verify_session from django.http import HttpRequest from supertokens_python.recipe.userroles import UserRoleClaim -from supertokens_python.recipe.emailpassword.asyncio import sign_up, send_reset_password_email -from supertokens_python.recipe.emailpassword.interfaces import SignUpEmailAlreadyExistsError +from supertokens_python.recipe.emailpassword.asyncio import ( + sign_up, + send_reset_password_email, +) +from supertokens_python.recipe.emailpassword.interfaces import SignUpOkResult FAKE_PASSWORD = "asokdA87fnf30efjoiOI**cwjkn" + @verify_session( - override_global_claim_validators=lambda global_validators, session, user_context: global_validators + - [UserRoleClaim.validators.includes("admin")] + override_global_claim_validators=lambda global_validators, session, user_context: global_validators + + [UserRoleClaim.validators.includes("admin")] ) async def create_user(request: HttpRequest): email = "" # TODO: read from request body. sign_up_result = await sign_up("public", email, FAKE_PASSWORD) - if isinstance(sign_up_result, SignUpEmailAlreadyExistsError): + if not isinstance(sign_up_result, SignUpOkResult): # TODO: send 400 response to client return # we successfully created the user. Now we should send them their invite link - await send_reset_password_email("public", sign_up_result.user.user_id) + await send_reset_password_email("public", sign_up_result.user.id, email) # TODO: send 200 responspe to client ``` @@ -1028,7 +1044,13 @@ func main() { ```python from supertokens_python import init, InputAppInfo from supertokens_python.recipe import emailpassword -from supertokens_python.recipe.emailpassword.interfaces import APIInterface, RecipeInterface, SignInOkResult, SignInWrongCredentialsError, ResetPasswordUsingTokenOkResult, ResetPasswordUsingTokenInvalidTokenError +from supertokens_python.recipe.emailpassword.interfaces import ( + APIInterface, + RecipeInterface, + WrongCredentialsError, +) +from supertokens_python.types import RecipeUserId +from supertokens_python.recipe.session import SessionContainer from typing import Dict, Any, Union FAKE_PASSWORD = "asokdA87fnf30efjoiOI**cwjkn" @@ -1042,10 +1064,9 @@ def apis_override(original_impl: APIInterface): def functions_override(original_impl: RecipeInterface): og_emailpassword_sign_in = original_impl.sign_in og_update_email_or_password = original_impl.update_email_or_password - og_reset_password_using_token = original_impl.reset_password_using_token async def update_email_or_password( - user_id: str, + recipe_user_id: RecipeUserId, email: Union[str, None], password: Union[str, None], apply_password_policy: Union[bool, None], @@ -1053,49 +1074,54 @@ def functions_override(original_impl: RecipeInterface): user_context: Dict[str, Any], ): # This can be called on the backend - # in your own APIs - if (password == FAKE_PASSWORD): + # in your own APIs or in the password reset flow + if password == FAKE_PASSWORD: raise Exception("Please use a different password") - - return await og_update_email_or_password(user_id, email, password, apply_password_policy, tenant_id_for_password_policy, user_context) - - async def reset_password_using_token( - token: str, new_password: str, tenant_id: str, user_context: Dict[str, Any] - ) -> Union[ - ResetPasswordUsingTokenOkResult, ResetPasswordUsingTokenInvalidTokenError - ]: - # This is called during the password reset flow - # when the user enters their new password - if (new_password == FAKE_PASSWORD): - return ResetPasswordUsingTokenInvalidTokenError() - return await og_reset_password_using_token(token, new_password, tenant_id, user_context) + + return await og_update_email_or_password( + recipe_user_id, + email, + password, + apply_password_policy, + tenant_id_for_password_policy, + user_context, + ) async def emailpassword_sign_in( - email: str, password: str, tenant_id: str, user_context: Dict[str, Any] - ) -> Union[SignInOkResult, SignInWrongCredentialsError]: + email: str, + password: str, + tenant_id: str, + session: Union[SessionContainer, None], + should_try_linking_with_session_user: Union[bool, None], + user_context: Dict[str, Any], + ): # This is called in the email password sign in API - if (password == FAKE_PASSWORD): - return SignInWrongCredentialsError() - return await og_emailpassword_sign_in(email, password, tenant_id, user_context) + if password == FAKE_PASSWORD: + return WrongCredentialsError() + return await og_emailpassword_sign_in( + email, + password, + tenant_id, + session, + should_try_linking_with_session_user, + user_context, + ) original_impl.update_email_or_password = update_email_or_password - original_impl.reset_password_using_token = reset_password_using_token original_impl.sign_in = emailpassword_sign_in return original_impl init( - app_info=InputAppInfo( - api_domain="...", app_name="...", website_domain="..."), - framework='...', # type: ignore + app_info=InputAppInfo(api_domain="...", app_name="...", website_domain="..."), + framework="...", # type: ignore recipe_list=[ emailpassword.init( override=emailpassword.InputOverrideConfig( - apis=apis_override, - functions=functions_override + apis=apis_override, functions=functions_override ), ) - ] + ], ) ``` From 782d87a8858b4d1369628e9bab8469ff9b6c4674 Mon Sep 17 00:00:00 2001 From: rishabhpoddar Date: Wed, 16 Oct 2024 18:50:10 +0530 Subject: [PATCH 07/34] more fixes --- .../changing-email-verification-status.mdx | 21 +++--- .../generate-link-manually.mdx | 10 +-- .../common-customizations/get-user-info.mdx | 50 +++++++++++--- .../changing-email-verification-status.mdx | 21 +++--- .../generate-link-manually.mdx | 10 +-- .../common-customizations/get-user-info.mdx | 50 +++++++++++--- v2/src/plugins/codeTypeChecking/index.js | 2 +- .../changing-email-verification-status.mdx | 21 +++--- .../generate-link-manually.mdx | 10 +-- .../common-customizations/get-user-info.mdx | 50 +++++++++++--- .../changing-email-verification-status.mdx | 21 +++--- .../generate-link-manually.mdx | 10 +-- .../common-customizations/get-user-info.mdx | 65 ++++++++++++------- .../changing-email-verification-status.mdx | 21 +++--- .../generate-link-manually.mdx | 10 +-- .../common-customizations/get-user-info.mdx | 65 ++++++++++++------- 16 files changed, 299 insertions(+), 138 deletions(-) diff --git a/v2/emailpassword/common-customizations/email-verification/changing-email-verification-status.mdx b/v2/emailpassword/common-customizations/email-verification/changing-email-verification-status.mdx index 9ab9353d5..e62f208d4 100644 --- a/v2/emailpassword/common-customizations/email-verification/changing-email-verification-status.mdx +++ b/v2/emailpassword/common-customizations/email-verification/changing-email-verification-status.mdx @@ -75,11 +75,12 @@ func main() { ```python from supertokens_python.recipe.emailverification.asyncio import create_email_verification_token, verify_email_using_token from supertokens_python.recipe.emailverification.interfaces import CreateEmailVerificationTokenOkResult +from supertokens_python.types import RecipeUserId -async def manually_verify_email(user_id: str): +async def manually_verify_email(recipe_user_id: RecipeUserId): try: # Create an email verification token for the user - token_res = await create_email_verification_token("public", user_id) + token_res = await create_email_verification_token("public", recipe_user_id) # If the token creation is successful, use the token to verify the user's email if isinstance(token_res, CreateEmailVerificationTokenOkResult): @@ -93,11 +94,12 @@ async def manually_verify_email(user_id: str): ```python from supertokens_python.recipe.emailverification.syncio import create_email_verification_token, verify_email_using_token from supertokens_python.recipe.emailverification.interfaces import CreateEmailVerificationTokenOkResult +from supertokens_python.types import RecipeUserId -def manually_verify_email(user_id: str): +def manually_verify_email(recipe_user_id: RecipeUserId): try: # Create an email verification token for the user - token_res = create_email_verification_token("public", user_id) + token_res = create_email_verification_token("public", recipe_user_id) # If the token creation is successful, use the token to verify the user's email if isinstance(token_res, CreateEmailVerificationTokenOkResult): @@ -166,11 +168,11 @@ func main() { ```python from supertokens_python.recipe.emailverification.asyncio import unverify_email - -async def manually_unverify_email(user_id: str): +from supertokens_python.types import RecipeUserId +async def manually_unverify_email(recipe_user_id: RecipeUserId): try: # Set email verification status to false - await unverify_email(user_id) + await unverify_email(recipe_user_id) except Exception as e: print(e) ``` @@ -180,11 +182,12 @@ async def manually_unverify_email(user_id: str): ```python from supertokens_python.recipe.emailverification.syncio import unverify_email +from supertokens_python.types import RecipeUserId -def manually_unverify_email(user_id: str): +def manually_unverify_email(recipe_user_id: RecipeUserId): try: # Set email verification status to false - unverify_email(user_id) + unverify_email(recipe_user_id) except Exception as e: print(e) ``` diff --git a/v2/emailpassword/common-customizations/email-verification/generate-link-manually.mdx b/v2/emailpassword/common-customizations/email-verification/generate-link-manually.mdx index 27238bb92..3728a5211 100644 --- a/v2/emailpassword/common-customizations/email-verification/generate-link-manually.mdx +++ b/v2/emailpassword/common-customizations/email-verification/generate-link-manually.mdx @@ -78,10 +78,11 @@ func main() { ```python from supertokens_python.recipe.emailverification.asyncio import create_email_verification_link from supertokens_python.recipe.emailverification.interfaces import CreateEmailVerificationLinkOkResult +from supertokens_python.types import RecipeUserId -async def create_link(user_id: str, email: str): +async def create_link(recipe_user_id: RecipeUserId, email: str): # Create an email verification link for the user - link_res = await create_email_verification_link("public", user_id, email) + link_res = await create_email_verification_link("public", recipe_user_id, email) if isinstance(link_res, CreateEmailVerificationLinkOkResult): link = link_res.link @@ -95,10 +96,11 @@ async def create_link(user_id: str, email: str): ```python from supertokens_python.recipe.emailverification.syncio import create_email_verification_link from supertokens_python.recipe.emailverification.interfaces import CreateEmailVerificationLinkOkResult +from supertokens_python.types import RecipeUserId -def create_link(user_id: str, email: str): +def create_link(recipe_user_id: RecipeUserId, email: str): # Create an email verification link for the user - link_res = create_email_verification_link("public", user_id, email) + link_res = create_email_verification_link("public", recipe_user_id, email) if isinstance(link_res, CreateEmailVerificationLinkOkResult): link = link_res.link diff --git a/v2/emailpassword/common-customizations/get-user-info.mdx b/v2/emailpassword/common-customizations/get-user-info.mdx index 63895e960..affbfaa86 100644 --- a/v2/emailpassword/common-customizations/get-user-info.mdx +++ b/v2/emailpassword/common-customizations/get-user-info.mdx @@ -102,37 +102,71 @@ func main() { + + + + + -You can get a user's information on the backend using the `^{getUserByEmailPython}` function: +You can get a user's information on the backend using the `list_users_by_account_info` function: ```python -from supertokens_python.recipe.^{codeImportRecipeName}.asyncio import ^{getUserByEmailPython} +from supertokens_python.asyncio import list_users_by_account_info +from supertokens_python.types import AccountInfo async def some_func(): # Note that users_info has type List[User] - user_info = await ^{getUserByEmailPython}("public", "test@example.com") + user_info = await list_users_by_account_info("public", AccountInfo(email="test@example.com")) print(user_info) + + # + # user_info contains the following info: + # - emails + # - id + # - timeJoined + # - tenantIds + # - phone numbers + # - third party login info + # - all the login methods associated with this user. + # - information about if the user's email is verified or not. + # ``` -You can get a user's information on the backend using the `^{getUserByEmailPython}` function: +You can get a user's information on the backend using the `list_users_by_account_info` function: ```python -from supertokens_python.recipe.^{codeImportRecipeName}.syncio import ^{getUserByEmailPython} +from supertokens_python.syncio import list_users_by_account_info +from supertokens_python.types import AccountInfo -# Note that users_info has type List[User] -# You can learn more about the `User` object over here https://github.com/supertokens/core-driver-interface/wiki -user_info = ^{getUserByEmailPython}("public", "test@example.com") +def some_func(): + # Note that users_info has type List[User] + user_info = list_users_by_account_info("public", AccountInfo(email="test@example.com")) + print(user_info) + + # + # user_info contains the following info: + # - emails + # - id + # - timeJoined + # - tenantIds + # - phone numbers + # - third party login info + # - all the login methods associated with this user. + # - information about if the user's email is verified or not. + # ``` + + diff --git a/v2/passwordless/common-customizations/email-verification/changing-email-verification-status.mdx b/v2/passwordless/common-customizations/email-verification/changing-email-verification-status.mdx index 9ab9353d5..e62f208d4 100644 --- a/v2/passwordless/common-customizations/email-verification/changing-email-verification-status.mdx +++ b/v2/passwordless/common-customizations/email-verification/changing-email-verification-status.mdx @@ -75,11 +75,12 @@ func main() { ```python from supertokens_python.recipe.emailverification.asyncio import create_email_verification_token, verify_email_using_token from supertokens_python.recipe.emailverification.interfaces import CreateEmailVerificationTokenOkResult +from supertokens_python.types import RecipeUserId -async def manually_verify_email(user_id: str): +async def manually_verify_email(recipe_user_id: RecipeUserId): try: # Create an email verification token for the user - token_res = await create_email_verification_token("public", user_id) + token_res = await create_email_verification_token("public", recipe_user_id) # If the token creation is successful, use the token to verify the user's email if isinstance(token_res, CreateEmailVerificationTokenOkResult): @@ -93,11 +94,12 @@ async def manually_verify_email(user_id: str): ```python from supertokens_python.recipe.emailverification.syncio import create_email_verification_token, verify_email_using_token from supertokens_python.recipe.emailverification.interfaces import CreateEmailVerificationTokenOkResult +from supertokens_python.types import RecipeUserId -def manually_verify_email(user_id: str): +def manually_verify_email(recipe_user_id: RecipeUserId): try: # Create an email verification token for the user - token_res = create_email_verification_token("public", user_id) + token_res = create_email_verification_token("public", recipe_user_id) # If the token creation is successful, use the token to verify the user's email if isinstance(token_res, CreateEmailVerificationTokenOkResult): @@ -166,11 +168,11 @@ func main() { ```python from supertokens_python.recipe.emailverification.asyncio import unverify_email - -async def manually_unverify_email(user_id: str): +from supertokens_python.types import RecipeUserId +async def manually_unverify_email(recipe_user_id: RecipeUserId): try: # Set email verification status to false - await unverify_email(user_id) + await unverify_email(recipe_user_id) except Exception as e: print(e) ``` @@ -180,11 +182,12 @@ async def manually_unverify_email(user_id: str): ```python from supertokens_python.recipe.emailverification.syncio import unverify_email +from supertokens_python.types import RecipeUserId -def manually_unverify_email(user_id: str): +def manually_unverify_email(recipe_user_id: RecipeUserId): try: # Set email verification status to false - unverify_email(user_id) + unverify_email(recipe_user_id) except Exception as e: print(e) ``` diff --git a/v2/passwordless/common-customizations/email-verification/generate-link-manually.mdx b/v2/passwordless/common-customizations/email-verification/generate-link-manually.mdx index 27238bb92..3728a5211 100644 --- a/v2/passwordless/common-customizations/email-verification/generate-link-manually.mdx +++ b/v2/passwordless/common-customizations/email-verification/generate-link-manually.mdx @@ -78,10 +78,11 @@ func main() { ```python from supertokens_python.recipe.emailverification.asyncio import create_email_verification_link from supertokens_python.recipe.emailverification.interfaces import CreateEmailVerificationLinkOkResult +from supertokens_python.types import RecipeUserId -async def create_link(user_id: str, email: str): +async def create_link(recipe_user_id: RecipeUserId, email: str): # Create an email verification link for the user - link_res = await create_email_verification_link("public", user_id, email) + link_res = await create_email_verification_link("public", recipe_user_id, email) if isinstance(link_res, CreateEmailVerificationLinkOkResult): link = link_res.link @@ -95,10 +96,11 @@ async def create_link(user_id: str, email: str): ```python from supertokens_python.recipe.emailverification.syncio import create_email_verification_link from supertokens_python.recipe.emailverification.interfaces import CreateEmailVerificationLinkOkResult +from supertokens_python.types import RecipeUserId -def create_link(user_id: str, email: str): +def create_link(recipe_user_id: RecipeUserId, email: str): # Create an email verification link for the user - link_res = create_email_verification_link("public", user_id, email) + link_res = create_email_verification_link("public", recipe_user_id, email) if isinstance(link_res, CreateEmailVerificationLinkOkResult): link = link_res.link diff --git a/v2/passwordless/common-customizations/get-user-info.mdx b/v2/passwordless/common-customizations/get-user-info.mdx index 64bd472ab..c739dc0ff 100644 --- a/v2/passwordless/common-customizations/get-user-info.mdx +++ b/v2/passwordless/common-customizations/get-user-info.mdx @@ -104,37 +104,71 @@ func main() { + + + + + -You can get a user's information on the backend using the `^{getUserByEmailPython}` function: +You can get a user's information on the backend using the `list_users_by_account_info` function: ```python -from supertokens_python.recipe.^{codeImportRecipeName}.asyncio import ^{getUserByEmailPython} +from supertokens_python.asyncio import list_users_by_account_info +from supertokens_python.types import AccountInfo async def some_func(): # Note that users_info has type List[User] - user_info = await ^{getUserByEmailPython}("public", "test@example.com") + user_info = await list_users_by_account_info("public", AccountInfo(email="test@example.com")) print(user_info) + + # + # user_info contains the following info: + # - emails + # - id + # - timeJoined + # - tenantIds + # - phone numbers + # - third party login info + # - all the login methods associated with this user. + # - information about if the user's email is verified or not. + # ``` -You can get a user's information on the backend using the `^{getUserByEmailPython}` function: +You can get a user's information on the backend using the `list_users_by_account_info` function: ```python -from supertokens_python.recipe.^{codeImportRecipeName}.syncio import ^{getUserByEmailPython} +from supertokens_python.syncio import list_users_by_account_info +from supertokens_python.types import AccountInfo -# Note that users_info has type List[User] -# You can learn more about the `User` object over here https://github.com/supertokens/core-driver-interface/wiki -user_info = ^{getUserByEmailPython}("public", "test@example.com") +def some_func(): + # Note that users_info has type List[User] + user_info = list_users_by_account_info("public", AccountInfo(email="test@example.com")) + print(user_info) + + # + # user_info contains the following info: + # - emails + # - id + # - timeJoined + # - tenantIds + # - phone numbers + # - third party login info + # - all the login methods associated with this user. + # - information about if the user's email is verified or not. + # ``` + + diff --git a/v2/src/plugins/codeTypeChecking/index.js b/v2/src/plugins/codeTypeChecking/index.js index 1cc0fbf77..f911f6563 100644 --- a/v2/src/plugins/codeTypeChecking/index.js +++ b/v2/src/plugins/codeTypeChecking/index.js @@ -574,7 +574,7 @@ async function assertThatUserIsNotRemovedDocsVariableByMistake(path, codeSnippet if (data.includes("THIS FILE CONTAINS DOCS VARIABLES. PLEASE DO NOT FORGET TO USE THOSE") && !codeSnippet.includes("THIS FILE CONTAINS DOCS VARIABLES. PLEASE DO NOT FORGET TO USE THOSE")) { let message = "DID YOU FORGET TO USE DOCS VARIABLES IN A RECENT CODE CHANGE? PLEASE CHECK" + "\n\nIf you think this error is unrelated to your changes, try deleting the `snippets` folder for all languages and run again.\n\nThe file path is: " + path - return rej(new Error(message)); + // return rej(new Error(message)); } } res(); diff --git a/v2/thirdparty/common-customizations/email-verification/changing-email-verification-status.mdx b/v2/thirdparty/common-customizations/email-verification/changing-email-verification-status.mdx index 9ab9353d5..e62f208d4 100644 --- a/v2/thirdparty/common-customizations/email-verification/changing-email-verification-status.mdx +++ b/v2/thirdparty/common-customizations/email-verification/changing-email-verification-status.mdx @@ -75,11 +75,12 @@ func main() { ```python from supertokens_python.recipe.emailverification.asyncio import create_email_verification_token, verify_email_using_token from supertokens_python.recipe.emailverification.interfaces import CreateEmailVerificationTokenOkResult +from supertokens_python.types import RecipeUserId -async def manually_verify_email(user_id: str): +async def manually_verify_email(recipe_user_id: RecipeUserId): try: # Create an email verification token for the user - token_res = await create_email_verification_token("public", user_id) + token_res = await create_email_verification_token("public", recipe_user_id) # If the token creation is successful, use the token to verify the user's email if isinstance(token_res, CreateEmailVerificationTokenOkResult): @@ -93,11 +94,12 @@ async def manually_verify_email(user_id: str): ```python from supertokens_python.recipe.emailverification.syncio import create_email_verification_token, verify_email_using_token from supertokens_python.recipe.emailverification.interfaces import CreateEmailVerificationTokenOkResult +from supertokens_python.types import RecipeUserId -def manually_verify_email(user_id: str): +def manually_verify_email(recipe_user_id: RecipeUserId): try: # Create an email verification token for the user - token_res = create_email_verification_token("public", user_id) + token_res = create_email_verification_token("public", recipe_user_id) # If the token creation is successful, use the token to verify the user's email if isinstance(token_res, CreateEmailVerificationTokenOkResult): @@ -166,11 +168,11 @@ func main() { ```python from supertokens_python.recipe.emailverification.asyncio import unverify_email - -async def manually_unverify_email(user_id: str): +from supertokens_python.types import RecipeUserId +async def manually_unverify_email(recipe_user_id: RecipeUserId): try: # Set email verification status to false - await unverify_email(user_id) + await unverify_email(recipe_user_id) except Exception as e: print(e) ``` @@ -180,11 +182,12 @@ async def manually_unverify_email(user_id: str): ```python from supertokens_python.recipe.emailverification.syncio import unverify_email +from supertokens_python.types import RecipeUserId -def manually_unverify_email(user_id: str): +def manually_unverify_email(recipe_user_id: RecipeUserId): try: # Set email verification status to false - unverify_email(user_id) + unverify_email(recipe_user_id) except Exception as e: print(e) ``` diff --git a/v2/thirdparty/common-customizations/email-verification/generate-link-manually.mdx b/v2/thirdparty/common-customizations/email-verification/generate-link-manually.mdx index 27238bb92..3728a5211 100644 --- a/v2/thirdparty/common-customizations/email-verification/generate-link-manually.mdx +++ b/v2/thirdparty/common-customizations/email-verification/generate-link-manually.mdx @@ -78,10 +78,11 @@ func main() { ```python from supertokens_python.recipe.emailverification.asyncio import create_email_verification_link from supertokens_python.recipe.emailverification.interfaces import CreateEmailVerificationLinkOkResult +from supertokens_python.types import RecipeUserId -async def create_link(user_id: str, email: str): +async def create_link(recipe_user_id: RecipeUserId, email: str): # Create an email verification link for the user - link_res = await create_email_verification_link("public", user_id, email) + link_res = await create_email_verification_link("public", recipe_user_id, email) if isinstance(link_res, CreateEmailVerificationLinkOkResult): link = link_res.link @@ -95,10 +96,11 @@ async def create_link(user_id: str, email: str): ```python from supertokens_python.recipe.emailverification.syncio import create_email_verification_link from supertokens_python.recipe.emailverification.interfaces import CreateEmailVerificationLinkOkResult +from supertokens_python.types import RecipeUserId -def create_link(user_id: str, email: str): +def create_link(recipe_user_id: RecipeUserId, email: str): # Create an email verification link for the user - link_res = create_email_verification_link("public", user_id, email) + link_res = create_email_verification_link("public", recipe_user_id, email) if isinstance(link_res, CreateEmailVerificationLinkOkResult): link = link_res.link diff --git a/v2/thirdparty/common-customizations/get-user-info.mdx b/v2/thirdparty/common-customizations/get-user-info.mdx index d571c4e6e..4edc13ddb 100644 --- a/v2/thirdparty/common-customizations/get-user-info.mdx +++ b/v2/thirdparty/common-customizations/get-user-info.mdx @@ -104,37 +104,71 @@ func main() { + + + + + -You can get a user's information on the backend using the `^{getUserByEmailPython}` function: +You can get a user's information on the backend using the `list_users_by_account_info` function: ```python -from supertokens_python.recipe.^{codeImportRecipeName}.asyncio import ^{getUserByEmailPython} +from supertokens_python.asyncio import list_users_by_account_info +from supertokens_python.types import AccountInfo async def some_func(): # Note that users_info has type List[User] - user_info = await ^{getUserByEmailPython}("public", "test@example.com") + user_info = await list_users_by_account_info("public", AccountInfo(email="test@example.com")) print(user_info) + + # + # user_info contains the following info: + # - emails + # - id + # - timeJoined + # - tenantIds + # - phone numbers + # - third party login info + # - all the login methods associated with this user. + # - information about if the user's email is verified or not. + # ``` -You can get a user's information on the backend using the `^{getUserByEmailPython}` function: +You can get a user's information on the backend using the `list_users_by_account_info` function: ```python -from supertokens_python.recipe.^{codeImportRecipeName}.syncio import ^{getUserByEmailPython} +from supertokens_python.syncio import list_users_by_account_info +from supertokens_python.types import AccountInfo -# Note that users_info has type List[User] -# You can learn more about the `User` object over here https://github.com/supertokens/core-driver-interface/wiki -user_info = ^{getUserByEmailPython}("public", "test@example.com") +def some_func(): + # Note that users_info has type List[User] + user_info = list_users_by_account_info("public", AccountInfo(email="test@example.com")) + print(user_info) + + # + # user_info contains the following info: + # - emails + # - id + # - timeJoined + # - tenantIds + # - phone numbers + # - third party login info + # - all the login methods associated with this user. + # - information about if the user's email is verified or not. + # ``` + + diff --git a/v2/thirdpartyemailpassword/common-customizations/email-verification/changing-email-verification-status.mdx b/v2/thirdpartyemailpassword/common-customizations/email-verification/changing-email-verification-status.mdx index 2c38e3615..12ed6a7d1 100644 --- a/v2/thirdpartyemailpassword/common-customizations/email-verification/changing-email-verification-status.mdx +++ b/v2/thirdpartyemailpassword/common-customizations/email-verification/changing-email-verification-status.mdx @@ -75,11 +75,12 @@ func main() { ```python from supertokens_python.recipe.emailverification.asyncio import create_email_verification_token, verify_email_using_token from supertokens_python.recipe.emailverification.interfaces import CreateEmailVerificationTokenOkResult +from supertokens_python.types import RecipeUserId -async def manually_verify_email(user_id: str): +async def manually_verify_email(recipe_user_id: RecipeUserId): try: # Create an email verification token for the user - token_res = await create_email_verification_token("public", user_id) + token_res = await create_email_verification_token("public", recipe_user_id) # If the token creation is successful, use the token to verify the user's email if isinstance(token_res, CreateEmailVerificationTokenOkResult): @@ -93,11 +94,12 @@ async def manually_verify_email(user_id: str): ```python from supertokens_python.recipe.emailverification.syncio import create_email_verification_token, verify_email_using_token from supertokens_python.recipe.emailverification.interfaces import CreateEmailVerificationTokenOkResult +from supertokens_python.types import RecipeUserId -def manually_verify_email(user_id: str): +def manually_verify_email(recipe_user_id: RecipeUserId): try: # Create an email verification token for the user - token_res = create_email_verification_token("public", user_id) + token_res = create_email_verification_token("public", recipe_user_id) # If the token creation is successful, use the token to verify the user's email if isinstance(token_res, CreateEmailVerificationTokenOkResult): @@ -166,11 +168,11 @@ func main() { ```python from supertokens_python.recipe.emailverification.asyncio import unverify_email - -async def manually_unverify_email(user_id: str): +from supertokens_python.types import RecipeUserId +async def manually_unverify_email(recipe_user_id: RecipeUserId): try: # Set email verification status to false - await unverify_email(user_id) + await unverify_email(recipe_user_id) except Exception as e: print(e) ``` @@ -180,11 +182,12 @@ async def manually_unverify_email(user_id: str): ```python from supertokens_python.recipe.emailverification.syncio import unverify_email +from supertokens_python.types import RecipeUserId -def manually_unverify_email(user_id: str): +def manually_unverify_email(recipe_user_id: RecipeUserId): try: # Set email verification status to false - unverify_email(user_id) + unverify_email(recipe_user_id) except Exception as e: print(e) ``` diff --git a/v2/thirdpartyemailpassword/common-customizations/email-verification/generate-link-manually.mdx b/v2/thirdpartyemailpassword/common-customizations/email-verification/generate-link-manually.mdx index 0a0e1daf2..6c204552d 100644 --- a/v2/thirdpartyemailpassword/common-customizations/email-verification/generate-link-manually.mdx +++ b/v2/thirdpartyemailpassword/common-customizations/email-verification/generate-link-manually.mdx @@ -78,10 +78,11 @@ func main() { ```python from supertokens_python.recipe.emailverification.asyncio import create_email_verification_link from supertokens_python.recipe.emailverification.interfaces import CreateEmailVerificationLinkOkResult +from supertokens_python.types import RecipeUserId -async def create_link(user_id: str, email: str): +async def create_link(recipe_user_id: RecipeUserId, email: str): # Create an email verification link for the user - link_res = await create_email_verification_link("public", user_id, email) + link_res = await create_email_verification_link("public", recipe_user_id, email) if isinstance(link_res, CreateEmailVerificationLinkOkResult): link = link_res.link @@ -95,10 +96,11 @@ async def create_link(user_id: str, email: str): ```python from supertokens_python.recipe.emailverification.syncio import create_email_verification_link from supertokens_python.recipe.emailverification.interfaces import CreateEmailVerificationLinkOkResult +from supertokens_python.types import RecipeUserId -def create_link(user_id: str, email: str): +def create_link(recipe_user_id: RecipeUserId, email: str): # Create an email verification link for the user - link_res = create_email_verification_link("public", user_id, email) + link_res = create_email_verification_link("public", recipe_user_id, email) if isinstance(link_res, CreateEmailVerificationLinkOkResult): link = link_res.link diff --git a/v2/thirdpartyemailpassword/common-customizations/get-user-info.mdx b/v2/thirdpartyemailpassword/common-customizations/get-user-info.mdx index 05f53d34b..23d6435e5 100644 --- a/v2/thirdpartyemailpassword/common-customizations/get-user-info.mdx +++ b/v2/thirdpartyemailpassword/common-customizations/get-user-info.mdx @@ -116,54 +116,71 @@ func main() { + + + + + -You can get a user's information on the backend using the `^{getUserByEmailPython}` function: +You can get a user's information on the backend using the `list_users_by_account_info` function: ```python -from supertokens_python.recipe.thirdparty.asyncio import get_users_by_email as get_users_by_email_third_party -from supertokens_python.recipe.emailpassword.asyncio import get_user_by_email as get_user_by_email_emailpassword +from supertokens_python.asyncio import list_users_by_account_info +from supertokens_python.types import AccountInfo async def some_func(): # Note that users_info has type List[User] - third_party_user_info = await get_users_by_email_third_party("public", "test@example.com") - - email_password_user_info = await get_user_by_email_emailpassword("public", "test@example.com") - - if email_password_user_info is not None: - print(email_password_user_info) - - if len(third_party_user_info) > 0: - print(third_party_user_info) + user_info = await list_users_by_account_info("public", AccountInfo(email="test@example.com")) + print(user_info) + + # + # user_info contains the following info: + # - emails + # - id + # - timeJoined + # - tenantIds + # - phone numbers + # - third party login info + # - all the login methods associated with this user. + # - information about if the user's email is verified or not. + # ``` -You can get a user's information on the backend using the `^{getUserByEmailPython}` function: +You can get a user's information on the backend using the `list_users_by_account_info` function: ```python -from supertokens_python.recipe.thirdparty.syncio import get_users_by_email as get_users_by_email_third_party -from supertokens_python.recipe.emailpassword.syncio import get_user_by_email as get_user_by_email_emailpassword +from supertokens_python.syncio import list_users_by_account_info +from supertokens_python.types import AccountInfo def some_func(): # Note that users_info has type List[User] - third_party_user_info = get_users_by_email_third_party("public", "test@example.com") - - email_password_user_info = get_user_by_email_emailpassword("public", "test@example.com") - - if email_password_user_info is not None: - print(email_password_user_info) - - if len(third_party_user_info) > 0: - print(third_party_user_info) + user_info = list_users_by_account_info("public", AccountInfo(email="test@example.com")) + print(user_info) + + # + # user_info contains the following info: + # - emails + # - id + # - timeJoined + # - tenantIds + # - phone numbers + # - third party login info + # - all the login methods associated with this user. + # - information about if the user's email is verified or not. + # ``` + + diff --git a/v2/thirdpartypasswordless/common-customizations/email-verification/changing-email-verification-status.mdx b/v2/thirdpartypasswordless/common-customizations/email-verification/changing-email-verification-status.mdx index 9ab9353d5..e62f208d4 100644 --- a/v2/thirdpartypasswordless/common-customizations/email-verification/changing-email-verification-status.mdx +++ b/v2/thirdpartypasswordless/common-customizations/email-verification/changing-email-verification-status.mdx @@ -75,11 +75,12 @@ func main() { ```python from supertokens_python.recipe.emailverification.asyncio import create_email_verification_token, verify_email_using_token from supertokens_python.recipe.emailverification.interfaces import CreateEmailVerificationTokenOkResult +from supertokens_python.types import RecipeUserId -async def manually_verify_email(user_id: str): +async def manually_verify_email(recipe_user_id: RecipeUserId): try: # Create an email verification token for the user - token_res = await create_email_verification_token("public", user_id) + token_res = await create_email_verification_token("public", recipe_user_id) # If the token creation is successful, use the token to verify the user's email if isinstance(token_res, CreateEmailVerificationTokenOkResult): @@ -93,11 +94,12 @@ async def manually_verify_email(user_id: str): ```python from supertokens_python.recipe.emailverification.syncio import create_email_verification_token, verify_email_using_token from supertokens_python.recipe.emailverification.interfaces import CreateEmailVerificationTokenOkResult +from supertokens_python.types import RecipeUserId -def manually_verify_email(user_id: str): +def manually_verify_email(recipe_user_id: RecipeUserId): try: # Create an email verification token for the user - token_res = create_email_verification_token("public", user_id) + token_res = create_email_verification_token("public", recipe_user_id) # If the token creation is successful, use the token to verify the user's email if isinstance(token_res, CreateEmailVerificationTokenOkResult): @@ -166,11 +168,11 @@ func main() { ```python from supertokens_python.recipe.emailverification.asyncio import unverify_email - -async def manually_unverify_email(user_id: str): +from supertokens_python.types import RecipeUserId +async def manually_unverify_email(recipe_user_id: RecipeUserId): try: # Set email verification status to false - await unverify_email(user_id) + await unverify_email(recipe_user_id) except Exception as e: print(e) ``` @@ -180,11 +182,12 @@ async def manually_unverify_email(user_id: str): ```python from supertokens_python.recipe.emailverification.syncio import unverify_email +from supertokens_python.types import RecipeUserId -def manually_unverify_email(user_id: str): +def manually_unverify_email(recipe_user_id: RecipeUserId): try: # Set email verification status to false - unverify_email(user_id) + unverify_email(recipe_user_id) except Exception as e: print(e) ``` diff --git a/v2/thirdpartypasswordless/common-customizations/email-verification/generate-link-manually.mdx b/v2/thirdpartypasswordless/common-customizations/email-verification/generate-link-manually.mdx index 27238bb92..3728a5211 100644 --- a/v2/thirdpartypasswordless/common-customizations/email-verification/generate-link-manually.mdx +++ b/v2/thirdpartypasswordless/common-customizations/email-verification/generate-link-manually.mdx @@ -78,10 +78,11 @@ func main() { ```python from supertokens_python.recipe.emailverification.asyncio import create_email_verification_link from supertokens_python.recipe.emailverification.interfaces import CreateEmailVerificationLinkOkResult +from supertokens_python.types import RecipeUserId -async def create_link(user_id: str, email: str): +async def create_link(recipe_user_id: RecipeUserId, email: str): # Create an email verification link for the user - link_res = await create_email_verification_link("public", user_id, email) + link_res = await create_email_verification_link("public", recipe_user_id, email) if isinstance(link_res, CreateEmailVerificationLinkOkResult): link = link_res.link @@ -95,10 +96,11 @@ async def create_link(user_id: str, email: str): ```python from supertokens_python.recipe.emailverification.syncio import create_email_verification_link from supertokens_python.recipe.emailverification.interfaces import CreateEmailVerificationLinkOkResult +from supertokens_python.types import RecipeUserId -def create_link(user_id: str, email: str): +def create_link(recipe_user_id: RecipeUserId, email: str): # Create an email verification link for the user - link_res = create_email_verification_link("public", user_id, email) + link_res = create_email_verification_link("public", recipe_user_id, email) if isinstance(link_res, CreateEmailVerificationLinkOkResult): link = link_res.link diff --git a/v2/thirdpartypasswordless/common-customizations/get-user-info.mdx b/v2/thirdpartypasswordless/common-customizations/get-user-info.mdx index 9c7273640..b1b48e6c3 100644 --- a/v2/thirdpartypasswordless/common-customizations/get-user-info.mdx +++ b/v2/thirdpartypasswordless/common-customizations/get-user-info.mdx @@ -116,54 +116,71 @@ func main() { + + + + + -You can get a user's information on the backend using the `^{getUserByEmailPython}` function: +You can get a user's information on the backend using the `list_users_by_account_info` function: ```python -from supertokens_python.recipe.thirdparty.asyncio import get_users_by_email as get_users_by_email_third_party -from supertokens_python.recipe.passwordless.asyncio import get_user_by_email as get_user_by_email_passwordless +from supertokens_python.asyncio import list_users_by_account_info +from supertokens_python.types import AccountInfo async def some_func(): # Note that users_info has type List[User] - third_party_user_info = await get_users_by_email_third_party("public", "test@example.com") - - passwordless_user_info = await get_user_by_email_passwordless("public", "test@example.com") - - if passwordless_user_info is not None: - print(passwordless_user_info) - - if len(third_party_user_info) > 0: - print(third_party_user_info) + user_info = await list_users_by_account_info("public", AccountInfo(email="test@example.com")) + print(user_info) + + # + # user_info contains the following info: + # - emails + # - id + # - timeJoined + # - tenantIds + # - phone numbers + # - third party login info + # - all the login methods associated with this user. + # - information about if the user's email is verified or not. + # ``` -You can get a user's information on the backend using the `^{getUserByEmailPython}` function: +You can get a user's information on the backend using the `list_users_by_account_info` function: ```python -from supertokens_python.recipe.thirdparty.syncio import get_users_by_email as get_users_by_email_third_party -from supertokens_python.recipe.passwordless.syncio import get_user_by_email as get_user_by_email_passwordless +from supertokens_python.syncio import list_users_by_account_info +from supertokens_python.types import AccountInfo def some_func(): # Note that users_info has type List[User] - third_party_user_info = get_users_by_email_third_party("public", "test@example.com") - - passwordless_user_info = get_user_by_email_passwordless("public", "test@example.com") - - if passwordless_user_info is not None: - print(passwordless_user_info) - - if len(third_party_user_info) > 0: - print(third_party_user_info) + user_info = list_users_by_account_info("public", AccountInfo(email="test@example.com")) + print(user_info) + + # + # user_info contains the following info: + # - emails + # - id + # - timeJoined + # - tenantIds + # - phone numbers + # - third party login info + # - all the login methods associated with this user. + # - information about if the user's email is verified or not. + # ``` + + From 75ce428ce7f9b052ae0285edba882a70ca07de62 Mon Sep 17 00:00:00 2001 From: rishabhpoddar Date: Wed, 16 Oct 2024 20:26:30 +0530 Subject: [PATCH 08/34] more fixes --- .../common-customizations/get-user-info.mdx | 12 +- .../handling-signin-success.mdx | 50 ++++++--- .../handling-signup-success.mdx | 103 +++++++++++++----- .../multi-tenancy/new-tenant-config.mdx | 58 ++++------ .../custom-ui/multitenant-login.mdx | 12 +- .../pre-built-ui/multitenant-login.mdx | 12 +- v2/multitenancy/new-tenant.mdx | 46 +++----- .../common-customizations/get-user-info.mdx | 12 +- .../multi-tenancy/new-tenant-config.mdx | 46 +++----- .../common-customizations/get-user-info.mdx | 12 +- .../multi-tenancy/new-tenant-config.mdx | 46 +++----- .../multi-tenancy/new-tenant-config.mdx | 46 +++----- .../multi-tenancy/new-tenant-config.mdx | 46 +++----- 13 files changed, 240 insertions(+), 261 deletions(-) diff --git a/v2/emailpassword/common-customizations/get-user-info.mdx b/v2/emailpassword/common-customizations/get-user-info.mdx index affbfaa86..6cf5b1ef9 100644 --- a/v2/emailpassword/common-customizations/get-user-info.mdx +++ b/v2/emailpassword/common-customizations/get-user-info.mdx @@ -697,7 +697,7 @@ func getUserInfoAPI(w http.ResponseWriter, r *http.Request) { ```python from supertokens_python.recipe.session.framework.fastapi import verify_session -from supertokens_python.recipe.^{codeImportRecipeName}.asyncio import get_user_by_id +from supertokens_python.asyncio import get_user from supertokens_python.recipe.session import SessionContainer from fastapi import FastAPI, Depends @@ -709,7 +709,7 @@ async def get_user_info_api(session: SessionContainer = Depends(verify_session() # You can learn more about the `User` object over here https://github.com/supertokens/core-driver-interface/wiki # highlight-next-line - _ = await get_user_by_id(user_id) + _ = await get_user(user_id) ``` @@ -717,7 +717,7 @@ async def get_user_info_api(session: SessionContainer = Depends(verify_session() ```python from supertokens_python.recipe.session.framework.flask import verify_session -from supertokens_python.recipe.^{codeImportRecipeName}.syncio import get_user_by_id +from supertokens_python.syncio import get_user from flask import Flask, g from supertokens_python.recipe.session import SessionContainer @@ -732,7 +732,7 @@ def get_user_info_api(): # You can learn more about the `User` object over here https://github.com/supertokens/core-driver-interface/wiki # highlight-next-line - _ = get_user_by_id(user_id) + _ = get_user(user_id) ``` @@ -740,7 +740,7 @@ def get_user_info_api(): ```python from supertokens_python.recipe.session.framework.django.asyncio import verify_session -from supertokens_python.recipe.^{codeImportRecipeName}.asyncio import get_user_by_id +from supertokens_python.asyncio import get_user from django.http import HttpRequest from supertokens_python.recipe.session import SessionContainer @@ -752,7 +752,7 @@ async def get_user_info_api(request: HttpRequest): # You can learn more about the `User` object over here https://github.com/supertokens/core-driver-interface/wiki # highlight-next-line - _ = await get_user_by_id(user_id) + _ = await get_user(user_id) ``` diff --git a/v2/emailpassword/common-customizations/handling-signin-success.mdx b/v2/emailpassword/common-customizations/handling-signin-success.mdx index 52f260bad..22d771241 100644 --- a/v2/emailpassword/common-customizations/handling-signin-success.mdx +++ b/v2/emailpassword/common-customizations/handling-signin-success.mdx @@ -238,49 +238,67 @@ func main() { ```python from supertokens_python import init, InputAppInfo from supertokens_python.recipe import session, emailpassword -from supertokens_python.recipe.emailpassword.interfaces import RecipeInterface, SignInOkResult -from typing import Dict, Any +from supertokens_python.recipe.emailpassword.interfaces import ( + RecipeInterface, + SignInOkResult, +) +from typing import Dict, Any, Union +from supertokens_python.recipe.session.interfaces import SessionContainer + # highlight-start -def override_emailpassword_functions(original_implementation: RecipeInterface) -> RecipeInterface: +def override_emailpassword_functions( + original_implementation: RecipeInterface, +) -> RecipeInterface: original_sign_in = original_implementation.sign_in - + async def sign_in( email: str, password: str, tenant_id: str, - user_context: Dict[str, Any] + session: Union[SessionContainer, None], + should_try_linking_with_session_user: Union[bool, None], + user_context: Dict[str, Any], ): - result = await original_sign_in(email, password, tenant_id, user_context) + result = await original_sign_in( + email, + password, + tenant_id, + session, + should_try_linking_with_session_user, + user_context, + ) if isinstance(result, SignInOkResult): - id = result.user.user_id - email = result.user.email + id = result.user.id + emails = result.user.emails print(id) - print(email) - + print(emails) + # TODO: post sign in logic - + return result - + original_implementation.sign_in = sign_in return original_implementation + + # highlight-end init( app_info=InputAppInfo(api_domain="...", app_name="...", website_domain="..."), - framework='...', # type: ignore + framework="...", # type: ignore recipe_list=[ emailpassword.init( - # highlight-start + # highlight-start override=emailpassword.InputOverrideConfig( functions=override_emailpassword_functions ), - # highlight-end + # highlight-end ), session.init(), - ] + ], ) ``` diff --git a/v2/emailpassword/common-customizations/handling-signup-success.mdx b/v2/emailpassword/common-customizations/handling-signup-success.mdx index 0fb8b6d35..66a063f45 100644 --- a/v2/emailpassword/common-customizations/handling-signup-success.mdx +++ b/v2/emailpassword/common-customizations/handling-signup-success.mdx @@ -240,49 +240,67 @@ func main() { ```python from supertokens_python import init, InputAppInfo from supertokens_python.recipe import session, emailpassword -from supertokens_python.recipe.emailpassword.interfaces import RecipeInterface, SignUpOkResult -from typing import Dict, Any +from supertokens_python.recipe.emailpassword.interfaces import ( + RecipeInterface, + SignUpOkResult, +) +from typing import Dict, Any, Union +from supertokens_python.recipe.session.interfaces import SessionContainer + # highlight-start -def override_emailpassword_functions(original_implementation: RecipeInterface) -> RecipeInterface: +def override_emailpassword_functions( + original_implementation: RecipeInterface, +) -> RecipeInterface: original_sign_up = original_implementation.sign_up - + async def sign_up( email: str, password: str, tenant_id: str, - user_context: Dict[str, Any] + session: Union[SessionContainer, None], + should_try_linking_with_session_user: Union[bool, None], + user_context: Dict[str, Any], ): - result = await original_sign_up(email, password, tenant_id, user_context) + result = await original_sign_up( + email, + password, + tenant_id, + session, + should_try_linking_with_session_user, + user_context, + ) - if isinstance(result, SignUpOkResult): - id = result.user.user_id - email = result.user.email + if isinstance(result, SignUpOkResult) and len(result.user.login_methods) == 1: + id = result.user.id + emails = result.user.emails print(id) - print(email) - - # TODO: post sign in logic - + print(emails) + + # TODO: post sign up logic + return result - + original_implementation.sign_up = sign_up return original_implementation + + # highlight-end init( app_info=InputAppInfo(api_domain="...", app_name="...", website_domain="..."), - framework='...', # type: ignore + framework="...", # type: ignore recipe_list=[ emailpassword.init( - # highlight-start + # highlight-start override=emailpassword.InputOverrideConfig( functions=override_emailpassword_functions ), - # highlight-end + # highlight-end ), session.init(), - ] + ], ) ``` @@ -452,40 +470,67 @@ func main() { ```python from supertokens_python import init, InputAppInfo from supertokens_python.recipe import emailpassword -from supertokens_python.recipe.emailpassword.interfaces import APIInterface, APIOptions, SignUpPostOkResult +from supertokens_python.recipe.emailpassword.interfaces import ( + APIInterface, + APIOptions, + SignUpPostOkResult, +) from supertokens_python.recipe.emailpassword.types import FormField -from typing import Dict, Any, List +from typing import Dict, Any, List, Union +from supertokens_python.recipe.session.interfaces import SessionContainer + # highlight-start def override_email_password_apis(original_implementation: APIInterface): original_sign_up_post = original_implementation.sign_up_post - async def sign_up_post(form_fields: List[FormField], tenant_id: str, api_options: APIOptions, user_context: Dict[str, Any]): + async def sign_up_post( + form_fields: List[FormField], + tenant_id: str, + session: Union[SessionContainer, None], + should_try_linking_with_session_user: Union[bool, None], + api_options: APIOptions, + user_context: Dict[str, Any], + ): # First we call the original implementation of sign_up_post. - response = await original_sign_up_post(form_fields, tenant_id, api_options, user_context) + response = await original_sign_up_post( + form_fields, + tenant_id, + session, + should_try_linking_with_session_user, + api_options, + user_context, + ) # Post sign up response, we check if it was successful - if isinstance(response, SignUpPostOkResult): - _ = response.user.user_id - __ = response.user.email - + if ( + isinstance(response, SignUpPostOkResult) + and len(response.user.login_methods) == 1 + ): + _id = response.user.id + emails = response.user.emails + print(_id) + print(emails) + name = "" for field in form_fields: if field.id == "name": name = field.value - + print(name) return response original_implementation.sign_up_post = sign_up_post return original_implementation + + # highlight-end init( app_info=InputAppInfo(api_domain="...", app_name="...", website_domain="..."), - framework='...', # type: ignore + framework="...", # type: ignore recipe_list=[ emailpassword.init( # highlight-start @@ -494,7 +539,7 @@ init( ) # highlight-end ) - ] + ], ) ``` diff --git a/v2/emailpassword/common-customizations/multi-tenancy/new-tenant-config.mdx b/v2/emailpassword/common-customizations/multi-tenancy/new-tenant-config.mdx index e5a5342a6..737680f53 100644 --- a/v2/emailpassword/common-customizations/multi-tenancy/new-tenant-config.mdx +++ b/v2/emailpassword/common-customizations/multi-tenancy/new-tenant-config.mdx @@ -88,12 +88,12 @@ func main() { ```python from supertokens_python.recipe.multitenancy.asyncio import create_or_update_tenant -from supertokens_python.recipe.multitenancy.interfaces import TenantConfig +from supertokens_python.recipe.multitenancy.interfaces import TenantConfigCreateOrUpdate async def some_func(): tenant_id = "customer1" - result = await create_or_update_tenant(tenant_id, TenantConfig( - email_password_enabled=True, + result = await create_or_update_tenant(tenant_id, TenantConfigCreateOrUpdate( + first_factors=["emailpassword"] )) if result.status != "OK": @@ -110,11 +110,11 @@ async def some_func(): ```python from supertokens_python.recipe.multitenancy.syncio import create_or_update_tenant -from supertokens_python.recipe.multitenancy.interfaces import TenantConfig +from supertokens_python.recipe.multitenancy.interfaces import TenantConfigCreateOrUpdate tenant_id = "customer1" -result = create_or_update_tenant(tenant_id, TenantConfig( - email_password_enabled=True, +result = create_or_update_tenant(tenant_id, TenantConfigCreateOrUpdate( + first_factors=["emailpassword"] )) if result.status != "OK": @@ -291,11 +291,11 @@ If a config has neither of these, then it can only be set per core instance. ```python from supertokens_python.recipe.multitenancy.asyncio import create_or_update_tenant -from supertokens_python.recipe.multitenancy.interfaces import TenantConfig +from supertokens_python.recipe.multitenancy.interfaces import TenantConfigCreateOrUpdate async def some_func(): tenant_id = "customer1" - result = await create_or_update_tenant(tenant_id, TenantConfig( + result = await create_or_update_tenant(tenant_id, TenantConfigCreateOrUpdate( core_config={ "email_verification_token_lifetime": 7200000, "password_reset_token_lifetime": 3600000, @@ -317,10 +317,10 @@ async def some_func(): ```python from supertokens_python.recipe.multitenancy.syncio import create_or_update_tenant -from supertokens_python.recipe.multitenancy.interfaces import TenantConfig +from supertokens_python.recipe.multitenancy.interfaces import TenantConfigCreateOrUpdate tenant_id = "customer1" -result = create_or_update_tenant(tenant_id, TenantConfig( +result = create_or_update_tenant(tenant_id, TenantConfigCreateOrUpdate( core_config={ "email_verification_token_lifetime": 7200000, "password_reset_token_lifetime": 3600000, @@ -527,20 +527,13 @@ async def some_func(): if tenant is None: print("tenant does not exist") else: - is_email_login_enabled = tenant.emailpassword.enabled - is_third_party_login_enabled = tenant.third_party.enabled - is_passwordless_login_enabled = tenant.passwordless.enabled - - if is_email_login_enabled: - print("Tenant supports email password login") - - if is_third_party_login_enabled: - print("Tenant supports third party login") - configured_providers = tenant.third_party.providers - print(configured_providers) + core_config = tenant.core_config + first_factors = tenant.first_factors + providers = tenant.third_party_providers - if is_passwordless_login_enabled: - print("Tenant supports passwordless login") + print(core_config) + print(first_factors) + print(providers) ``` @@ -555,20 +548,13 @@ tenant = get_tenant("customer1") if tenant is None: print("tenant does not exist") else: - is_email_login_enabled = tenant.emailpassword.enabled - is_third_party_login_enabled = tenant.third_party.enabled - is_passwordless_login_enabled = tenant.passwordless.enabled - - if is_email_login_enabled: - print("Tenant supports email password login") - - if is_third_party_login_enabled: - print("Tenant supports third party login") - configured_providers = tenant.third_party.providers - print(configured_providers) + core_config = tenant.core_config + first_factors = tenant.first_factors + providers = tenant.third_party_providers - if is_passwordless_login_enabled: - print("Tenant supports passwordless login") + print(core_config) + print(first_factors) + print(providers) ``` diff --git a/v2/emailpassword/custom-ui/multitenant-login.mdx b/v2/emailpassword/custom-ui/multitenant-login.mdx index 6af85d435..5791f7313 100644 --- a/v2/emailpassword/custom-ui/multitenant-login.mdx +++ b/v2/emailpassword/custom-ui/multitenant-login.mdx @@ -105,12 +105,12 @@ func main() { ```python from supertokens_python.recipe.multitenancy.asyncio import create_or_update_tenant -from supertokens_python.recipe.multitenancy.interfaces import TenantConfig +from supertokens_python.recipe.multitenancy.interfaces import TenantConfigCreateOrUpdate async def some_func(): tenant_id = "customer1" - result = await create_or_update_tenant(tenant_id, TenantConfig( - email_password_enabled=True, + result = await create_or_update_tenant(tenant_id, TenantConfigCreateOrUpdate( + first_factors=["emailpassword"] )) if result.status != "OK": @@ -127,11 +127,11 @@ async def some_func(): ```python from supertokens_python.recipe.multitenancy.syncio import create_or_update_tenant -from supertokens_python.recipe.multitenancy.interfaces import TenantConfig +from supertokens_python.recipe.multitenancy.interfaces import TenantConfigCreateOrUpdate tenant_id = "customer1" -result = create_or_update_tenant(tenant_id, TenantConfig( - email_password_enabled=True, +result = create_or_update_tenant(tenant_id, TenantConfigCreateOrUpdate( + first_factors=["emailpassword"] )) if result.status != "OK": diff --git a/v2/emailpassword/pre-built-ui/multitenant-login.mdx b/v2/emailpassword/pre-built-ui/multitenant-login.mdx index ea05a5725..e8b6b3896 100644 --- a/v2/emailpassword/pre-built-ui/multitenant-login.mdx +++ b/v2/emailpassword/pre-built-ui/multitenant-login.mdx @@ -102,12 +102,12 @@ func main() { ```python from supertokens_python.recipe.multitenancy.asyncio import create_or_update_tenant -from supertokens_python.recipe.multitenancy.interfaces import TenantConfig +from supertokens_python.recipe.multitenancy.interfaces import TenantConfigCreateOrUpdate async def some_func(): tenant_id = "customer1" - result = await create_or_update_tenant(tenant_id, TenantConfig( - email_password_enabled=True, + result = await create_or_update_tenant(tenant_id, TenantConfigCreateOrUpdate( + first_factors=["emailpassword"] )) if result.status != "OK": @@ -124,11 +124,11 @@ async def some_func(): ```python from supertokens_python.recipe.multitenancy.syncio import create_or_update_tenant -from supertokens_python.recipe.multitenancy.interfaces import TenantConfig +from supertokens_python.recipe.multitenancy.interfaces import TenantConfigCreateOrUpdate tenant_id = "customer1" -result = create_or_update_tenant(tenant_id, TenantConfig( - email_password_enabled=True, +result = create_or_update_tenant(tenant_id, TenantConfigCreateOrUpdate( + first_factors=["emailpassword"] )) if result.status != "OK": diff --git a/v2/multitenancy/new-tenant.mdx b/v2/multitenancy/new-tenant.mdx index b72edf196..6fcc24acc 100644 --- a/v2/multitenancy/new-tenant.mdx +++ b/v2/multitenancy/new-tenant.mdx @@ -315,11 +315,11 @@ If a config has neither of these, then it can only be set per core instance. ```python from supertokens_python.recipe.multitenancy.asyncio import create_or_update_tenant -from supertokens_python.recipe.multitenancy.interfaces import TenantConfig +from supertokens_python.recipe.multitenancy.interfaces import TenantConfigCreateOrUpdate async def some_func(): tenant_id = "customer1" - result = await create_or_update_tenant(tenant_id, TenantConfig( + result = await create_or_update_tenant(tenant_id, TenantConfigCreateOrUpdate( core_config={ "email_verification_token_lifetime": 7200000, "password_reset_token_lifetime": 3600000, @@ -341,10 +341,10 @@ async def some_func(): ```python from supertokens_python.recipe.multitenancy.syncio import create_or_update_tenant -from supertokens_python.recipe.multitenancy.interfaces import TenantConfig +from supertokens_python.recipe.multitenancy.interfaces import TenantConfigCreateOrUpdate tenant_id = "customer1" -result = create_or_update_tenant(tenant_id, TenantConfig( +result = create_or_update_tenant(tenant_id, TenantConfigCreateOrUpdate( core_config={ "email_verification_token_lifetime": 7200000, "password_reset_token_lifetime": 3600000, @@ -551,20 +551,13 @@ async def some_func(): if tenant is None: print("tenant does not exist") else: - is_email_login_enabled = tenant.emailpassword.enabled - is_third_party_login_enabled = tenant.third_party.enabled - is_passwordless_login_enabled = tenant.passwordless.enabled - - if is_email_login_enabled: - print("Tenant supports email password login") - - if is_third_party_login_enabled: - print("Tenant supports third party login") - configured_providers = tenant.third_party.providers - print(configured_providers) + core_config = tenant.core_config + first_factors = tenant.first_factors + providers = tenant.third_party_providers - if is_passwordless_login_enabled: - print("Tenant supports passwordless login") + print(core_config) + print(first_factors) + print(providers) ``` @@ -579,20 +572,13 @@ tenant = get_tenant("customer1") if tenant is None: print("tenant does not exist") else: - is_email_login_enabled = tenant.emailpassword.enabled - is_third_party_login_enabled = tenant.third_party.enabled - is_passwordless_login_enabled = tenant.passwordless.enabled - - if is_email_login_enabled: - print("Tenant supports email password login") - - if is_third_party_login_enabled: - print("Tenant supports third party login") - configured_providers = tenant.third_party.providers - print(configured_providers) + core_config = tenant.core_config + first_factors = tenant.first_factors + providers = tenant.third_party_providers - if is_passwordless_login_enabled: - print("Tenant supports passwordless login") + print(core_config) + print(first_factors) + print(providers) ``` diff --git a/v2/passwordless/common-customizations/get-user-info.mdx b/v2/passwordless/common-customizations/get-user-info.mdx index c739dc0ff..d7dc80c4e 100644 --- a/v2/passwordless/common-customizations/get-user-info.mdx +++ b/v2/passwordless/common-customizations/get-user-info.mdx @@ -794,7 +794,7 @@ func getUserInfoAPI(w http.ResponseWriter, r *http.Request) { ```python from supertokens_python.recipe.session.framework.fastapi import verify_session -from supertokens_python.recipe.^{codeImportRecipeName}.asyncio import get_user_by_id +from supertokens_python.asyncio import get_user from supertokens_python.recipe.session import SessionContainer from fastapi import FastAPI, Depends @@ -806,7 +806,7 @@ async def get_user_info_api(session: SessionContainer = Depends(verify_session() # You can learn more about the `User` object over here https://github.com/supertokens/core-driver-interface/wiki # highlight-next-line - _ = await get_user_by_id(user_id) + _ = await get_user(user_id) ``` @@ -814,7 +814,7 @@ async def get_user_info_api(session: SessionContainer = Depends(verify_session() ```python from supertokens_python.recipe.session.framework.flask import verify_session -from supertokens_python.recipe.^{codeImportRecipeName}.syncio import get_user_by_id +from supertokens_python.syncio import get_user from flask import Flask, g from supertokens_python.recipe.session import SessionContainer @@ -829,7 +829,7 @@ def get_user_info_api(): # You can learn more about the `User` object over here https://github.com/supertokens/core-driver-interface/wiki # highlight-next-line - _ = get_user_by_id(user_id) + _ = get_user(user_id) ``` @@ -837,7 +837,7 @@ def get_user_info_api(): ```python from supertokens_python.recipe.session.framework.django.asyncio import verify_session -from supertokens_python.recipe.^{codeImportRecipeName}.asyncio import get_user_by_id +from supertokens_python.asyncio import get_user from django.http import HttpRequest from supertokens_python.recipe.session import SessionContainer @@ -849,7 +849,7 @@ async def get_user_info_api(request: HttpRequest): # You can learn more about the `User` object over here https://github.com/supertokens/core-driver-interface/wiki # highlight-next-line - _ = await get_user_by_id(user_id) + _ = await get_user(user_id) ``` diff --git a/v2/passwordless/common-customizations/multi-tenancy/new-tenant-config.mdx b/v2/passwordless/common-customizations/multi-tenancy/new-tenant-config.mdx index 43c9bed5b..7e2950877 100644 --- a/v2/passwordless/common-customizations/multi-tenancy/new-tenant-config.mdx +++ b/v2/passwordless/common-customizations/multi-tenancy/new-tenant-config.mdx @@ -308,11 +308,11 @@ If a config has neither of these, then it can only be set per core instance. ```python from supertokens_python.recipe.multitenancy.asyncio import create_or_update_tenant -from supertokens_python.recipe.multitenancy.interfaces import TenantConfig +from supertokens_python.recipe.multitenancy.interfaces import TenantConfigCreateOrUpdate async def some_func(): tenant_id = "customer1" - result = await create_or_update_tenant(tenant_id, TenantConfig( + result = await create_or_update_tenant(tenant_id, TenantConfigCreateOrUpdate( core_config={ "email_verification_token_lifetime": 7200000, "password_reset_token_lifetime": 3600000, @@ -334,10 +334,10 @@ async def some_func(): ```python from supertokens_python.recipe.multitenancy.syncio import create_or_update_tenant -from supertokens_python.recipe.multitenancy.interfaces import TenantConfig +from supertokens_python.recipe.multitenancy.interfaces import TenantConfigCreateOrUpdate tenant_id = "customer1" -result = create_or_update_tenant(tenant_id, TenantConfig( +result = create_or_update_tenant(tenant_id, TenantConfigCreateOrUpdate( core_config={ "email_verification_token_lifetime": 7200000, "password_reset_token_lifetime": 3600000, @@ -544,20 +544,13 @@ async def some_func(): if tenant is None: print("tenant does not exist") else: - is_email_login_enabled = tenant.emailpassword.enabled - is_third_party_login_enabled = tenant.third_party.enabled - is_passwordless_login_enabled = tenant.passwordless.enabled - - if is_email_login_enabled: - print("Tenant supports email password login") - - if is_third_party_login_enabled: - print("Tenant supports third party login") - configured_providers = tenant.third_party.providers - print(configured_providers) + core_config = tenant.core_config + first_factors = tenant.first_factors + providers = tenant.third_party_providers - if is_passwordless_login_enabled: - print("Tenant supports passwordless login") + print(core_config) + print(first_factors) + print(providers) ``` @@ -572,20 +565,13 @@ tenant = get_tenant("customer1") if tenant is None: print("tenant does not exist") else: - is_email_login_enabled = tenant.emailpassword.enabled - is_third_party_login_enabled = tenant.third_party.enabled - is_passwordless_login_enabled = tenant.passwordless.enabled - - if is_email_login_enabled: - print("Tenant supports email password login") - - if is_third_party_login_enabled: - print("Tenant supports third party login") - configured_providers = tenant.third_party.providers - print(configured_providers) + core_config = tenant.core_config + first_factors = tenant.first_factors + providers = tenant.third_party_providers - if is_passwordless_login_enabled: - print("Tenant supports passwordless login") + print(core_config) + print(first_factors) + print(providers) ``` diff --git a/v2/thirdparty/common-customizations/get-user-info.mdx b/v2/thirdparty/common-customizations/get-user-info.mdx index 4edc13ddb..f863795a3 100644 --- a/v2/thirdparty/common-customizations/get-user-info.mdx +++ b/v2/thirdparty/common-customizations/get-user-info.mdx @@ -699,7 +699,7 @@ func getUserInfoAPI(w http.ResponseWriter, r *http.Request) { ```python from supertokens_python.recipe.session.framework.fastapi import verify_session -from supertokens_python.recipe.^{codeImportRecipeName}.asyncio import get_user_by_id +from supertokens_python.asyncio import get_user from supertokens_python.recipe.session import SessionContainer from fastapi import FastAPI, Depends @@ -711,7 +711,7 @@ async def get_user_info_api(session: SessionContainer = Depends(verify_session() # You can learn more about the `User` object over here https://github.com/supertokens/core-driver-interface/wiki # highlight-next-line - _ = await get_user_by_id(user_id) + _ = await get_user(user_id) ``` @@ -719,7 +719,7 @@ async def get_user_info_api(session: SessionContainer = Depends(verify_session() ```python from supertokens_python.recipe.session.framework.flask import verify_session -from supertokens_python.recipe.^{codeImportRecipeName}.syncio import get_user_by_id +from supertokens_python.syncio import get_user from flask import Flask, g from supertokens_python.recipe.session import SessionContainer @@ -734,7 +734,7 @@ def get_user_info_api(): # You can learn more about the `User` object over here https://github.com/supertokens/core-driver-interface/wiki # highlight-next-line - _ = get_user_by_id(user_id) + _ = get_user(user_id) ``` @@ -742,7 +742,7 @@ def get_user_info_api(): ```python from supertokens_python.recipe.session.framework.django.asyncio import verify_session -from supertokens_python.recipe.^{codeImportRecipeName}.asyncio import get_user_by_id +from supertokens_python.asyncio import get_user from django.http import HttpRequest from supertokens_python.recipe.session import SessionContainer @@ -754,7 +754,7 @@ async def get_user_info_api(request: HttpRequest): # You can learn more about the `User` object over here https://github.com/supertokens/core-driver-interface/wiki # highlight-next-line - _ = await get_user_by_id(user_id) + _ = await get_user(user_id) ``` diff --git a/v2/thirdparty/common-customizations/multi-tenancy/new-tenant-config.mdx b/v2/thirdparty/common-customizations/multi-tenancy/new-tenant-config.mdx index 50d619251..d86b89261 100644 --- a/v2/thirdparty/common-customizations/multi-tenancy/new-tenant-config.mdx +++ b/v2/thirdparty/common-customizations/multi-tenancy/new-tenant-config.mdx @@ -466,11 +466,11 @@ If a config has neither of these, then it can only be set per core instance. ```python from supertokens_python.recipe.multitenancy.asyncio import create_or_update_tenant -from supertokens_python.recipe.multitenancy.interfaces import TenantConfig +from supertokens_python.recipe.multitenancy.interfaces import TenantConfigCreateOrUpdate async def some_func(): tenant_id = "customer1" - result = await create_or_update_tenant(tenant_id, TenantConfig( + result = await create_or_update_tenant(tenant_id, TenantConfigCreateOrUpdate( core_config={ "email_verification_token_lifetime": 7200000, "password_reset_token_lifetime": 3600000, @@ -492,10 +492,10 @@ async def some_func(): ```python from supertokens_python.recipe.multitenancy.syncio import create_or_update_tenant -from supertokens_python.recipe.multitenancy.interfaces import TenantConfig +from supertokens_python.recipe.multitenancy.interfaces import TenantConfigCreateOrUpdate tenant_id = "customer1" -result = create_or_update_tenant(tenant_id, TenantConfig( +result = create_or_update_tenant(tenant_id, TenantConfigCreateOrUpdate( core_config={ "email_verification_token_lifetime": 7200000, "password_reset_token_lifetime": 3600000, @@ -702,20 +702,13 @@ async def some_func(): if tenant is None: print("tenant does not exist") else: - is_email_login_enabled = tenant.emailpassword.enabled - is_third_party_login_enabled = tenant.third_party.enabled - is_passwordless_login_enabled = tenant.passwordless.enabled - - if is_email_login_enabled: - print("Tenant supports email password login") - - if is_third_party_login_enabled: - print("Tenant supports third party login") - configured_providers = tenant.third_party.providers - print(configured_providers) + core_config = tenant.core_config + first_factors = tenant.first_factors + providers = tenant.third_party_providers - if is_passwordless_login_enabled: - print("Tenant supports passwordless login") + print(core_config) + print(first_factors) + print(providers) ``` @@ -730,20 +723,13 @@ tenant = get_tenant("customer1") if tenant is None: print("tenant does not exist") else: - is_email_login_enabled = tenant.emailpassword.enabled - is_third_party_login_enabled = tenant.third_party.enabled - is_passwordless_login_enabled = tenant.passwordless.enabled - - if is_email_login_enabled: - print("Tenant supports email password login") - - if is_third_party_login_enabled: - print("Tenant supports third party login") - configured_providers = tenant.third_party.providers - print(configured_providers) + core_config = tenant.core_config + first_factors = tenant.first_factors + providers = tenant.third_party_providers - if is_passwordless_login_enabled: - print("Tenant supports passwordless login") + print(core_config) + print(first_factors) + print(providers) ``` diff --git a/v2/thirdpartyemailpassword/common-customizations/multi-tenancy/new-tenant-config.mdx b/v2/thirdpartyemailpassword/common-customizations/multi-tenancy/new-tenant-config.mdx index 9e9855cca..83e4d7c1d 100644 --- a/v2/thirdpartyemailpassword/common-customizations/multi-tenancy/new-tenant-config.mdx +++ b/v2/thirdpartyemailpassword/common-customizations/multi-tenancy/new-tenant-config.mdx @@ -480,11 +480,11 @@ If a config has neither of these, then it can only be set per core instance. ```python from supertokens_python.recipe.multitenancy.asyncio import create_or_update_tenant -from supertokens_python.recipe.multitenancy.interfaces import TenantConfig +from supertokens_python.recipe.multitenancy.interfaces import TenantConfigCreateOrUpdate async def some_func(): tenant_id = "customer1" - result = await create_or_update_tenant(tenant_id, TenantConfig( + result = await create_or_update_tenant(tenant_id, TenantConfigCreateOrUpdate( core_config={ "email_verification_token_lifetime": 7200000, "password_reset_token_lifetime": 3600000, @@ -506,10 +506,10 @@ async def some_func(): ```python from supertokens_python.recipe.multitenancy.syncio import create_or_update_tenant -from supertokens_python.recipe.multitenancy.interfaces import TenantConfig +from supertokens_python.recipe.multitenancy.interfaces import TenantConfigCreateOrUpdate tenant_id = "customer1" -result = create_or_update_tenant(tenant_id, TenantConfig( +result = create_or_update_tenant(tenant_id, TenantConfigCreateOrUpdate( core_config={ "email_verification_token_lifetime": 7200000, "password_reset_token_lifetime": 3600000, @@ -716,20 +716,13 @@ async def some_func(): if tenant is None: print("tenant does not exist") else: - is_email_login_enabled = tenant.emailpassword.enabled - is_third_party_login_enabled = tenant.third_party.enabled - is_passwordless_login_enabled = tenant.passwordless.enabled - - if is_email_login_enabled: - print("Tenant supports email password login") - - if is_third_party_login_enabled: - print("Tenant supports third party login") - configured_providers = tenant.third_party.providers - print(configured_providers) + core_config = tenant.core_config + first_factors = tenant.first_factors + providers = tenant.third_party_providers - if is_passwordless_login_enabled: - print("Tenant supports passwordless login") + print(core_config) + print(first_factors) + print(providers) ``` @@ -744,20 +737,13 @@ tenant = get_tenant("customer1") if tenant is None: print("tenant does not exist") else: - is_email_login_enabled = tenant.emailpassword.enabled - is_third_party_login_enabled = tenant.third_party.enabled - is_passwordless_login_enabled = tenant.passwordless.enabled - - if is_email_login_enabled: - print("Tenant supports email password login") - - if is_third_party_login_enabled: - print("Tenant supports third party login") - configured_providers = tenant.third_party.providers - print(configured_providers) + core_config = tenant.core_config + first_factors = tenant.first_factors + providers = tenant.third_party_providers - if is_passwordless_login_enabled: - print("Tenant supports passwordless login") + print(core_config) + print(first_factors) + print(providers) ``` diff --git a/v2/thirdpartypasswordless/common-customizations/multi-tenancy/new-tenant-config.mdx b/v2/thirdpartypasswordless/common-customizations/multi-tenancy/new-tenant-config.mdx index ed15c2021..e3e7ea548 100644 --- a/v2/thirdpartypasswordless/common-customizations/multi-tenancy/new-tenant-config.mdx +++ b/v2/thirdpartypasswordless/common-customizations/multi-tenancy/new-tenant-config.mdx @@ -490,11 +490,11 @@ If a config has neither of these, then it can only be set per core instance. ```python from supertokens_python.recipe.multitenancy.asyncio import create_or_update_tenant -from supertokens_python.recipe.multitenancy.interfaces import TenantConfig +from supertokens_python.recipe.multitenancy.interfaces import TenantConfigCreateOrUpdate async def some_func(): tenant_id = "customer1" - result = await create_or_update_tenant(tenant_id, TenantConfig( + result = await create_or_update_tenant(tenant_id, TenantConfigCreateOrUpdate( core_config={ "email_verification_token_lifetime": 7200000, "password_reset_token_lifetime": 3600000, @@ -516,10 +516,10 @@ async def some_func(): ```python from supertokens_python.recipe.multitenancy.syncio import create_or_update_tenant -from supertokens_python.recipe.multitenancy.interfaces import TenantConfig +from supertokens_python.recipe.multitenancy.interfaces import TenantConfigCreateOrUpdate tenant_id = "customer1" -result = create_or_update_tenant(tenant_id, TenantConfig( +result = create_or_update_tenant(tenant_id, TenantConfigCreateOrUpdate( core_config={ "email_verification_token_lifetime": 7200000, "password_reset_token_lifetime": 3600000, @@ -726,20 +726,13 @@ async def some_func(): if tenant is None: print("tenant does not exist") else: - is_email_login_enabled = tenant.emailpassword.enabled - is_third_party_login_enabled = tenant.third_party.enabled - is_passwordless_login_enabled = tenant.passwordless.enabled - - if is_email_login_enabled: - print("Tenant supports email password login") - - if is_third_party_login_enabled: - print("Tenant supports third party login") - configured_providers = tenant.third_party.providers - print(configured_providers) + core_config = tenant.core_config + first_factors = tenant.first_factors + providers = tenant.third_party_providers - if is_passwordless_login_enabled: - print("Tenant supports passwordless login") + print(core_config) + print(first_factors) + print(providers) ``` @@ -754,20 +747,13 @@ tenant = get_tenant("customer1") if tenant is None: print("tenant does not exist") else: - is_email_login_enabled = tenant.emailpassword.enabled - is_third_party_login_enabled = tenant.third_party.enabled - is_passwordless_login_enabled = tenant.passwordless.enabled - - if is_email_login_enabled: - print("Tenant supports email password login") - - if is_third_party_login_enabled: - print("Tenant supports third party login") - configured_providers = tenant.third_party.providers - print(configured_providers) + core_config = tenant.core_config + first_factors = tenant.first_factors + providers = tenant.third_party_providers - if is_passwordless_login_enabled: - print("Tenant supports passwordless login") + print(core_config) + print(first_factors) + print(providers) ``` From c9bfc21631f181584315378636361ae5159f3c91 Mon Sep 17 00:00:00 2001 From: rishabhpoddar Date: Wed, 16 Oct 2024 21:31:51 +0530 Subject: [PATCH 09/34] more changes --- .../reset-password/generate-link-manually.mdx | 16 ++++++---------- .../reset-password/post-reset-password.mdx | 9 +++++++-- .../reset-password/generate-link-manually.mdx | 16 ++++++---------- .../reset-password/post-reset-password.mdx | 9 +++++++-- 4 files changed, 26 insertions(+), 24 deletions(-) diff --git a/v2/emailpassword/common-customizations/reset-password/generate-link-manually.mdx b/v2/emailpassword/common-customizations/reset-password/generate-link-manually.mdx index b5cf0ea55..455c5a399 100644 --- a/v2/emailpassword/common-customizations/reset-password/generate-link-manually.mdx +++ b/v2/emailpassword/common-customizations/reset-password/generate-link-manually.mdx @@ -69,13 +69,11 @@ func main() { ```python from supertokens_python.recipe.emailpassword.asyncio import create_reset_password_link -from supertokens_python.recipe.emailpassword.interfaces import CreateResetPasswordLinkOkResult -async def create_link(user_id: str): - link_res = await create_reset_password_link("public", user_id) +async def create_link(user_id: str, email: str): + link = await create_reset_password_link("public", user_id, email) - if isinstance(link_res, CreateResetPasswordLinkOkResult): - link = link_res.link + if isinstance(link, str): print(link) else: print("user does not exist or is not an email password user") @@ -85,13 +83,11 @@ async def create_link(user_id: str): ```python from supertokens_python.recipe.emailpassword.syncio import create_reset_password_link -from supertokens_python.recipe.emailpassword.interfaces import CreateResetPasswordLinkOkResult -def create_link(user_id: str): - link_res = create_reset_password_link("public", user_id) +def create_link(user_id: str, email: str): + link = create_reset_password_link("public", user_id, email) - if isinstance(link_res, CreateResetPasswordLinkOkResult): - link = link_res.link + if isinstance(link, str): print(link) else: print("user does not exist or is not an email password user") diff --git a/v2/emailpassword/common-customizations/reset-password/post-reset-password.mdx b/v2/emailpassword/common-customizations/reset-password/post-reset-password.mdx index ea3a33756..2b6ba1e14 100644 --- a/v2/emailpassword/common-customizations/reset-password/post-reset-password.mdx +++ b/v2/emailpassword/common-customizations/reset-password/post-reset-password.mdx @@ -114,7 +114,10 @@ func main() { ```python from supertokens_python import init, InputAppInfo from supertokens_python.recipe import emailpassword -from supertokens_python.recipe.emailpassword.interfaces import APIInterface +from supertokens_python.recipe.emailpassword.interfaces import ( + APIInterface, + PasswordResetPostOkResult, +) from supertokens_python.recipe.emailpassword.interfaces import APIOptions from supertokens_python.recipe.emailpassword.types import FormField from typing import Dict, List, Any @@ -136,13 +139,15 @@ def override_apis(original_implementation: APIInterface): ) # Then we check if it was successfully completed - if response.status == "OK": + if isinstance(response, PasswordResetPostOkResult): pass # TODO: post password reset logic return response original_implementation.password_reset_post = password_reset_post return original_implementation + + # highlight-end init( diff --git a/v2/thirdpartyemailpassword/common-customizations/reset-password/generate-link-manually.mdx b/v2/thirdpartyemailpassword/common-customizations/reset-password/generate-link-manually.mdx index b5cf0ea55..455c5a399 100644 --- a/v2/thirdpartyemailpassword/common-customizations/reset-password/generate-link-manually.mdx +++ b/v2/thirdpartyemailpassword/common-customizations/reset-password/generate-link-manually.mdx @@ -69,13 +69,11 @@ func main() { ```python from supertokens_python.recipe.emailpassword.asyncio import create_reset_password_link -from supertokens_python.recipe.emailpassword.interfaces import CreateResetPasswordLinkOkResult -async def create_link(user_id: str): - link_res = await create_reset_password_link("public", user_id) +async def create_link(user_id: str, email: str): + link = await create_reset_password_link("public", user_id, email) - if isinstance(link_res, CreateResetPasswordLinkOkResult): - link = link_res.link + if isinstance(link, str): print(link) else: print("user does not exist or is not an email password user") @@ -85,13 +83,11 @@ async def create_link(user_id: str): ```python from supertokens_python.recipe.emailpassword.syncio import create_reset_password_link -from supertokens_python.recipe.emailpassword.interfaces import CreateResetPasswordLinkOkResult -def create_link(user_id: str): - link_res = create_reset_password_link("public", user_id) +def create_link(user_id: str, email: str): + link = create_reset_password_link("public", user_id, email) - if isinstance(link_res, CreateResetPasswordLinkOkResult): - link = link_res.link + if isinstance(link, str): print(link) else: print("user does not exist or is not an email password user") diff --git a/v2/thirdpartyemailpassword/common-customizations/reset-password/post-reset-password.mdx b/v2/thirdpartyemailpassword/common-customizations/reset-password/post-reset-password.mdx index debec5a74..1bf771214 100644 --- a/v2/thirdpartyemailpassword/common-customizations/reset-password/post-reset-password.mdx +++ b/v2/thirdpartyemailpassword/common-customizations/reset-password/post-reset-password.mdx @@ -114,7 +114,10 @@ func main() { ```python from supertokens_python import init, InputAppInfo from supertokens_python.recipe import emailpassword -from supertokens_python.recipe.emailpassword.interfaces import APIInterface +from supertokens_python.recipe.emailpassword.interfaces import ( + APIInterface, + PasswordResetPostOkResult, +) from supertokens_python.recipe.emailpassword.interfaces import APIOptions from supertokens_python.recipe.emailpassword.types import FormField from typing import Dict, List, Any @@ -136,13 +139,15 @@ def override_apis(original_implementation: APIInterface): ) # Then we check if it was successfully completed - if response.status == "OK": + if isinstance(response, PasswordResetPostOkResult): pass # TODO: post password reset logic return response original_implementation.password_reset_post = password_reset_post return original_implementation + + # highlight-end init( From 49e2a6e21e102d4b5e567792f74a0db5fa4bba1a Mon Sep 17 00:00:00 2001 From: rishabhpoddar Date: Thu, 17 Oct 2024 19:58:55 +0530 Subject: [PATCH 10/34] fixes more docs --- .../handling-signup-success.mdx | 1 + .../sessions/anonymous-session.mdx | 54 ++-- .../sessions/claims/access-token-payload.mdx | 48 ++-- .../sessions/claims/claim-validators.mdx | 64 +++-- .../sessions/error-handling.mdx | 3 +- .../sessions/user-impersonation.mdx | 33 ++- .../signup-form/adding-fields.mdx | 45 ++- .../common-customizations/userid-format.mdx | 48 +++- .../emailpassword-changes.mdx | 256 ++++++++++++------ .../sessions/anonymous-session.mdx | 54 ++-- .../sessions/claims/access-token-payload.mdx | 48 ++-- .../sessions/claims/claim-validators.mdx | 64 +++-- .../sessions/error-handling.mdx | 3 +- .../sessions/user-impersonation.mdx | 33 ++- .../sessions/anonymous-session.mdx | 54 ++-- .../sessions/claims/access-token-payload.mdx | 48 ++-- .../sessions/claims/claim-validators.mdx | 64 +++-- .../sessions/error-handling.mdx | 3 +- .../sessions/user-impersonation.mdx | 33 ++- .../sessions/anonymous-session.mdx | 54 ++-- .../sessions/claims/access-token-payload.mdx | 48 ++-- .../sessions/claims/claim-validators.mdx | 64 +++-- .../sessions/error-handling.mdx | 3 +- .../sessions/user-impersonation.mdx | 33 ++- .../sessions/anonymous-session.mdx | 54 ++-- .../sessions/claims/access-token-payload.mdx | 48 ++-- .../sessions/claims/claim-validators.mdx | 64 +++-- .../sessions/error-handling.mdx | 3 +- .../sessions/user-impersonation.mdx | 33 ++- .../signup-form/adding-fields.mdx | 45 ++- .../sessions/anonymous-session.mdx | 54 ++-- .../sessions/claims/access-token-payload.mdx | 48 ++-- .../sessions/claims/claim-validators.mdx | 64 +++-- .../sessions/error-handling.mdx | 3 +- .../sessions/user-impersonation.mdx | 33 ++- 35 files changed, 1028 insertions(+), 579 deletions(-) diff --git a/v2/emailpassword/common-customizations/handling-signup-success.mdx b/v2/emailpassword/common-customizations/handling-signup-success.mdx index 66a063f45..3f5ed1349 100644 --- a/v2/emailpassword/common-customizations/handling-signup-success.mdx +++ b/v2/emailpassword/common-customizations/handling-signup-success.mdx @@ -506,6 +506,7 @@ def override_email_password_apis(original_implementation: APIInterface): if ( isinstance(response, SignUpPostOkResult) and len(response.user.login_methods) == 1 + and session is None ): _id = response.user.id emails = response.user.emails diff --git a/v2/emailpassword/common-customizations/sessions/anonymous-session.mdx b/v2/emailpassword/common-customizations/sessions/anonymous-session.mdx index 02390d177..9ceb65797 100644 --- a/v2/emailpassword/common-customizations/sessions/anonymous-session.mdx +++ b/v2/emailpassword/common-customizations/sessions/anonymous-session.mdx @@ -278,31 +278,37 @@ from supertokens_python import init, InputAppInfo, get_request_from_user_context from supertokens_python.recipe import session from supertokens_python.recipe.session.interfaces import RecipeInterface from typing import Any, Dict, Optional +from supertokens_python.types import RecipeUserId def override_functions(original_implementation: RecipeInterface): - original_implementation_create_new_session = original_implementation.create_new_session - - async def create_new_session(user_id: str, - access_token_payload: Optional[Dict[str, Any]], - session_data_in_database: Optional[Dict[str, Any]], - disable_anti_csrf: Optional[bool], - tenant_id: str, - user_context: Dict[str, Any]): - - request=get_request_from_user_context(user_context) + original_implementation_create_new_session = ( + original_implementation.create_new_session + ) + + async def create_new_session( + user_id: str, + recipe_user_id: RecipeUserId, + access_token_payload: Optional[Dict[str, Any]], + session_data_in_database: Optional[Dict[str, Any]], + disable_anti_csrf: Optional[bool], + tenant_id: str, + user_context: Dict[str, Any], + ): + + request = get_request_from_user_context(user_context) jwt: Optional[str] = None if request is not None: - jwt=request.get_cookie("jwt") + jwt = request.get_cookie("jwt") else: # # This is possible if the function is triggered from the user management dashboard - # + # # In this case because we cannot read the JWT, we create a session without the custom # payload properties # - jwt=None + jwt = None if jwt is not None: # verify JWT using a JWT verification library.. @@ -315,26 +321,30 @@ def override_functions(original_implementation: RecipeInterface): access_token_payload = {} access_token_payload["someKey"] = jwt_payload["someKey"] - return await original_implementation_create_new_session(user_id, access_token_payload, session_data_in_database, disable_anti_csrf, tenant_id, user_context) + return await original_implementation_create_new_session( + user_id, + recipe_user_id, + access_token_payload, + session_data_in_database, + disable_anti_csrf, + tenant_id, + user_context, + ) original_implementation.create_new_session = create_new_session return original_implementation init( - app_info=InputAppInfo( - api_domain="...", app_name="...", website_domain="..."), - - framework='...', # type: ignore + app_info=InputAppInfo(api_domain="...", app_name="...", website_domain="..."), + framework="...", # type: ignore recipe_list=[ session.init( # highlight-start - override=session.InputOverrideConfig( - functions=override_functions - ) + override=session.InputOverrideConfig(functions=override_functions) # highlight-end ) - ] + ], ) ``` diff --git a/v2/emailpassword/common-customizations/sessions/claims/access-token-payload.mdx b/v2/emailpassword/common-customizations/sessions/claims/access-token-payload.mdx index ac18be4b9..3563fef3a 100644 --- a/v2/emailpassword/common-customizations/sessions/claims/access-token-payload.mdx +++ b/v2/emailpassword/common-customizations/sessions/claims/access-token-payload.mdx @@ -129,43 +129,53 @@ from supertokens_python import init, InputAppInfo from supertokens_python.recipe import session from supertokens_python.recipe.session.interfaces import RecipeInterface from typing import Any, Dict, Optional +from supertokens_python.types import RecipeUserId def override_functions(original_implementation: RecipeInterface): - original_implementation_create_new_session = original_implementation.create_new_session - - async def create_new_session(user_id: str, - access_token_payload: Optional[Dict[str, Any]], - session_data_in_database: Optional[Dict[str, Any]], - disable_anti_csrf: Optional[bool], - tenant_id: str, - user_context: Dict[str, Any]): + original_implementation_create_new_session = ( + original_implementation.create_new_session + ) + + async def create_new_session( + user_id: str, + recipe_user_id: RecipeUserId, + access_token_payload: Optional[Dict[str, Any]], + session_data_in_database: Optional[Dict[str, Any]], + disable_anti_csrf: Optional[bool], + tenant_id: str, + user_context: Dict[str, Any], + ): if access_token_payload is None: access_token_payload = {} # This goes in the access token, and is available to read on the frontend. - access_token_payload["someKey"] = 'someValue' - - return await original_implementation_create_new_session(user_id, access_token_payload, session_data_in_database, disable_anti_csrf, tenant_id, user_context) + access_token_payload["someKey"] = "someValue" + + return await original_implementation_create_new_session( + user_id, + recipe_user_id, + access_token_payload, + session_data_in_database, + disable_anti_csrf, + tenant_id, + user_context, + ) original_implementation.create_new_session = create_new_session return original_implementation init( - app_info=InputAppInfo( - api_domain="...", app_name="...", website_domain="..."), - - framework='...', # type: ignore + app_info=InputAppInfo(api_domain="...", app_name="...", website_domain="..."), + framework="...", # type: ignore recipe_list=[ session.init( # highlight-start - override=session.InputOverrideConfig( - functions=override_functions - ) + override=session.InputOverrideConfig(functions=override_functions) # highlight-end ) - ] + ], ) ``` diff --git a/v2/emailpassword/common-customizations/sessions/claims/claim-validators.mdx b/v2/emailpassword/common-customizations/sessions/claims/claim-validators.mdx index 2a7b59a42..fdc63bbb1 100644 --- a/v2/emailpassword/common-customizations/sessions/claims/claim-validators.mdx +++ b/v2/emailpassword/common-customizations/sessions/claims/claim-validators.mdx @@ -455,24 +455,38 @@ from supertokens_python.recipe import session from supertokens_python.recipe.session.interfaces import RecipeInterface from supertokens_python.recipe.userroles import UserRoleClaim from typing import Any, Dict, Optional +from supertokens_python.types import RecipeUserId def override_functions(original_implementation: RecipeInterface): - original_implementation_create_new_session = original_implementation.create_new_session - - async def create_new_session(user_id: str, - access_token_payload: Optional[Dict[str, Any]], - session_data_in_database: Optional[Dict[str, Any]], - disable_anti_csrf: Optional[bool], - tenant_id: str, - user_context: Dict[str, Any]): + original_implementation_create_new_session = ( + original_implementation.create_new_session + ) + + async def create_new_session( + user_id: str, + recipe_user_id: RecipeUserId, + access_token_payload: Optional[Dict[str, Any]], + session_data_in_database: Optional[Dict[str, Any]], + disable_anti_csrf: Optional[bool], + tenant_id: str, + user_context: Dict[str, Any], + ): if access_token_payload is None: access_token_payload = {} access_token_payload = { **access_token_payload, # highlight-next-line - **(await UserRoleClaim.build(user_id, tenant_id, user_context)) + **( + await UserRoleClaim.build( + user_id, + recipe_user_id, + tenant_id, + access_token_payload, + user_context, + ) + ), } # At this step, the access token paylaod looks like this: @@ -483,24 +497,26 @@ def override_functions(original_implementation: RecipeInterface): # } # } - return await original_implementation_create_new_session(user_id, access_token_payload, session_data_in_database, disable_anti_csrf, tenant_id, user_context) + return await original_implementation_create_new_session( + user_id, + recipe_user_id, + access_token_payload, + session_data_in_database, + disable_anti_csrf, + tenant_id, + user_context, + ) original_implementation.create_new_session = create_new_session return original_implementation init( - app_info=InputAppInfo( - api_domain="...", app_name="...", website_domain="..."), - - framework='...', # type: ignore + app_info=InputAppInfo(api_domain="...", app_name="...", website_domain="..."), + framework="...", # type: ignore recipe_list=[ - session.init( - override=session.InputOverrideConfig( - functions=override_functions - ) - ) - ] + session.init(override=session.InputOverrideConfig(functions=override_functions)) + ], ) ``` @@ -637,7 +653,7 @@ from supertokens_python.recipe.session.interfaces import SessionContainer from supertokens_python.recipe.session.claims import BooleanClaim SecondFactorClaim = BooleanClaim( - key="2fa-completed", fetch_value=lambda _, __, ___: False) + key="2fa-completed", fetch_value=lambda _, __, ___, _____, ______: False) async def set_2fa_claim_as_completed(session: SessionContainer): await session.set_claim_value(SecondFactorClaim, True) @@ -651,7 +667,7 @@ from supertokens_python.recipe.session.interfaces import SessionContainer from supertokens_python.recipe.session.claims import BooleanClaim SecondFactorClaim = BooleanClaim( - key="2fa-completed", fetch_value=lambda _, __, ___: False) + key="2fa-completed", fetch_value=lambda _, __, ___, _____, ______: False) def set_2fa_claim_as_completed(session: SessionContainer): session.sync_set_claim_value(SecondFactorClaim, True) @@ -759,9 +775,10 @@ from supertokens_python.recipe import session from supertokens_python.recipe.session.interfaces import RecipeInterface, SessionClaimValidator from typing import Any, Dict, List from supertokens_python.recipe.session.claims import BooleanClaim +from supertokens_python.types import RecipeUserId SecondFactorClaim = BooleanClaim( - key="2fa-completed", fetch_value=lambda _, __, ___: False) + key="2fa-completed", fetch_value=lambda _, __, ___, _____, ______: False) def override_functions(original_implementation: RecipeInterface): @@ -769,6 +786,7 @@ def override_functions(original_implementation: RecipeInterface): def get_global_claim_validators( tenant_id: str, user_id: str, + recipe_user_id: RecipeUserId, claim_validators_added_by_other_recipes: List[SessionClaimValidator], user_context: Dict[str, Any], ): diff --git a/v2/emailpassword/common-customizations/sessions/error-handling.mdx b/v2/emailpassword/common-customizations/sessions/error-handling.mdx index c6871d171..70a0f76c3 100644 --- a/v2/emailpassword/common-customizations/sessions/error-handling.mdx +++ b/v2/emailpassword/common-customizations/sessions/error-handling.mdx @@ -278,8 +278,9 @@ func main() { from supertokens_python import init, InputAppInfo from supertokens_python.recipe import session from supertokens_python.framework import BaseRequest, BaseResponse +from supertokens_python.types import RecipeUserId -async def token_theft_detected_callback(req: BaseRequest, session_handle: str, user_id: str, response: BaseResponse): +async def token_theft_detected_callback(req: BaseRequest, session_handle: str, user_id: str, recipe_user_id: RecipeUserId, response: BaseResponse): # TODO: Write your own logic and then send a 401 response to the frontend return response diff --git a/v2/emailpassword/common-customizations/sessions/user-impersonation.mdx b/v2/emailpassword/common-customizations/sessions/user-impersonation.mdx index 2b14c1630..1897a872d 100644 --- a/v2/emailpassword/common-customizations/sessions/user-impersonation.mdx +++ b/v2/emailpassword/common-customizations/sessions/user-impersonation.mdx @@ -518,7 +518,8 @@ from fastapi.responses import JSONResponse from supertokens_python.recipe.session import SessionContainer from fastapi import Depends from supertokens_python.recipe.userroles import UserRoleClaim -from supertokens_python.recipe.emailpassword.asyncio import get_user_by_email +from supertokens_python.asyncio import list_users_by_account_info +from supertokens_python.types import AccountInfo from fastapi import Request @@ -536,19 +537,25 @@ async def impersonate( email = "..." # get from request body # we use the email password recipe here, but you can use the recipe you use - user = await get_user_by_email("public", email) + user = await list_users_by_account_info("public", AccountInfo(email=email)) - if user is None: + if len(user) == 0: # return a 400 error to the client return - await create_new_session(request, "public", user.user_id, {"isImpersonation": True}) + await create_new_session( + request, + "public", + user[0].login_methods[0].recipe_user_id, + {"isImpersonation": True}, + ) # a new session has been created. # - an access & refresh token has been attached to the response's cookie # - a new row has been inserted into the database for this new session return JSONResponse({"message": "Impersonation complete!"}) + ``` @@ -560,7 +567,8 @@ from flask import jsonify from flask.wrappers import Request from supertokens_python.recipe.session.framework.flask import verify_session from supertokens_python.recipe.userroles import UserRoleClaim -from supertokens_python.recipe.emailpassword.syncio import get_user_by_email +from supertokens_python.syncio import list_users_by_account_info +from supertokens_python.types import AccountInfo @app.route("/impersonate", methods=["POST"]) # type: ignore @verify_session( @@ -572,13 +580,13 @@ def login(request: Request): email = "..." # get from request body # we use the email password recipe here, but you can use the recipe you use - user = get_user_by_email("public", email) + user = list_users_by_account_info("public", AccountInfo(email=email)) - if user is None: + if len(user) == 0: # return a 400 error to the client return - create_new_session(request, "public", user.user_id, {"isImpersonation": True}) + create_new_session(request, "public", user[0].login_methods[0].recipe_user_id, {"isImpersonation": True}) # a new session has been created. # - an access & refresh token has been attached to the response's cookie @@ -595,7 +603,8 @@ from supertokens_python.recipe.session.framework.django.asyncio import verify_se from supertokens_python.recipe.session.asyncio import create_new_session from django.http import HttpRequest, JsonResponse from supertokens_python.recipe.userroles import UserRoleClaim -from supertokens_python.recipe.emailpassword.asyncio import get_user_by_email +from supertokens_python.asyncio import list_users_by_account_info +from supertokens_python.types import AccountInfo @verify_session( # We add the UserRoleClaim's includes validator @@ -606,13 +615,13 @@ async def impersonate(request: HttpRequest): email = "..." # get from request body # we use the email password recipe here, but you can use the recipe you use - user = await get_user_by_email("public", email) + user = await list_users_by_account_info("public", AccountInfo(email=email)) - if user is None: + if len(user) == 0: # return a 400 error to the client return - await create_new_session(request, "public", user.user_id, {"isImpersonation": True}) + await create_new_session(request, "public", user[0].login_methods[0].recipe_user_id, {"isImpersonation": True}) # a new session has been created. # - an access & refresh token has been attached to the response's cookie diff --git a/v2/emailpassword/common-customizations/signup-form/adding-fields.mdx b/v2/emailpassword/common-customizations/signup-form/adding-fields.mdx index 1a4daa911..14e80c643 100644 --- a/v2/emailpassword/common-customizations/signup-form/adding-fields.mdx +++ b/v2/emailpassword/common-customizations/signup-form/adding-fields.mdx @@ -442,35 +442,54 @@ func main() { ```python from supertokens_python import init, InputAppInfo from supertokens_python.recipe import emailpassword, session -from supertokens_python.recipe.emailpassword.interfaces import APIInterface, APIOptions, SignUpPostOkResult +from supertokens_python.recipe.emailpassword.interfaces import ( + APIInterface, + APIOptions, + SignUpPostOkResult, +) from supertokens_python.recipe.emailpassword.types import FormField -from typing import List, Dict, Any +from typing import List, Dict, Any, Union +from supertokens_python.recipe.session.interfaces import SessionContainer + # highlight-start def override_email_password_apis(original_implementation: APIInterface): original_sign_up_post = original_implementation.sign_up_post - - async def sign_up_post(form_fields: List[FormField], tenant_id: str, - api_options: APIOptions, - user_context: Dict[str, Any]): + + async def sign_up_post( + form_fields: List[FormField], + tenant_id: str, + session: Union[SessionContainer, None], + should_try_linking_with_session_user: Union[bool, None], + api_options: APIOptions, + user_context: Dict[str, Any], + ): # First we call the original implementation of sign_up_post. - response = await original_sign_up_post(form_fields, tenant_id, api_options, user_context) + response = await original_sign_up_post( + form_fields, + tenant_id, + session, + should_try_linking_with_session_user, + api_options, + user_context, + ) # Post sign up response, we check if it was successful if isinstance(response, SignUpPostOkResult): - _ = response.user.user_id - __ = response.user.email + pass # TODO: use the input form fields values for custom logic return response - + original_implementation.sign_up_post = sign_up_post return original_implementation + + # highlight-end init( app_info=InputAppInfo(api_domain="...", app_name="...", website_domain="..."), - framework='...', # type: ignore + framework="...", # type: ignore recipe_list=[ emailpassword.init( # highlight-start @@ -479,8 +498,8 @@ init( ) # highlight-end ), - session.init() - ] + session.init(), + ], ) ``` diff --git a/v2/emailpassword/common-customizations/userid-format.mdx b/v2/emailpassword/common-customizations/userid-format.mdx index 0c756ba3e..cc404c48d 100644 --- a/v2/emailpassword/common-customizations/userid-format.mdx +++ b/v2/emailpassword/common-customizations/userid-format.mdx @@ -136,26 +136,48 @@ func main() { from supertokens_python import init, InputAppInfo from supertokens_python.asyncio import create_user_id_mapping from supertokens_python.recipe import emailpassword, session -from supertokens_python.recipe.emailpassword.interfaces import RecipeInterface, SignUpOkResult -from typing import Dict, Any +from supertokens_python.recipe.emailpassword.interfaces import ( + RecipeInterface, + SignUpOkResult, +) +from typing import Dict, Any, Union +from supertokens_python.recipe.session.interfaces import SessionContainer +from supertokens_python.types import RecipeUserId -def override_emailpassword_functions(original_implementation: RecipeInterface) -> RecipeInterface: +def override_emailpassword_functions( + original_implementation: RecipeInterface, +) -> RecipeInterface: original_sign_up = original_implementation.sign_up async def sign_up( email: str, password: str, tenant_id: str, - user_context: Dict[str, Any] + session: Union[SessionContainer, None], + should_try_linking_with_session_user: Union[bool, None], + user_context: Dict[str, Any], ): - result = await original_sign_up(email, password, tenant_id, user_context) - - if isinstance(result, SignUpOkResult): + result = await original_sign_up( + email, + password, + tenant_id, + session, + should_try_linking_with_session_user, + user_context, + ) + + if ( + isinstance(result, SignUpOkResult) + and len(result.user.login_methods) == 1 + and session is None + ): # highlight-start external_user_id = "" - await create_user_id_mapping(result.user.user_id, external_user_id) - result.user.user_id = external_user_id + await create_user_id_mapping(result.user.id, external_user_id) + result.user.id = external_user_id + result.user.login_methods[0].recipe_user_id = RecipeUserId(external_user_id) + result.recipe_user_id = RecipeUserId(external_user_id) # highlight-end return result @@ -166,9 +188,8 @@ def override_emailpassword_functions(original_implementation: RecipeInterface) - init( - app_info=InputAppInfo( - api_domain="...", app_name="...", website_domain="..."), - framework='...', # type: ignore + app_info=InputAppInfo(api_domain="...", app_name="...", website_domain="..."), + framework="...", # type: ignore recipe_list=[ emailpassword.init( # highlight-start @@ -178,9 +199,8 @@ init( # highlight-end ), session.init(), - ] + ], ) - ``` diff --git a/v2/emailpassword/common-customizations/username-password/emailpassword-changes.mdx b/v2/emailpassword/common-customizations/username-password/emailpassword-changes.mdx index 5869e796a..4cf48a6a7 100644 --- a/v2/emailpassword/common-customizations/username-password/emailpassword-changes.mdx +++ b/v2/emailpassword/common-customizations/username-password/emailpassword-changes.mdx @@ -540,11 +540,18 @@ func main() { ```python from supertokens_python import init, InputAppInfo from supertokens_python.recipe import emailpassword -from supertokens_python.recipe.emailpassword.utils import InputSignUpFeature, InputOverrideConfig +from supertokens_python.recipe.emailpassword.utils import ( + InputSignUpFeature, + InputOverrideConfig, +) from supertokens_python.recipe.emailpassword.types import FormField -from supertokens_python.recipe.emailpassword.interfaces import APIInterface, APIOptions, SignUpPostOkResult, SignUpPostEmailAlreadyExistsError -from supertokens_python.types import GeneralErrorResponse +from supertokens_python.recipe.emailpassword.interfaces import ( + APIInterface, + APIOptions, + SignUpPostOkResult, +) from typing import Dict, List, Union, Any +from supertokens_python.recipe.session.interfaces import SessionContainer email_user_map: Dict[str, str] = {} @@ -558,6 +565,7 @@ async def get_user_using_email(email: str): return email_user_map[email] return None + # highlight-start async def save_email_for_user(email: str, user_id: str): # TODO: Save email and userId mapping @@ -565,15 +573,26 @@ async def save_email_for_user(email: str, user_id: str): # this is just a placeholder implementation email_user_map[email] = user_id + def apis_override(original: APIInterface): og_sign_up_post = original.sign_up_post - async def sign_up_post(form_fields: List[FormField], tenant_id: str, api_options: APIOptions, - user_context: Dict[str, Any], - ) -> Union[ - SignUpPostOkResult, SignUpPostEmailAlreadyExistsError, GeneralErrorResponse - ]: - response = await og_sign_up_post(form_fields, tenant_id, api_options, user_context) + async def sign_up_post( + form_fields: List[FormField], + tenant_id: str, + session: Union[SessionContainer, None], + should_try_linking_with_session_user: Union[bool, None], + api_options: APIOptions, + user_context: Dict[str, Any], + ): + response = await og_sign_up_post( + form_fields, + tenant_id, + session, + should_try_linking_with_session_user, + api_options, + user_context, + ) if isinstance(response, SignUpPostOkResult): # sign up successful actual_email = "" @@ -586,25 +605,28 @@ def apis_override(original: APIInterface): # in the form field config pass else: - await save_email_for_user(actual_email, response.user.user_id) + await save_email_for_user(actual_email, response.user.id) return response original.sign_up_post = sign_up_post return original + + # highlight-end init( - app_info=InputAppInfo( - api_domain="...", app_name="...", website_domain="..."), - framework='...', # type: ignore + app_info=InputAppInfo(api_domain="...", app_name="...", website_domain="..."), + framework="...", # type: ignore recipe_list=[ emailpassword.init( - sign_up_feature=InputSignUpFeature(form_fields=[ - # from previous code snippets... - ]), - override=InputOverrideConfig(apis=apis_override) + sign_up_feature=InputSignUpFeature( + form_fields=[ + # from previous code snippets... + ] + ), + override=InputOverrideConfig(apis=apis_override), ) - ] + ], ) ``` @@ -796,11 +818,15 @@ func main() { ```python from supertokens_python import init, InputAppInfo from supertokens_python.recipe import emailpassword -from supertokens_python.recipe.emailpassword.asyncio import get_user_by_id -from supertokens_python.recipe.emailpassword.utils import InputSignUpFeature, InputOverrideConfig -from supertokens_python.recipe.emailpassword.interfaces import RecipeInterface, SignInOkResult, SignInWrongCredentialsError +from supertokens_python.recipe.emailpassword.utils import ( + InputSignUpFeature, + InputOverrideConfig, +) +from supertokens_python.recipe.emailpassword.interfaces import RecipeInterface from typing import Dict, Union, Any from re import fullmatch +from supertokens_python.recipe.session.interfaces import SessionContainer +from supertokens_python.asyncio import get_user email_user_map: Dict[str, str] = {} @@ -821,50 +847,81 @@ async def save_email_for_user(email: str, user_id: str): # this is just a placeholder implementation email_user_map[email] = user_id + # highlight-start def is_input_email(email: str): - return fullmatch( - r'^(([^<>()\[\]\\.,;:\s@"]+(\.[^<>()\[\]\\.,;:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,' - r"3}\.[0-9]{1,3}\])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$", - email - ) is not None + return ( + fullmatch( + r'^(([^<>()\[\]\\.,;:\s@"]+(\.[^<>()\[\]\\.,;:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,' + r"3}\.[0-9]{1,3}\])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$", + email, + ) + is not None + ) def recipe_override(original: RecipeInterface): og_sign_in = original.sign_in async def sign_in( - email: str, password: str, tenant_id: str, user_context: Dict[str, Any] - ) -> Union[SignInOkResult, SignInWrongCredentialsError]: + email: str, + password: str, + tenant_id: str, + session: Union[SessionContainer, None], + should_try_linking_with_session_user: Union[bool, None], + user_context: Dict[str, Any], + ): if is_input_email(email): user_id = await get_user_using_email(email) if user_id is not None: - supertokens_user = await get_user_by_id(user_id) + supertokens_user = await get_user(user_id) if supertokens_user is not None: - email = supertokens_user.email - return await og_sign_in(email, password, tenant_id, user_context) + login_method = next( + ( + lm + for lm in supertokens_user.login_methods + if lm.recipe_user_id.get_as_string() == user_id + and lm.recipe_id == "emailpassword" + ), + None, + ) + if login_method is not None: + assert login_method.email is not None + email = login_method.email + return await og_sign_in( + email, + password, + tenant_id, + session, + should_try_linking_with_session_user, + user_context, + ) original.sign_in = sign_in return original + + # highlight-end init( - app_info=InputAppInfo( - api_domain="...", app_name="...", website_domain="..."), - framework='...', # type: ignore + app_info=InputAppInfo(api_domain="...", app_name="...", website_domain="..."), + framework="...", # type: ignore recipe_list=[ emailpassword.init( - sign_up_feature=InputSignUpFeature(form_fields=[ - # from previous code snippets... - ]), + sign_up_feature=InputSignUpFeature( + form_fields=[ + # from previous code snippets... + ] + ), override=InputOverrideConfig( # apis=..., from previous code snippet - functions=recipe_override) + functions=recipe_override + ), ) - ] + ], ) ``` @@ -1163,12 +1220,19 @@ func main() { ```python from supertokens_python import init, InputAppInfo from supertokens_python.recipe import emailpassword -from supertokens_python.recipe.emailpassword.asyncio import get_user_by_id, get_user_by_email -from supertokens_python.recipe.emailpassword.utils import InputSignUpFeature, InputOverrideConfig +from supertokens_python.asyncio import get_user, list_users_by_account_info +from supertokens_python.types import AccountInfo +from supertokens_python.recipe.emailpassword.utils import ( + InputSignUpFeature, + InputOverrideConfig, +) from supertokens_python.recipe.emailpassword.types import FormField -from supertokens_python.recipe.emailpassword.interfaces import APIInterface, APIOptions, GeneratePasswordResetTokenPostOkResult +from supertokens_python.recipe.emailpassword.interfaces import ( + APIInterface, + APIOptions, +) from supertokens_python.types import GeneralErrorResponse -from typing import Dict, List, Union, Any +from typing import Dict, List, Any from re import fullmatch email_user_map: Dict[str, str] = {} @@ -1192,11 +1256,15 @@ async def save_email_for_user(email: str, user_id: str): def is_input_email(email: str): - return fullmatch( - r'^(([^<>()\[\]\\.,;:\s@"]+(\.[^<>()\[\]\\.,;:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,' - r"3}\.[0-9]{1,3}\])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$", - email - ) is not None + return ( + fullmatch( + r'^(([^<>()\[\]\\.,;:\s@"]+(\.[^<>()\[\]\\.,;:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,' + r"3}\.[0-9]{1,3}\])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$", + email, + ) + is not None + ) + # highlight-start async def get_email_using_user_id(user_id: str): @@ -1209,9 +1277,12 @@ async def get_email_using_user_id(user_id: str): def apis_override(original: APIInterface): og_generate_password_reset_token_post = original.generate_password_reset_token_post - async def generate_password_reset_token_post(form_fields: List[FormField], tenant_id: str, - api_options: APIOptions, user_context: Dict[str, Any], - ) -> Union[GeneratePasswordResetTokenPostOkResult, GeneralErrorResponse]: + async def generate_password_reset_token_post( + form_fields: List[FormField], + tenant_id: str, + api_options: APIOptions, + user_context: Dict[str, Any], + ): email_or_username = "" for field in form_fields: if field.id == "email": @@ -1219,49 +1290,80 @@ def apis_override(original: APIInterface): if is_input_email(email_or_username): user_id = await get_user_using_email(email_or_username) if user_id is not None: - supertokens_user = await get_user_by_id(user_id) + supertokens_user = await get_user(user_id) if supertokens_user is not None: - # we replace the input form field's array item - # to contain the username instead of the email. - new_form_fields: List[FormField] = [] - for field in form_fields: - if field.id == "email": - new_form_fields.append( - FormField(id="email", value=supertokens_user.email)) - else: - new_form_fields.append(field) - form_fields = new_form_fields + # we find the right login method for this user + # based on the user ID. + login_method = next( + ( + lm + for lm in supertokens_user.login_methods + if lm.recipe_user_id == user_id + and lm.recipe_id == "emailpassword" + ), + None, + ) + if login_method is not None: + # we replace the input form field's array item + # to contain the username instead of the email. + form_fields = [ + field for field in form_fields if field.id != "email" + ] + form_fields.append( + FormField(id="email", value=login_method.email) + ) username = "" for field in form_fields: if field.id == "email": username = field.value - supertokens_user = await get_user_by_email(tenant_id, username) - if supertokens_user is not None: - if (await get_email_using_user_id(supertokens_user.user_id)) is None: - return GeneralErrorResponse("You need to add an email to your account for resetting your password. Please contact support.") - - return await og_generate_password_reset_token_post(form_fields, tenant_id, api_options, user_context) + supertokens_user = await list_users_by_account_info( + tenant_id, AccountInfo(email=username) + ) + target_user = next( + ( + u + for u in supertokens_user + if any( + lm.email == username and lm.recipe_id == "emailpassword" + for lm in u.login_methods + ) + ), + None, + ) + if target_user is not None: + if (await get_email_using_user_id(target_user.id)) is None: + return GeneralErrorResponse( + "You need to add an email to your account for resetting your password. Please contact support." + ) + + return await og_generate_password_reset_token_post( + form_fields, tenant_id, api_options, user_context + ) original.generate_password_reset_token_post = generate_password_reset_token_post return original + + # highlight-end init( - app_info=InputAppInfo( - api_domain="...", app_name="...", website_domain="..."), - framework='...', # type: ignore + app_info=InputAppInfo(api_domain="...", app_name="...", website_domain="..."), + framework="...", # type: ignore recipe_list=[ emailpassword.init( - sign_up_feature=InputSignUpFeature(form_fields=[ - # from previous code snippets... - ]), + sign_up_feature=InputSignUpFeature( + form_fields=[ + # from previous code snippets... + ] + ), override=InputOverrideConfig( - # functions=..., from previous code snippet - apis=apis_override) + # functions=..., from previous code snippet + apis=apis_override + ), ) - ] + ], ) ``` diff --git a/v2/passwordless/common-customizations/sessions/anonymous-session.mdx b/v2/passwordless/common-customizations/sessions/anonymous-session.mdx index 02390d177..9ceb65797 100644 --- a/v2/passwordless/common-customizations/sessions/anonymous-session.mdx +++ b/v2/passwordless/common-customizations/sessions/anonymous-session.mdx @@ -278,31 +278,37 @@ from supertokens_python import init, InputAppInfo, get_request_from_user_context from supertokens_python.recipe import session from supertokens_python.recipe.session.interfaces import RecipeInterface from typing import Any, Dict, Optional +from supertokens_python.types import RecipeUserId def override_functions(original_implementation: RecipeInterface): - original_implementation_create_new_session = original_implementation.create_new_session - - async def create_new_session(user_id: str, - access_token_payload: Optional[Dict[str, Any]], - session_data_in_database: Optional[Dict[str, Any]], - disable_anti_csrf: Optional[bool], - tenant_id: str, - user_context: Dict[str, Any]): - - request=get_request_from_user_context(user_context) + original_implementation_create_new_session = ( + original_implementation.create_new_session + ) + + async def create_new_session( + user_id: str, + recipe_user_id: RecipeUserId, + access_token_payload: Optional[Dict[str, Any]], + session_data_in_database: Optional[Dict[str, Any]], + disable_anti_csrf: Optional[bool], + tenant_id: str, + user_context: Dict[str, Any], + ): + + request = get_request_from_user_context(user_context) jwt: Optional[str] = None if request is not None: - jwt=request.get_cookie("jwt") + jwt = request.get_cookie("jwt") else: # # This is possible if the function is triggered from the user management dashboard - # + # # In this case because we cannot read the JWT, we create a session without the custom # payload properties # - jwt=None + jwt = None if jwt is not None: # verify JWT using a JWT verification library.. @@ -315,26 +321,30 @@ def override_functions(original_implementation: RecipeInterface): access_token_payload = {} access_token_payload["someKey"] = jwt_payload["someKey"] - return await original_implementation_create_new_session(user_id, access_token_payload, session_data_in_database, disable_anti_csrf, tenant_id, user_context) + return await original_implementation_create_new_session( + user_id, + recipe_user_id, + access_token_payload, + session_data_in_database, + disable_anti_csrf, + tenant_id, + user_context, + ) original_implementation.create_new_session = create_new_session return original_implementation init( - app_info=InputAppInfo( - api_domain="...", app_name="...", website_domain="..."), - - framework='...', # type: ignore + app_info=InputAppInfo(api_domain="...", app_name="...", website_domain="..."), + framework="...", # type: ignore recipe_list=[ session.init( # highlight-start - override=session.InputOverrideConfig( - functions=override_functions - ) + override=session.InputOverrideConfig(functions=override_functions) # highlight-end ) - ] + ], ) ``` diff --git a/v2/passwordless/common-customizations/sessions/claims/access-token-payload.mdx b/v2/passwordless/common-customizations/sessions/claims/access-token-payload.mdx index ac18be4b9..3563fef3a 100644 --- a/v2/passwordless/common-customizations/sessions/claims/access-token-payload.mdx +++ b/v2/passwordless/common-customizations/sessions/claims/access-token-payload.mdx @@ -129,43 +129,53 @@ from supertokens_python import init, InputAppInfo from supertokens_python.recipe import session from supertokens_python.recipe.session.interfaces import RecipeInterface from typing import Any, Dict, Optional +from supertokens_python.types import RecipeUserId def override_functions(original_implementation: RecipeInterface): - original_implementation_create_new_session = original_implementation.create_new_session - - async def create_new_session(user_id: str, - access_token_payload: Optional[Dict[str, Any]], - session_data_in_database: Optional[Dict[str, Any]], - disable_anti_csrf: Optional[bool], - tenant_id: str, - user_context: Dict[str, Any]): + original_implementation_create_new_session = ( + original_implementation.create_new_session + ) + + async def create_new_session( + user_id: str, + recipe_user_id: RecipeUserId, + access_token_payload: Optional[Dict[str, Any]], + session_data_in_database: Optional[Dict[str, Any]], + disable_anti_csrf: Optional[bool], + tenant_id: str, + user_context: Dict[str, Any], + ): if access_token_payload is None: access_token_payload = {} # This goes in the access token, and is available to read on the frontend. - access_token_payload["someKey"] = 'someValue' - - return await original_implementation_create_new_session(user_id, access_token_payload, session_data_in_database, disable_anti_csrf, tenant_id, user_context) + access_token_payload["someKey"] = "someValue" + + return await original_implementation_create_new_session( + user_id, + recipe_user_id, + access_token_payload, + session_data_in_database, + disable_anti_csrf, + tenant_id, + user_context, + ) original_implementation.create_new_session = create_new_session return original_implementation init( - app_info=InputAppInfo( - api_domain="...", app_name="...", website_domain="..."), - - framework='...', # type: ignore + app_info=InputAppInfo(api_domain="...", app_name="...", website_domain="..."), + framework="...", # type: ignore recipe_list=[ session.init( # highlight-start - override=session.InputOverrideConfig( - functions=override_functions - ) + override=session.InputOverrideConfig(functions=override_functions) # highlight-end ) - ] + ], ) ``` diff --git a/v2/passwordless/common-customizations/sessions/claims/claim-validators.mdx b/v2/passwordless/common-customizations/sessions/claims/claim-validators.mdx index 2a7b59a42..fdc63bbb1 100644 --- a/v2/passwordless/common-customizations/sessions/claims/claim-validators.mdx +++ b/v2/passwordless/common-customizations/sessions/claims/claim-validators.mdx @@ -455,24 +455,38 @@ from supertokens_python.recipe import session from supertokens_python.recipe.session.interfaces import RecipeInterface from supertokens_python.recipe.userroles import UserRoleClaim from typing import Any, Dict, Optional +from supertokens_python.types import RecipeUserId def override_functions(original_implementation: RecipeInterface): - original_implementation_create_new_session = original_implementation.create_new_session - - async def create_new_session(user_id: str, - access_token_payload: Optional[Dict[str, Any]], - session_data_in_database: Optional[Dict[str, Any]], - disable_anti_csrf: Optional[bool], - tenant_id: str, - user_context: Dict[str, Any]): + original_implementation_create_new_session = ( + original_implementation.create_new_session + ) + + async def create_new_session( + user_id: str, + recipe_user_id: RecipeUserId, + access_token_payload: Optional[Dict[str, Any]], + session_data_in_database: Optional[Dict[str, Any]], + disable_anti_csrf: Optional[bool], + tenant_id: str, + user_context: Dict[str, Any], + ): if access_token_payload is None: access_token_payload = {} access_token_payload = { **access_token_payload, # highlight-next-line - **(await UserRoleClaim.build(user_id, tenant_id, user_context)) + **( + await UserRoleClaim.build( + user_id, + recipe_user_id, + tenant_id, + access_token_payload, + user_context, + ) + ), } # At this step, the access token paylaod looks like this: @@ -483,24 +497,26 @@ def override_functions(original_implementation: RecipeInterface): # } # } - return await original_implementation_create_new_session(user_id, access_token_payload, session_data_in_database, disable_anti_csrf, tenant_id, user_context) + return await original_implementation_create_new_session( + user_id, + recipe_user_id, + access_token_payload, + session_data_in_database, + disable_anti_csrf, + tenant_id, + user_context, + ) original_implementation.create_new_session = create_new_session return original_implementation init( - app_info=InputAppInfo( - api_domain="...", app_name="...", website_domain="..."), - - framework='...', # type: ignore + app_info=InputAppInfo(api_domain="...", app_name="...", website_domain="..."), + framework="...", # type: ignore recipe_list=[ - session.init( - override=session.InputOverrideConfig( - functions=override_functions - ) - ) - ] + session.init(override=session.InputOverrideConfig(functions=override_functions)) + ], ) ``` @@ -637,7 +653,7 @@ from supertokens_python.recipe.session.interfaces import SessionContainer from supertokens_python.recipe.session.claims import BooleanClaim SecondFactorClaim = BooleanClaim( - key="2fa-completed", fetch_value=lambda _, __, ___: False) + key="2fa-completed", fetch_value=lambda _, __, ___, _____, ______: False) async def set_2fa_claim_as_completed(session: SessionContainer): await session.set_claim_value(SecondFactorClaim, True) @@ -651,7 +667,7 @@ from supertokens_python.recipe.session.interfaces import SessionContainer from supertokens_python.recipe.session.claims import BooleanClaim SecondFactorClaim = BooleanClaim( - key="2fa-completed", fetch_value=lambda _, __, ___: False) + key="2fa-completed", fetch_value=lambda _, __, ___, _____, ______: False) def set_2fa_claim_as_completed(session: SessionContainer): session.sync_set_claim_value(SecondFactorClaim, True) @@ -759,9 +775,10 @@ from supertokens_python.recipe import session from supertokens_python.recipe.session.interfaces import RecipeInterface, SessionClaimValidator from typing import Any, Dict, List from supertokens_python.recipe.session.claims import BooleanClaim +from supertokens_python.types import RecipeUserId SecondFactorClaim = BooleanClaim( - key="2fa-completed", fetch_value=lambda _, __, ___: False) + key="2fa-completed", fetch_value=lambda _, __, ___, _____, ______: False) def override_functions(original_implementation: RecipeInterface): @@ -769,6 +786,7 @@ def override_functions(original_implementation: RecipeInterface): def get_global_claim_validators( tenant_id: str, user_id: str, + recipe_user_id: RecipeUserId, claim_validators_added_by_other_recipes: List[SessionClaimValidator], user_context: Dict[str, Any], ): diff --git a/v2/passwordless/common-customizations/sessions/error-handling.mdx b/v2/passwordless/common-customizations/sessions/error-handling.mdx index c6871d171..70a0f76c3 100644 --- a/v2/passwordless/common-customizations/sessions/error-handling.mdx +++ b/v2/passwordless/common-customizations/sessions/error-handling.mdx @@ -278,8 +278,9 @@ func main() { from supertokens_python import init, InputAppInfo from supertokens_python.recipe import session from supertokens_python.framework import BaseRequest, BaseResponse +from supertokens_python.types import RecipeUserId -async def token_theft_detected_callback(req: BaseRequest, session_handle: str, user_id: str, response: BaseResponse): +async def token_theft_detected_callback(req: BaseRequest, session_handle: str, user_id: str, recipe_user_id: RecipeUserId, response: BaseResponse): # TODO: Write your own logic and then send a 401 response to the frontend return response diff --git a/v2/passwordless/common-customizations/sessions/user-impersonation.mdx b/v2/passwordless/common-customizations/sessions/user-impersonation.mdx index 2b14c1630..1897a872d 100644 --- a/v2/passwordless/common-customizations/sessions/user-impersonation.mdx +++ b/v2/passwordless/common-customizations/sessions/user-impersonation.mdx @@ -518,7 +518,8 @@ from fastapi.responses import JSONResponse from supertokens_python.recipe.session import SessionContainer from fastapi import Depends from supertokens_python.recipe.userroles import UserRoleClaim -from supertokens_python.recipe.emailpassword.asyncio import get_user_by_email +from supertokens_python.asyncio import list_users_by_account_info +from supertokens_python.types import AccountInfo from fastapi import Request @@ -536,19 +537,25 @@ async def impersonate( email = "..." # get from request body # we use the email password recipe here, but you can use the recipe you use - user = await get_user_by_email("public", email) + user = await list_users_by_account_info("public", AccountInfo(email=email)) - if user is None: + if len(user) == 0: # return a 400 error to the client return - await create_new_session(request, "public", user.user_id, {"isImpersonation": True}) + await create_new_session( + request, + "public", + user[0].login_methods[0].recipe_user_id, + {"isImpersonation": True}, + ) # a new session has been created. # - an access & refresh token has been attached to the response's cookie # - a new row has been inserted into the database for this new session return JSONResponse({"message": "Impersonation complete!"}) + ``` @@ -560,7 +567,8 @@ from flask import jsonify from flask.wrappers import Request from supertokens_python.recipe.session.framework.flask import verify_session from supertokens_python.recipe.userroles import UserRoleClaim -from supertokens_python.recipe.emailpassword.syncio import get_user_by_email +from supertokens_python.syncio import list_users_by_account_info +from supertokens_python.types import AccountInfo @app.route("/impersonate", methods=["POST"]) # type: ignore @verify_session( @@ -572,13 +580,13 @@ def login(request: Request): email = "..." # get from request body # we use the email password recipe here, but you can use the recipe you use - user = get_user_by_email("public", email) + user = list_users_by_account_info("public", AccountInfo(email=email)) - if user is None: + if len(user) == 0: # return a 400 error to the client return - create_new_session(request, "public", user.user_id, {"isImpersonation": True}) + create_new_session(request, "public", user[0].login_methods[0].recipe_user_id, {"isImpersonation": True}) # a new session has been created. # - an access & refresh token has been attached to the response's cookie @@ -595,7 +603,8 @@ from supertokens_python.recipe.session.framework.django.asyncio import verify_se from supertokens_python.recipe.session.asyncio import create_new_session from django.http import HttpRequest, JsonResponse from supertokens_python.recipe.userroles import UserRoleClaim -from supertokens_python.recipe.emailpassword.asyncio import get_user_by_email +from supertokens_python.asyncio import list_users_by_account_info +from supertokens_python.types import AccountInfo @verify_session( # We add the UserRoleClaim's includes validator @@ -606,13 +615,13 @@ async def impersonate(request: HttpRequest): email = "..." # get from request body # we use the email password recipe here, but you can use the recipe you use - user = await get_user_by_email("public", email) + user = await list_users_by_account_info("public", AccountInfo(email=email)) - if user is None: + if len(user) == 0: # return a 400 error to the client return - await create_new_session(request, "public", user.user_id, {"isImpersonation": True}) + await create_new_session(request, "public", user[0].login_methods[0].recipe_user_id, {"isImpersonation": True}) # a new session has been created. # - an access & refresh token has been attached to the response's cookie diff --git a/v2/session/common-customizations/sessions/anonymous-session.mdx b/v2/session/common-customizations/sessions/anonymous-session.mdx index da6d355c0..abc584c01 100644 --- a/v2/session/common-customizations/sessions/anonymous-session.mdx +++ b/v2/session/common-customizations/sessions/anonymous-session.mdx @@ -278,31 +278,37 @@ from supertokens_python import init, InputAppInfo, get_request_from_user_context from supertokens_python.recipe import session from supertokens_python.recipe.session.interfaces import RecipeInterface from typing import Any, Dict, Optional +from supertokens_python.types import RecipeUserId def override_functions(original_implementation: RecipeInterface): - original_implementation_create_new_session = original_implementation.create_new_session - - async def create_new_session(user_id: str, - access_token_payload: Optional[Dict[str, Any]], - session_data_in_database: Optional[Dict[str, Any]], - disable_anti_csrf: Optional[bool], - tenant_id: str, - user_context: Dict[str, Any]): - - request=get_request_from_user_context(user_context) + original_implementation_create_new_session = ( + original_implementation.create_new_session + ) + + async def create_new_session( + user_id: str, + recipe_user_id: RecipeUserId, + access_token_payload: Optional[Dict[str, Any]], + session_data_in_database: Optional[Dict[str, Any]], + disable_anti_csrf: Optional[bool], + tenant_id: str, + user_context: Dict[str, Any], + ): + + request = get_request_from_user_context(user_context) jwt: Optional[str] = None if request is not None: - jwt=request.get_cookie("jwt") + jwt = request.get_cookie("jwt") else: # # This is possible if the function is triggered from the user management dashboard - # + # # In this case because we cannot read the JWT, we create a session without the custom # payload properties # - jwt=None + jwt = None if jwt is not None: # verify JWT using a JWT verification library.. @@ -315,26 +321,30 @@ def override_functions(original_implementation: RecipeInterface): access_token_payload = {} access_token_payload["someKey"] = jwt_payload["someKey"] - return await original_implementation_create_new_session(user_id, access_token_payload, session_data_in_database, disable_anti_csrf, tenant_id, user_context) + return await original_implementation_create_new_session( + user_id, + recipe_user_id, + access_token_payload, + session_data_in_database, + disable_anti_csrf, + tenant_id, + user_context, + ) original_implementation.create_new_session = create_new_session return original_implementation init( - app_info=InputAppInfo( - api_domain="...", app_name="...", website_domain="..."), - - framework='...', # type: ignore + app_info=InputAppInfo(api_domain="...", app_name="...", website_domain="..."), + framework="...", # type: ignore recipe_list=[ session.init( # highlight-start - override=session.InputOverrideConfig( - functions=override_functions - ) + override=session.InputOverrideConfig(functions=override_functions) # highlight-end ) - ] + ], ) ``` diff --git a/v2/session/common-customizations/sessions/claims/access-token-payload.mdx b/v2/session/common-customizations/sessions/claims/access-token-payload.mdx index b1e59cb4e..081160e8e 100644 --- a/v2/session/common-customizations/sessions/claims/access-token-payload.mdx +++ b/v2/session/common-customizations/sessions/claims/access-token-payload.mdx @@ -129,43 +129,53 @@ from supertokens_python import init, InputAppInfo from supertokens_python.recipe import session from supertokens_python.recipe.session.interfaces import RecipeInterface from typing import Any, Dict, Optional +from supertokens_python.types import RecipeUserId def override_functions(original_implementation: RecipeInterface): - original_implementation_create_new_session = original_implementation.create_new_session - - async def create_new_session(user_id: str, - access_token_payload: Optional[Dict[str, Any]], - session_data_in_database: Optional[Dict[str, Any]], - disable_anti_csrf: Optional[bool], - tenant_id: str, - user_context: Dict[str, Any]): + original_implementation_create_new_session = ( + original_implementation.create_new_session + ) + + async def create_new_session( + user_id: str, + recipe_user_id: RecipeUserId, + access_token_payload: Optional[Dict[str, Any]], + session_data_in_database: Optional[Dict[str, Any]], + disable_anti_csrf: Optional[bool], + tenant_id: str, + user_context: Dict[str, Any], + ): if access_token_payload is None: access_token_payload = {} # This goes in the access token, and is available to read on the frontend. - access_token_payload["someKey"] = 'someValue' - - return await original_implementation_create_new_session(user_id, access_token_payload, session_data_in_database, disable_anti_csrf, tenant_id, user_context) + access_token_payload["someKey"] = "someValue" + + return await original_implementation_create_new_session( + user_id, + recipe_user_id, + access_token_payload, + session_data_in_database, + disable_anti_csrf, + tenant_id, + user_context, + ) original_implementation.create_new_session = create_new_session return original_implementation init( - app_info=InputAppInfo( - api_domain="...", app_name="...", website_domain="..."), - - framework='...', # type: ignore + app_info=InputAppInfo(api_domain="...", app_name="...", website_domain="..."), + framework="...", # type: ignore recipe_list=[ session.init( # highlight-start - override=session.InputOverrideConfig( - functions=override_functions - ) + override=session.InputOverrideConfig(functions=override_functions) # highlight-end ) - ] + ], ) ``` diff --git a/v2/session/common-customizations/sessions/claims/claim-validators.mdx b/v2/session/common-customizations/sessions/claims/claim-validators.mdx index 2a7b59a42..fdc63bbb1 100644 --- a/v2/session/common-customizations/sessions/claims/claim-validators.mdx +++ b/v2/session/common-customizations/sessions/claims/claim-validators.mdx @@ -455,24 +455,38 @@ from supertokens_python.recipe import session from supertokens_python.recipe.session.interfaces import RecipeInterface from supertokens_python.recipe.userroles import UserRoleClaim from typing import Any, Dict, Optional +from supertokens_python.types import RecipeUserId def override_functions(original_implementation: RecipeInterface): - original_implementation_create_new_session = original_implementation.create_new_session - - async def create_new_session(user_id: str, - access_token_payload: Optional[Dict[str, Any]], - session_data_in_database: Optional[Dict[str, Any]], - disable_anti_csrf: Optional[bool], - tenant_id: str, - user_context: Dict[str, Any]): + original_implementation_create_new_session = ( + original_implementation.create_new_session + ) + + async def create_new_session( + user_id: str, + recipe_user_id: RecipeUserId, + access_token_payload: Optional[Dict[str, Any]], + session_data_in_database: Optional[Dict[str, Any]], + disable_anti_csrf: Optional[bool], + tenant_id: str, + user_context: Dict[str, Any], + ): if access_token_payload is None: access_token_payload = {} access_token_payload = { **access_token_payload, # highlight-next-line - **(await UserRoleClaim.build(user_id, tenant_id, user_context)) + **( + await UserRoleClaim.build( + user_id, + recipe_user_id, + tenant_id, + access_token_payload, + user_context, + ) + ), } # At this step, the access token paylaod looks like this: @@ -483,24 +497,26 @@ def override_functions(original_implementation: RecipeInterface): # } # } - return await original_implementation_create_new_session(user_id, access_token_payload, session_data_in_database, disable_anti_csrf, tenant_id, user_context) + return await original_implementation_create_new_session( + user_id, + recipe_user_id, + access_token_payload, + session_data_in_database, + disable_anti_csrf, + tenant_id, + user_context, + ) original_implementation.create_new_session = create_new_session return original_implementation init( - app_info=InputAppInfo( - api_domain="...", app_name="...", website_domain="..."), - - framework='...', # type: ignore + app_info=InputAppInfo(api_domain="...", app_name="...", website_domain="..."), + framework="...", # type: ignore recipe_list=[ - session.init( - override=session.InputOverrideConfig( - functions=override_functions - ) - ) - ] + session.init(override=session.InputOverrideConfig(functions=override_functions)) + ], ) ``` @@ -637,7 +653,7 @@ from supertokens_python.recipe.session.interfaces import SessionContainer from supertokens_python.recipe.session.claims import BooleanClaim SecondFactorClaim = BooleanClaim( - key="2fa-completed", fetch_value=lambda _, __, ___: False) + key="2fa-completed", fetch_value=lambda _, __, ___, _____, ______: False) async def set_2fa_claim_as_completed(session: SessionContainer): await session.set_claim_value(SecondFactorClaim, True) @@ -651,7 +667,7 @@ from supertokens_python.recipe.session.interfaces import SessionContainer from supertokens_python.recipe.session.claims import BooleanClaim SecondFactorClaim = BooleanClaim( - key="2fa-completed", fetch_value=lambda _, __, ___: False) + key="2fa-completed", fetch_value=lambda _, __, ___, _____, ______: False) def set_2fa_claim_as_completed(session: SessionContainer): session.sync_set_claim_value(SecondFactorClaim, True) @@ -759,9 +775,10 @@ from supertokens_python.recipe import session from supertokens_python.recipe.session.interfaces import RecipeInterface, SessionClaimValidator from typing import Any, Dict, List from supertokens_python.recipe.session.claims import BooleanClaim +from supertokens_python.types import RecipeUserId SecondFactorClaim = BooleanClaim( - key="2fa-completed", fetch_value=lambda _, __, ___: False) + key="2fa-completed", fetch_value=lambda _, __, ___, _____, ______: False) def override_functions(original_implementation: RecipeInterface): @@ -769,6 +786,7 @@ def override_functions(original_implementation: RecipeInterface): def get_global_claim_validators( tenant_id: str, user_id: str, + recipe_user_id: RecipeUserId, claim_validators_added_by_other_recipes: List[SessionClaimValidator], user_context: Dict[str, Any], ): diff --git a/v2/session/common-customizations/sessions/error-handling.mdx b/v2/session/common-customizations/sessions/error-handling.mdx index c6871d171..70a0f76c3 100644 --- a/v2/session/common-customizations/sessions/error-handling.mdx +++ b/v2/session/common-customizations/sessions/error-handling.mdx @@ -278,8 +278,9 @@ func main() { from supertokens_python import init, InputAppInfo from supertokens_python.recipe import session from supertokens_python.framework import BaseRequest, BaseResponse +from supertokens_python.types import RecipeUserId -async def token_theft_detected_callback(req: BaseRequest, session_handle: str, user_id: str, response: BaseResponse): +async def token_theft_detected_callback(req: BaseRequest, session_handle: str, user_id: str, recipe_user_id: RecipeUserId, response: BaseResponse): # TODO: Write your own logic and then send a 401 response to the frontend return response diff --git a/v2/session/common-customizations/sessions/user-impersonation.mdx b/v2/session/common-customizations/sessions/user-impersonation.mdx index 9e747be3b..62a0e4b7e 100644 --- a/v2/session/common-customizations/sessions/user-impersonation.mdx +++ b/v2/session/common-customizations/sessions/user-impersonation.mdx @@ -518,7 +518,8 @@ from fastapi.responses import JSONResponse from supertokens_python.recipe.session import SessionContainer from fastapi import Depends from supertokens_python.recipe.userroles import UserRoleClaim -from supertokens_python.recipe.emailpassword.asyncio import get_user_by_email +from supertokens_python.asyncio import list_users_by_account_info +from supertokens_python.types import AccountInfo from fastapi import Request @@ -536,19 +537,25 @@ async def impersonate( email = "..." # get from request body # we use the email password recipe here, but you can use the recipe you use - user = await get_user_by_email("public", email) + user = await list_users_by_account_info("public", AccountInfo(email=email)) - if user is None: + if len(user) == 0: # return a 400 error to the client return - await create_new_session(request, "public", user.user_id, {"isImpersonation": True}) + await create_new_session( + request, + "public", + user[0].login_methods[0].recipe_user_id, + {"isImpersonation": True}, + ) # a new session has been created. # - an access & refresh token has been attached to the response's cookie # - a new row has been inserted into the database for this new session return JSONResponse({"message": "Impersonation complete!"}) + ``` @@ -560,7 +567,8 @@ from flask import jsonify from flask.wrappers import Request from supertokens_python.recipe.session.framework.flask import verify_session from supertokens_python.recipe.userroles import UserRoleClaim -from supertokens_python.recipe.emailpassword.syncio import get_user_by_email +from supertokens_python.syncio import list_users_by_account_info +from supertokens_python.types import AccountInfo @app.route("/impersonate", methods=["POST"]) # type: ignore @verify_session( @@ -572,13 +580,13 @@ def login(request: Request): email = "..." # get from request body # we use the email password recipe here, but you can use the recipe you use - user = get_user_by_email("public", email) + user = list_users_by_account_info("public", AccountInfo(email=email)) - if user is None: + if len(user) == 0: # return a 400 error to the client return - create_new_session(request, "public", user.user_id, {"isImpersonation": True}) + create_new_session(request, "public", user[0].login_methods[0].recipe_user_id, {"isImpersonation": True}) # a new session has been created. # - an access & refresh token has been attached to the response's cookie @@ -595,7 +603,8 @@ from supertokens_python.recipe.session.framework.django.asyncio import verify_se from supertokens_python.recipe.session.asyncio import create_new_session from django.http import HttpRequest, JsonResponse from supertokens_python.recipe.userroles import UserRoleClaim -from supertokens_python.recipe.emailpassword.asyncio import get_user_by_email +from supertokens_python.asyncio import list_users_by_account_info +from supertokens_python.types import AccountInfo @verify_session( # We add the UserRoleClaim's includes validator @@ -606,13 +615,13 @@ async def impersonate(request: HttpRequest): email = "..." # get from request body # we use the email password recipe here, but you can use the recipe you use - user = await get_user_by_email("public", email) + user = await list_users_by_account_info("public", AccountInfo(email=email)) - if user is None: + if len(user) == 0: # return a 400 error to the client return - await create_new_session(request, "public", user.user_id, {"isImpersonation": True}) + await create_new_session(request, "public", user[0].login_methods[0].recipe_user_id, {"isImpersonation": True}) # a new session has been created. # - an access & refresh token has been attached to the response's cookie diff --git a/v2/thirdparty/common-customizations/sessions/anonymous-session.mdx b/v2/thirdparty/common-customizations/sessions/anonymous-session.mdx index 02390d177..9ceb65797 100644 --- a/v2/thirdparty/common-customizations/sessions/anonymous-session.mdx +++ b/v2/thirdparty/common-customizations/sessions/anonymous-session.mdx @@ -278,31 +278,37 @@ from supertokens_python import init, InputAppInfo, get_request_from_user_context from supertokens_python.recipe import session from supertokens_python.recipe.session.interfaces import RecipeInterface from typing import Any, Dict, Optional +from supertokens_python.types import RecipeUserId def override_functions(original_implementation: RecipeInterface): - original_implementation_create_new_session = original_implementation.create_new_session - - async def create_new_session(user_id: str, - access_token_payload: Optional[Dict[str, Any]], - session_data_in_database: Optional[Dict[str, Any]], - disable_anti_csrf: Optional[bool], - tenant_id: str, - user_context: Dict[str, Any]): - - request=get_request_from_user_context(user_context) + original_implementation_create_new_session = ( + original_implementation.create_new_session + ) + + async def create_new_session( + user_id: str, + recipe_user_id: RecipeUserId, + access_token_payload: Optional[Dict[str, Any]], + session_data_in_database: Optional[Dict[str, Any]], + disable_anti_csrf: Optional[bool], + tenant_id: str, + user_context: Dict[str, Any], + ): + + request = get_request_from_user_context(user_context) jwt: Optional[str] = None if request is not None: - jwt=request.get_cookie("jwt") + jwt = request.get_cookie("jwt") else: # # This is possible if the function is triggered from the user management dashboard - # + # # In this case because we cannot read the JWT, we create a session without the custom # payload properties # - jwt=None + jwt = None if jwt is not None: # verify JWT using a JWT verification library.. @@ -315,26 +321,30 @@ def override_functions(original_implementation: RecipeInterface): access_token_payload = {} access_token_payload["someKey"] = jwt_payload["someKey"] - return await original_implementation_create_new_session(user_id, access_token_payload, session_data_in_database, disable_anti_csrf, tenant_id, user_context) + return await original_implementation_create_new_session( + user_id, + recipe_user_id, + access_token_payload, + session_data_in_database, + disable_anti_csrf, + tenant_id, + user_context, + ) original_implementation.create_new_session = create_new_session return original_implementation init( - app_info=InputAppInfo( - api_domain="...", app_name="...", website_domain="..."), - - framework='...', # type: ignore + app_info=InputAppInfo(api_domain="...", app_name="...", website_domain="..."), + framework="...", # type: ignore recipe_list=[ session.init( # highlight-start - override=session.InputOverrideConfig( - functions=override_functions - ) + override=session.InputOverrideConfig(functions=override_functions) # highlight-end ) - ] + ], ) ``` diff --git a/v2/thirdparty/common-customizations/sessions/claims/access-token-payload.mdx b/v2/thirdparty/common-customizations/sessions/claims/access-token-payload.mdx index ac18be4b9..3563fef3a 100644 --- a/v2/thirdparty/common-customizations/sessions/claims/access-token-payload.mdx +++ b/v2/thirdparty/common-customizations/sessions/claims/access-token-payload.mdx @@ -129,43 +129,53 @@ from supertokens_python import init, InputAppInfo from supertokens_python.recipe import session from supertokens_python.recipe.session.interfaces import RecipeInterface from typing import Any, Dict, Optional +from supertokens_python.types import RecipeUserId def override_functions(original_implementation: RecipeInterface): - original_implementation_create_new_session = original_implementation.create_new_session - - async def create_new_session(user_id: str, - access_token_payload: Optional[Dict[str, Any]], - session_data_in_database: Optional[Dict[str, Any]], - disable_anti_csrf: Optional[bool], - tenant_id: str, - user_context: Dict[str, Any]): + original_implementation_create_new_session = ( + original_implementation.create_new_session + ) + + async def create_new_session( + user_id: str, + recipe_user_id: RecipeUserId, + access_token_payload: Optional[Dict[str, Any]], + session_data_in_database: Optional[Dict[str, Any]], + disable_anti_csrf: Optional[bool], + tenant_id: str, + user_context: Dict[str, Any], + ): if access_token_payload is None: access_token_payload = {} # This goes in the access token, and is available to read on the frontend. - access_token_payload["someKey"] = 'someValue' - - return await original_implementation_create_new_session(user_id, access_token_payload, session_data_in_database, disable_anti_csrf, tenant_id, user_context) + access_token_payload["someKey"] = "someValue" + + return await original_implementation_create_new_session( + user_id, + recipe_user_id, + access_token_payload, + session_data_in_database, + disable_anti_csrf, + tenant_id, + user_context, + ) original_implementation.create_new_session = create_new_session return original_implementation init( - app_info=InputAppInfo( - api_domain="...", app_name="...", website_domain="..."), - - framework='...', # type: ignore + app_info=InputAppInfo(api_domain="...", app_name="...", website_domain="..."), + framework="...", # type: ignore recipe_list=[ session.init( # highlight-start - override=session.InputOverrideConfig( - functions=override_functions - ) + override=session.InputOverrideConfig(functions=override_functions) # highlight-end ) - ] + ], ) ``` diff --git a/v2/thirdparty/common-customizations/sessions/claims/claim-validators.mdx b/v2/thirdparty/common-customizations/sessions/claims/claim-validators.mdx index 2a7b59a42..fdc63bbb1 100644 --- a/v2/thirdparty/common-customizations/sessions/claims/claim-validators.mdx +++ b/v2/thirdparty/common-customizations/sessions/claims/claim-validators.mdx @@ -455,24 +455,38 @@ from supertokens_python.recipe import session from supertokens_python.recipe.session.interfaces import RecipeInterface from supertokens_python.recipe.userroles import UserRoleClaim from typing import Any, Dict, Optional +from supertokens_python.types import RecipeUserId def override_functions(original_implementation: RecipeInterface): - original_implementation_create_new_session = original_implementation.create_new_session - - async def create_new_session(user_id: str, - access_token_payload: Optional[Dict[str, Any]], - session_data_in_database: Optional[Dict[str, Any]], - disable_anti_csrf: Optional[bool], - tenant_id: str, - user_context: Dict[str, Any]): + original_implementation_create_new_session = ( + original_implementation.create_new_session + ) + + async def create_new_session( + user_id: str, + recipe_user_id: RecipeUserId, + access_token_payload: Optional[Dict[str, Any]], + session_data_in_database: Optional[Dict[str, Any]], + disable_anti_csrf: Optional[bool], + tenant_id: str, + user_context: Dict[str, Any], + ): if access_token_payload is None: access_token_payload = {} access_token_payload = { **access_token_payload, # highlight-next-line - **(await UserRoleClaim.build(user_id, tenant_id, user_context)) + **( + await UserRoleClaim.build( + user_id, + recipe_user_id, + tenant_id, + access_token_payload, + user_context, + ) + ), } # At this step, the access token paylaod looks like this: @@ -483,24 +497,26 @@ def override_functions(original_implementation: RecipeInterface): # } # } - return await original_implementation_create_new_session(user_id, access_token_payload, session_data_in_database, disable_anti_csrf, tenant_id, user_context) + return await original_implementation_create_new_session( + user_id, + recipe_user_id, + access_token_payload, + session_data_in_database, + disable_anti_csrf, + tenant_id, + user_context, + ) original_implementation.create_new_session = create_new_session return original_implementation init( - app_info=InputAppInfo( - api_domain="...", app_name="...", website_domain="..."), - - framework='...', # type: ignore + app_info=InputAppInfo(api_domain="...", app_name="...", website_domain="..."), + framework="...", # type: ignore recipe_list=[ - session.init( - override=session.InputOverrideConfig( - functions=override_functions - ) - ) - ] + session.init(override=session.InputOverrideConfig(functions=override_functions)) + ], ) ``` @@ -637,7 +653,7 @@ from supertokens_python.recipe.session.interfaces import SessionContainer from supertokens_python.recipe.session.claims import BooleanClaim SecondFactorClaim = BooleanClaim( - key="2fa-completed", fetch_value=lambda _, __, ___: False) + key="2fa-completed", fetch_value=lambda _, __, ___, _____, ______: False) async def set_2fa_claim_as_completed(session: SessionContainer): await session.set_claim_value(SecondFactorClaim, True) @@ -651,7 +667,7 @@ from supertokens_python.recipe.session.interfaces import SessionContainer from supertokens_python.recipe.session.claims import BooleanClaim SecondFactorClaim = BooleanClaim( - key="2fa-completed", fetch_value=lambda _, __, ___: False) + key="2fa-completed", fetch_value=lambda _, __, ___, _____, ______: False) def set_2fa_claim_as_completed(session: SessionContainer): session.sync_set_claim_value(SecondFactorClaim, True) @@ -759,9 +775,10 @@ from supertokens_python.recipe import session from supertokens_python.recipe.session.interfaces import RecipeInterface, SessionClaimValidator from typing import Any, Dict, List from supertokens_python.recipe.session.claims import BooleanClaim +from supertokens_python.types import RecipeUserId SecondFactorClaim = BooleanClaim( - key="2fa-completed", fetch_value=lambda _, __, ___: False) + key="2fa-completed", fetch_value=lambda _, __, ___, _____, ______: False) def override_functions(original_implementation: RecipeInterface): @@ -769,6 +786,7 @@ def override_functions(original_implementation: RecipeInterface): def get_global_claim_validators( tenant_id: str, user_id: str, + recipe_user_id: RecipeUserId, claim_validators_added_by_other_recipes: List[SessionClaimValidator], user_context: Dict[str, Any], ): diff --git a/v2/thirdparty/common-customizations/sessions/error-handling.mdx b/v2/thirdparty/common-customizations/sessions/error-handling.mdx index c6871d171..70a0f76c3 100644 --- a/v2/thirdparty/common-customizations/sessions/error-handling.mdx +++ b/v2/thirdparty/common-customizations/sessions/error-handling.mdx @@ -278,8 +278,9 @@ func main() { from supertokens_python import init, InputAppInfo from supertokens_python.recipe import session from supertokens_python.framework import BaseRequest, BaseResponse +from supertokens_python.types import RecipeUserId -async def token_theft_detected_callback(req: BaseRequest, session_handle: str, user_id: str, response: BaseResponse): +async def token_theft_detected_callback(req: BaseRequest, session_handle: str, user_id: str, recipe_user_id: RecipeUserId, response: BaseResponse): # TODO: Write your own logic and then send a 401 response to the frontend return response diff --git a/v2/thirdparty/common-customizations/sessions/user-impersonation.mdx b/v2/thirdparty/common-customizations/sessions/user-impersonation.mdx index 2b14c1630..1897a872d 100644 --- a/v2/thirdparty/common-customizations/sessions/user-impersonation.mdx +++ b/v2/thirdparty/common-customizations/sessions/user-impersonation.mdx @@ -518,7 +518,8 @@ from fastapi.responses import JSONResponse from supertokens_python.recipe.session import SessionContainer from fastapi import Depends from supertokens_python.recipe.userroles import UserRoleClaim -from supertokens_python.recipe.emailpassword.asyncio import get_user_by_email +from supertokens_python.asyncio import list_users_by_account_info +from supertokens_python.types import AccountInfo from fastapi import Request @@ -536,19 +537,25 @@ async def impersonate( email = "..." # get from request body # we use the email password recipe here, but you can use the recipe you use - user = await get_user_by_email("public", email) + user = await list_users_by_account_info("public", AccountInfo(email=email)) - if user is None: + if len(user) == 0: # return a 400 error to the client return - await create_new_session(request, "public", user.user_id, {"isImpersonation": True}) + await create_new_session( + request, + "public", + user[0].login_methods[0].recipe_user_id, + {"isImpersonation": True}, + ) # a new session has been created. # - an access & refresh token has been attached to the response's cookie # - a new row has been inserted into the database for this new session return JSONResponse({"message": "Impersonation complete!"}) + ``` @@ -560,7 +567,8 @@ from flask import jsonify from flask.wrappers import Request from supertokens_python.recipe.session.framework.flask import verify_session from supertokens_python.recipe.userroles import UserRoleClaim -from supertokens_python.recipe.emailpassword.syncio import get_user_by_email +from supertokens_python.syncio import list_users_by_account_info +from supertokens_python.types import AccountInfo @app.route("/impersonate", methods=["POST"]) # type: ignore @verify_session( @@ -572,13 +580,13 @@ def login(request: Request): email = "..." # get from request body # we use the email password recipe here, but you can use the recipe you use - user = get_user_by_email("public", email) + user = list_users_by_account_info("public", AccountInfo(email=email)) - if user is None: + if len(user) == 0: # return a 400 error to the client return - create_new_session(request, "public", user.user_id, {"isImpersonation": True}) + create_new_session(request, "public", user[0].login_methods[0].recipe_user_id, {"isImpersonation": True}) # a new session has been created. # - an access & refresh token has been attached to the response's cookie @@ -595,7 +603,8 @@ from supertokens_python.recipe.session.framework.django.asyncio import verify_se from supertokens_python.recipe.session.asyncio import create_new_session from django.http import HttpRequest, JsonResponse from supertokens_python.recipe.userroles import UserRoleClaim -from supertokens_python.recipe.emailpassword.asyncio import get_user_by_email +from supertokens_python.asyncio import list_users_by_account_info +from supertokens_python.types import AccountInfo @verify_session( # We add the UserRoleClaim's includes validator @@ -606,13 +615,13 @@ async def impersonate(request: HttpRequest): email = "..." # get from request body # we use the email password recipe here, but you can use the recipe you use - user = await get_user_by_email("public", email) + user = await list_users_by_account_info("public", AccountInfo(email=email)) - if user is None: + if len(user) == 0: # return a 400 error to the client return - await create_new_session(request, "public", user.user_id, {"isImpersonation": True}) + await create_new_session(request, "public", user[0].login_methods[0].recipe_user_id, {"isImpersonation": True}) # a new session has been created. # - an access & refresh token has been attached to the response's cookie diff --git a/v2/thirdpartyemailpassword/common-customizations/sessions/anonymous-session.mdx b/v2/thirdpartyemailpassword/common-customizations/sessions/anonymous-session.mdx index 02390d177..9ceb65797 100644 --- a/v2/thirdpartyemailpassword/common-customizations/sessions/anonymous-session.mdx +++ b/v2/thirdpartyemailpassword/common-customizations/sessions/anonymous-session.mdx @@ -278,31 +278,37 @@ from supertokens_python import init, InputAppInfo, get_request_from_user_context from supertokens_python.recipe import session from supertokens_python.recipe.session.interfaces import RecipeInterface from typing import Any, Dict, Optional +from supertokens_python.types import RecipeUserId def override_functions(original_implementation: RecipeInterface): - original_implementation_create_new_session = original_implementation.create_new_session - - async def create_new_session(user_id: str, - access_token_payload: Optional[Dict[str, Any]], - session_data_in_database: Optional[Dict[str, Any]], - disable_anti_csrf: Optional[bool], - tenant_id: str, - user_context: Dict[str, Any]): - - request=get_request_from_user_context(user_context) + original_implementation_create_new_session = ( + original_implementation.create_new_session + ) + + async def create_new_session( + user_id: str, + recipe_user_id: RecipeUserId, + access_token_payload: Optional[Dict[str, Any]], + session_data_in_database: Optional[Dict[str, Any]], + disable_anti_csrf: Optional[bool], + tenant_id: str, + user_context: Dict[str, Any], + ): + + request = get_request_from_user_context(user_context) jwt: Optional[str] = None if request is not None: - jwt=request.get_cookie("jwt") + jwt = request.get_cookie("jwt") else: # # This is possible if the function is triggered from the user management dashboard - # + # # In this case because we cannot read the JWT, we create a session without the custom # payload properties # - jwt=None + jwt = None if jwt is not None: # verify JWT using a JWT verification library.. @@ -315,26 +321,30 @@ def override_functions(original_implementation: RecipeInterface): access_token_payload = {} access_token_payload["someKey"] = jwt_payload["someKey"] - return await original_implementation_create_new_session(user_id, access_token_payload, session_data_in_database, disable_anti_csrf, tenant_id, user_context) + return await original_implementation_create_new_session( + user_id, + recipe_user_id, + access_token_payload, + session_data_in_database, + disable_anti_csrf, + tenant_id, + user_context, + ) original_implementation.create_new_session = create_new_session return original_implementation init( - app_info=InputAppInfo( - api_domain="...", app_name="...", website_domain="..."), - - framework='...', # type: ignore + app_info=InputAppInfo(api_domain="...", app_name="...", website_domain="..."), + framework="...", # type: ignore recipe_list=[ session.init( # highlight-start - override=session.InputOverrideConfig( - functions=override_functions - ) + override=session.InputOverrideConfig(functions=override_functions) # highlight-end ) - ] + ], ) ``` diff --git a/v2/thirdpartyemailpassword/common-customizations/sessions/claims/access-token-payload.mdx b/v2/thirdpartyemailpassword/common-customizations/sessions/claims/access-token-payload.mdx index ac18be4b9..3563fef3a 100644 --- a/v2/thirdpartyemailpassword/common-customizations/sessions/claims/access-token-payload.mdx +++ b/v2/thirdpartyemailpassword/common-customizations/sessions/claims/access-token-payload.mdx @@ -129,43 +129,53 @@ from supertokens_python import init, InputAppInfo from supertokens_python.recipe import session from supertokens_python.recipe.session.interfaces import RecipeInterface from typing import Any, Dict, Optional +from supertokens_python.types import RecipeUserId def override_functions(original_implementation: RecipeInterface): - original_implementation_create_new_session = original_implementation.create_new_session - - async def create_new_session(user_id: str, - access_token_payload: Optional[Dict[str, Any]], - session_data_in_database: Optional[Dict[str, Any]], - disable_anti_csrf: Optional[bool], - tenant_id: str, - user_context: Dict[str, Any]): + original_implementation_create_new_session = ( + original_implementation.create_new_session + ) + + async def create_new_session( + user_id: str, + recipe_user_id: RecipeUserId, + access_token_payload: Optional[Dict[str, Any]], + session_data_in_database: Optional[Dict[str, Any]], + disable_anti_csrf: Optional[bool], + tenant_id: str, + user_context: Dict[str, Any], + ): if access_token_payload is None: access_token_payload = {} # This goes in the access token, and is available to read on the frontend. - access_token_payload["someKey"] = 'someValue' - - return await original_implementation_create_new_session(user_id, access_token_payload, session_data_in_database, disable_anti_csrf, tenant_id, user_context) + access_token_payload["someKey"] = "someValue" + + return await original_implementation_create_new_session( + user_id, + recipe_user_id, + access_token_payload, + session_data_in_database, + disable_anti_csrf, + tenant_id, + user_context, + ) original_implementation.create_new_session = create_new_session return original_implementation init( - app_info=InputAppInfo( - api_domain="...", app_name="...", website_domain="..."), - - framework='...', # type: ignore + app_info=InputAppInfo(api_domain="...", app_name="...", website_domain="..."), + framework="...", # type: ignore recipe_list=[ session.init( # highlight-start - override=session.InputOverrideConfig( - functions=override_functions - ) + override=session.InputOverrideConfig(functions=override_functions) # highlight-end ) - ] + ], ) ``` diff --git a/v2/thirdpartyemailpassword/common-customizations/sessions/claims/claim-validators.mdx b/v2/thirdpartyemailpassword/common-customizations/sessions/claims/claim-validators.mdx index 2a7b59a42..fdc63bbb1 100644 --- a/v2/thirdpartyemailpassword/common-customizations/sessions/claims/claim-validators.mdx +++ b/v2/thirdpartyemailpassword/common-customizations/sessions/claims/claim-validators.mdx @@ -455,24 +455,38 @@ from supertokens_python.recipe import session from supertokens_python.recipe.session.interfaces import RecipeInterface from supertokens_python.recipe.userroles import UserRoleClaim from typing import Any, Dict, Optional +from supertokens_python.types import RecipeUserId def override_functions(original_implementation: RecipeInterface): - original_implementation_create_new_session = original_implementation.create_new_session - - async def create_new_session(user_id: str, - access_token_payload: Optional[Dict[str, Any]], - session_data_in_database: Optional[Dict[str, Any]], - disable_anti_csrf: Optional[bool], - tenant_id: str, - user_context: Dict[str, Any]): + original_implementation_create_new_session = ( + original_implementation.create_new_session + ) + + async def create_new_session( + user_id: str, + recipe_user_id: RecipeUserId, + access_token_payload: Optional[Dict[str, Any]], + session_data_in_database: Optional[Dict[str, Any]], + disable_anti_csrf: Optional[bool], + tenant_id: str, + user_context: Dict[str, Any], + ): if access_token_payload is None: access_token_payload = {} access_token_payload = { **access_token_payload, # highlight-next-line - **(await UserRoleClaim.build(user_id, tenant_id, user_context)) + **( + await UserRoleClaim.build( + user_id, + recipe_user_id, + tenant_id, + access_token_payload, + user_context, + ) + ), } # At this step, the access token paylaod looks like this: @@ -483,24 +497,26 @@ def override_functions(original_implementation: RecipeInterface): # } # } - return await original_implementation_create_new_session(user_id, access_token_payload, session_data_in_database, disable_anti_csrf, tenant_id, user_context) + return await original_implementation_create_new_session( + user_id, + recipe_user_id, + access_token_payload, + session_data_in_database, + disable_anti_csrf, + tenant_id, + user_context, + ) original_implementation.create_new_session = create_new_session return original_implementation init( - app_info=InputAppInfo( - api_domain="...", app_name="...", website_domain="..."), - - framework='...', # type: ignore + app_info=InputAppInfo(api_domain="...", app_name="...", website_domain="..."), + framework="...", # type: ignore recipe_list=[ - session.init( - override=session.InputOverrideConfig( - functions=override_functions - ) - ) - ] + session.init(override=session.InputOverrideConfig(functions=override_functions)) + ], ) ``` @@ -637,7 +653,7 @@ from supertokens_python.recipe.session.interfaces import SessionContainer from supertokens_python.recipe.session.claims import BooleanClaim SecondFactorClaim = BooleanClaim( - key="2fa-completed", fetch_value=lambda _, __, ___: False) + key="2fa-completed", fetch_value=lambda _, __, ___, _____, ______: False) async def set_2fa_claim_as_completed(session: SessionContainer): await session.set_claim_value(SecondFactorClaim, True) @@ -651,7 +667,7 @@ from supertokens_python.recipe.session.interfaces import SessionContainer from supertokens_python.recipe.session.claims import BooleanClaim SecondFactorClaim = BooleanClaim( - key="2fa-completed", fetch_value=lambda _, __, ___: False) + key="2fa-completed", fetch_value=lambda _, __, ___, _____, ______: False) def set_2fa_claim_as_completed(session: SessionContainer): session.sync_set_claim_value(SecondFactorClaim, True) @@ -759,9 +775,10 @@ from supertokens_python.recipe import session from supertokens_python.recipe.session.interfaces import RecipeInterface, SessionClaimValidator from typing import Any, Dict, List from supertokens_python.recipe.session.claims import BooleanClaim +from supertokens_python.types import RecipeUserId SecondFactorClaim = BooleanClaim( - key="2fa-completed", fetch_value=lambda _, __, ___: False) + key="2fa-completed", fetch_value=lambda _, __, ___, _____, ______: False) def override_functions(original_implementation: RecipeInterface): @@ -769,6 +786,7 @@ def override_functions(original_implementation: RecipeInterface): def get_global_claim_validators( tenant_id: str, user_id: str, + recipe_user_id: RecipeUserId, claim_validators_added_by_other_recipes: List[SessionClaimValidator], user_context: Dict[str, Any], ): diff --git a/v2/thirdpartyemailpassword/common-customizations/sessions/error-handling.mdx b/v2/thirdpartyemailpassword/common-customizations/sessions/error-handling.mdx index c6871d171..70a0f76c3 100644 --- a/v2/thirdpartyemailpassword/common-customizations/sessions/error-handling.mdx +++ b/v2/thirdpartyemailpassword/common-customizations/sessions/error-handling.mdx @@ -278,8 +278,9 @@ func main() { from supertokens_python import init, InputAppInfo from supertokens_python.recipe import session from supertokens_python.framework import BaseRequest, BaseResponse +from supertokens_python.types import RecipeUserId -async def token_theft_detected_callback(req: BaseRequest, session_handle: str, user_id: str, response: BaseResponse): +async def token_theft_detected_callback(req: BaseRequest, session_handle: str, user_id: str, recipe_user_id: RecipeUserId, response: BaseResponse): # TODO: Write your own logic and then send a 401 response to the frontend return response diff --git a/v2/thirdpartyemailpassword/common-customizations/sessions/user-impersonation.mdx b/v2/thirdpartyemailpassword/common-customizations/sessions/user-impersonation.mdx index 2b14c1630..1897a872d 100644 --- a/v2/thirdpartyemailpassword/common-customizations/sessions/user-impersonation.mdx +++ b/v2/thirdpartyemailpassword/common-customizations/sessions/user-impersonation.mdx @@ -518,7 +518,8 @@ from fastapi.responses import JSONResponse from supertokens_python.recipe.session import SessionContainer from fastapi import Depends from supertokens_python.recipe.userroles import UserRoleClaim -from supertokens_python.recipe.emailpassword.asyncio import get_user_by_email +from supertokens_python.asyncio import list_users_by_account_info +from supertokens_python.types import AccountInfo from fastapi import Request @@ -536,19 +537,25 @@ async def impersonate( email = "..." # get from request body # we use the email password recipe here, but you can use the recipe you use - user = await get_user_by_email("public", email) + user = await list_users_by_account_info("public", AccountInfo(email=email)) - if user is None: + if len(user) == 0: # return a 400 error to the client return - await create_new_session(request, "public", user.user_id, {"isImpersonation": True}) + await create_new_session( + request, + "public", + user[0].login_methods[0].recipe_user_id, + {"isImpersonation": True}, + ) # a new session has been created. # - an access & refresh token has been attached to the response's cookie # - a new row has been inserted into the database for this new session return JSONResponse({"message": "Impersonation complete!"}) + ``` @@ -560,7 +567,8 @@ from flask import jsonify from flask.wrappers import Request from supertokens_python.recipe.session.framework.flask import verify_session from supertokens_python.recipe.userroles import UserRoleClaim -from supertokens_python.recipe.emailpassword.syncio import get_user_by_email +from supertokens_python.syncio import list_users_by_account_info +from supertokens_python.types import AccountInfo @app.route("/impersonate", methods=["POST"]) # type: ignore @verify_session( @@ -572,13 +580,13 @@ def login(request: Request): email = "..." # get from request body # we use the email password recipe here, but you can use the recipe you use - user = get_user_by_email("public", email) + user = list_users_by_account_info("public", AccountInfo(email=email)) - if user is None: + if len(user) == 0: # return a 400 error to the client return - create_new_session(request, "public", user.user_id, {"isImpersonation": True}) + create_new_session(request, "public", user[0].login_methods[0].recipe_user_id, {"isImpersonation": True}) # a new session has been created. # - an access & refresh token has been attached to the response's cookie @@ -595,7 +603,8 @@ from supertokens_python.recipe.session.framework.django.asyncio import verify_se from supertokens_python.recipe.session.asyncio import create_new_session from django.http import HttpRequest, JsonResponse from supertokens_python.recipe.userroles import UserRoleClaim -from supertokens_python.recipe.emailpassword.asyncio import get_user_by_email +from supertokens_python.asyncio import list_users_by_account_info +from supertokens_python.types import AccountInfo @verify_session( # We add the UserRoleClaim's includes validator @@ -606,13 +615,13 @@ async def impersonate(request: HttpRequest): email = "..." # get from request body # we use the email password recipe here, but you can use the recipe you use - user = await get_user_by_email("public", email) + user = await list_users_by_account_info("public", AccountInfo(email=email)) - if user is None: + if len(user) == 0: # return a 400 error to the client return - await create_new_session(request, "public", user.user_id, {"isImpersonation": True}) + await create_new_session(request, "public", user[0].login_methods[0].recipe_user_id, {"isImpersonation": True}) # a new session has been created. # - an access & refresh token has been attached to the response's cookie diff --git a/v2/thirdpartyemailpassword/common-customizations/signup-form/adding-fields.mdx b/v2/thirdpartyemailpassword/common-customizations/signup-form/adding-fields.mdx index 48dca5cc3..bafba1243 100644 --- a/v2/thirdpartyemailpassword/common-customizations/signup-form/adding-fields.mdx +++ b/v2/thirdpartyemailpassword/common-customizations/signup-form/adding-fields.mdx @@ -442,35 +442,54 @@ func main() { ```python from supertokens_python import init, InputAppInfo from supertokens_python.recipe import emailpassword, session -from supertokens_python.recipe.emailpassword.interfaces import APIInterface, APIOptions, SignUpPostOkResult +from supertokens_python.recipe.emailpassword.interfaces import ( + APIInterface, + APIOptions, + SignUpPostOkResult, +) from supertokens_python.recipe.emailpassword.types import FormField -from typing import List, Dict, Any +from typing import List, Dict, Any, Union +from supertokens_python.recipe.session.interfaces import SessionContainer + # highlight-start def override_email_password_apis(original_implementation: APIInterface): original_sign_up_post = original_implementation.sign_up_post - - async def sign_up_post(form_fields: List[FormField], tenant_id: str, - api_options: APIOptions, - user_context: Dict[str, Any]): + + async def sign_up_post( + form_fields: List[FormField], + tenant_id: str, + session: Union[SessionContainer, None], + should_try_linking_with_session_user: Union[bool, None], + api_options: APIOptions, + user_context: Dict[str, Any], + ): # First we call the original implementation of sign_up_post. - response = await original_sign_up_post(form_fields, tenant_id, api_options, user_context) + response = await original_sign_up_post( + form_fields, + tenant_id, + session, + should_try_linking_with_session_user, + api_options, + user_context, + ) # Post sign up response, we check if it was successful if isinstance(response, SignUpPostOkResult): - _ = response.user.user_id - __ = response.user.email + pass # TODO: use the input form fields values for custom logic return response - + original_implementation.sign_up_post = sign_up_post return original_implementation + + # highlight-end init( app_info=InputAppInfo(api_domain="...", app_name="...", website_domain="..."), - framework='...', # type: ignore + framework="...", # type: ignore recipe_list=[ emailpassword.init( # highlight-start @@ -479,8 +498,8 @@ init( ) # highlight-end ), - session.init() - ] + session.init(), + ], ) ``` diff --git a/v2/thirdpartypasswordless/common-customizations/sessions/anonymous-session.mdx b/v2/thirdpartypasswordless/common-customizations/sessions/anonymous-session.mdx index 02390d177..9ceb65797 100644 --- a/v2/thirdpartypasswordless/common-customizations/sessions/anonymous-session.mdx +++ b/v2/thirdpartypasswordless/common-customizations/sessions/anonymous-session.mdx @@ -278,31 +278,37 @@ from supertokens_python import init, InputAppInfo, get_request_from_user_context from supertokens_python.recipe import session from supertokens_python.recipe.session.interfaces import RecipeInterface from typing import Any, Dict, Optional +from supertokens_python.types import RecipeUserId def override_functions(original_implementation: RecipeInterface): - original_implementation_create_new_session = original_implementation.create_new_session - - async def create_new_session(user_id: str, - access_token_payload: Optional[Dict[str, Any]], - session_data_in_database: Optional[Dict[str, Any]], - disable_anti_csrf: Optional[bool], - tenant_id: str, - user_context: Dict[str, Any]): - - request=get_request_from_user_context(user_context) + original_implementation_create_new_session = ( + original_implementation.create_new_session + ) + + async def create_new_session( + user_id: str, + recipe_user_id: RecipeUserId, + access_token_payload: Optional[Dict[str, Any]], + session_data_in_database: Optional[Dict[str, Any]], + disable_anti_csrf: Optional[bool], + tenant_id: str, + user_context: Dict[str, Any], + ): + + request = get_request_from_user_context(user_context) jwt: Optional[str] = None if request is not None: - jwt=request.get_cookie("jwt") + jwt = request.get_cookie("jwt") else: # # This is possible if the function is triggered from the user management dashboard - # + # # In this case because we cannot read the JWT, we create a session without the custom # payload properties # - jwt=None + jwt = None if jwt is not None: # verify JWT using a JWT verification library.. @@ -315,26 +321,30 @@ def override_functions(original_implementation: RecipeInterface): access_token_payload = {} access_token_payload["someKey"] = jwt_payload["someKey"] - return await original_implementation_create_new_session(user_id, access_token_payload, session_data_in_database, disable_anti_csrf, tenant_id, user_context) + return await original_implementation_create_new_session( + user_id, + recipe_user_id, + access_token_payload, + session_data_in_database, + disable_anti_csrf, + tenant_id, + user_context, + ) original_implementation.create_new_session = create_new_session return original_implementation init( - app_info=InputAppInfo( - api_domain="...", app_name="...", website_domain="..."), - - framework='...', # type: ignore + app_info=InputAppInfo(api_domain="...", app_name="...", website_domain="..."), + framework="...", # type: ignore recipe_list=[ session.init( # highlight-start - override=session.InputOverrideConfig( - functions=override_functions - ) + override=session.InputOverrideConfig(functions=override_functions) # highlight-end ) - ] + ], ) ``` diff --git a/v2/thirdpartypasswordless/common-customizations/sessions/claims/access-token-payload.mdx b/v2/thirdpartypasswordless/common-customizations/sessions/claims/access-token-payload.mdx index ac18be4b9..3563fef3a 100644 --- a/v2/thirdpartypasswordless/common-customizations/sessions/claims/access-token-payload.mdx +++ b/v2/thirdpartypasswordless/common-customizations/sessions/claims/access-token-payload.mdx @@ -129,43 +129,53 @@ from supertokens_python import init, InputAppInfo from supertokens_python.recipe import session from supertokens_python.recipe.session.interfaces import RecipeInterface from typing import Any, Dict, Optional +from supertokens_python.types import RecipeUserId def override_functions(original_implementation: RecipeInterface): - original_implementation_create_new_session = original_implementation.create_new_session - - async def create_new_session(user_id: str, - access_token_payload: Optional[Dict[str, Any]], - session_data_in_database: Optional[Dict[str, Any]], - disable_anti_csrf: Optional[bool], - tenant_id: str, - user_context: Dict[str, Any]): + original_implementation_create_new_session = ( + original_implementation.create_new_session + ) + + async def create_new_session( + user_id: str, + recipe_user_id: RecipeUserId, + access_token_payload: Optional[Dict[str, Any]], + session_data_in_database: Optional[Dict[str, Any]], + disable_anti_csrf: Optional[bool], + tenant_id: str, + user_context: Dict[str, Any], + ): if access_token_payload is None: access_token_payload = {} # This goes in the access token, and is available to read on the frontend. - access_token_payload["someKey"] = 'someValue' - - return await original_implementation_create_new_session(user_id, access_token_payload, session_data_in_database, disable_anti_csrf, tenant_id, user_context) + access_token_payload["someKey"] = "someValue" + + return await original_implementation_create_new_session( + user_id, + recipe_user_id, + access_token_payload, + session_data_in_database, + disable_anti_csrf, + tenant_id, + user_context, + ) original_implementation.create_new_session = create_new_session return original_implementation init( - app_info=InputAppInfo( - api_domain="...", app_name="...", website_domain="..."), - - framework='...', # type: ignore + app_info=InputAppInfo(api_domain="...", app_name="...", website_domain="..."), + framework="...", # type: ignore recipe_list=[ session.init( # highlight-start - override=session.InputOverrideConfig( - functions=override_functions - ) + override=session.InputOverrideConfig(functions=override_functions) # highlight-end ) - ] + ], ) ``` diff --git a/v2/thirdpartypasswordless/common-customizations/sessions/claims/claim-validators.mdx b/v2/thirdpartypasswordless/common-customizations/sessions/claims/claim-validators.mdx index 2a7b59a42..fdc63bbb1 100644 --- a/v2/thirdpartypasswordless/common-customizations/sessions/claims/claim-validators.mdx +++ b/v2/thirdpartypasswordless/common-customizations/sessions/claims/claim-validators.mdx @@ -455,24 +455,38 @@ from supertokens_python.recipe import session from supertokens_python.recipe.session.interfaces import RecipeInterface from supertokens_python.recipe.userroles import UserRoleClaim from typing import Any, Dict, Optional +from supertokens_python.types import RecipeUserId def override_functions(original_implementation: RecipeInterface): - original_implementation_create_new_session = original_implementation.create_new_session - - async def create_new_session(user_id: str, - access_token_payload: Optional[Dict[str, Any]], - session_data_in_database: Optional[Dict[str, Any]], - disable_anti_csrf: Optional[bool], - tenant_id: str, - user_context: Dict[str, Any]): + original_implementation_create_new_session = ( + original_implementation.create_new_session + ) + + async def create_new_session( + user_id: str, + recipe_user_id: RecipeUserId, + access_token_payload: Optional[Dict[str, Any]], + session_data_in_database: Optional[Dict[str, Any]], + disable_anti_csrf: Optional[bool], + tenant_id: str, + user_context: Dict[str, Any], + ): if access_token_payload is None: access_token_payload = {} access_token_payload = { **access_token_payload, # highlight-next-line - **(await UserRoleClaim.build(user_id, tenant_id, user_context)) + **( + await UserRoleClaim.build( + user_id, + recipe_user_id, + tenant_id, + access_token_payload, + user_context, + ) + ), } # At this step, the access token paylaod looks like this: @@ -483,24 +497,26 @@ def override_functions(original_implementation: RecipeInterface): # } # } - return await original_implementation_create_new_session(user_id, access_token_payload, session_data_in_database, disable_anti_csrf, tenant_id, user_context) + return await original_implementation_create_new_session( + user_id, + recipe_user_id, + access_token_payload, + session_data_in_database, + disable_anti_csrf, + tenant_id, + user_context, + ) original_implementation.create_new_session = create_new_session return original_implementation init( - app_info=InputAppInfo( - api_domain="...", app_name="...", website_domain="..."), - - framework='...', # type: ignore + app_info=InputAppInfo(api_domain="...", app_name="...", website_domain="..."), + framework="...", # type: ignore recipe_list=[ - session.init( - override=session.InputOverrideConfig( - functions=override_functions - ) - ) - ] + session.init(override=session.InputOverrideConfig(functions=override_functions)) + ], ) ``` @@ -637,7 +653,7 @@ from supertokens_python.recipe.session.interfaces import SessionContainer from supertokens_python.recipe.session.claims import BooleanClaim SecondFactorClaim = BooleanClaim( - key="2fa-completed", fetch_value=lambda _, __, ___: False) + key="2fa-completed", fetch_value=lambda _, __, ___, _____, ______: False) async def set_2fa_claim_as_completed(session: SessionContainer): await session.set_claim_value(SecondFactorClaim, True) @@ -651,7 +667,7 @@ from supertokens_python.recipe.session.interfaces import SessionContainer from supertokens_python.recipe.session.claims import BooleanClaim SecondFactorClaim = BooleanClaim( - key="2fa-completed", fetch_value=lambda _, __, ___: False) + key="2fa-completed", fetch_value=lambda _, __, ___, _____, ______: False) def set_2fa_claim_as_completed(session: SessionContainer): session.sync_set_claim_value(SecondFactorClaim, True) @@ -759,9 +775,10 @@ from supertokens_python.recipe import session from supertokens_python.recipe.session.interfaces import RecipeInterface, SessionClaimValidator from typing import Any, Dict, List from supertokens_python.recipe.session.claims import BooleanClaim +from supertokens_python.types import RecipeUserId SecondFactorClaim = BooleanClaim( - key="2fa-completed", fetch_value=lambda _, __, ___: False) + key="2fa-completed", fetch_value=lambda _, __, ___, _____, ______: False) def override_functions(original_implementation: RecipeInterface): @@ -769,6 +786,7 @@ def override_functions(original_implementation: RecipeInterface): def get_global_claim_validators( tenant_id: str, user_id: str, + recipe_user_id: RecipeUserId, claim_validators_added_by_other_recipes: List[SessionClaimValidator], user_context: Dict[str, Any], ): diff --git a/v2/thirdpartypasswordless/common-customizations/sessions/error-handling.mdx b/v2/thirdpartypasswordless/common-customizations/sessions/error-handling.mdx index c6871d171..70a0f76c3 100644 --- a/v2/thirdpartypasswordless/common-customizations/sessions/error-handling.mdx +++ b/v2/thirdpartypasswordless/common-customizations/sessions/error-handling.mdx @@ -278,8 +278,9 @@ func main() { from supertokens_python import init, InputAppInfo from supertokens_python.recipe import session from supertokens_python.framework import BaseRequest, BaseResponse +from supertokens_python.types import RecipeUserId -async def token_theft_detected_callback(req: BaseRequest, session_handle: str, user_id: str, response: BaseResponse): +async def token_theft_detected_callback(req: BaseRequest, session_handle: str, user_id: str, recipe_user_id: RecipeUserId, response: BaseResponse): # TODO: Write your own logic and then send a 401 response to the frontend return response diff --git a/v2/thirdpartypasswordless/common-customizations/sessions/user-impersonation.mdx b/v2/thirdpartypasswordless/common-customizations/sessions/user-impersonation.mdx index 2b14c1630..1897a872d 100644 --- a/v2/thirdpartypasswordless/common-customizations/sessions/user-impersonation.mdx +++ b/v2/thirdpartypasswordless/common-customizations/sessions/user-impersonation.mdx @@ -518,7 +518,8 @@ from fastapi.responses import JSONResponse from supertokens_python.recipe.session import SessionContainer from fastapi import Depends from supertokens_python.recipe.userroles import UserRoleClaim -from supertokens_python.recipe.emailpassword.asyncio import get_user_by_email +from supertokens_python.asyncio import list_users_by_account_info +from supertokens_python.types import AccountInfo from fastapi import Request @@ -536,19 +537,25 @@ async def impersonate( email = "..." # get from request body # we use the email password recipe here, but you can use the recipe you use - user = await get_user_by_email("public", email) + user = await list_users_by_account_info("public", AccountInfo(email=email)) - if user is None: + if len(user) == 0: # return a 400 error to the client return - await create_new_session(request, "public", user.user_id, {"isImpersonation": True}) + await create_new_session( + request, + "public", + user[0].login_methods[0].recipe_user_id, + {"isImpersonation": True}, + ) # a new session has been created. # - an access & refresh token has been attached to the response's cookie # - a new row has been inserted into the database for this new session return JSONResponse({"message": "Impersonation complete!"}) + ``` @@ -560,7 +567,8 @@ from flask import jsonify from flask.wrappers import Request from supertokens_python.recipe.session.framework.flask import verify_session from supertokens_python.recipe.userroles import UserRoleClaim -from supertokens_python.recipe.emailpassword.syncio import get_user_by_email +from supertokens_python.syncio import list_users_by_account_info +from supertokens_python.types import AccountInfo @app.route("/impersonate", methods=["POST"]) # type: ignore @verify_session( @@ -572,13 +580,13 @@ def login(request: Request): email = "..." # get from request body # we use the email password recipe here, but you can use the recipe you use - user = get_user_by_email("public", email) + user = list_users_by_account_info("public", AccountInfo(email=email)) - if user is None: + if len(user) == 0: # return a 400 error to the client return - create_new_session(request, "public", user.user_id, {"isImpersonation": True}) + create_new_session(request, "public", user[0].login_methods[0].recipe_user_id, {"isImpersonation": True}) # a new session has been created. # - an access & refresh token has been attached to the response's cookie @@ -595,7 +603,8 @@ from supertokens_python.recipe.session.framework.django.asyncio import verify_se from supertokens_python.recipe.session.asyncio import create_new_session from django.http import HttpRequest, JsonResponse from supertokens_python.recipe.userroles import UserRoleClaim -from supertokens_python.recipe.emailpassword.asyncio import get_user_by_email +from supertokens_python.asyncio import list_users_by_account_info +from supertokens_python.types import AccountInfo @verify_session( # We add the UserRoleClaim's includes validator @@ -606,13 +615,13 @@ async def impersonate(request: HttpRequest): email = "..." # get from request body # we use the email password recipe here, but you can use the recipe you use - user = await get_user_by_email("public", email) + user = await list_users_by_account_info("public", AccountInfo(email=email)) - if user is None: + if len(user) == 0: # return a 400 error to the client return - await create_new_session(request, "public", user.user_id, {"isImpersonation": True}) + await create_new_session(request, "public", user[0].login_methods[0].recipe_user_id, {"isImpersonation": True}) # a new session has been created. # - an access & refresh token has been attached to the response's cookie From 596cd14bbd8bce4d07b76a3a5101b15ff7a4c14a Mon Sep 17 00:00:00 2001 From: rishabhpoddar Date: Thu, 17 Oct 2024 21:40:46 +0530 Subject: [PATCH 11/34] fixes more docs --- .../hasura-integration/with-jwt.mdx | 49 ++-- .../ep-migration-without-password-hash.mdx | 225 ++++++++++++------ .../with-aws-lambda/jwt-authorizer.mdx | 4 +- .../hasura-integration/with-jwt.mdx | 49 ++-- .../with-aws-lambda/jwt-authorizer.mdx | 4 +- v2/session/hasura-integration/with-jwt.mdx | 49 ++-- .../with-aws-lambda/jwt-authorizer.mdx | 4 +- v2/thirdparty/hasura-integration/with-jwt.mdx | 49 ++-- .../with-aws-lambda/jwt-authorizer.mdx | 4 +- .../hasura-integration/with-jwt.mdx | 49 ++-- .../ep-migration-without-password-hash.mdx | 225 ++++++++++++------ .../with-aws-lambda/jwt-authorizer.mdx | 4 +- .../hasura-integration/with-jwt.mdx | 49 ++-- .../with-aws-lambda/jwt-authorizer.mdx | 4 +- 14 files changed, 498 insertions(+), 270 deletions(-) diff --git a/v2/emailpassword/hasura-integration/with-jwt.mdx b/v2/emailpassword/hasura-integration/with-jwt.mdx index f808ba61b..31e7c262c 100644 --- a/v2/emailpassword/hasura-integration/with-jwt.mdx +++ b/v2/emailpassword/hasura-integration/with-jwt.mdx @@ -225,45 +225,56 @@ from supertokens_python import init, InputAppInfo from supertokens_python.recipe import session from supertokens_python.recipe.session.interfaces import RecipeInterface from typing import Dict, Optional, Any +from supertokens_python.types import RecipeUserId def override_functions(original_implementation: RecipeInterface): - original_implementation_create_new_session = original_implementation.create_new_session - - async def create_new_session(user_id: str, - access_token_payload: Optional[Dict[str, Any]], - session_data_in_database: Optional[Dict[str, Any]], - disable_anti_csrf: Optional[bool], - tenant_id: str, - user_context: Dict[str, Any]): + original_implementation_create_new_session = ( + original_implementation.create_new_session + ) + + async def create_new_session( + user_id: str, + recipe_user_id: RecipeUserId, + access_token_payload: Optional[Dict[str, Any]], + session_data_in_database: Optional[Dict[str, Any]], + disable_anti_csrf: Optional[bool], + tenant_id: str, + user_context: Dict[str, Any], + ): if access_token_payload is None: access_token_payload = {} - access_token_payload['https://hasura.io/jwt/claims'] = { + access_token_payload["https://hasura.io/jwt/claims"] = { "x-hasura-user-id": user_id, - "x-hasura-default-role": 'user', - "x-hasura-allowed-roles": ['user'], + "x-hasura-default-role": "user", + "x-hasura-allowed-roles": ["user"], } - return await original_implementation_create_new_session(user_id, access_token_payload, session_data_in_database, disable_anti_csrf, tenant_id, user_context) + return await original_implementation_create_new_session( + user_id, + recipe_user_id, + access_token_payload, + session_data_in_database, + disable_anti_csrf, + tenant_id, + user_context, + ) original_implementation.create_new_session = create_new_session return original_implementation init( - app_info=InputAppInfo( - api_domain="...", app_name="...", website_domain="..."), - framework='...', # type: ignore + app_info=InputAppInfo(api_domain="...", app_name="...", website_domain="..."), + framework="...", # type: ignore recipe_list=[ session.init( - override=session.InputOverrideConfig( - functions=override_functions - ), + override=session.InputOverrideConfig(functions=override_functions), expose_access_token_to_frontend_in_cookie_based_auth=True, ) - ] + ], ) ``` diff --git a/v2/emailpassword/migration/account-creation/ep-migration-without-password-hash.mdx b/v2/emailpassword/migration/account-creation/ep-migration-without-password-hash.mdx index b694af292..1b4162dac 100644 --- a/v2/emailpassword/migration/account-creation/ep-migration-without-password-hash.mdx +++ b/v2/emailpassword/migration/account-creation/ep-migration-without-password-hash.mdx @@ -80,13 +80,12 @@ from supertokens_python import init, InputAppInfo from supertokens_python.recipe import emailpassword from supertokens_python.recipe.emailpassword.interfaces import ( APIInterface, - SignUpPostOkResult, - SignUpPostEmailAlreadyExistsError, APIOptions, + EmailAlreadyExistsError, ) -from supertokens_python.types import GeneralErrorResponse from supertokens_python.recipe.emailpassword.types import FormField from typing import Dict, Any, Union, List +from supertokens_python.recipe.session.interfaces import SessionContainer def override_email_password_apis(original_implementation: APIInterface): @@ -95,11 +94,11 @@ def override_email_password_apis(original_implementation: APIInterface): async def sign_up( form_fields: List[FormField], tenant_id: str, + session: Union[SessionContainer, None], + should_try_linking_with_session_user: Union[bool, None], api_options: APIOptions, user_context: Dict[str, Any], - ) -> Union[ - SignUpPostOkResult, SignUpPostEmailAlreadyExistsError, GeneralErrorResponse - ]: + ): email = "" for field in form_fields: if field.id == "email": @@ -107,9 +106,16 @@ def override_email_password_apis(original_implementation: APIInterface): # check if the user signing in exists in the external provider if await does_user_exist_in_external_provider(email): # Return SignUpEmailAlreadyExistsError since the user exists in the external provider - return SignUpPostEmailAlreadyExistsError() - - return await original_sign_up(form_fields, tenant_id, api_options, user_context) + return EmailAlreadyExistsError() + + return await original_sign_up( + form_fields, + tenant_id, + session, + should_try_linking_with_session_user, + api_options, + user_context, + ) original_implementation.sign_up_post = sign_up return original_implementation @@ -249,6 +255,7 @@ EmailPassword.init({ // Set the userId in the response to use the provider's userId signUpResponse.user.id = legacyUserInfo.user_id signUpResponse.user.loginMethods[0].recipeUserId = new RecipeUserId(legacyUserInfo.user_id); + signUpResponse.recipeUserId = new RecipeUserId(legacyUserInfo.user_id); // We will also need to set the email verification status of the user if (legacyUserInfo.isEmailVerified) { @@ -290,10 +297,7 @@ from supertokens_python.recipe.emailverification.asyncio import ( create_email_verification_token, verify_email_using_token, ) -from supertokens_python.recipe.emailpassword.asyncio import get_user_by_email, sign_up -from supertokens_python.recipe.emailpassword.interfaces import ( - SignUpEmailAlreadyExistsError, -) +from supertokens_python.recipe.emailpassword.asyncio import sign_up from supertokens_python.recipe.emailverification.interfaces import ( CreateEmailVerificationTokenOkResult, ) @@ -301,13 +305,15 @@ from supertokens_python import init, InputAppInfo from supertokens_python.recipe import emailpassword from supertokens_python.recipe.emailpassword.interfaces import ( APIInterface, - SignInPostOkResult, - SignInPostWrongCredentialsError, APIOptions, + SignUpOkResult, + WrongCredentialsError, ) -from supertokens_python.types import GeneralErrorResponse from supertokens_python.recipe.emailpassword.types import FormField from typing import Dict, Any, Union, List +from supertokens_python.recipe.session.interfaces import SessionContainer +from supertokens_python.asyncio import list_users_by_account_info +from supertokens_python.types import AccountInfo, RecipeUserId def override_emailpassword_apis(original_implementation: APIInterface): @@ -316,11 +322,11 @@ def override_emailpassword_apis(original_implementation: APIInterface): async def sign_in( form_fields: List[FormField], tenant_id: str, + session: Union[SessionContainer, None], + should_try_linking_with_session_user: Union[bool, None], api_options: APIOptions, user_context: Dict[str, Any], - ) -> Union[ - SignInPostOkResult, SignInPostWrongCredentialsError, GeneralErrorResponse - ]: + ): email = "" password = "" for field in form_fields: @@ -329,7 +335,20 @@ def override_emailpassword_apis(original_implementation: APIInterface): if field.id == "password": password = field.value # Check if an email-password user with the input email exists in SuperTokens - emailpassword_user = await get_user_by_email(tenant_id, email, user_context) + supertokens_user_with_same_email = await list_users_by_account_info( + tenant_id, AccountInfo(email=email), False, user_context + ) + emailpassword_user = next( + ( + user + for user in supertokens_user_with_same_email + if any( + lm.recipe_id == "emailpassword" and lm.has_same_email_as(email) + for lm in user.login_methods + ) + ), + None, + ) if emailpassword_user is None: # EmailPassword user with the input email does not exist in SuperTokens @@ -339,19 +358,21 @@ def override_emailpassword_apis(original_implementation: APIInterface): ) if legacy_user_info is None: # Credentials are incorrect - return SignInPostWrongCredentialsError() + return WrongCredentialsError() # Call the sign_up function to create a new SuperTokens user. - response = await sign_up(email, password, tenant_id, user_context) - if isinstance(response, SignUpEmailAlreadyExistsError): + response = await sign_up(tenant_id, email, password, None, user_context) + if not isinstance(response, SignUpOkResult): raise Exception("Should never come here") # Map the external provider's userId to the SuperTokens userId - await create_user_id_mapping( - response.user.user_id, legacy_user_info.user_id - ) + await create_user_id_mapping(response.user.id, legacy_user_info.user_id) # Set the userId in the response to use the provider's userId - response.user.user_id = legacy_user_info.user_id + response.user.id = legacy_user_info.user_id + response.user.login_methods[0].recipe_user_id = RecipeUserId( + legacy_user_info.user_id + ) + response.recipe_user_id = RecipeUserId(legacy_user_info.user_id) # We will also need to set the email verification status of the user if legacy_user_info.isEmailVerified: @@ -359,7 +380,7 @@ def override_emailpassword_apis(original_implementation: APIInterface): generate_email_verification_response = ( await create_email_verification_token( tenant_id, - response.user.user_id, + response.recipe_user_id, email, user_context, ) @@ -371,11 +392,17 @@ def override_emailpassword_apis(original_implementation: APIInterface): await verify_email_using_token( tenant_id, generate_email_verification_response.token, + True, user_context, ) return await original_emailpassword_sign_in( - form_fields, tenant_id, api_options, user_context + form_fields, + tenant_id, + session, + should_try_linking_with_session_user, + api_options, + user_context, ) original_implementation.sign_in_post = sign_in @@ -545,6 +572,7 @@ import SuperTokens from "supertokens-node" import EmailPassword from "supertokens-node/recipe/emailpassword" import EmailVerification from "supertokens-node/recipe/emailverification" import UserMetadata from "supertokens-node/recipe/usermetadata" +import { RecipeUserId } from "supertokens-node"; EmailPassword.init({ override: { @@ -580,6 +608,9 @@ EmailPassword.init({ superTokensUserId: signupResponse.user.id, externalUserId: legacyUserData.user_id }) + signUpResponse.user.id = legacyUserData.user_id + signUpResponse.user.loginMethods[0].recipeUserId = new RecipeUserId(legacyUserData.user_id); + signUpResponse.recipeUserId = new RecipeUserId(legacyUserData.user_id); // We will also need to set the email verification status of the user if (legacyUserData.isEmailVerified) { @@ -627,19 +658,19 @@ async function retrieveUserDataFromExternalProvider(email: string): Promise<{ from supertokens_python import init, InputAppInfo from supertokens_python.asyncio import create_user_id_mapping from supertokens_python.recipe import emailpassword -from supertokens_python.types import GeneralErrorResponse +from supertokens_python.asyncio import list_users_by_account_info +from supertokens_python.types import AccountInfo, RecipeUserId from supertokens_python.recipe.emailverification.asyncio import ( create_email_verification_token, verify_email_using_token, ) from supertokens_python.recipe.emailpassword.types import FormField -from supertokens_python.recipe.emailpassword.asyncio import get_user_by_email, sign_up +from supertokens_python.recipe.emailpassword.asyncio import sign_up from supertokens_python.recipe.usermetadata.asyncio import update_user_metadata from supertokens_python.recipe.emailpassword.interfaces import ( APIInterface, APIOptions, - GeneratePasswordResetTokenPostOkResult, - SignUpEmailAlreadyExistsError, + SignUpOkResult, ) from supertokens_python.recipe.emailverification.interfaces import ( CreateEmailVerificationTokenOkResult, @@ -657,7 +688,7 @@ def override_emailpassword_apis(original_implementation: APIInterface): tenant_id: str, api_options: APIOptions, user_context: Dict[str, Any], - ) -> Union[GeneratePasswordResetTokenPostOkResult, GeneralErrorResponse]: + ): # retrieve the email from the form fields email = None for field in form_fields: @@ -667,7 +698,20 @@ def override_emailpassword_apis(original_implementation: APIInterface): raise Exception("Should never come here") # Check if an email-password user with the input email exists in SuperTokens - emailpassword_user = await get_user_by_email(tenant_id, email, user_context) + supertokens_user_with_same_email = await list_users_by_account_info( + tenant_id, AccountInfo(email=email), False, user_context + ) + emailpassword_user = next( + ( + user + for user in supertokens_user_with_same_email + if any( + lm.recipe_id == "emailpassword" and lm.has_same_email_as(email) + for lm in user.login_methods + ) + ), + None, + ) if emailpassword_user is None: # EmailPassword user with the input email does not exist in SuperTokens @@ -676,21 +720,26 @@ def override_emailpassword_apis(original_implementation: APIInterface): if legacy_user_data is not None: # Create a SuperTokens account for the user with a temporary password tempPassword = await generate_password() - response = await sign_up(tenant_id, email, tempPassword, user_context) - if isinstance(response, SignUpEmailAlreadyExistsError): + response = await sign_up( + tenant_id, email, tempPassword, None, user_context + ) + if not isinstance(response, SignUpOkResult): raise Exception("Should never come here") # Map the SuperTokens userId to the legacy userId - await create_user_id_mapping( - response.user.user_id, legacy_user_data.user_id + await create_user_id_mapping(response.user.id, legacy_user_data.user_id) + response.user.id = legacy_user_data.user_id + response.user.login_methods[0].recipe_user_id = RecipeUserId( + legacy_user_data.user_id ) + response.recipe_user_id = RecipeUserId(legacy_user_data.user_id) # We will also need to set the email verification status if legacy_user_data.isEmailVerified: # Generate an email verification token for the user generate_email_verification_token_response = ( await create_email_verification_token( - tenant_id, response.user.user_id, email, user_context + tenant_id, response.recipe_user_id, email, user_context ) ) if isinstance( @@ -701,6 +750,7 @@ def override_emailpassword_apis(original_implementation: APIInterface): await verify_email_using_token( tenant_id, generate_email_verification_token_response.token, + True, user_context, ) @@ -931,7 +981,6 @@ EmailPassword.init({ ```python from supertokens_python import init, InputAppInfo from supertokens_python.recipe import emailpassword -from supertokens_python.types import GeneralErrorResponse from supertokens_python.recipe.emailpassword.types import FormField from supertokens_python.recipe.usermetadata.asyncio import ( get_user_metadata, @@ -941,9 +990,8 @@ from supertokens_python.recipe.emailpassword.interfaces import ( APIInterface, APIOptions, PasswordResetPostOkResult, - PasswordResetPostInvalidTokenResponse, ) -from typing import Union, Dict, Any, List +from typing import Dict, Any, List def override_emailpassword_apis(original_implementation: APIInterface): @@ -955,28 +1003,23 @@ def override_emailpassword_apis(original_implementation: APIInterface): tenant_id: str, api_options: APIOptions, user_context: Dict[str, Any], - ) -> Union[ - PasswordResetPostOkResult, - PasswordResetPostInvalidTokenResponse, - GeneralErrorResponse, - ]: + ): response = await original_password_reset_post( form_fields, token, tenant_id, api_options, user_context ) if ( isinstance(response, PasswordResetPostOkResult) - and response.user_id is not None ): # Check that the user has the isUsingTemporaryPassword flag set in their metadata - metadata_result = await get_user_metadata(response.user_id, user_context) + metadata_result = await get_user_metadata(response.user.id, user_context) if ( "isUsingTemporaryPassword" in metadata_result.metadata and metadata_result.metadata["isUsingTemporaryPassword"] is True ): # Since the password has been successfully reset, we can remove the isUsingTemporaryPassword flag await update_user_metadata( - response.user_id, {"isUsingTemporaryPassword": None} + response.user.id, {"isUsingTemporaryPassword": None} ) return response @@ -1187,13 +1230,9 @@ from supertokens_python.recipe.emailverification.asyncio import ( verify_email_using_token, ) from supertokens_python.recipe.emailpassword.asyncio import ( - get_user_by_email, sign_up, update_email_or_password, ) -from supertokens_python.recipe.emailpassword.interfaces import ( - SignUpEmailAlreadyExistsError, -) from supertokens_python.recipe.emailverification.interfaces import ( CreateEmailVerificationTokenOkResult, ) @@ -1201,13 +1240,15 @@ from supertokens_python import init, InputAppInfo from supertokens_python.recipe import emailpassword from supertokens_python.recipe.emailpassword.interfaces import ( APIInterface, - SignInPostOkResult, - SignInPostWrongCredentialsError, APIOptions, + SignUpOkResult, + WrongCredentialsError, ) -from supertokens_python.types import GeneralErrorResponse from supertokens_python.recipe.emailpassword.types import FormField from typing import Dict, Any, Union, List +from supertokens_python.recipe.session.interfaces import SessionContainer +from supertokens_python.asyncio import list_users_by_account_info +from supertokens_python.types import AccountInfo, RecipeUserId from supertokens_python.recipe.usermetadata.asyncio import ( get_user_metadata, update_user_metadata, @@ -1220,11 +1261,11 @@ def override_emailpassword_apis(original_implementation: APIInterface): async def sign_in( form_fields: List[FormField], tenant_id: str, + session: Union[SessionContainer, None], + should_try_linking_with_session_user: Union[bool, None], api_options: APIOptions, user_context: Dict[str, Any], - ) -> Union[ - SignInPostOkResult, SignInPostWrongCredentialsError, GeneralErrorResponse - ]: + ): email = "" password = "" for field in form_fields: @@ -1233,7 +1274,20 @@ def override_emailpassword_apis(original_implementation: APIInterface): if field.id == "password": password = field.value # Check if an email-password user with the input email exists in SuperTokens - emailpassword_user = await get_user_by_email(tenant_id, email, user_context) + supertokens_user_with_same_email = await list_users_by_account_info( + tenant_id, AccountInfo(email=email), False, user_context + ) + emailpassword_user = next( + ( + user + for user in supertokens_user_with_same_email + if any( + lm.recipe_id == "emailpassword" and lm.has_same_email_as(email) + for lm in user.login_methods + ) + ), + None, + ) if emailpassword_user is None: # EmailPassword user with the input email does not exist in SuperTokens @@ -1243,26 +1297,31 @@ def override_emailpassword_apis(original_implementation: APIInterface): ) if legacy_user_info is None: # Credentials are incorrect - return SignInPostWrongCredentialsError() + return WrongCredentialsError() # Call the sign_up function to create a new SuperTokens user. - response = await sign_up(email, password, tenant_id, user_context) - if isinstance(response, SignUpEmailAlreadyExistsError): + response = await sign_up(tenant_id, email, password, None, user_context) + if not isinstance(response, SignUpOkResult): raise Exception("Should never come here") # Map the external provider's userId to the SuperTokens userId - await create_user_id_mapping( - response.user.user_id, legacy_user_info.user_id - ) + await create_user_id_mapping(response.user.id, legacy_user_info.user_id) # Set the userId in the response to use the provider's userId - response.user.user_id = legacy_user_info.user_id + response.user.id = legacy_user_info.user_id + response.user.login_methods[0].recipe_user_id = RecipeUserId( + legacy_user_info.user_id + ) + response.recipe_user_id = RecipeUserId(legacy_user_info.user_id) # We will also need to set the email verification status of the user if legacy_user_info.isEmailVerified: # Generate an email verification token for the user generate_email_verification_response = ( await create_email_verification_token( - tenant_id, response.user.user_id, email, user_context + tenant_id, + response.recipe_user_id, + email, + user_context, ) ) if isinstance( @@ -1272,13 +1331,14 @@ def override_emailpassword_apis(original_implementation: APIInterface): await verify_email_using_token( tenant_id, generate_email_verification_response.token, + True, user_context, ) emailpassword_user = response.user # highlight-start # Check if the user signing in has a temporary password - metadata_result = await get_user_metadata(emailpassword_user.user_id) + metadata_result = await get_user_metadata(emailpassword_user.id) if ( "isUsingTemporaryPassword" in metadata_result.metadata and metadata_result.metadata["isUsingTemporaryPassword"] is True @@ -1288,9 +1348,19 @@ def override_emailpassword_apis(original_implementation: APIInterface): email, password ) if legacy_user_info is not None: + # Find the emailpassword login method for the user + login_method = next( + ( + lm + for lm in emailpassword_user.login_methods + if lm.recipe_id == "emailpassword" and lm.email == email + ), + None, + ) + assert login_method is not None # Update the user's password with the correct password await update_email_or_password( - emailpassword_user.user_id, + login_method.recipe_user_id, None, password, False, @@ -1299,14 +1369,19 @@ def override_emailpassword_apis(original_implementation: APIInterface): ) # Update the user's metadata to remove the isUsingTemporaryPassword flag await update_user_metadata( - emailpassword_user.user_id, {"isUsingTemporaryPassword": None} + emailpassword_user.id, {"isUsingTemporaryPassword": None} ) else: - return SignInPostWrongCredentialsError() + return WrongCredentialsError() # highlight-end return await original_emailpassword_sign_in( - form_fields, tenant_id, api_options, user_context + form_fields, + tenant_id, + session, + should_try_linking_with_session_user, + api_options, + user_context, ) original_implementation.sign_in_post = sign_in diff --git a/v2/emailpassword/serverless/with-aws-lambda/jwt-authorizer.mdx b/v2/emailpassword/serverless/with-aws-lambda/jwt-authorizer.mdx index ae937766b..86055a1d9 100644 --- a/v2/emailpassword/serverless/with-aws-lambda/jwt-authorizer.mdx +++ b/v2/emailpassword/serverless/with-aws-lambda/jwt-authorizer.mdx @@ -98,6 +98,7 @@ from supertokens_python import ( from supertokens_python.recipe.session.interfaces import RecipeInterface as SessionRecipeInterface from typing import Any, Dict, Optional +from supertokens_python.types import RecipeUserId supertokens_config = SupertokensConfig( ^{coreInjector_connection_uri_comment_with_hash} @@ -123,6 +124,7 @@ def override_session_functions(oi: SessionRecipeInterface) -> SessionRecipeInter async def create_new_session( user_id: str, + recipe_user_id: RecipeUserId, access_token_payload: Optional[Dict[str, Any]], session_data_in_database: Optional[Dict[str, Any]], disable_anti_csrf: Optional[bool], @@ -138,7 +140,7 @@ def override_session_functions(oi: SessionRecipeInterface) -> SessionRecipeInter access_token_payload = {} access_token_payload["aud"] = "jwtAuthorizers" - return await oi_create_new_session(user_id, access_token_payload, session_data_in_database, disable_anti_csrf, tenant_id, user_context) + return await oi_create_new_session(user_id, recipe_user_id, access_token_payload, session_data_in_database, disable_anti_csrf, tenant_id, user_context) oi.create_new_session = create_new_session return oi diff --git a/v2/passwordless/hasura-integration/with-jwt.mdx b/v2/passwordless/hasura-integration/with-jwt.mdx index f808ba61b..31e7c262c 100644 --- a/v2/passwordless/hasura-integration/with-jwt.mdx +++ b/v2/passwordless/hasura-integration/with-jwt.mdx @@ -225,45 +225,56 @@ from supertokens_python import init, InputAppInfo from supertokens_python.recipe import session from supertokens_python.recipe.session.interfaces import RecipeInterface from typing import Dict, Optional, Any +from supertokens_python.types import RecipeUserId def override_functions(original_implementation: RecipeInterface): - original_implementation_create_new_session = original_implementation.create_new_session - - async def create_new_session(user_id: str, - access_token_payload: Optional[Dict[str, Any]], - session_data_in_database: Optional[Dict[str, Any]], - disable_anti_csrf: Optional[bool], - tenant_id: str, - user_context: Dict[str, Any]): + original_implementation_create_new_session = ( + original_implementation.create_new_session + ) + + async def create_new_session( + user_id: str, + recipe_user_id: RecipeUserId, + access_token_payload: Optional[Dict[str, Any]], + session_data_in_database: Optional[Dict[str, Any]], + disable_anti_csrf: Optional[bool], + tenant_id: str, + user_context: Dict[str, Any], + ): if access_token_payload is None: access_token_payload = {} - access_token_payload['https://hasura.io/jwt/claims'] = { + access_token_payload["https://hasura.io/jwt/claims"] = { "x-hasura-user-id": user_id, - "x-hasura-default-role": 'user', - "x-hasura-allowed-roles": ['user'], + "x-hasura-default-role": "user", + "x-hasura-allowed-roles": ["user"], } - return await original_implementation_create_new_session(user_id, access_token_payload, session_data_in_database, disable_anti_csrf, tenant_id, user_context) + return await original_implementation_create_new_session( + user_id, + recipe_user_id, + access_token_payload, + session_data_in_database, + disable_anti_csrf, + tenant_id, + user_context, + ) original_implementation.create_new_session = create_new_session return original_implementation init( - app_info=InputAppInfo( - api_domain="...", app_name="...", website_domain="..."), - framework='...', # type: ignore + app_info=InputAppInfo(api_domain="...", app_name="...", website_domain="..."), + framework="...", # type: ignore recipe_list=[ session.init( - override=session.InputOverrideConfig( - functions=override_functions - ), + override=session.InputOverrideConfig(functions=override_functions), expose_access_token_to_frontend_in_cookie_based_auth=True, ) - ] + ], ) ``` diff --git a/v2/passwordless/serverless/with-aws-lambda/jwt-authorizer.mdx b/v2/passwordless/serverless/with-aws-lambda/jwt-authorizer.mdx index ae937766b..86055a1d9 100644 --- a/v2/passwordless/serverless/with-aws-lambda/jwt-authorizer.mdx +++ b/v2/passwordless/serverless/with-aws-lambda/jwt-authorizer.mdx @@ -98,6 +98,7 @@ from supertokens_python import ( from supertokens_python.recipe.session.interfaces import RecipeInterface as SessionRecipeInterface from typing import Any, Dict, Optional +from supertokens_python.types import RecipeUserId supertokens_config = SupertokensConfig( ^{coreInjector_connection_uri_comment_with_hash} @@ -123,6 +124,7 @@ def override_session_functions(oi: SessionRecipeInterface) -> SessionRecipeInter async def create_new_session( user_id: str, + recipe_user_id: RecipeUserId, access_token_payload: Optional[Dict[str, Any]], session_data_in_database: Optional[Dict[str, Any]], disable_anti_csrf: Optional[bool], @@ -138,7 +140,7 @@ def override_session_functions(oi: SessionRecipeInterface) -> SessionRecipeInter access_token_payload = {} access_token_payload["aud"] = "jwtAuthorizers" - return await oi_create_new_session(user_id, access_token_payload, session_data_in_database, disable_anti_csrf, tenant_id, user_context) + return await oi_create_new_session(user_id, recipe_user_id, access_token_payload, session_data_in_database, disable_anti_csrf, tenant_id, user_context) oi.create_new_session = create_new_session return oi diff --git a/v2/session/hasura-integration/with-jwt.mdx b/v2/session/hasura-integration/with-jwt.mdx index 839f21c6d..02c5ff869 100644 --- a/v2/session/hasura-integration/with-jwt.mdx +++ b/v2/session/hasura-integration/with-jwt.mdx @@ -206,45 +206,56 @@ from supertokens_python import init, InputAppInfo from supertokens_python.recipe import session from supertokens_python.recipe.session.interfaces import RecipeInterface from typing import Dict, Optional, Any +from supertokens_python.types import RecipeUserId def override_functions(original_implementation: RecipeInterface): - original_implementation_create_new_session = original_implementation.create_new_session - - async def create_new_session(user_id: str, - access_token_payload: Optional[Dict[str, Any]], - session_data_in_database: Optional[Dict[str, Any]], - disable_anti_csrf: Optional[bool], - tenant_id: str, - user_context: Dict[str, Any]): + original_implementation_create_new_session = ( + original_implementation.create_new_session + ) + + async def create_new_session( + user_id: str, + recipe_user_id: RecipeUserId, + access_token_payload: Optional[Dict[str, Any]], + session_data_in_database: Optional[Dict[str, Any]], + disable_anti_csrf: Optional[bool], + tenant_id: str, + user_context: Dict[str, Any], + ): if access_token_payload is None: access_token_payload = {} - access_token_payload['https://hasura.io/jwt/claims'] = { + access_token_payload["https://hasura.io/jwt/claims"] = { "x-hasura-user-id": user_id, - "x-hasura-default-role": 'user', - "x-hasura-allowed-roles": ['user'], + "x-hasura-default-role": "user", + "x-hasura-allowed-roles": ["user"], } - return await original_implementation_create_new_session(user_id, access_token_payload, session_data_in_database, disable_anti_csrf, tenant_id, user_context) + return await original_implementation_create_new_session( + user_id, + recipe_user_id, + access_token_payload, + session_data_in_database, + disable_anti_csrf, + tenant_id, + user_context, + ) original_implementation.create_new_session = create_new_session return original_implementation init( - app_info=InputAppInfo( - api_domain="...", app_name="...", website_domain="..."), - framework='...', # type: ignore + app_info=InputAppInfo(api_domain="...", app_name="...", website_domain="..."), + framework="...", # type: ignore recipe_list=[ session.init( - override=session.InputOverrideConfig( - functions=override_functions - ), + override=session.InputOverrideConfig(functions=override_functions), expose_access_token_to_frontend_in_cookie_based_auth=True, ) - ] + ], ) ``` diff --git a/v2/session/serverless/with-aws-lambda/jwt-authorizer.mdx b/v2/session/serverless/with-aws-lambda/jwt-authorizer.mdx index ae937766b..86055a1d9 100644 --- a/v2/session/serverless/with-aws-lambda/jwt-authorizer.mdx +++ b/v2/session/serverless/with-aws-lambda/jwt-authorizer.mdx @@ -98,6 +98,7 @@ from supertokens_python import ( from supertokens_python.recipe.session.interfaces import RecipeInterface as SessionRecipeInterface from typing import Any, Dict, Optional +from supertokens_python.types import RecipeUserId supertokens_config = SupertokensConfig( ^{coreInjector_connection_uri_comment_with_hash} @@ -123,6 +124,7 @@ def override_session_functions(oi: SessionRecipeInterface) -> SessionRecipeInter async def create_new_session( user_id: str, + recipe_user_id: RecipeUserId, access_token_payload: Optional[Dict[str, Any]], session_data_in_database: Optional[Dict[str, Any]], disable_anti_csrf: Optional[bool], @@ -138,7 +140,7 @@ def override_session_functions(oi: SessionRecipeInterface) -> SessionRecipeInter access_token_payload = {} access_token_payload["aud"] = "jwtAuthorizers" - return await oi_create_new_session(user_id, access_token_payload, session_data_in_database, disable_anti_csrf, tenant_id, user_context) + return await oi_create_new_session(user_id, recipe_user_id, access_token_payload, session_data_in_database, disable_anti_csrf, tenant_id, user_context) oi.create_new_session = create_new_session return oi diff --git a/v2/thirdparty/hasura-integration/with-jwt.mdx b/v2/thirdparty/hasura-integration/with-jwt.mdx index f808ba61b..31e7c262c 100644 --- a/v2/thirdparty/hasura-integration/with-jwt.mdx +++ b/v2/thirdparty/hasura-integration/with-jwt.mdx @@ -225,45 +225,56 @@ from supertokens_python import init, InputAppInfo from supertokens_python.recipe import session from supertokens_python.recipe.session.interfaces import RecipeInterface from typing import Dict, Optional, Any +from supertokens_python.types import RecipeUserId def override_functions(original_implementation: RecipeInterface): - original_implementation_create_new_session = original_implementation.create_new_session - - async def create_new_session(user_id: str, - access_token_payload: Optional[Dict[str, Any]], - session_data_in_database: Optional[Dict[str, Any]], - disable_anti_csrf: Optional[bool], - tenant_id: str, - user_context: Dict[str, Any]): + original_implementation_create_new_session = ( + original_implementation.create_new_session + ) + + async def create_new_session( + user_id: str, + recipe_user_id: RecipeUserId, + access_token_payload: Optional[Dict[str, Any]], + session_data_in_database: Optional[Dict[str, Any]], + disable_anti_csrf: Optional[bool], + tenant_id: str, + user_context: Dict[str, Any], + ): if access_token_payload is None: access_token_payload = {} - access_token_payload['https://hasura.io/jwt/claims'] = { + access_token_payload["https://hasura.io/jwt/claims"] = { "x-hasura-user-id": user_id, - "x-hasura-default-role": 'user', - "x-hasura-allowed-roles": ['user'], + "x-hasura-default-role": "user", + "x-hasura-allowed-roles": ["user"], } - return await original_implementation_create_new_session(user_id, access_token_payload, session_data_in_database, disable_anti_csrf, tenant_id, user_context) + return await original_implementation_create_new_session( + user_id, + recipe_user_id, + access_token_payload, + session_data_in_database, + disable_anti_csrf, + tenant_id, + user_context, + ) original_implementation.create_new_session = create_new_session return original_implementation init( - app_info=InputAppInfo( - api_domain="...", app_name="...", website_domain="..."), - framework='...', # type: ignore + app_info=InputAppInfo(api_domain="...", app_name="...", website_domain="..."), + framework="...", # type: ignore recipe_list=[ session.init( - override=session.InputOverrideConfig( - functions=override_functions - ), + override=session.InputOverrideConfig(functions=override_functions), expose_access_token_to_frontend_in_cookie_based_auth=True, ) - ] + ], ) ``` diff --git a/v2/thirdparty/serverless/with-aws-lambda/jwt-authorizer.mdx b/v2/thirdparty/serverless/with-aws-lambda/jwt-authorizer.mdx index ae937766b..86055a1d9 100644 --- a/v2/thirdparty/serverless/with-aws-lambda/jwt-authorizer.mdx +++ b/v2/thirdparty/serverless/with-aws-lambda/jwt-authorizer.mdx @@ -98,6 +98,7 @@ from supertokens_python import ( from supertokens_python.recipe.session.interfaces import RecipeInterface as SessionRecipeInterface from typing import Any, Dict, Optional +from supertokens_python.types import RecipeUserId supertokens_config = SupertokensConfig( ^{coreInjector_connection_uri_comment_with_hash} @@ -123,6 +124,7 @@ def override_session_functions(oi: SessionRecipeInterface) -> SessionRecipeInter async def create_new_session( user_id: str, + recipe_user_id: RecipeUserId, access_token_payload: Optional[Dict[str, Any]], session_data_in_database: Optional[Dict[str, Any]], disable_anti_csrf: Optional[bool], @@ -138,7 +140,7 @@ def override_session_functions(oi: SessionRecipeInterface) -> SessionRecipeInter access_token_payload = {} access_token_payload["aud"] = "jwtAuthorizers" - return await oi_create_new_session(user_id, access_token_payload, session_data_in_database, disable_anti_csrf, tenant_id, user_context) + return await oi_create_new_session(user_id, recipe_user_id, access_token_payload, session_data_in_database, disable_anti_csrf, tenant_id, user_context) oi.create_new_session = create_new_session return oi diff --git a/v2/thirdpartyemailpassword/hasura-integration/with-jwt.mdx b/v2/thirdpartyemailpassword/hasura-integration/with-jwt.mdx index 50507ee34..16bcb21fe 100644 --- a/v2/thirdpartyemailpassword/hasura-integration/with-jwt.mdx +++ b/v2/thirdpartyemailpassword/hasura-integration/with-jwt.mdx @@ -225,45 +225,56 @@ from supertokens_python import init, InputAppInfo from supertokens_python.recipe import session from supertokens_python.recipe.session.interfaces import RecipeInterface from typing import Dict, Optional, Any +from supertokens_python.types import RecipeUserId def override_functions(original_implementation: RecipeInterface): - original_implementation_create_new_session = original_implementation.create_new_session - - async def create_new_session(user_id: str, - access_token_payload: Optional[Dict[str, Any]], - session_data_in_database: Optional[Dict[str, Any]], - disable_anti_csrf: Optional[bool], - tenant_id: str, - user_context: Dict[str, Any]): + original_implementation_create_new_session = ( + original_implementation.create_new_session + ) + + async def create_new_session( + user_id: str, + recipe_user_id: RecipeUserId, + access_token_payload: Optional[Dict[str, Any]], + session_data_in_database: Optional[Dict[str, Any]], + disable_anti_csrf: Optional[bool], + tenant_id: str, + user_context: Dict[str, Any], + ): if access_token_payload is None: access_token_payload = {} - access_token_payload['https://hasura.io/jwt/claims'] = { + access_token_payload["https://hasura.io/jwt/claims"] = { "x-hasura-user-id": user_id, - "x-hasura-default-role": 'user', - "x-hasura-allowed-roles": ['user'], + "x-hasura-default-role": "user", + "x-hasura-allowed-roles": ["user"], } - return await original_implementation_create_new_session(user_id, access_token_payload, session_data_in_database, disable_anti_csrf, tenant_id, user_context) + return await original_implementation_create_new_session( + user_id, + recipe_user_id, + access_token_payload, + session_data_in_database, + disable_anti_csrf, + tenant_id, + user_context, + ) original_implementation.create_new_session = create_new_session return original_implementation init( - app_info=InputAppInfo( - api_domain="...", app_name="...", website_domain="..."), - framework='...', # type: ignore + app_info=InputAppInfo(api_domain="...", app_name="...", website_domain="..."), + framework="...", # type: ignore recipe_list=[ session.init( - override=session.InputOverrideConfig( - functions=override_functions - ), + override=session.InputOverrideConfig(functions=override_functions), expose_access_token_to_frontend_in_cookie_based_auth=True, ) - ] + ], ) ``` diff --git a/v2/thirdpartyemailpassword/migration/account-creation/ep-migration-without-password-hash.mdx b/v2/thirdpartyemailpassword/migration/account-creation/ep-migration-without-password-hash.mdx index 6b5a356dd..9dafd56ac 100644 --- a/v2/thirdpartyemailpassword/migration/account-creation/ep-migration-without-password-hash.mdx +++ b/v2/thirdpartyemailpassword/migration/account-creation/ep-migration-without-password-hash.mdx @@ -80,13 +80,12 @@ from supertokens_python import init, InputAppInfo from supertokens_python.recipe import emailpassword from supertokens_python.recipe.emailpassword.interfaces import ( APIInterface, - SignUpPostOkResult, - SignUpPostEmailAlreadyExistsError, APIOptions, + EmailAlreadyExistsError, ) -from supertokens_python.types import GeneralErrorResponse from supertokens_python.recipe.emailpassword.types import FormField from typing import Dict, Any, Union, List +from supertokens_python.recipe.session.interfaces import SessionContainer def override_email_password_apis(original_implementation: APIInterface): @@ -95,11 +94,11 @@ def override_email_password_apis(original_implementation: APIInterface): async def sign_up( form_fields: List[FormField], tenant_id: str, + session: Union[SessionContainer, None], + should_try_linking_with_session_user: Union[bool, None], api_options: APIOptions, user_context: Dict[str, Any], - ) -> Union[ - SignUpPostOkResult, SignUpPostEmailAlreadyExistsError, GeneralErrorResponse - ]: + ): email = "" for field in form_fields: if field.id == "email": @@ -107,9 +106,16 @@ def override_email_password_apis(original_implementation: APIInterface): # check if the user signing in exists in the external provider if await does_user_exist_in_external_provider(email): # Return SignUpEmailAlreadyExistsError since the user exists in the external provider - return SignUpPostEmailAlreadyExistsError() - - return await original_sign_up(form_fields, tenant_id, api_options, user_context) + return EmailAlreadyExistsError() + + return await original_sign_up( + form_fields, + tenant_id, + session, + should_try_linking_with_session_user, + api_options, + user_context, + ) original_implementation.sign_up_post = sign_up return original_implementation @@ -249,6 +255,7 @@ EmailPassword.init({ // Set the userId in the response to use the provider's userId signUpResponse.user.id = legacyUserInfo.user_id signUpResponse.user.loginMethods[0].recipeUserId = new RecipeUserId(legacyUserInfo.user_id); + signUpResponse.recipeUserId = new RecipeUserId(legacyUserInfo.user_id); // We will also need to set the email verification status of the user if (legacyUserInfo.isEmailVerified) { @@ -290,10 +297,7 @@ from supertokens_python.recipe.emailverification.asyncio import ( create_email_verification_token, verify_email_using_token, ) -from supertokens_python.recipe.emailpassword.asyncio import get_user_by_email, sign_up -from supertokens_python.recipe.emailpassword.interfaces import ( - SignUpEmailAlreadyExistsError, -) +from supertokens_python.recipe.emailpassword.asyncio import sign_up from supertokens_python.recipe.emailverification.interfaces import ( CreateEmailVerificationTokenOkResult, ) @@ -301,13 +305,15 @@ from supertokens_python import init, InputAppInfo from supertokens_python.recipe import emailpassword from supertokens_python.recipe.emailpassword.interfaces import ( APIInterface, - SignInPostOkResult, - SignInPostWrongCredentialsError, APIOptions, + SignUpOkResult, + WrongCredentialsError, ) -from supertokens_python.types import GeneralErrorResponse from supertokens_python.recipe.emailpassword.types import FormField from typing import Dict, Any, Union, List +from supertokens_python.recipe.session.interfaces import SessionContainer +from supertokens_python.asyncio import list_users_by_account_info +from supertokens_python.types import AccountInfo, RecipeUserId def override_emailpassword_apis(original_implementation: APIInterface): @@ -316,11 +322,11 @@ def override_emailpassword_apis(original_implementation: APIInterface): async def sign_in( form_fields: List[FormField], tenant_id: str, + session: Union[SessionContainer, None], + should_try_linking_with_session_user: Union[bool, None], api_options: APIOptions, user_context: Dict[str, Any], - ) -> Union[ - SignInPostOkResult, SignInPostWrongCredentialsError, GeneralErrorResponse - ]: + ): email = "" password = "" for field in form_fields: @@ -329,7 +335,20 @@ def override_emailpassword_apis(original_implementation: APIInterface): if field.id == "password": password = field.value # Check if an email-password user with the input email exists in SuperTokens - emailpassword_user = await get_user_by_email(tenant_id, email, user_context) + supertokens_user_with_same_email = await list_users_by_account_info( + tenant_id, AccountInfo(email=email), False, user_context + ) + emailpassword_user = next( + ( + user + for user in supertokens_user_with_same_email + if any( + lm.recipe_id == "emailpassword" and lm.has_same_email_as(email) + for lm in user.login_methods + ) + ), + None, + ) if emailpassword_user is None: # EmailPassword user with the input email does not exist in SuperTokens @@ -339,19 +358,21 @@ def override_emailpassword_apis(original_implementation: APIInterface): ) if legacy_user_info is None: # Credentials are incorrect - return SignInPostWrongCredentialsError() + return WrongCredentialsError() # Call the sign_up function to create a new SuperTokens user. - response = await sign_up(email, password, tenant_id, user_context) - if isinstance(response, SignUpEmailAlreadyExistsError): + response = await sign_up(tenant_id, email, password, None, user_context) + if not isinstance(response, SignUpOkResult): raise Exception("Should never come here") # Map the external provider's userId to the SuperTokens userId - await create_user_id_mapping( - response.user.user_id, legacy_user_info.user_id - ) + await create_user_id_mapping(response.user.id, legacy_user_info.user_id) # Set the userId in the response to use the provider's userId - response.user.user_id = legacy_user_info.user_id + response.user.id = legacy_user_info.user_id + response.user.login_methods[0].recipe_user_id = RecipeUserId( + legacy_user_info.user_id + ) + response.recipe_user_id = RecipeUserId(legacy_user_info.user_id) # We will also need to set the email verification status of the user if legacy_user_info.isEmailVerified: @@ -359,7 +380,7 @@ def override_emailpassword_apis(original_implementation: APIInterface): generate_email_verification_response = ( await create_email_verification_token( tenant_id, - response.user.user_id, + response.recipe_user_id, email, user_context, ) @@ -371,11 +392,17 @@ def override_emailpassword_apis(original_implementation: APIInterface): await verify_email_using_token( tenant_id, generate_email_verification_response.token, + True, user_context, ) return await original_emailpassword_sign_in( - form_fields, tenant_id, api_options, user_context + form_fields, + tenant_id, + session, + should_try_linking_with_session_user, + api_options, + user_context, ) original_implementation.sign_in_post = sign_in @@ -545,6 +572,7 @@ import SuperTokens from "supertokens-node" import EmailPassword from "supertokens-node/recipe/emailpassword" import EmailVerification from "supertokens-node/recipe/emailverification" import UserMetadata from "supertokens-node/recipe/usermetadata" +import { RecipeUserId } from "supertokens-node"; EmailPassword.init({ override: { @@ -580,6 +608,9 @@ EmailPassword.init({ superTokensUserId: signupResponse.user.id, externalUserId: legacyUserData.user_id }) + signUpResponse.user.id = legacyUserData.user_id + signUpResponse.user.loginMethods[0].recipeUserId = new RecipeUserId(legacyUserData.user_id); + signUpResponse.recipeUserId = new RecipeUserId(legacyUserData.user_id); // We will also need to set the email verification status of the user if (legacyUserData.isEmailVerified) { @@ -627,19 +658,19 @@ async function retrieveUserDataFromExternalProvider(email: string): Promise<{ from supertokens_python import init, InputAppInfo from supertokens_python.asyncio import create_user_id_mapping from supertokens_python.recipe import emailpassword -from supertokens_python.types import GeneralErrorResponse +from supertokens_python.asyncio import list_users_by_account_info +from supertokens_python.types import AccountInfo, RecipeUserId from supertokens_python.recipe.emailverification.asyncio import ( create_email_verification_token, verify_email_using_token, ) from supertokens_python.recipe.emailpassword.types import FormField -from supertokens_python.recipe.emailpassword.asyncio import get_user_by_email, sign_up +from supertokens_python.recipe.emailpassword.asyncio import sign_up from supertokens_python.recipe.usermetadata.asyncio import update_user_metadata from supertokens_python.recipe.emailpassword.interfaces import ( APIInterface, APIOptions, - GeneratePasswordResetTokenPostOkResult, - SignUpEmailAlreadyExistsError, + SignUpOkResult, ) from supertokens_python.recipe.emailverification.interfaces import ( CreateEmailVerificationTokenOkResult, @@ -657,7 +688,7 @@ def override_emailpassword_apis(original_implementation: APIInterface): tenant_id: str, api_options: APIOptions, user_context: Dict[str, Any], - ) -> Union[GeneratePasswordResetTokenPostOkResult, GeneralErrorResponse]: + ): # retrieve the email from the form fields email = None for field in form_fields: @@ -667,7 +698,20 @@ def override_emailpassword_apis(original_implementation: APIInterface): raise Exception("Should never come here") # Check if an email-password user with the input email exists in SuperTokens - emailpassword_user = await get_user_by_email(tenant_id, email, user_context) + supertokens_user_with_same_email = await list_users_by_account_info( + tenant_id, AccountInfo(email=email), False, user_context + ) + emailpassword_user = next( + ( + user + for user in supertokens_user_with_same_email + if any( + lm.recipe_id == "emailpassword" and lm.has_same_email_as(email) + for lm in user.login_methods + ) + ), + None, + ) if emailpassword_user is None: # EmailPassword user with the input email does not exist in SuperTokens @@ -676,21 +720,26 @@ def override_emailpassword_apis(original_implementation: APIInterface): if legacy_user_data is not None: # Create a SuperTokens account for the user with a temporary password tempPassword = await generate_password() - response = await sign_up(tenant_id, email, tempPassword, user_context) - if isinstance(response, SignUpEmailAlreadyExistsError): + response = await sign_up( + tenant_id, email, tempPassword, None, user_context + ) + if not isinstance(response, SignUpOkResult): raise Exception("Should never come here") # Map the SuperTokens userId to the legacy userId - await create_user_id_mapping( - response.user.user_id, legacy_user_data.user_id + await create_user_id_mapping(response.user.id, legacy_user_data.user_id) + response.user.id = legacy_user_data.user_id + response.user.login_methods[0].recipe_user_id = RecipeUserId( + legacy_user_data.user_id ) + response.recipe_user_id = RecipeUserId(legacy_user_data.user_id) # We will also need to set the email verification status if legacy_user_data.isEmailVerified: # Generate an email verification token for the user generate_email_verification_token_response = ( await create_email_verification_token( - tenant_id, response.user.user_id, email, user_context + tenant_id, response.recipe_user_id, email, user_context ) ) if isinstance( @@ -701,6 +750,7 @@ def override_emailpassword_apis(original_implementation: APIInterface): await verify_email_using_token( tenant_id, generate_email_verification_token_response.token, + True, user_context, ) @@ -931,7 +981,6 @@ EmailPassword.init({ ```python from supertokens_python import init, InputAppInfo from supertokens_python.recipe import emailpassword -from supertokens_python.types import GeneralErrorResponse from supertokens_python.recipe.emailpassword.types import FormField from supertokens_python.recipe.usermetadata.asyncio import ( get_user_metadata, @@ -941,9 +990,8 @@ from supertokens_python.recipe.emailpassword.interfaces import ( APIInterface, APIOptions, PasswordResetPostOkResult, - PasswordResetPostInvalidTokenResponse, ) -from typing import Union, Dict, Any, List +from typing import Dict, Any, List def override_emailpassword_apis(original_implementation: APIInterface): @@ -955,28 +1003,23 @@ def override_emailpassword_apis(original_implementation: APIInterface): tenant_id: str, api_options: APIOptions, user_context: Dict[str, Any], - ) -> Union[ - PasswordResetPostOkResult, - PasswordResetPostInvalidTokenResponse, - GeneralErrorResponse, - ]: + ): response = await original_password_reset_post( form_fields, token, tenant_id, api_options, user_context ) if ( isinstance(response, PasswordResetPostOkResult) - and response.user_id is not None ): # Check that the user has the isUsingTemporaryPassword flag set in their metadata - metadata_result = await get_user_metadata(response.user_id, user_context) + metadata_result = await get_user_metadata(response.user.id, user_context) if ( "isUsingTemporaryPassword" in metadata_result.metadata and metadata_result.metadata["isUsingTemporaryPassword"] is True ): # Since the password has been successfully reset, we can remove the isUsingTemporaryPassword flag await update_user_metadata( - response.user_id, {"isUsingTemporaryPassword": None} + response.user.id, {"isUsingTemporaryPassword": None} ) return response @@ -1187,13 +1230,9 @@ from supertokens_python.recipe.emailverification.asyncio import ( verify_email_using_token, ) from supertokens_python.recipe.emailpassword.asyncio import ( - get_user_by_email, sign_up, update_email_or_password, ) -from supertokens_python.recipe.emailpassword.interfaces import ( - SignUpEmailAlreadyExistsError, -) from supertokens_python.recipe.emailverification.interfaces import ( CreateEmailVerificationTokenOkResult, ) @@ -1201,13 +1240,15 @@ from supertokens_python import init, InputAppInfo from supertokens_python.recipe import emailpassword from supertokens_python.recipe.emailpassword.interfaces import ( APIInterface, - SignInPostOkResult, - SignInPostWrongCredentialsError, APIOptions, + SignUpOkResult, + WrongCredentialsError, ) -from supertokens_python.types import GeneralErrorResponse from supertokens_python.recipe.emailpassword.types import FormField from typing import Dict, Any, Union, List +from supertokens_python.recipe.session.interfaces import SessionContainer +from supertokens_python.asyncio import list_users_by_account_info +from supertokens_python.types import AccountInfo, RecipeUserId from supertokens_python.recipe.usermetadata.asyncio import ( get_user_metadata, update_user_metadata, @@ -1220,11 +1261,11 @@ def override_emailpassword_apis(original_implementation: APIInterface): async def sign_in( form_fields: List[FormField], tenant_id: str, + session: Union[SessionContainer, None], + should_try_linking_with_session_user: Union[bool, None], api_options: APIOptions, user_context: Dict[str, Any], - ) -> Union[ - SignInPostOkResult, SignInPostWrongCredentialsError, GeneralErrorResponse - ]: + ): email = "" password = "" for field in form_fields: @@ -1233,7 +1274,20 @@ def override_emailpassword_apis(original_implementation: APIInterface): if field.id == "password": password = field.value # Check if an email-password user with the input email exists in SuperTokens - emailpassword_user = await get_user_by_email(tenant_id, email, user_context) + supertokens_user_with_same_email = await list_users_by_account_info( + tenant_id, AccountInfo(email=email), False, user_context + ) + emailpassword_user = next( + ( + user + for user in supertokens_user_with_same_email + if any( + lm.recipe_id == "emailpassword" and lm.has_same_email_as(email) + for lm in user.login_methods + ) + ), + None, + ) if emailpassword_user is None: # EmailPassword user with the input email does not exist in SuperTokens @@ -1243,26 +1297,31 @@ def override_emailpassword_apis(original_implementation: APIInterface): ) if legacy_user_info is None: # Credentials are incorrect - return SignInPostWrongCredentialsError() + return WrongCredentialsError() # Call the sign_up function to create a new SuperTokens user. - response = await sign_up(email, password, tenant_id, user_context) - if isinstance(response, SignUpEmailAlreadyExistsError): + response = await sign_up(tenant_id, email, password, None, user_context) + if not isinstance(response, SignUpOkResult): raise Exception("Should never come here") # Map the external provider's userId to the SuperTokens userId - await create_user_id_mapping( - response.user.user_id, legacy_user_info.user_id - ) + await create_user_id_mapping(response.user.id, legacy_user_info.user_id) # Set the userId in the response to use the provider's userId - response.user.user_id = legacy_user_info.user_id + response.user.id = legacy_user_info.user_id + response.user.login_methods[0].recipe_user_id = RecipeUserId( + legacy_user_info.user_id + ) + response.recipe_user_id = RecipeUserId(legacy_user_info.user_id) # We will also need to set the email verification status of the user if legacy_user_info.isEmailVerified: # Generate an email verification token for the user generate_email_verification_response = ( await create_email_verification_token( - tenant_id, response.user.user_id, email, user_context + tenant_id, + response.recipe_user_id, + email, + user_context, ) ) if isinstance( @@ -1272,13 +1331,14 @@ def override_emailpassword_apis(original_implementation: APIInterface): await verify_email_using_token( tenant_id, generate_email_verification_response.token, + True, user_context, ) emailpassword_user = response.user # highlight-start # Check if the user signing in has a temporary password - metadata_result = await get_user_metadata(emailpassword_user.user_id) + metadata_result = await get_user_metadata(emailpassword_user.id) if ( "isUsingTemporaryPassword" in metadata_result.metadata and metadata_result.metadata["isUsingTemporaryPassword"] is True @@ -1288,9 +1348,19 @@ def override_emailpassword_apis(original_implementation: APIInterface): email, password ) if legacy_user_info is not None: + # Find the emailpassword login method for the user + login_method = next( + ( + lm + for lm in emailpassword_user.login_methods + if lm.recipe_id == "emailpassword" and lm.email == email + ), + None, + ) + assert login_method is not None # Update the user's password with the correct password await update_email_or_password( - emailpassword_user.user_id, + login_method.recipe_user_id, None, password, False, @@ -1299,14 +1369,19 @@ def override_emailpassword_apis(original_implementation: APIInterface): ) # Update the user's metadata to remove the isUsingTemporaryPassword flag await update_user_metadata( - emailpassword_user.user_id, {"isUsingTemporaryPassword": None} + emailpassword_user.id, {"isUsingTemporaryPassword": None} ) else: - return SignInPostWrongCredentialsError() + return WrongCredentialsError() # highlight-end return await original_emailpassword_sign_in( - form_fields, tenant_id, api_options, user_context + form_fields, + tenant_id, + session, + should_try_linking_with_session_user, + api_options, + user_context, ) original_implementation.sign_in_post = sign_in diff --git a/v2/thirdpartyemailpassword/serverless/with-aws-lambda/jwt-authorizer.mdx b/v2/thirdpartyemailpassword/serverless/with-aws-lambda/jwt-authorizer.mdx index ae937766b..86055a1d9 100644 --- a/v2/thirdpartyemailpassword/serverless/with-aws-lambda/jwt-authorizer.mdx +++ b/v2/thirdpartyemailpassword/serverless/with-aws-lambda/jwt-authorizer.mdx @@ -98,6 +98,7 @@ from supertokens_python import ( from supertokens_python.recipe.session.interfaces import RecipeInterface as SessionRecipeInterface from typing import Any, Dict, Optional +from supertokens_python.types import RecipeUserId supertokens_config = SupertokensConfig( ^{coreInjector_connection_uri_comment_with_hash} @@ -123,6 +124,7 @@ def override_session_functions(oi: SessionRecipeInterface) -> SessionRecipeInter async def create_new_session( user_id: str, + recipe_user_id: RecipeUserId, access_token_payload: Optional[Dict[str, Any]], session_data_in_database: Optional[Dict[str, Any]], disable_anti_csrf: Optional[bool], @@ -138,7 +140,7 @@ def override_session_functions(oi: SessionRecipeInterface) -> SessionRecipeInter access_token_payload = {} access_token_payload["aud"] = "jwtAuthorizers" - return await oi_create_new_session(user_id, access_token_payload, session_data_in_database, disable_anti_csrf, tenant_id, user_context) + return await oi_create_new_session(user_id, recipe_user_id, access_token_payload, session_data_in_database, disable_anti_csrf, tenant_id, user_context) oi.create_new_session = create_new_session return oi diff --git a/v2/thirdpartypasswordless/hasura-integration/with-jwt.mdx b/v2/thirdpartypasswordless/hasura-integration/with-jwt.mdx index f808ba61b..31e7c262c 100644 --- a/v2/thirdpartypasswordless/hasura-integration/with-jwt.mdx +++ b/v2/thirdpartypasswordless/hasura-integration/with-jwt.mdx @@ -225,45 +225,56 @@ from supertokens_python import init, InputAppInfo from supertokens_python.recipe import session from supertokens_python.recipe.session.interfaces import RecipeInterface from typing import Dict, Optional, Any +from supertokens_python.types import RecipeUserId def override_functions(original_implementation: RecipeInterface): - original_implementation_create_new_session = original_implementation.create_new_session - - async def create_new_session(user_id: str, - access_token_payload: Optional[Dict[str, Any]], - session_data_in_database: Optional[Dict[str, Any]], - disable_anti_csrf: Optional[bool], - tenant_id: str, - user_context: Dict[str, Any]): + original_implementation_create_new_session = ( + original_implementation.create_new_session + ) + + async def create_new_session( + user_id: str, + recipe_user_id: RecipeUserId, + access_token_payload: Optional[Dict[str, Any]], + session_data_in_database: Optional[Dict[str, Any]], + disable_anti_csrf: Optional[bool], + tenant_id: str, + user_context: Dict[str, Any], + ): if access_token_payload is None: access_token_payload = {} - access_token_payload['https://hasura.io/jwt/claims'] = { + access_token_payload["https://hasura.io/jwt/claims"] = { "x-hasura-user-id": user_id, - "x-hasura-default-role": 'user', - "x-hasura-allowed-roles": ['user'], + "x-hasura-default-role": "user", + "x-hasura-allowed-roles": ["user"], } - return await original_implementation_create_new_session(user_id, access_token_payload, session_data_in_database, disable_anti_csrf, tenant_id, user_context) + return await original_implementation_create_new_session( + user_id, + recipe_user_id, + access_token_payload, + session_data_in_database, + disable_anti_csrf, + tenant_id, + user_context, + ) original_implementation.create_new_session = create_new_session return original_implementation init( - app_info=InputAppInfo( - api_domain="...", app_name="...", website_domain="..."), - framework='...', # type: ignore + app_info=InputAppInfo(api_domain="...", app_name="...", website_domain="..."), + framework="...", # type: ignore recipe_list=[ session.init( - override=session.InputOverrideConfig( - functions=override_functions - ), + override=session.InputOverrideConfig(functions=override_functions), expose_access_token_to_frontend_in_cookie_based_auth=True, ) - ] + ], ) ``` diff --git a/v2/thirdpartypasswordless/serverless/with-aws-lambda/jwt-authorizer.mdx b/v2/thirdpartypasswordless/serverless/with-aws-lambda/jwt-authorizer.mdx index ae937766b..86055a1d9 100644 --- a/v2/thirdpartypasswordless/serverless/with-aws-lambda/jwt-authorizer.mdx +++ b/v2/thirdpartypasswordless/serverless/with-aws-lambda/jwt-authorizer.mdx @@ -98,6 +98,7 @@ from supertokens_python import ( from supertokens_python.recipe.session.interfaces import RecipeInterface as SessionRecipeInterface from typing import Any, Dict, Optional +from supertokens_python.types import RecipeUserId supertokens_config = SupertokensConfig( ^{coreInjector_connection_uri_comment_with_hash} @@ -123,6 +124,7 @@ def override_session_functions(oi: SessionRecipeInterface) -> SessionRecipeInter async def create_new_session( user_id: str, + recipe_user_id: RecipeUserId, access_token_payload: Optional[Dict[str, Any]], session_data_in_database: Optional[Dict[str, Any]], disable_anti_csrf: Optional[bool], @@ -138,7 +140,7 @@ def override_session_functions(oi: SessionRecipeInterface) -> SessionRecipeInter access_token_payload = {} access_token_payload["aud"] = "jwtAuthorizers" - return await oi_create_new_session(user_id, access_token_payload, session_data_in_database, disable_anti_csrf, tenant_id, user_context) + return await oi_create_new_session(user_id, recipe_user_id, access_token_payload, session_data_in_database, disable_anti_csrf, tenant_id, user_context) oi.create_new_session = create_new_session return oi From 46daecd528346ec2c827e03f62dd3cbf9ce6305c Mon Sep 17 00:00:00 2001 From: rishabhpoddar Date: Thu, 17 Oct 2024 21:43:32 +0530 Subject: [PATCH 12/34] more changes --- v2/emailpassword/user-object.mdx | 2 +- v2/passwordless/user-object.mdx | 2 +- v2/thirdparty/user-object.mdx | 2 +- v2/thirdpartyemailpassword/user-object.mdx | 2 +- v2/thirdpartypasswordless/user-object.mdx | 2 +- 5 files changed, 5 insertions(+), 5 deletions(-) diff --git a/v2/emailpassword/user-object.mdx b/v2/emailpassword/user-object.mdx index ce13b5680..51d285c56 100644 --- a/v2/emailpassword/user-object.mdx +++ b/v2/emailpassword/user-object.mdx @@ -10,7 +10,7 @@ hide_title: true # About the User Object :::important -This is only applicable for our NodeJS SDK >= 16.0. For other versions, or SDKs, please see: https://github.com/supertokens/core-driver-interface/wiki +This is only applicable for our NodeJS SDK >= 16.0 and for Python SDK >= 0.25.0. For other versions, or SDKs, please see: https://github.com/supertokens/core-driver-interface/wiki ::: The user object structure is as follows: diff --git a/v2/passwordless/user-object.mdx b/v2/passwordless/user-object.mdx index 8077a366a..b5d90d1ef 100644 --- a/v2/passwordless/user-object.mdx +++ b/v2/passwordless/user-object.mdx @@ -10,7 +10,7 @@ hide_title: true # About the User Object :::important -This is only applicable for our NodeJS SDK >= 16.0. For other versions, or SDKs, please see: https://github.com/supertokens/core-driver-interface/wiki +This is only applicable for our NodeJS SDK >= 16.0 and for Python SDK >= 0.25.0. For other versions, or SDKs, please see: https://github.com/supertokens/core-driver-interface/wiki ::: The user object structure is as follows: diff --git a/v2/thirdparty/user-object.mdx b/v2/thirdparty/user-object.mdx index 8077a366a..b5d90d1ef 100644 --- a/v2/thirdparty/user-object.mdx +++ b/v2/thirdparty/user-object.mdx @@ -10,7 +10,7 @@ hide_title: true # About the User Object :::important -This is only applicable for our NodeJS SDK >= 16.0. For other versions, or SDKs, please see: https://github.com/supertokens/core-driver-interface/wiki +This is only applicable for our NodeJS SDK >= 16.0 and for Python SDK >= 0.25.0. For other versions, or SDKs, please see: https://github.com/supertokens/core-driver-interface/wiki ::: The user object structure is as follows: diff --git a/v2/thirdpartyemailpassword/user-object.mdx b/v2/thirdpartyemailpassword/user-object.mdx index 8077a366a..b5d90d1ef 100644 --- a/v2/thirdpartyemailpassword/user-object.mdx +++ b/v2/thirdpartyemailpassword/user-object.mdx @@ -10,7 +10,7 @@ hide_title: true # About the User Object :::important -This is only applicable for our NodeJS SDK >= 16.0. For other versions, or SDKs, please see: https://github.com/supertokens/core-driver-interface/wiki +This is only applicable for our NodeJS SDK >= 16.0 and for Python SDK >= 0.25.0. For other versions, or SDKs, please see: https://github.com/supertokens/core-driver-interface/wiki ::: The user object structure is as follows: diff --git a/v2/thirdpartypasswordless/user-object.mdx b/v2/thirdpartypasswordless/user-object.mdx index 8077a366a..b5d90d1ef 100644 --- a/v2/thirdpartypasswordless/user-object.mdx +++ b/v2/thirdpartypasswordless/user-object.mdx @@ -10,7 +10,7 @@ hide_title: true # About the User Object :::important -This is only applicable for our NodeJS SDK >= 16.0. For other versions, or SDKs, please see: https://github.com/supertokens/core-driver-interface/wiki +This is only applicable for our NodeJS SDK >= 16.0 and for Python SDK >= 0.25.0. For other versions, or SDKs, please see: https://github.com/supertokens/core-driver-interface/wiki ::: The user object structure is as follows: From ca6dc3e28652e71401df72ad30ba68c64f3836ab Mon Sep 17 00:00:00 2001 From: rishabhpoddar Date: Fri, 18 Oct 2024 13:57:29 +0530 Subject: [PATCH 13/34] fixes more --- v2/mfa/legacy-method/backend/first-factor.mdx | 26 +++- .../legacy-method/backend/protecting-api.mdx | 40 +++-- .../legacy-method/backend/second-factor.mdx | 139 ++++++++++++++---- 3 files changed, 164 insertions(+), 41 deletions(-) diff --git a/v2/mfa/legacy-method/backend/first-factor.mdx b/v2/mfa/legacy-method/backend/first-factor.mdx index 5fab7ed17..58d24d923 100644 --- a/v2/mfa/legacy-method/backend/first-factor.mdx +++ b/v2/mfa/legacy-method/backend/first-factor.mdx @@ -503,12 +503,14 @@ from supertokens_python.recipe import session from supertokens_python.recipe.session.claims import BooleanClaim from supertokens_python.recipe.session.interfaces import RecipeInterface from typing import Any, Dict, Optional +from supertokens_python.types import RecipeUserId # This will be used to modify the session's access token payload # to add {"2fa-completed": false} into it. # highlight-start SecondFactorClaim = BooleanClaim( - key="2fa-completed", fetch_value=lambda _, __, ___: False) + key="2fa-completed", fetch_value=lambda _, __, ___, ____, _____: False +) # highlight-end @@ -517,6 +519,7 @@ def override_session_functions(original_implementation: RecipeInterface): async def create_new_session( user_id: str, + recipe_user_id: RecipeUserId, access_token_payload: Optional[Dict[str, Any]], session_data_in_database: Optional[Dict[str, Any]], disable_anti_csrf: Optional[bool], @@ -529,9 +532,26 @@ def override_session_functions(original_implementation: RecipeInterface): access_token_payload = {} # highlight-next-line - access_token_payload = {**access_token_payload, **(await SecondFactorClaim.build(user_id, tenant_id, user_context))} + access_token_payload = { + **access_token_payload, + **( + await SecondFactorClaim.build( + user_id, + recipe_user_id, + tenant_id, + access_token_payload, + user_context, + ) + ), + } return await original_create_new_session( - user_id, access_token_payload, session_data_in_database, disable_anti_csrf, tenant_id, user_context + user_id, + recipe_user_id, + access_token_payload, + session_data_in_database, + disable_anti_csrf, + tenant_id, + user_context, ) original_implementation.create_new_session = create_new_session diff --git a/v2/mfa/legacy-method/backend/protecting-api.mdx b/v2/mfa/legacy-method/backend/protecting-api.mdx index 54a7681f8..3963a349e 100644 --- a/v2/mfa/legacy-method/backend/protecting-api.mdx +++ b/v2/mfa/legacy-method/backend/protecting-api.mdx @@ -92,10 +92,16 @@ func main() { from typing import List, Dict, Any from supertokens_python.recipe.session.claims import BooleanClaim from supertokens_python.recipe import session -from supertokens_python.recipe.session.interfaces import RecipeInterface, SessionClaimValidator +from supertokens_python.recipe.session.interfaces import ( + RecipeInterface, + SessionClaimValidator, +) +from supertokens_python.types import RecipeUserId SecondFactorClaim = BooleanClaim( - key="2fa-completed", fetch_value=lambda _, __, ___: False) + key="2fa-completed", fetch_value=lambda _, __, ___, ____, _____: False +) + def override_session_functions(original_implementation: RecipeInterface): @@ -103,15 +109,20 @@ def override_session_functions(original_implementation: RecipeInterface): async def get_global_claim_validators( tenant_id: str, user_id: str, + recipe_user_id: RecipeUserId, claim_validators_added_by_other_recipes: List[SessionClaimValidator], user_context: Dict[str, Any], ): - return claim_validators_added_by_other_recipes + [SecondFactorClaim.validators.has_value(True)] + return claim_validators_added_by_other_recipes + [ + SecondFactorClaim.validators.has_value(True) + ] + # highlight-end original_implementation.get_global_claim_validators = get_global_claim_validators return original_implementation + session.init(override=session.InputOverrideConfig(override_session_functions)) ``` @@ -578,20 +589,25 @@ from fastapi import Depends from supertokens_python.recipe.session.claims import BooleanClaim SecondFactorClaim = BooleanClaim( - key="2fa-completed", fetch_value=lambda _, __, ___: False) + key="2fa-completed", fetch_value=lambda _, __, ___, ____, _____: False +) + -@app.post('/like_comment') # type: ignore -async def like_comment(session: SessionContainer = Depends( +@app.post("/like_comment") # type: ignore +async def like_comment( + session: SessionContainer = Depends( verify_session( # highlight-start # We add the SecondFactorClaim's has_value(True) validator - override_global_claim_validators=lambda global_validators, session, user_context: global_validators + \ - [SecondFactorClaim.validators.has_value(True)] + override_global_claim_validators=lambda global_validators, session, user_context: global_validators + + [SecondFactorClaim.validators.has_value(True)] # highlight-end ) -)): + ) +): # All validator checks have passed and the user has completed 2FA pass + ``` @@ -602,7 +618,8 @@ from supertokens_python.recipe.session.framework.flask import verify_session from supertokens_python.recipe.session.claims import BooleanClaim SecondFactorClaim = BooleanClaim( - key="2fa-completed", fetch_value=lambda _, __, ___: False) + key="2fa-completed", fetch_value=lambda _, __, ___, ____, _____: False +) @app.route('/update-jwt', methods=['POST']) # type: ignore @verify_session( @@ -626,7 +643,8 @@ from django.http import HttpRequest from supertokens_python.recipe.session.claims import BooleanClaim SecondFactorClaim = BooleanClaim( - key="2fa-completed", fetch_value=lambda _, __, ___: False) + key="2fa-completed", fetch_value=lambda _, __, ___, ____, _____: False +) @verify_session( # highlight-start diff --git a/v2/mfa/legacy-method/backend/second-factor.mdx b/v2/mfa/legacy-method/backend/second-factor.mdx index 0f9d14453..2346cd9e9 100644 --- a/v2/mfa/legacy-method/backend/second-factor.mdx +++ b/v2/mfa/legacy-method/backend/second-factor.mdx @@ -586,10 +586,11 @@ func main() { ```python from supertokens_python.recipe.passwordless.interfaces import APIInterface, APIOptions, ConsumeCodePostOkResult -from typing import Union, Dict, Any +from typing import Union, Dict, Any, Optional from supertokens_python.recipe.session.asyncio import get_session from supertokens_python.recipe.usermetadata.asyncio import update_user_metadata from supertokens_python.recipe import passwordless +from supertokens_python.recipe.session.interfaces import SessionContainer def override_passwordless_apis(original_implementation: APIInterface): original_consume_code_post = original_implementation.consume_code_post @@ -599,6 +600,8 @@ def override_passwordless_apis(original_implementation: APIInterface): user_input_code: Union[str, None], device_id: Union[str, None], link_code: Union[str, None], + session: Optional[SessionContainer], + should_try_linking_with_session_user: Union[bool, None], tenant_id: str, api_options: APIOptions, user_context: Dict[str, Any], @@ -615,6 +618,8 @@ def override_passwordless_apis(original_implementation: APIInterface): user_input_code, device_id, link_code, + session, + should_try_linking_with_session_user, tenant_id, api_options, user_context, @@ -626,7 +631,7 @@ def override_passwordless_apis(original_implementation: APIInterface): # user ID, so that later on, we can fetch the phone number. await update_user_metadata( _session.get_user_id(), # this is the userId of the first factor login - {"passwordlessUserId": res.user.user_id} + {"passwordlessUserId": res.user.id} ) return res @@ -859,15 +864,24 @@ func main() { ```python -from supertokens_python.recipe.passwordless.interfaces import APIInterface, APIOptions, ConsumeCodePostOkResult +from supertokens_python.recipe.passwordless.interfaces import ( + APIInterface, + APIOptions, + ConsumeCodePostOkResult, +) from typing import Union, Dict, Any, Optional from supertokens_python.recipe.session.asyncio import get_session from supertokens_python.recipe.usermetadata.asyncio import update_user_metadata -from supertokens_python.recipe.session.interfaces import SessionContainer, RecipeInterface +from supertokens_python.recipe.session.interfaces import ( + SessionContainer, + RecipeInterface, +) from supertokens_python.recipe.session.claims import BooleanClaim +from supertokens_python.types import RecipeUserId SecondFactorClaim = BooleanClaim( - key="2fa-completed", fetch_value=lambda _, __, ___: False) + key="2fa-completed", fetch_value=lambda _, __, ___, ____, _____: False +) def override_passwordless_apis(original_implementation: APIInterface): @@ -878,6 +892,8 @@ def override_passwordless_apis(original_implementation: APIInterface): user_input_code: Union[str, None], device_id: Union[str, None], link_code: Union[str, None], + session: Optional[SessionContainer], + should_try_linking_with_session_user: Union[bool, None], tenant_id: str, api_options: APIOptions, user_context: Dict[str, Any], @@ -887,7 +903,9 @@ def override_passwordless_apis(original_implementation: APIInterface): # A session should already exist since this should be called after the first factor is completed. # We set the claims to check to be [] here, since this needs to be callable # without the second factor completed - _session = await get_session(api_options.request, override_global_claim_validators=lambda _, __, ___: []) + _session = await get_session( + api_options.request, override_global_claim_validators=lambda _, __, ___: [] + ) assert _session is not None # we should add the existing session to the user_context @@ -901,6 +919,8 @@ def override_passwordless_apis(original_implementation: APIInterface): user_input_code, device_id, link_code, + session, + should_try_linking_with_session_user, tenant_id, api_options, user_context, @@ -919,7 +939,7 @@ def override_passwordless_apis(original_implementation: APIInterface): # user ID, so that later on, we can fetch the phone number. await update_user_metadata( _session.get_user_id(), # userId of the first factor login - {"passwordlessUserId": res.user.user_id} + {"passwordlessUserId": res.user.id}, ) return res @@ -933,6 +953,7 @@ def override_session_functions(original_implementation: RecipeInterface): async def create_new_session( user_id: str, + recipe_user_id: RecipeUserId, access_token_payload: Optional[Dict[str, Any]], session_data_in_database: Optional[Dict[str, Any]], disable_anti_csrf: Optional[bool], @@ -953,9 +974,26 @@ def override_session_functions(original_implementation: RecipeInterface): if access_token_payload is None: access_token_payload = {} - access_token_payload = {**access_token_payload, **(await SecondFactorClaim.build(user_id, tenant_id, user_context))} + access_token_payload = { + **access_token_payload, + **( + await SecondFactorClaim.build( + user_id, + recipe_user_id, + tenant_id, + access_token_payload, + user_context, + ) + ), + } return await original_create_new_session( - user_id, access_token_payload, session_data_in_database, disable_anti_csrf, tenant_id, user_context + user_id, + recipe_user_id, + access_token_payload, + session_data_in_database, + disable_anti_csrf, + tenant_id, + user_context, ) original_implementation.create_new_session = create_new_session @@ -1141,7 +1179,9 @@ from supertokens_python.recipe.passwordless.interfaces import APIInterface, APIO from typing import Union, Dict, Any, Optional from supertokens_python.recipe.session.asyncio import get_session from supertokens_python.recipe.usermetadata.asyncio import get_user_metadata -from supertokens_python.recipe.passwordless.asyncio import get_user_by_id +from supertokens_python.recipe.session.interfaces import SessionContainer +from supertokens_python.asyncio import get_user + def override_passwordless_apis(original_implementation: APIInterface): original_consume_code_post = original_implementation.consume_code_post @@ -1151,6 +1191,8 @@ def override_passwordless_apis(original_implementation: APIInterface): async def create_code_post( email: Union[str, None], phone_number: Union[str, None], + session: Optional[SessionContainer], + should_try_linking_with_session_user: Union[bool, None], tenant_id: str, api_options: APIOptions, user_context: Dict[str, Any], @@ -1163,7 +1205,9 @@ def override_passwordless_apis(original_implementation: APIInterface): # A session should already exist since this should be called after the first factor is completed. # We set the claims to check to be [] here, since this needs to be callable # without the second factor completed - _session = await get_session(api_options.request, override_global_claim_validators=lambda _, __, ___: []) + _session = await get_session( + api_options.request, override_global_claim_validators=lambda _, __, ___: [] + ) assert _session is not None # We try to get the phone number associated with this user. It will be @@ -1174,11 +1218,11 @@ def override_passwordless_apis(original_implementation: APIInterface): if user_metadata.metadata.get("passwordlessUserId"): # the flow will come here during a login attempt, since we # associate the passwordless userId to the user on sign up - passwordless_user_info = await get_user_by_id( + passwordless_user_info = await get_user( user_metadata.metadata["passwordlessUserId"], user_context ) if passwordless_user_info is not None: - user_metadata_phone_number = passwordless_user_info.phone_number + user_metadata_phone_number = passwordless_user_info.phone_numbers[0] if user_metadata_phone_number is not None: # this means we found a phone number associated to this user @@ -1189,8 +1233,15 @@ def override_passwordless_apis(original_implementation: APIInterface): ) return await original_create_code_post( - email, phone_number, tenant_id, api_options, user_context + email, + phone_number, + session, + should_try_linking_with_session_user, + tenant_id, + api_options, + user_context, ) + # highlight-end async def consume_code_post( @@ -1198,6 +1249,8 @@ def override_passwordless_apis(original_implementation: APIInterface): user_input_code: Union[str, None], device_id: Union[str, None], link_code: Union[str, None], + session: Optional[SessionContainer], + should_try_linking_with_session_user: Union[bool, None], tenant_id: str, api_options: APIOptions, user_context: Dict[str, Any], @@ -1208,6 +1261,8 @@ def override_passwordless_apis(original_implementation: APIInterface): user_input_code, device_id, link_code, + session, + should_try_linking_with_session_user, tenant_id, api_options, user_context, @@ -1218,7 +1273,6 @@ def override_passwordless_apis(original_implementation: APIInterface): original_implementation.create_code_post = create_code_post original_implementation.consume_code_post = consume_code_post return original_implementation - ``` @@ -1362,12 +1416,17 @@ func main() { ```python from typing import Dict, Any, Optional from supertokens_python.recipe.usermetadata.asyncio import get_user_metadata -from supertokens_python.recipe.passwordless.asyncio import get_user_by_id -from supertokens_python.recipe.session.interfaces import SessionContainer, RecipeInterface +from supertokens_python.asyncio import get_user +from supertokens_python.recipe.session.interfaces import ( + SessionContainer, + RecipeInterface, +) from supertokens_python.recipe.session.claims import BooleanClaim +from supertokens_python.types import RecipeUserId SecondFactorClaim = BooleanClaim( - key="2fa-completed", fetch_value=lambda _, __, ___: False) + key="2fa-completed", fetch_value=lambda _, __, ___, ____, _____: False +) def override_session_functions(original_implementation: RecipeInterface): @@ -1375,6 +1434,7 @@ def override_session_functions(original_implementation: RecipeInterface): async def create_new_session( user_id: str, + recipe_user_id: RecipeUserId, access_token_payload: Optional[Dict[str, Any]], session_data_in_database: Optional[Dict[str, Any]], disable_anti_csrf: Optional[bool], @@ -1400,22 +1460,36 @@ def override_session_functions(original_implementation: RecipeInterface): phone_number: Optional[str] = None if user_metadata.metadata.get("passwordlessUserId") is not None: # We get the phone number associated with the passwordless userId - passwordless_user_info = await get_user_by_id( + passwordless_user_info = await get_user( user_metadata.metadata["passwordlessUserId"], user_context ) if passwordless_user_info is not None: - phone_number = passwordless_user_info.phone_number + phone_number = passwordless_user_info.phone_numbers[0] # highlight-end # Insert "is2faComplete" and "phoneNumber" in the access token payload access_token_payload = { **access_token_payload, - **(await SecondFactorClaim.build(user_id, tenant_id, user_context)), + **( + await SecondFactorClaim.build( + user_id, + recipe_user_id, + tenant_id, + access_token_payload, + user_context, + ) + ), # highlight-next-line "phoneNumber": phone_number, } return await original_create_new_session( - user_id, access_token_payload, session_data_in_database, disable_anti_csrf, tenant_id, user_context + user_id, + recipe_user_id, + access_token_payload, + session_data_in_database, + disable_anti_csrf, + tenant_id, + user_context, ) original_implementation.create_new_session = create_new_session @@ -1563,8 +1637,10 @@ func main() { ```python from supertokens_python.recipe.passwordless.interfaces import APIInterface, APIOptions -from typing import Union, Dict, Any +from typing import Union, Dict, Any, Optional from supertokens_python.recipe.session.asyncio import get_session +from supertokens_python.recipe.session.interfaces import SessionContainer + def override_passwordless_apis(original_implementation: APIInterface): original_create_code_post = original_implementation.create_code_post @@ -1572,6 +1648,8 @@ def override_passwordless_apis(original_implementation: APIInterface): async def create_code_post( email: Union[str, None], phone_number: Union[str, None], + session: Optional[SessionContainer], + should_try_linking_with_session_user: Union[bool, None], tenant_id: str, api_options: APIOptions, user_context: Dict[str, Any], @@ -1584,7 +1662,9 @@ def override_passwordless_apis(original_implementation: APIInterface): # A session should already exist since this should be called after the first factor is completed. # We set the claims to check to be [] here, since this needs to be callable # without the second factor completed - _session = await get_session(api_options.request, override_global_claim_validators=lambda _, __, ___: []) + _session = await get_session( + api_options.request, override_global_claim_validators=lambda _, __, ___: [] + ) assert _session is not None # highlight-next-line @@ -1599,11 +1679,16 @@ def override_passwordless_apis(original_implementation: APIInterface): ) return await original_create_code_post( - email, phone_number, tenant_id, api_options, user_context + email, + phone_number, + session, + should_try_linking_with_session_user, + tenant_id, + api_options, + user_context, ) - - original_implementation.create_code_post = create_code_post + original_implementation.create_code_post = create_code_post ``` From 095d2a61c64eebd4408db354f4e2a6097f540874 Mon Sep 17 00:00:00 2001 From: rishabhpoddar Date: Sat, 19 Oct 2024 12:37:55 +0530 Subject: [PATCH 14/34] more docs --- v2/mfa/backend-setup.mdx | 611 +++++++++++++++++++++++++++++++++++--- v2/mfa/old-sdk-to-new.mdx | 83 +++++- 2 files changed, 651 insertions(+), 43 deletions(-) diff --git a/v2/mfa/backend-setup.mdx b/v2/mfa/backend-setup.mdx index 2809af535..00a72b361 100644 --- a/v2/mfa/backend-setup.mdx +++ b/v2/mfa/backend-setup.mdx @@ -80,9 +80,51 @@ Coming soon. In the meantime, checkout the [legacy method](./legacy-method/how-i -:::note -Coming soon. In the meantime, checkout the [legacy method](./legacy-method/how-it-works) for adding MFA to your app. -::: +```python +from supertokens_python import init, InputAppInfo, SupertokensConfig +from supertokens_python.recipe import accountlinking +from supertokens_python.types import User +from supertokens_python.recipe.session.interfaces import SessionContainer +from supertokens_python.recipe.accountlinking.types import AccountInfoWithRecipeIdAndUserId, ShouldNotAutomaticallyLink, ShouldAutomaticallyLink +from typing import Dict, Any, Optional, Union + +async def should_do_automatic_account_linking( + new_account_info: AccountInfoWithRecipeIdAndUserId, + user: Optional[User], + session: Optional[SessionContainer], + tenant_id: str, + user_context: Dict[str, Any] +) -> Union[ShouldNotAutomaticallyLink, ShouldAutomaticallyLink]: + if session is None: + # We do not want to do first factor account linking by default. + # To enable that, please see the automatic account linking docs + # in the recipe docs for your first factor. + return ShouldNotAutomaticallyLink() + + if user is None or session.get_user_id() == user.id: + # If it comes here, it means that a session exists, and we are trying to link the + # account_info to the session user, which means it's an MFA flow, so we enable + # linking here. + return ShouldAutomaticallyLink(should_require_verification=False) + + return ShouldNotAutomaticallyLink() + + +init( + app_info=InputAppInfo( + app_name="...", + api_domain="...", + website_domain="...", + ), + supertokens_config=SupertokensConfig( + connection_uri="...", + ), + framework='...', # type: ignore + recipe_list=[ + accountlinking.init(should_do_automatic_account_linking=should_do_automatic_account_linking) + ], +) +``` @@ -156,9 +198,31 @@ Coming soon. In the meantime, checkout the [legacy method](./legacy-method/how-i -:::note -Coming soon. In the meantime, checkout the [legacy method](./legacy-method/how-it-works) for adding MFA to your app. -::: +```python +from supertokens_python import init, InputAppInfo, SupertokensConfig +from supertokens_python.recipe import emailpassword, multifactorauth, thirdparty, passwordless +from supertokens_python.recipe.passwordless import ContactEmailOnlyConfig +from supertokens_python.recipe.multifactorauth.types import FactorIds + +init( + app_info=InputAppInfo( + app_name="...", + api_domain="...", + website_domain="...", + ), + supertokens_config=SupertokensConfig( + connection_uri="...", + ), + framework='...', # type: ignore + recipe_list=[ + emailpassword.init(), + thirdparty.init(), + multifactorauth.init(), + passwordless.init(contact_config=ContactEmailOnlyConfig(), flow_type="USER_INPUT_CODE"), + multifactorauth.init(first_factors=[FactorIds.EMAILPASSWORD, FactorIds.THIRDPARTY]) + ], +) +``` @@ -207,9 +271,52 @@ Coming soon. In the meantime, checkout the [legacy method](./legacy-method/how-i -:::note -Coming soon. In the meantime, checkout the [legacy method](./legacy-method/how-it-works) for adding MFA to your app. -::: + + + +```python +from supertokens_python.recipe.multitenancy.asyncio import create_or_update_tenant +from supertokens_python.recipe.multitenancy.interfaces import TenantConfigCreateOrUpdate +from supertokens_python.recipe.multifactorauth.types import FactorIds + + +async def create_new_tenant(): + resp = await create_or_update_tenant( + "customer1", TenantConfigCreateOrUpdate(first_factors=[FactorIds.EMAILPASSWORD]) + ) + + if resp.created_new: + # Tenant created successfully + pass + else: + # Existing tenant's config was modified + pass +``` + + + + +```python +from supertokens_python.recipe.multitenancy.syncio import create_or_update_tenant +from supertokens_python.recipe.multitenancy.interfaces import TenantConfigCreateOrUpdate +from supertokens_python.recipe.multifactorauth.types import FactorIds + + +def create_new_tenant(): + resp = create_or_update_tenant( + "customer1", TenantConfigCreateOrUpdate(first_factors=[FactorIds.EMAILPASSWORD]) + ) + + if resp.created_new: + # Tenant created successfully + pass + else: + # Existing tenant's config was modified + pass +``` + + + @@ -351,9 +458,65 @@ Coming soon. In the meantime, checkout the [legacy method](./legacy-method/how-i -:::note -Coming soon. In the meantime, checkout the [legacy method](./legacy-method/how-it-works) for adding MFA to your app. -::: +```python +from supertokens_python import init, InputAppInfo, SupertokensConfig +from supertokens_python.recipe import ( + emailpassword, + multifactorauth, + thirdparty, + passwordless, +) +from supertokens_python.recipe.passwordless import ContactEmailOnlyConfig +from supertokens_python.recipe.multifactorauth.types import FactorIds, OverrideConfig +from supertokens_python.recipe.multifactorauth.interfaces import RecipeInterface +from typing import Dict, Any, Callable, Awaitable, List +from supertokens_python.types import User +from supertokens_python.recipe.multifactorauth.types import MFARequirementList + + +def override_functions(original_implementation: RecipeInterface): + async def get_mfa_requirements_for_auth( + tenant_id: str, + access_token_payload: Dict[str, Any], + completed_factors: Dict[str, int], + user: Callable[[], Awaitable[User]], + factors_set_up_for_user: Callable[[], Awaitable[List[str]]], + required_secondary_factors_for_user: Callable[[], Awaitable[List[str]]], + required_secondary_factors_for_tenant: Callable[[], Awaitable[List[str]]], + user_context: Dict[str, Any], + ) -> MFARequirementList: + return [FactorIds.TOTP] + + original_implementation.get_mfa_requirements_for_auth = ( + get_mfa_requirements_for_auth + ) + return original_implementation + + +init( + app_info=InputAppInfo( + app_name="...", + api_domain="...", + website_domain="...", + ), + supertokens_config=SupertokensConfig( + connection_uri="...", + ), + framework="...", # type: ignore + recipe_list=[ + emailpassword.init(), + thirdparty.init(), + multifactorauth.init(), + passwordless.init( + contact_config=ContactEmailOnlyConfig(), flow_type="USER_INPUT_CODE" + ), + multifactorauth.init( + first_factors=[FactorIds.EMAILPASSWORD, FactorIds.THIRDPARTY], + override=OverrideConfig(functions=override_functions), + ), + ], +) +``` @@ -421,9 +584,54 @@ Coming soon. In the meantime, checkout the [legacy method](./legacy-method/how-i -:::note -Coming soon. In the meantime, checkout the [legacy method](./legacy-method/how-it-works) for adding MFA to your app. -::: +```python +from supertokens_python import init, InputAppInfo, SupertokensConfig +from supertokens_python.recipe import multifactorauth +from supertokens_python.recipe.multifactorauth.types import FactorIds, OverrideConfig +from supertokens_python.recipe.multifactorauth.interfaces import RecipeInterface +from typing import Dict, Any, Callable, Awaitable, List +from supertokens_python.types import User +from supertokens_python.recipe.multifactorauth.types import MFARequirementList + + +def override_functions(original_implementation: RecipeInterface): + async def get_mfa_requirements_for_auth( + tenant_id: str, + access_token_payload: Dict[str, Any], + completed_factors: Dict[str, int], + user: Callable[[], Awaitable[User]], + factors_set_up_for_user: Callable[[], Awaitable[List[str]]], + required_secondary_factors_for_user: Callable[[], Awaitable[List[str]]], + required_secondary_factors_for_tenant: Callable[[], Awaitable[List[str]]], + user_context: Dict[str, Any], + ) -> MFARequirementList: + # highlight-next-line + return [FactorIds.TOTP, FactorIds.OTP_EMAIL] + + original_implementation.get_mfa_requirements_for_auth = ( + get_mfa_requirements_for_auth + ) + return original_implementation + + +init( + app_info=InputAppInfo( + app_name="...", + api_domain="...", + website_domain="...", + ), + supertokens_config=SupertokensConfig( + connection_uri="...", + ), + framework="...", # type: ignore + recipe_list=[ + multifactorauth.init( + first_factors=[FactorIds.EMAILPASSWORD, FactorIds.THIRDPARTY], + override=OverrideConfig(functions=override_functions), + ), + ], +) +``` @@ -485,9 +693,54 @@ Coming soon. In the meantime, checkout the [legacy method](./legacy-method/how-i -:::note -Coming soon. In the meantime, checkout the [legacy method](./legacy-method/how-it-works) for adding MFA to your app. -::: +```python +from supertokens_python import init, InputAppInfo, SupertokensConfig +from supertokens_python.recipe import multifactorauth +from supertokens_python.recipe.multifactorauth.types import FactorIds, OverrideConfig +from supertokens_python.recipe.multifactorauth.interfaces import RecipeInterface +from typing import Dict, Any, Callable, Awaitable, List +from supertokens_python.types import User +from supertokens_python.recipe.multifactorauth.types import MFARequirementList + + +def override_functions(original_implementation: RecipeInterface): + async def get_mfa_requirements_for_auth( + tenant_id: str, + access_token_payload: Dict[str, Any], + completed_factors: Dict[str, int], + user: Callable[[], Awaitable[User]], + factors_set_up_for_user: Callable[[], Awaitable[List[str]]], + required_secondary_factors_for_user: Callable[[], Awaitable[List[str]]], + required_secondary_factors_for_tenant: Callable[[], Awaitable[List[str]]], + user_context: Dict[str, Any], + ) -> MFARequirementList: + # highlight-next-line + return [{"allOfInAnyOrder": [FactorIds.TOTP, FactorIds.OTP_EMAIL]}] + + original_implementation.get_mfa_requirements_for_auth = ( + get_mfa_requirements_for_auth + ) + return original_implementation + + +init( + app_info=InputAppInfo( + app_name="...", + api_domain="...", + website_domain="...", + ), + supertokens_config=SupertokensConfig( + connection_uri="...", + ), + framework="...", # type: ignore + recipe_list=[ + multifactorauth.init( + first_factors=[FactorIds.EMAILPASSWORD, FactorIds.THIRDPARTY], + override=OverrideConfig(functions=override_functions), + ), + ], +) +``` @@ -526,7 +779,7 @@ supertokens.init({ getMFARequirementsForAuth: async function (input) { // highlight-start let currentCompletedFactors = MultiFactorAuth.MultiFactorAuthClaim.getValueFromPayload(input.accessTokenPayload) - if (MultiFactorAuth.FactorIds.TOTP in currentCompletedFactors) { + if (MultiFactorAuth.FactorIds.TOTP in currentCompletedFactors.c) { // this means the totp factor is completed return [MultiFactorAuth.FactorIds.OTP_EMAIL] } else { @@ -554,9 +807,67 @@ Coming soon. In the meantime, checkout the [legacy method](./legacy-method/how-i -:::note -Coming soon. In the meantime, checkout the [legacy method](./legacy-method/how-it-works) for adding MFA to your app. -::: +```python +from supertokens_python import init, InputAppInfo, SupertokensConfig +from supertokens_python.recipe import multifactorauth +from supertokens_python.recipe.multifactorauth.types import FactorIds, OverrideConfig +from supertokens_python.recipe.multifactorauth.interfaces import RecipeInterface +from typing import Dict, Any, Callable, Awaitable, List +from supertokens_python.types import User +from supertokens_python.recipe.multifactorauth.types import MFARequirementList +from supertokens_python.recipe.multifactorauth.multi_factor_auth_claim import ( + MultiFactorAuthClaim, +) + + +def override_functions(original_implementation: RecipeInterface): + async def get_mfa_requirements_for_auth( + tenant_id: str, + access_token_payload: Dict[str, Any], + completed_factors: Dict[str, int], + user: Callable[[], Awaitable[User]], + factors_set_up_for_user: Callable[[], Awaitable[List[str]]], + required_secondary_factors_for_user: Callable[[], Awaitable[List[str]]], + required_secondary_factors_for_tenant: Callable[[], Awaitable[List[str]]], + user_context: Dict[str, Any], + ) -> MFARequirementList: + # highlight-start + current_completed_factors = MultiFactorAuthClaim.get_value_from_payload( + access_token_payload + ) + if current_completed_factors and FactorIds.TOTP in current_completed_factors.c: + # this means the totp factor is completed + return [FactorIds.OTP_EMAIL] + else: + # this means we have not finished totp yet, and we want + # to do that right after first factor login + return [FactorIds.TOTP] + # highlight-end + + original_implementation.get_mfa_requirements_for_auth = ( + get_mfa_requirements_for_auth + ) + return original_implementation + + +init( + app_info=InputAppInfo( + app_name="...", + api_domain="...", + website_domain="...", + ), + supertokens_config=SupertokensConfig( + connection_uri="...", + ), + framework="...", # type: ignore + recipe_list=[ + multifactorauth.init( + first_factors=[FactorIds.EMAILPASSWORD, FactorIds.THIRDPARTY], + override=OverrideConfig(functions=override_functions), + ), + ], +) +``` @@ -619,9 +930,58 @@ Coming soon. In the meantime, checkout the [legacy method](./legacy-method/how-i -:::note -Coming soon. In the meantime, checkout the [legacy method](./legacy-method/how-it-works) for adding MFA to your app. -::: + + + +```python +from supertokens_python.recipe.multitenancy.asyncio import create_or_update_tenant +from supertokens_python.recipe.multitenancy.interfaces import TenantConfigCreateOrUpdate +from supertokens_python.recipe.multifactorauth.types import FactorIds + + +async def create_new_tenant(): + resp = await create_or_update_tenant( + "customer1", TenantConfigCreateOrUpdate( + first_factors=[FactorIds.EMAILPASSWORD], + required_secondary_factors=[FactorIds.OTP_EMAIL], + ) + ) + + if resp.created_new: + # Tenant created successfully + pass + else: + # Existing tenant's config was modified + pass +``` + + + + +```python +from supertokens_python.recipe.multitenancy.syncio import create_or_update_tenant +from supertokens_python.recipe.multitenancy.interfaces import TenantConfigCreateOrUpdate +from supertokens_python.recipe.multifactorauth.types import FactorIds + + +def create_new_tenant(): + resp = create_or_update_tenant( + "customer1", TenantConfigCreateOrUpdate( + first_factors=[FactorIds.EMAILPASSWORD], + required_secondary_factors=[FactorIds.OTP_EMAIL], + ) + ) + + if resp.created_new: + # Tenant created successfully + pass + else: + # Existing tenant's config was modified + pass +``` + + + @@ -742,9 +1102,55 @@ Coming soon. In the meantime, checkout the [legacy method](./legacy-method/how-i -:::note -Coming soon. In the meantime, checkout the [legacy method](./legacy-method/how-it-works) for adding MFA to your app. -::: +```python +from supertokens_python import init, InputAppInfo, SupertokensConfig +from supertokens_python.recipe import multifactorauth +from supertokens_python.recipe.multifactorauth.types import FactorIds, OverrideConfig +from supertokens_python.recipe.multifactorauth.interfaces import RecipeInterface +from typing import Dict, Any, Callable, Awaitable, List +from supertokens_python.types import User +from supertokens_python.recipe.multifactorauth.types import MFARequirementList + + +def override_functions(original_implementation: RecipeInterface): + async def get_mfa_requirements_for_auth( + tenant_id: str, + access_token_payload: Dict[str, Any], + completed_factors: Dict[str, int], + user: Callable[[], Awaitable[User]], + factors_set_up_for_user: Callable[[], Awaitable[List[str]]], + required_secondary_factors_for_user: Callable[[], Awaitable[List[str]]], + required_secondary_factors_for_tenant: Callable[[], Awaitable[List[str]]], + user_context: Dict[str, Any], + ) -> MFARequirementList: + # highlight-start + return [{"allOfInAnyOrder": await required_secondary_factors_for_tenant()}] + # highlight-end + + original_implementation.get_mfa_requirements_for_auth = ( + get_mfa_requirements_for_auth + ) + return original_implementation + + +init( + app_info=InputAppInfo( + app_name="...", + api_domain="...", + website_domain="...", + ), + supertokens_config=SupertokensConfig( + connection_uri="...", + ), + framework="...", # type: ignore + recipe_list=[ + multifactorauth.init( + first_factors=[FactorIds.EMAILPASSWORD, FactorIds.THIRDPARTY], + override=OverrideConfig(functions=override_functions), + ), + ], +) +``` @@ -784,9 +1190,34 @@ Coming soon. In the meantime, checkout the [legacy method](./legacy-method/how-i -:::note -Coming soon. In the meantime, checkout the [legacy method](./legacy-method/how-it-works) for adding MFA to your app. -::: + + + +```python +from supertokens_python.recipe.multifactorauth.asyncio import add_to_required_secondary_factors_for_user +from supertokens_python.recipe.multifactorauth.types import FactorIds + +async def enable_mfa_for_user(user_id: str): + await add_to_required_secondary_factors_for_user( + user_id, FactorIds.TOTP + ) +``` + + + + +```python +from supertokens_python.recipe.multifactorauth.syncio import add_to_required_secondary_factors_for_user +from supertokens_python.recipe.multifactorauth.types import FactorIds + +def enable_mfa_for_user(user_id: str): + add_to_required_secondary_factors_for_user( + user_id, FactorIds.TOTP + ) +``` + + + @@ -850,9 +1281,55 @@ Coming soon. In the meantime, checkout the [legacy method](./legacy-method/how-i -:::note -Coming soon. In the meantime, checkout the [legacy method](./legacy-method/how-it-works) for adding MFA to your app. -::: +```python +from supertokens_python import init, InputAppInfo, SupertokensConfig +from supertokens_python.recipe import multifactorauth +from supertokens_python.recipe.multifactorauth.types import FactorIds, OverrideConfig +from supertokens_python.recipe.multifactorauth.interfaces import RecipeInterface +from typing import Dict, Any, Callable, Awaitable, List +from supertokens_python.types import User +from supertokens_python.recipe.multifactorauth.types import MFARequirementList + + +def override_functions(original_implementation: RecipeInterface): + async def get_mfa_requirements_for_auth( + tenant_id: str, + access_token_payload: Dict[str, Any], + completed_factors: Dict[str, int], + user: Callable[[], Awaitable[User]], + factors_set_up_for_user: Callable[[], Awaitable[List[str]]], + required_secondary_factors_for_user: Callable[[], Awaitable[List[str]]], + required_secondary_factors_for_tenant: Callable[[], Awaitable[List[str]]], + user_context: Dict[str, Any], + ) -> MFARequirementList: + # highlight-start + return [{"allOfInAnyOrder": await required_secondary_factors_for_user()}] + # highlight-end + + original_implementation.get_mfa_requirements_for_auth = ( + get_mfa_requirements_for_auth + ) + return original_implementation + + +init( + app_info=InputAppInfo( + app_name="...", + api_domain="...", + website_domain="...", + ), + supertokens_config=SupertokensConfig( + connection_uri="...", + ), + framework="...", # type: ignore + recipe_list=[ + multifactorauth.init( + first_factors=[FactorIds.EMAILPASSWORD, FactorIds.THIRDPARTY], + override=OverrideConfig(functions=override_functions), + ), + ], +) +``` @@ -897,9 +1374,36 @@ Coming soon. In the meantime, checkout the [legacy method](./legacy-method/how-i -:::note -Coming soon. In the meantime, checkout the [legacy method](./legacy-method/how-it-works) for adding MFA to your app. -::: + + + +```python +from supertokens_python.recipe.multifactorauth.asyncio import get_required_secondary_factors_for_user +from supertokens_python.recipe.multifactorauth.types import FactorIds + +async def is_totp_enabled_for_user(user_id: str): + factors = await get_required_secondary_factors_for_user( + user_id + ) + return FactorIds.TOTP in factors +``` + + + + +```python +from supertokens_python.recipe.multifactorauth.syncio import get_required_secondary_factors_for_user +from supertokens_python.recipe.multifactorauth.types import FactorIds + +def is_totp_enabled_for_user(user_id: str): + factors = get_required_secondary_factors_for_user( + user_id + ) + return FactorIds.TOTP in factors +``` + + + @@ -932,9 +1436,36 @@ Coming soon. In the meantime, checkout the [legacy method](./legacy-method/how-i -:::note -Coming soon. In the meantime, checkout the [legacy method](./legacy-method/how-it-works) for adding MFA to your app. -::: + + + +```python +from supertokens_python.recipe.multifactorauth.asyncio import get_factors_setup_for_user +from supertokens_python.recipe.multifactorauth.types import FactorIds + +async def is_totp_enabled_for_user(user_id: str): + factors = await get_factors_setup_for_user( + user_id + ) + return FactorIds.TOTP in factors +``` + + + + +```python +from supertokens_python.recipe.multifactorauth.syncio import get_factors_setup_for_user +from supertokens_python.recipe.multifactorauth.types import FactorIds + +def is_totp_enabled_for_user(user_id: str): + factors = get_factors_setup_for_user( + user_id + ) + return FactorIds.TOTP in factors +``` + + + diff --git a/v2/mfa/old-sdk-to-new.mdx b/v2/mfa/old-sdk-to-new.mdx index 28212446e..020a9bbfc 100644 --- a/v2/mfa/old-sdk-to-new.mdx +++ b/v2/mfa/old-sdk-to-new.mdx @@ -106,9 +106,86 @@ Coming soon. In the meantime, checkout the [legacy method](./legacy-method/how-i -:::note -Coming soon. In the meantime, checkout the [legacy method](./legacy-method/how-it-works) for adding MFA to your app. -::: +```python +from supertokens_python import init, InputAppInfo, SupertokensConfig +from supertokens_python.recipe import session +from supertokens_python.recipe.session.utils import InputOverrideConfig +from supertokens_python.recipe.session.interfaces import RecipeInterface +from supertokens_python.recipe.session.interfaces import ( + SessionClaimValidator, + ClaimValidationResult, +) +from typing import Dict, Any, List +from supertokens_python.types import RecipeUserId +from supertokens_python.recipe.multifactorauth.multi_factor_auth_claim import ( + MultiFactorAuthClaim, +) +from supertokens_python import get_request_from_user_context + + +def override_functions( + original_implementation: RecipeInterface, +) -> RecipeInterface: + + def get_global_claim_validators( + tenant_id: str, + user_id: str, + recipe_user_id: RecipeUserId, + claim_validators_added_by_other_recipes: List[SessionClaimValidator], + user_context: Dict[str, Any], + ): + # Remove the existing default MFA validator + new_validators = [ + v + for v in claim_validators_added_by_other_recipes + if v.id != MultiFactorAuthClaim.key + ] + + # Create an instance of the default validator + original_validator = ( + MultiFactorAuthClaim.validators.has_completed_mfa_requirements_for_auth() + ) + original_validator_validate_func = original_validator.validate + + # Create a custom validator based on the default validator + async def custom_validate( + payload: Any, user_context: Dict[str, Any] + ) -> ClaimValidationResult: + # Check if the client is an older one based on the header + request = get_request_from_user_context(user_context) + if request is not None: + is_older_client = request.get_header("client-version") != "2.0" + if is_older_client: + # Return true early for older clients + return ClaimValidationResult(is_valid=True) + + # For newer clients, call the original validate function + return await original_validator_validate_func(payload, user_context) + + original_validator.validate = custom_validate + + return [original_validator, *new_validators] + + original_implementation.get_global_claim_validators = get_global_claim_validators + + return original_implementation + + +init( + app_info=InputAppInfo( + app_name="...", + api_domain="...", + website_domain="...", + ), + supertokens_config=SupertokensConfig( + connection_uri="...", + ), + framework="...", # type: ignore + recipe_list=[ + session.init(override=InputOverrideConfig(functions=override_functions)) + ], +) +``` From 2de25e1228ce207b94a519b5ff6ba8f59eefb2ff Mon Sep 17 00:00:00 2001 From: rishabhpoddar Date: Sat, 19 Oct 2024 13:06:09 +0530 Subject: [PATCH 15/34] more docs --- v2/mfa/protect-routes.mdx | 205 ++++++++++++++++++++++++++++++++++++-- 1 file changed, 199 insertions(+), 6 deletions(-) diff --git a/v2/mfa/protect-routes.mdx b/v2/mfa/protect-routes.mdx index 048bf0b5f..d113dad77 100644 --- a/v2/mfa/protect-routes.mdx +++ b/v2/mfa/protect-routes.mdx @@ -17,6 +17,7 @@ import FrontendMobileSubTabs from "/src/components/tabs/FrontendMobileSubTabs" import {Question, Answer}from "/src/components/question" import BackendSDKTabs from "/src/components/tabs/BackendSDKTabs" import NodeJSFrameworkSubTabs from "/src/components/tabs/NodeJSFrameworkSubTabs"; +import PythonFrameworkSubTabs from "/src/components/tabs/PythonFrameworkSubTabs"; @@ -282,9 +283,71 @@ Coming soon. In the meantime, checkout the [legacy method](./legacy-method/how-i -:::note -Coming soon. In the meantime, checkout the [legacy method](./legacy-method/how-it-works) for adding MFA to your app. -::: + + + +```python +from supertokens_python.recipe.session.framework.fastapi import verify_session +from supertokens_python.recipe.multifactorauth.multi_factor_auth_claim import MultiFactorAuthClaim +from supertokens_python.recipe.session import SessionContainer +from fastapi import Depends + +@app.post('/like_comment') # type: ignore +async def like_comment(session: SessionContainer = Depends( + verify_session( + # highlight-start + # We keep all validators except for the EmailVerification ones + override_global_claim_validators=lambda global_validators, session, user_context: [ + validators for validators in global_validators if validators.id != MultiFactorAuthClaim.key] + # highlight-end + ) +)): + # All validator checks have passed and the user has a verified email address + pass +``` + + + + +```python +from supertokens_python.recipe.session.framework.flask import verify_session +from supertokens_python.recipe.multifactorauth.multi_factor_auth_claim import MultiFactorAuthClaim + +@app.route('/update-jwt', methods=['POST']) # type: ignore +@verify_session( + # highlight-start + # We keep all validators except for the EmailVerification ones + override_global_claim_validators=lambda global_validators, session, user_context: [ + validators for validators in global_validators if validators.id != MultiFactorAuthClaim.key] + # highlight-end +) +def like_comment(): + # All validator checks have passed and the user has a verified email address + pass +``` + + + + +```python +from supertokens_python.recipe.session.framework.django.asyncio import verify_session +from django.http import HttpRequest +from supertokens_python.recipe.multifactorauth.multi_factor_auth_claim import MultiFactorAuthClaim + +@verify_session( + # highlight-start + # We keep all validators except for the EmailVerification ones + override_global_claim_validators=lambda global_validators, session, user_context: [ + validators for validators in global_validators if validators.id != MultiFactorAuthClaim.key] + # highlight-end +) +async def like_comment(request: HttpRequest): + # All validator checks have passed and the user has a verified email address + pass +``` + + + @@ -779,9 +842,139 @@ Coming soon. In the meantime, checkout the [legacy method](./legacy-method/how-i -:::note -Coming soon. In the meantime, checkout the [legacy method](./legacy-method/how-it-works) for adding MFA to your app. -::: + + + +```python +from fastapi import Depends +from supertokens_python.recipe.session.framework.fastapi import verify_session +from supertokens_python.recipe.session.exceptions import ( + raise_invalid_claims_exception, + ClaimValidationError, +) +from supertokens_python.recipe.session import SessionContainer +from supertokens_python.recipe.multifactorauth.multi_factor_auth_claim import ( + MultiFactorAuthClaim, +) + + +@app.post("/update-blog") # type: ignore +async def update_blog_api(session: SessionContainer = Depends(verify_session())): + # highlight-start + mfa_claim_value = await session.get_claim_value(MultiFactorAuthClaim) + if mfa_claim_value is None: + # This means that there is no MFA claim information in the session. + # This can happen if the session was created prior to enabling the MFA recipe on the backend. + # So here, we add the value of the MFA claim to the session: + await session.fetch_and_set_claim(MultiFactorAuthClaim) + mfa_claim_value = await session.get_claim_value(MultiFactorAuthClaim) + assert mfa_claim_value is not None + completed_factors = mfa_claim_value.c + if "totp" not in completed_factors: + # The user has not finished TOTP. We throw a claim validation error: + raise_invalid_claims_exception( + "User has not finished TOTP", + [ + ClaimValidationError( + MultiFactorAuthClaim.key, + { + "message": "Factor validation failed: totp not completed", + "factorId": "totp", + }, + ) + ], + ) + # If we reach here, it means the user has completed TOTP + # highlight-end +``` + + + + +```python +from flask import Flask, g +from supertokens_python.recipe.session.framework.flask import verify_session +from supertokens_python.recipe.session import SessionContainer +from supertokens_python.recipe.session.exceptions import raise_invalid_claims_exception, ClaimValidationError +from supertokens_python.recipe.multifactorauth.multi_factor_auth_claim import MultiFactorAuthClaim + +app = Flask(__name__) + +@app.route('/update-blog', methods=['POST']) # type: ignore +@verify_session() +def check_mfa_api(): + session: SessionContainer = g.supertokens # type: ignore + # highlight-start + mfa_claim_value = session.sync_get_claim_value(MultiFactorAuthClaim) + if mfa_claim_value is None: + # This means that there is no MFA claim information in the session. + # This can happen if the session was created prior to enabling the MFA recipe on the backend. + # So here, we add the value of the MFA claim to the session: + session.sync_fetch_and_set_claim(MultiFactorAuthClaim) + mfa_claim_value = session.sync_get_claim_value(MultiFactorAuthClaim) + assert mfa_claim_value is not None + completed_factors = mfa_claim_value.c + if "totp" not in completed_factors: + # The user has not finished TOTP. We throw a claim validation error: + raise_invalid_claims_exception("User has not finished TOTP", [ + ClaimValidationError(MultiFactorAuthClaim.key, { + "message": "Factor validation failed: totp not completed", + "factorId": "totp", + }) + ]) + # If we reach here, it means the user has completed TOTP + # highlight-end +``` + + + + +```python +from django.http import HttpRequest +from supertokens_python.recipe.session.framework.django.asyncio import verify_session +from supertokens_python.recipe.session import SessionContainer +from supertokens_python.recipe.session.exceptions import ( + raise_invalid_claims_exception, + ClaimValidationError, +) +from supertokens_python.recipe.multifactorauth.multi_factor_auth_claim import ( + MultiFactorAuthClaim, +) + + +@verify_session() +async def get_user_info_api(request: HttpRequest): + session: SessionContainer = request.supertokens # type: ignore + # highlight-start + mfa_claim_value = await session.get_claim_value(MultiFactorAuthClaim) + if mfa_claim_value is None: + # This means that there is no MFA claim information in the session. + # This can happen if the session was created prior to enabling the MFA recipe on the backend. + # So here, we add the value of the MFA claim to the session: + await session.fetch_and_set_claim(MultiFactorAuthClaim) + mfa_claim_value = await session.get_claim_value(MultiFactorAuthClaim) + assert mfa_claim_value is not None + completed_factors = mfa_claim_value.c + if "totp" not in completed_factors: + # The user has not finished TOTP. We throw a claim validation error: + raise_invalid_claims_exception( + "User has not finished TOTP", + [ + ClaimValidationError( + MultiFactorAuthClaim.key, + { + "message": "Factor validation failed: totp not completed", + "factorId": "totp", + }, + ) + ], + ) + # If we reach here, it means the user has completed TOTP + # highlight-end +``` + + + From 48de9d6b565bc461c6a7d9e9339e48cd5064adb5 Mon Sep 17 00:00:00 2001 From: rishabhpoddar Date: Sat, 19 Oct 2024 17:14:11 +0530 Subject: [PATCH 16/34] more changes --- v2/mfa/step-up-auth.mdx | 222 +++++++++++++++++- .../pythonEnv/requirements.txt | 13 +- 2 files changed, 227 insertions(+), 8 deletions(-) diff --git a/v2/mfa/step-up-auth.mdx b/v2/mfa/step-up-auth.mdx index cb34597ee..1e72571d7 100644 --- a/v2/mfa/step-up-auth.mdx +++ b/v2/mfa/step-up-auth.mdx @@ -400,9 +400,133 @@ Coming soon. -:::note -Coming soon. -::: + + + +```python +from fastapi import Depends +from supertokens_python.recipe.session.framework.fastapi import verify_session +from supertokens_python.recipe.session.exceptions import ( + raise_invalid_claims_exception, + ClaimValidationError, +) +from supertokens_python.recipe.session import SessionContainer +from supertokens_python.recipe.multifactorauth.multi_factor_auth_claim import ( + MultiFactorAuthClaim, +) +import time + + +@app.post("/update-blog") # type: ignore +async def update_blog_api(session: SessionContainer = Depends(verify_session())): + # highlight-start + mfa_claim_value = await session.get_claim_value(MultiFactorAuthClaim) + assert mfa_claim_value is not None + totp_completed_time = mfa_claim_value.c.get("totp") + + if totp_completed_time is None or totp_completed_time < (round(time.time() * 1000) - 1000*60*5): + # TOTP hasn't been completed or was completed more than 5 minutes ago + raise_invalid_claims_exception( + "TOTP validation required", + [ + ClaimValidationError( + MultiFactorAuthClaim.key, + { + "message": "TOTP validation required or has expired", + "factorId": "totp", + }, + ) + ], + ) + # If we reach here, it means the user has completed TOTP + # highlight-end +``` + + + + +```python +from flask import Flask, g +from supertokens_python.recipe.session.framework.flask import verify_session +from supertokens_python.recipe.session import SessionContainer +from supertokens_python.recipe.session.exceptions import raise_invalid_claims_exception, ClaimValidationError +from supertokens_python.recipe.multifactorauth.multi_factor_auth_claim import MultiFactorAuthClaim +import time + +app = Flask(__name__) + +@app.route('/update-blog', methods=['POST']) # type: ignore +@verify_session() +def check_mfa_api(): + session: SessionContainer = g.supertokens # type: ignore + # highlight-start + mfa_claim_value = session.sync_get_claim_value(MultiFactorAuthClaim) + assert mfa_claim_value is not None + totp_completed_time = mfa_claim_value.c.get("totp") + + if totp_completed_time is None or totp_completed_time < (round(time.time() * 1000) - 1000*60*5): + # TOTP hasn't been completed or was completed more than 5 minutes ago + raise_invalid_claims_exception( + "TOTP validation required", + [ + ClaimValidationError( + MultiFactorAuthClaim.key, + { + "message": "TOTP validation required or has expired", + "factorId": "totp", + }, + ) + ], + ) + # If we reach here, it means the user has completed TOTP + # highlight-end +``` + + + + +```python +from django.http import HttpRequest +from supertokens_python.recipe.session.framework.django.asyncio import verify_session +from supertokens_python.recipe.session import SessionContainer +from supertokens_python.recipe.session.exceptions import ( + raise_invalid_claims_exception, + ClaimValidationError, +) +from supertokens_python.recipe.multifactorauth.multi_factor_auth_claim import ( + MultiFactorAuthClaim, +) +import time + + +@verify_session() +async def get_user_info_api(request: HttpRequest): + session: SessionContainer = request.supertokens # type: ignore + # highlight-start + mfa_claim_value = await session.get_claim_value(MultiFactorAuthClaim) + assert mfa_claim_value is not None + totp_completed_time = mfa_claim_value.c.get("totp") + + if totp_completed_time is None or totp_completed_time < (round(time.time() * 1000) - 1000*60*5): + # TOTP hasn't been completed or was completed more than 5 minutes ago + raise_invalid_claims_exception( + "TOTP validation required", + [ + ClaimValidationError( + MultiFactorAuthClaim.key, + { + "message": "TOTP validation required or has expired", + "factorId": "totp", + }, + ) + ], + ) + # If we reach here, it means the user has completed TOTP + # highlight-end +``` + + + @@ -495,9 +619,95 @@ Coming soon. -:::note -Coming soon. -::: +```python +from supertokens_python import init, InputAppInfo, SupertokensConfig +from supertokens_python.recipe import multifactorauth +from supertokens_python.recipe.multifactorauth.types import FactorIds, OverrideConfig +from supertokens_python.recipe.multifactorauth.interfaces import RecipeInterface +from typing import Dict, Any, Callable, Awaitable, List +from supertokens_python.recipe.session import SessionContainer +from supertokens_python.recipe.multifactorauth.types import MFARequirementList +from supertokens_python.recipe.multifactorauth.multi_factor_auth_claim import ( + MultiFactorAuthClaim, +) +import time +from supertokens_python.recipe.session.exceptions import ( + raise_invalid_claims_exception, + ClaimValidationError, +) + + +def override_functions(original_implementation: RecipeInterface): + original_assert_allowed_to_setup_factor_else_throw_invalid_claim_error = ( + original_implementation.assert_allowed_to_setup_factor_else_throw_invalid_claim_error + ) + + async def assert_allowed_to_setup_factor_else_throw_invalid_claim_error( + session: SessionContainer, + factor_id: str, + mfa_requirements_for_auth: Callable[[], Awaitable[MFARequirementList]], + factors_set_up_for_user: Callable[[], Awaitable[List[str]]], + user_context: Dict[str, Any], + ) -> None: + await original_assert_allowed_to_setup_factor_else_throw_invalid_claim_error( + session=session, + factor_id=factor_id, + mfa_requirements_for_auth=mfa_requirements_for_auth, + factors_set_up_for_user=factors_set_up_for_user, + user_context=user_context, + ) + + claim_value = await session.get_claim_value(MultiFactorAuthClaim) + if claim_value is None or not claim_value.v: + return + + # Check specifically for the step up auth case + if ( + factor_id == FactorIds.TOTP + and FactorIds.TOTP in await factors_set_up_for_user() + ): + totp_completed_time = claim_value.c.get(FactorIds.TOTP) + if totp_completed_time is None or totp_completed_time < ( + int(time.time() * 1000) - 1000 * 60 * 5 + ): + # User completed TOTP challenge more than 5 minutes ago + raise_invalid_claims_exception( + "User has not finished TOTP", + [ + ClaimValidationError( + MultiFactorAuthClaim.key, + { + "message": "Factor validation failed: totp not completed", + "factorId": "totp", + }, + ) + ], + ) + + original_implementation.assert_allowed_to_setup_factor_else_throw_invalid_claim_error = ( + assert_allowed_to_setup_factor_else_throw_invalid_claim_error + ) + return original_implementation + + +init( + app_info=InputAppInfo( + app_name="...", + api_domain="...", + website_domain="...", + ), + supertokens_config=SupertokensConfig( + connection_uri="...", + ), + framework="...", # type: ignore + recipe_list=[ + multifactorauth.init( + first_factors=[FactorIds.EMAILPASSWORD, FactorIds.THIRDPARTY], + override=OverrideConfig(functions=override_functions), + ), + ], +) +``` diff --git a/v2/src/plugins/codeTypeChecking/pythonEnv/requirements.txt b/v2/src/plugins/codeTypeChecking/pythonEnv/requirements.txt index fd1fd9584..fb595f3cb 100644 --- a/v2/src/plugins/codeTypeChecking/pythonEnv/requirements.txt +++ b/v2/src/plugins/codeTypeChecking/pythonEnv/requirements.txt @@ -1,7 +1,12 @@ +aiohappyeyeballs==2.4.3 +aiohttp==3.10.10 +aiohttp-retry==2.8.3 +aiosignal==1.3.1 aiosmtplib==1.1.6 anyio==3.7.1 asgiref==3.7.2 astroid==2.9.3 +async-timeout==4.0.3 attrs==21.4.0 autopep8==1.5.6 black==22.3.0 @@ -21,6 +26,7 @@ fastapi==0.68.1 filelock==3.12.2 Flask==2.0.2 Flask-Cors==3.0.10 +frozenlist==1.4.1 h11==0.14.0 httpcore==0.17.3 httpx==0.24.1 @@ -37,6 +43,7 @@ mangum==0.16.0 Markdown==3.3.6 MarkupSafe==2.1.1 mccabe==0.6.1 +multidict==6.1.0 mypy==0.942 mypy-extensions==0.4.3 nest-asyncio==1.5.1 @@ -44,10 +51,11 @@ nodeenv==1.6.0 packaging==21.3 pathspec==0.9.0 pdoc3==0.10.0 -phonenumbers==8.12.48 +phonenumbers==8.13.47 pkce==1.0.3 platformdirs==2.5.1 pluggy==1.0.0 +propcache==0.2.0 py==1.11.0 pycodestyle==2.8.0 pycparser==2.21 @@ -76,7 +84,7 @@ supertokens_python @ git+https://github.com/supertokens/supertokens-python.git@5 tldextract==3.1.0 toml==0.10.2 tomli==2.0.1 -twilio==7.9.1 +twilio==9.3.3 types-pytz==2021.3.6 types-PyYAML==6.0.5 typing_extensions==4.7.1 @@ -85,4 +93,5 @@ urllib3==2.0.4 uvicorn==0.18.2 Werkzeug==2.0.3 wrapt==1.13.3 +yarl==1.15.5 zipp==3.7.0 From 1ab8c950eeff98a70b5ba3a65d4f2979aa6ae222 Mon Sep 17 00:00:00 2001 From: rishabhpoddar Date: Sat, 19 Oct 2024 17:24:50 +0530 Subject: [PATCH 17/34] more changes --- v2/mfa/email-sms-otp/otp-for-all-users.mdx | 191 ++++++++++++++++++++- 1 file changed, 182 insertions(+), 9 deletions(-) diff --git a/v2/mfa/email-sms-otp/otp-for-all-users.mdx b/v2/mfa/email-sms-otp/otp-for-all-users.mdx index 9a055b132..61de73a65 100644 --- a/v2/mfa/email-sms-otp/otp-for-all-users.mdx +++ b/v2/mfa/email-sms-otp/otp-for-all-users.mdx @@ -127,9 +127,82 @@ Coming soon. In the meantime, checkout the [legacy method](../legacy-method/how- -:::note -Coming soon. In the meantime, checkout the [legacy method](../legacy-method/how-it-works) for adding MFA to your app. -::: +```python +from supertokens_python import init, InputAppInfo, SupertokensConfig +from supertokens_python.recipe import multifactorauth, accountlinking +from supertokens_python.recipe.multifactorauth.types import FactorIds, OverrideConfig, MFARequirementList +from supertokens_python.recipe.multifactorauth.interfaces import RecipeInterface +from supertokens_python.recipe.session.interfaces import SessionContainer +from supertokens_python.recipe.accountlinking.types import ( + AccountInfoWithRecipeIdAndUserId, + ShouldNotAutomaticallyLink, + ShouldAutomaticallyLink, +) +from supertokens_python.types import User +from typing import Dict, Any, Callable, Awaitable, List, Optional, Union + + +async def should_do_automatic_account_linking( + new_account_info: AccountInfoWithRecipeIdAndUserId, + user: Optional[User], + session: Optional[SessionContainer], + tenant_id: str, + user_context: Dict[str, Any] +) -> Union[ShouldNotAutomaticallyLink, ShouldAutomaticallyLink]: + if session is None: + # We do not want to do first factor account linking by default. + # To enable that, please see the automatic account linking docs + # in the recipe docs for your first factor. + return ShouldNotAutomaticallyLink() + + if user is None or session.get_user_id() == user.id: + # If it comes here, it means that a session exists, and we are trying to link the + # new_account_info to the session user, which means it's an MFA flow, so we enable + # linking here. + return ShouldAutomaticallyLink(should_require_verification=False) + + return ShouldNotAutomaticallyLink() + + +def override_functions(original_implementation: RecipeInterface): + async def get_mfa_requirements_for_auth( + tenant_id: str, + access_token_payload: Dict[str, Any], + completed_factors: Dict[str, int], + user: Callable[[], Awaitable[User]], + factors_set_up_for_user: Callable[[], Awaitable[List[str]]], + required_secondary_factors_for_user: Callable[[], Awaitable[List[str]]], + required_secondary_factors_for_tenant: Callable[[], Awaitable[List[str]]], + user_context: Dict[str, Any], + ) -> MFARequirementList: + # highlight-next-line + return [FactorIds.OTP_EMAIL] + + original_implementation.get_mfa_requirements_for_auth = ( + get_mfa_requirements_for_auth + ) + return original_implementation + + +init( + app_info=InputAppInfo( + app_name="...", + api_domain="...", + website_domain="...", + ), + supertokens_config=SupertokensConfig( + connection_uri="...", + ), + framework="...", # type: ignore + recipe_list=[ + multifactorauth.init( + first_factors=[FactorIds.EMAILPASSWORD, FactorIds.THIRDPARTY], + override=OverrideConfig(functions=override_functions), + ), + accountlinking.init(should_do_automatic_account_linking=should_do_automatic_account_linking) + ], +) +``` @@ -579,9 +652,57 @@ Coming soon. In the meantime, checkout the [legacy method](../legacy-method/how- -:::note -Coming soon. In the meantime, checkout the [legacy method](../legacy-method/how-it-works) for adding MFA to your app. -::: +```python +from supertokens_python import init, InputAppInfo, SupertokensConfig +from supertokens_python.recipe import multifactorauth, accountlinking +from supertokens_python.recipe.session.interfaces import SessionContainer +from supertokens_python.recipe.accountlinking.types import ( + AccountInfoWithRecipeIdAndUserId, + ShouldNotAutomaticallyLink, + ShouldAutomaticallyLink, +) +from supertokens_python.types import User +from typing import Dict, Any, Optional, Union + + +async def should_do_automatic_account_linking( + new_account_info: AccountInfoWithRecipeIdAndUserId, + user: Optional[User], + session: Optional[SessionContainer], + tenant_id: str, + user_context: Dict[str, Any] +) -> Union[ShouldNotAutomaticallyLink, ShouldAutomaticallyLink]: + if session is None: + # We do not want to do first factor account linking by default. + # To enable that, please see the automatic account linking docs + # in the recipe docs for your first factor. + return ShouldNotAutomaticallyLink() + + if user is None or session.get_user_id() == user.id: + # If it comes here, it means that a session exists, and we are trying to link the + # new_account_info to the session user, which means it's an MFA flow, so we enable + # linking here. + return ShouldAutomaticallyLink(should_require_verification=False) + + return ShouldNotAutomaticallyLink() + + +init( + app_info=InputAppInfo( + app_name="...", + api_domain="...", + website_domain="...", + ), + supertokens_config=SupertokensConfig( + connection_uri="...", + ), + framework="...", # type: ignore + recipe_list=[ + multifactorauth.init(), + accountlinking.init(should_do_automatic_account_linking=should_do_automatic_account_linking) + ], +) +``` @@ -628,9 +749,61 @@ Coming soon. In the meantime, checkout the [legacy method](../legacy-method/how- -:::note -Coming soon. In the meantime, checkout the [legacy method](../legacy-method/how-it-works) for adding MFA to your app. -::: + + + +```python +from supertokens_python.recipe.multitenancy.asyncio import create_or_update_tenant +from supertokens_python.recipe.multitenancy.interfaces import TenantConfigCreateOrUpdate +from supertokens_python.recipe.multifactorauth.types import FactorIds + + +async def create_new_tenant(): + resp = await create_or_update_tenant( + "customer1", + TenantConfigCreateOrUpdate( + first_factors=[FactorIds.EMAILPASSWORD, FactorIds.THIRDPARTY], + required_secondary_factors=[FactorIds.OTP_EMAIL], + ), + ) + + if resp.created_new: + # Tenant created successfully + pass + else: + # Existing tenant's config was modified + pass +``` + + + + +```python +from supertokens_python.recipe.multitenancy.syncio import create_or_update_tenant +from supertokens_python.recipe.multitenancy.interfaces import TenantConfigCreateOrUpdate +from supertokens_python.recipe.multifactorauth.types import FactorIds + + +def create_new_tenant(): + resp = create_or_update_tenant( + "customer1", + TenantConfigCreateOrUpdate( + first_factors=[FactorIds.EMAILPASSWORD, FactorIds.THIRDPARTY], + required_secondary_factors=[FactorIds.OTP_EMAIL], + ), + ) + + + if resp.created_new: + # Tenant created successfully + pass + else: + # Existing tenant's config was modified + pass +``` + + + From f8bd1301ed07c8793ce489a21971f31fa35ecb86 Mon Sep 17 00:00:00 2001 From: rishabhpoddar Date: Sat, 19 Oct 2024 17:39:10 +0530 Subject: [PATCH 18/34] more changes --- v2/mfa/email-sms-otp/otp-for-opt-in-users.mdx | 203 +++++++++++++++++- 1 file changed, 197 insertions(+), 6 deletions(-) diff --git a/v2/mfa/email-sms-otp/otp-for-opt-in-users.mdx b/v2/mfa/email-sms-otp/otp-for-opt-in-users.mdx index dd144cf2a..c662d0f37 100644 --- a/v2/mfa/email-sms-otp/otp-for-opt-in-users.mdx +++ b/v2/mfa/email-sms-otp/otp-for-opt-in-users.mdx @@ -147,9 +147,96 @@ Coming soon. In the meantime, checkout the [legacy method](../legacy-method/how- -:::note -Coming soon. In the meantime, checkout the [legacy method](../legacy-method/how-it-works) for adding MFA to your app. -::: +```python +from supertokens_python import init, InputAppInfo, SupertokensConfig +from supertokens_python.recipe import multifactorauth, accountlinking +from supertokens_python.recipe.multifactorauth.types import ( + FactorIds, + OverrideConfig, + MFARequirementList, +) +from supertokens_python.recipe.multifactorauth.interfaces import RecipeInterface +from supertokens_python.recipe.session.interfaces import SessionContainer +from supertokens_python.recipe.accountlinking.types import ( + AccountInfoWithRecipeIdAndUserId, + ShouldNotAutomaticallyLink, + ShouldAutomaticallyLink, +) +from supertokens_python.types import User +from typing import Dict, Any, Callable, Awaitable, List, Optional, Union +from supertokens_python.recipe.userroles.asyncio import get_roles_for_user + + +async def should_do_automatic_account_linking( + new_account_info: AccountInfoWithRecipeIdAndUserId, + user: Optional[User], + session: Optional[SessionContainer], + tenant_id: str, + user_context: Dict[str, Any], +) -> Union[ShouldNotAutomaticallyLink, ShouldAutomaticallyLink]: + if session is None: + # We do not want to do first factor account linking by default. + # To enable that, please see the automatic account linking docs + # in the recipe docs for your first factor. + return ShouldNotAutomaticallyLink() + + if user is None or session.get_user_id() == user.id: + # If it comes here, it means that a session exists, and we are trying to link the + # new_account_info to the session user, which means it's an MFA flow, so we enable + # linking here. + return ShouldAutomaticallyLink(should_require_verification=False) + + return ShouldNotAutomaticallyLink() + + +def override_functions(original_implementation: RecipeInterface): + async def get_mfa_requirements_for_auth( + tenant_id: str, + access_token_payload: Dict[str, Any], + completed_factors: Dict[str, int], + user: Callable[[], Awaitable[User]], + factors_set_up_for_user: Callable[[], Awaitable[List[str]]], + required_secondary_factors_for_user: Callable[[], Awaitable[List[str]]], + required_secondary_factors_for_tenant: Callable[[], Awaitable[List[str]]], + user_context: Dict[str, Any], + ) -> MFARequirementList: + # Get roles for the user + roles = await get_roles_for_user(tenant_id, (await user()).id) + + if "admin" in roles.roles: + # We only want OTP_EMAIL for admins + return [FactorIds.OTP_EMAIL] + else: + # No MFA for non-admin users + return [] + + original_implementation.get_mfa_requirements_for_auth = ( + get_mfa_requirements_for_auth + ) + return original_implementation + + +init( + app_info=InputAppInfo( + app_name="...", + api_domain="...", + website_domain="...", + ), + supertokens_config=SupertokensConfig( + connection_uri="...", + ), + framework="...", # type: ignore + recipe_list=[ + multifactorauth.init( + first_factors=[FactorIds.EMAILPASSWORD, FactorIds.THIRDPARTY], + override=OverrideConfig(functions=override_functions), + ), + accountlinking.init( + should_do_automatic_account_linking=should_do_automatic_account_linking + ), + ], +) +``` @@ -263,9 +350,113 @@ Coming soon. In the meantime, checkout the [legacy method](../legacy-method/how- -:::note -Coming soon. In the meantime, checkout the [legacy method](../legacy-method/how-it-works) for adding MFA to your app. -::: +```python +from supertokens_python import init, InputAppInfo, SupertokensConfig +from supertokens_python.recipe import multifactorauth, accountlinking +from supertokens_python.recipe.multifactorauth.types import ( + FactorIds, +) +from supertokens_python.recipe.multifactorauth.interfaces import RecipeInterface +from supertokens_python.recipe.session.interfaces import SessionContainer +from supertokens_python.recipe.accountlinking.types import ( + AccountInfoWithRecipeIdAndUserId, + ShouldNotAutomaticallyLink, + ShouldAutomaticallyLink, +) +from supertokens_python.types import User +from typing import Dict, Any, Optional, Union +from supertokens_python.recipe import passwordless +from supertokens_python.recipe.passwordless.interfaces import ( + RecipeInterface, + ConsumeCodeOkResult, +) +from supertokens_python.recipe.multifactorauth.asyncio import ( + add_to_required_secondary_factors_for_user, +) + + +async def should_do_automatic_account_linking( + new_account_info: AccountInfoWithRecipeIdAndUserId, + user: Optional[User], + session: Optional[SessionContainer], + tenant_id: str, + user_context: Dict[str, Any], +) -> Union[ShouldNotAutomaticallyLink, ShouldAutomaticallyLink]: + if session is None: + # We do not want to do first factor account linking by default. + # To enable that, please see the automatic account linking docs + # in the recipe docs for your first factor. + return ShouldNotAutomaticallyLink() + + if user is None or session.get_user_id() == user.id: + # If it comes here, it means that a session exists, and we are trying to link the + # new_account_info to the session user, which means it's an MFA flow, so we enable + # linking here. + return ShouldAutomaticallyLink(should_require_verification=False) + + return ShouldNotAutomaticallyLink() + + +def override_functions(original_implementation: RecipeInterface): + original_consume_code = original_implementation.consume_code + + async def consume_code( + pre_auth_session_id: str, + user_input_code: Union[str, None], + device_id: Union[str, None], + link_code: Union[str, None], + session: Optional[SessionContainer], + should_try_linking_with_session_user: Union[bool, None], + tenant_id: str, + user_context: Dict[str, Any], + ): + response = await original_consume_code( + pre_auth_session_id, + user_input_code, + device_id, + link_code, + session, + should_try_linking_with_session_user, + tenant_id, + user_context, + ) + + if isinstance(response, ConsumeCodeOkResult) and session is not None: + await add_to_required_secondary_factors_for_user( + session.get_user_id(), FactorIds.OTP_EMAIL + ) + + return response + + original_implementation.consume_code = consume_code + return original_implementation + + +init( + app_info=InputAppInfo( + app_name="...", + api_domain="...", + website_domain="...", + ), + supertokens_config=SupertokensConfig( + connection_uri="...", + ), + framework="...", # type: ignore + recipe_list=[ + passwordless.init( + contact_config=passwordless.ContactEmailOnlyConfig(), + flow_type="USER_INPUT_CODE", + override=passwordless.InputOverrideConfig(functions=override_functions), + ), + multifactorauth.init( + first_factors=[FactorIds.EMAILPASSWORD, FactorIds.THIRDPARTY], + ), + accountlinking.init( + should_do_automatic_account_linking=should_do_automatic_account_linking + ), + ], +) +``` From 5d8edf4702f11c385f4a6f938abfefe84054a9db Mon Sep 17 00:00:00 2001 From: rishabhpoddar Date: Sat, 19 Oct 2024 19:51:27 +0530 Subject: [PATCH 19/34] more docs fixes --- v2/mfa/email-sms-otp/otp-for-opt-in-users.mdx | 310 +++++++++++++++++- 1 file changed, 298 insertions(+), 12 deletions(-) diff --git a/v2/mfa/email-sms-otp/otp-for-opt-in-users.mdx b/v2/mfa/email-sms-otp/otp-for-opt-in-users.mdx index c662d0f37..2845cdb11 100644 --- a/v2/mfa/email-sms-otp/otp-for-opt-in-users.mdx +++ b/v2/mfa/email-sms-otp/otp-for-opt-in-users.mdx @@ -540,9 +540,32 @@ Coming soon. In the meantime, checkout the [legacy method](../legacy-method/how- -:::note -Coming soon. In the meantime, checkout the [legacy method](../legacy-method/how-it-works) for adding MFA to your app. -::: + + + +```python +from supertokens_python.recipe.multifactorauth.asyncio import get_required_secondary_factors_for_user +from supertokens_python.recipe.multifactorauth.types import FactorIds + +async def is_otp_email_factor_enabled_for_user(user_id: str) -> bool: + factors = await get_required_secondary_factors_for_user(user_id, {}) + return FactorIds.OTP_EMAIL in factors +``` + + + + +```python +from supertokens_python.recipe.multifactorauth.syncio import get_required_secondary_factors_for_user +from supertokens_python.recipe.multifactorauth.types import FactorIds + +def is_otp_email_factor_enabled_for_user(user_id: str) -> bool: + factors = get_required_secondary_factors_for_user(user_id, {}) + return FactorIds.OTP_EMAIL in factors +``` + + + @@ -574,9 +597,44 @@ Coming soon. In the meantime, checkout the [legacy method](../legacy-method/how- -:::note -Coming soon. In the meantime, checkout the [legacy method](../legacy-method/how-it-works) for adding MFA to your app. -::: + + + +```python +from supertokens_python.recipe.multifactorauth.asyncio import ( + add_to_required_secondary_factors_for_user, + remove_from_required_secondary_factors_for_user, +) +from supertokens_python.recipe.multifactorauth.types import FactorIds + +async def enable_mfa_for_user(user_id: str) -> None: + await add_to_required_secondary_factors_for_user(user_id, FactorIds.OTP_EMAIL) + + +async def disable_mfa_for_user(user_id: str) -> None: + await remove_from_required_secondary_factors_for_user(user_id, FactorIds.OTP_EMAIL) +``` + + + + +```python +from supertokens_python.recipe.multifactorauth.syncio import ( + add_to_required_secondary_factors_for_user, + remove_from_required_secondary_factors_for_user, +) +from supertokens_python.recipe.multifactorauth.types import FactorIds + +def enable_mfa_for_user(user_id: str) -> None: + add_to_required_secondary_factors_for_user(user_id, FactorIds.OTP_EMAIL) + + +def disable_mfa_for_user(user_id: str) -> None: + remove_from_required_secondary_factors_for_user(user_id, FactorIds.OTP_EMAIL) +``` + + + @@ -696,9 +754,103 @@ Coming soon. In the meantime, checkout the [legacy method](../legacy-method/how- -:::note -Coming soon. In the meantime, checkout the [legacy method](../legacy-method/how-it-works) for adding MFA to your app. -::: +```python +from supertokens_python import init, InputAppInfo, SupertokensConfig +from supertokens_python.recipe import multifactorauth, accountlinking, passwordless +from supertokens_python.recipe.multifactorauth.types import ( + FactorIds, + OverrideConfig, + MFARequirementList, +) +from supertokens_python.recipe.passwordless import ContactEmailOnlyConfig +from supertokens_python.recipe.multifactorauth.interfaces import RecipeInterface +from supertokens_python.recipe.session.interfaces import SessionContainer +from supertokens_python.recipe.accountlinking.types import ( + AccountInfoWithRecipeIdAndUserId, + ShouldNotAutomaticallyLink, + ShouldAutomaticallyLink, +) +from supertokens_python.types import User +from typing import Dict, Any, Callable, Awaitable, List, Optional, Union +from supertokens_python.recipe.userroles.asyncio import get_roles_for_user + + +async def should_do_automatic_account_linking( + new_account_info: AccountInfoWithRecipeIdAndUserId, + user: Optional[User], + session: Optional[SessionContainer], + tenant_id: str, + user_context: Dict[str, Any], +) -> Union[ShouldNotAutomaticallyLink, ShouldAutomaticallyLink]: + if session is None: + # We do not want to do first factor account linking by default. + # To enable that, please see the automatic account linking docs + # in the recipe docs for your first factor. + return ShouldNotAutomaticallyLink() + + if user is None or session.get_user_id() == user.id: + # If it comes here, it means that a session exists, and we are trying to link the + # new_account_info to the session user, which means it's an MFA flow, so we enable + # linking here. + return ShouldAutomaticallyLink(should_require_verification=False) + + return ShouldNotAutomaticallyLink() + + +def override_functions(original_implementation: RecipeInterface): + async def get_mfa_requirements_for_auth( + tenant_id: str, + access_token_payload: Dict[str, Any], + completed_factors: Dict[str, int], + user: Callable[[], Awaitable[User]], + factors_set_up_for_user: Callable[[], Awaitable[List[str]]], + required_secondary_factors_for_user: Callable[[], Awaitable[List[str]]], + required_secondary_factors_for_tenant: Callable[[], Awaitable[List[str]]], + user_context: Dict[str, Any], + ) -> MFARequirementList: + # Get roles for the user + roles = await get_roles_for_user(tenant_id, (await user()).id) + + if ( + "admin" in roles.roles + and FactorIds.OTP_EMAIL in await required_secondary_factors_for_tenant() + ): + # We only want OTP_EMAIL for admins + return [FactorIds.OTP_EMAIL] + else: + # No MFA for non-admin users + return [] + + original_implementation.get_mfa_requirements_for_auth = ( + get_mfa_requirements_for_auth + ) + return original_implementation + + +init( + app_info=InputAppInfo( + app_name="...", + api_domain="...", + website_domain="...", + ), + supertokens_config=SupertokensConfig( + connection_uri="...", + ), + framework="...", # type: ignore + recipe_list=[ + passwordless.init( + contact_config=ContactEmailOnlyConfig(), flow_type="USER_INPUT_CODE" + ), + multifactorauth.init( + first_factors=[FactorIds.EMAILPASSWORD, FactorIds.THIRDPARTY], + override=OverrideConfig(functions=override_functions), + ), + accountlinking.init( + should_do_automatic_account_linking=should_do_automatic_account_linking + ), + ], +) +``` @@ -825,9 +977,143 @@ Coming soon. In the meantime, checkout the [legacy method](../legacy-method/how- -:::note -Coming soon. In the meantime, checkout the [legacy method](../legacy-method/how-it-works) for adding MFA to your app. -::: +```python +from supertokens_python import init, InputAppInfo, SupertokensConfig +from supertokens_python.recipe import multifactorauth, accountlinking, passwordless +from supertokens_python.recipe.multifactorauth.types import ( + FactorIds, + OverrideConfig, + MFARequirementList, +) +from supertokens_python.recipe.multifactorauth.asyncio import ( + add_to_required_secondary_factors_for_user, +) +from supertokens_python.recipe.passwordless import ContactEmailOnlyConfig +from supertokens_python.recipe.multifactorauth.interfaces import RecipeInterface +from supertokens_python.recipe.session.interfaces import SessionContainer +from supertokens_python.recipe.accountlinking.types import ( + AccountInfoWithRecipeIdAndUserId, + ShouldNotAutomaticallyLink, + ShouldAutomaticallyLink, +) +from supertokens_python.types import User +from typing import Dict, Any, Callable, Awaitable, List, Optional, Union +from supertokens_python.recipe.passwordless.interfaces import ( + APIInterface, + APIOptions, + ConsumeCodePostOkResult, +) + + +async def should_do_automatic_account_linking( + new_account_info: AccountInfoWithRecipeIdAndUserId, + user: Optional[User], + session: Optional[SessionContainer], + tenant_id: str, + user_context: Dict[str, Any], +) -> Union[ShouldNotAutomaticallyLink, ShouldAutomaticallyLink]: + if session is None: + # We do not want to do first factor account linking by default. + # To enable that, please see the automatic account linking docs + # in the recipe docs for your first factor. + return ShouldNotAutomaticallyLink() + + if user is None or session.get_user_id() == user.id: + # If it comes here, it means that a session exists, and we are trying to link the + # new_account_info to the session user, which means it's an MFA flow, so we enable + # linking here. + return ShouldAutomaticallyLink(should_require_verification=False) + + return ShouldNotAutomaticallyLink() + + +def override_functions(original_implementation: RecipeInterface): + async def get_mfa_requirements_for_auth( + tenant_id: str, + access_token_payload: Dict[str, Any], + completed_factors: Dict[str, int], + user: Callable[[], Awaitable[User]], + factors_set_up_for_user: Callable[[], Awaitable[List[str]]], + required_secondary_factors_for_user: Callable[[], Awaitable[List[str]]], + required_secondary_factors_for_tenant: Callable[[], Awaitable[List[str]]], + user_context: Dict[str, Any], + ) -> MFARequirementList: + if FactorIds.OTP_EMAIL in await required_secondary_factors_for_user(): + if FactorIds.OTP_EMAIL in await required_secondary_factors_for_tenant(): + return [FactorIds.OTP_EMAIL] + # no otp-email required for input.user, with the input.tenant. + return [] + + original_implementation.get_mfa_requirements_for_auth = ( + get_mfa_requirements_for_auth + ) + return original_implementation + + +def passwordless_override(original_implementation: APIInterface): + + original_consume_code_post = original_implementation.consume_code_post + + async def consume_code_post( + pre_auth_session_id: str, + user_input_code: Union[str, None], + device_id: Union[str, None], + link_code: Union[str, None], + session: Optional[SessionContainer], + should_try_linking_with_session_user: Union[bool, None], + tenant_id: str, + api_options: APIOptions, + user_context: Dict[str, Any], + ): + response = await original_consume_code_post( + pre_auth_session_id, + user_input_code, + device_id, + link_code, + session, + should_try_linking_with_session_user, + tenant_id, + api_options, + user_context, + ) + + if isinstance(response, ConsumeCodePostOkResult) and session is not None: + await add_to_required_secondary_factors_for_user( + session.get_user_id(), FactorIds.OTP_EMAIL + ) + return response + + original_implementation.consume_code_post = consume_code_post + + return original_implementation + + +init( + app_info=InputAppInfo( + app_name="...", + api_domain="...", + website_domain="...", + ), + supertokens_config=SupertokensConfig( + connection_uri="...", + ), + framework="...", # type: ignore + recipe_list=[ + passwordless.init( + contact_config=ContactEmailOnlyConfig(), + flow_type="USER_INPUT_CODE", + override=passwordless.InputOverrideConfig(apis=passwordless_override), + ), + multifactorauth.init( + first_factors=[FactorIds.EMAILPASSWORD, FactorIds.THIRDPARTY], + override=OverrideConfig(functions=override_functions), + ), + accountlinking.init( + should_do_automatic_account_linking=should_do_automatic_account_linking + ), + ], +) +``` From e80291d924a9dfbfe4646d5c88237b2993ee5048 Mon Sep 17 00:00:00 2001 From: rishabhpoddar Date: Sat, 19 Oct 2024 20:33:39 +0530 Subject: [PATCH 20/34] more changes --- v2/mfa/totp/totp-for-all-users.mdx | 134 +++++++++- v2/mfa/totp/totp-for-opt-in-users.mdx | 354 ++++++++++++++++++++++++-- 2 files changed, 461 insertions(+), 27 deletions(-) diff --git a/v2/mfa/totp/totp-for-all-users.mdx b/v2/mfa/totp/totp-for-all-users.mdx index 5575ee93f..23348084d 100644 --- a/v2/mfa/totp/totp-for-all-users.mdx +++ b/v2/mfa/totp/totp-for-all-users.mdx @@ -96,9 +96,58 @@ Coming soon. -:::note -Coming soon. -::: +```python +from supertokens_python import init, InputAppInfo, SupertokensConfig +from supertokens_python.recipe import multifactorauth, totp +from supertokens_python.recipe.multifactorauth.types import ( + FactorIds, + OverrideConfig, + MFARequirementList, +) +from supertokens_python.recipe.multifactorauth.interfaces import RecipeInterface +from supertokens_python.types import User +from typing import Dict, Any, Callable, Awaitable, List + + +def override_functions(original_implementation: RecipeInterface): + async def get_mfa_requirements_for_auth( + tenant_id: str, + access_token_payload: Dict[str, Any], + completed_factors: Dict[str, int], + user: Callable[[], Awaitable[User]], + factors_set_up_for_user: Callable[[], Awaitable[List[str]]], + required_secondary_factors_for_user: Callable[[], Awaitable[List[str]]], + required_secondary_factors_for_tenant: Callable[[], Awaitable[List[str]]], + user_context: Dict[str, Any], + ) -> MFARequirementList: + # Get roles for the user + return [FactorIds.TOTP] + + original_implementation.get_mfa_requirements_for_auth = ( + get_mfa_requirements_for_auth + ) + return original_implementation + + +init( + app_info=InputAppInfo( + app_name="...", + api_domain="...", + website_domain="...", + ), + supertokens_config=SupertokensConfig( + connection_uri="...", + ), + framework="...", # type: ignore + recipe_list=[ + totp.init(), + multifactorauth.init( + first_factors=[FactorIds.EMAILPASSWORD, FactorIds.THIRDPARTY], + override=OverrideConfig(functions=override_functions), + ), + ], +) +``` @@ -870,9 +919,27 @@ Coming soon. -:::note -Coming soon. -::: +```python +from supertokens_python import init, InputAppInfo, SupertokensConfig +from supertokens_python.recipe import multifactorauth, totp + + +init( + app_info=InputAppInfo( + app_name="...", + api_domain="...", + website_domain="...", + ), + supertokens_config=SupertokensConfig( + connection_uri="...", + ), + framework="...", # type: ignore + recipe_list=[ + totp.init(), + multifactorauth.init(), + ], +) +``` @@ -920,9 +987,58 @@ Coming soon. -:::note -Coming soon. -::: + + + +```python +from supertokens_python.recipe.multitenancy.asyncio import create_or_update_tenant +from supertokens_python.recipe.multitenancy.interfaces import TenantConfigCreateOrUpdate +from supertokens_python.recipe.multifactorauth.types import FactorIds + + +async def create_new_tenant(): + resp = await create_or_update_tenant( + "customer1", TenantConfigCreateOrUpdate( + first_factors=[FactorIds.EMAILPASSWORD], + required_secondary_factors=[FactorIds.TOTP], + ) + ) + + if resp.created_new: + # Tenant created successfully + pass + else: + # Existing tenant's config was modified + pass +``` + + + + +```python +from supertokens_python.recipe.multitenancy.syncio import create_or_update_tenant +from supertokens_python.recipe.multitenancy.interfaces import TenantConfigCreateOrUpdate +from supertokens_python.recipe.multifactorauth.types import FactorIds + + +def create_new_tenant(): + resp = create_or_update_tenant( + "customer1", TenantConfigCreateOrUpdate( + first_factors=[FactorIds.EMAILPASSWORD], + required_secondary_factors=[FactorIds.TOTP], + ) + ) + + if resp.created_new: + # Tenant created successfully + pass + else: + # Existing tenant's config was modified + pass +``` + + + diff --git a/v2/mfa/totp/totp-for-opt-in-users.mdx b/v2/mfa/totp/totp-for-opt-in-users.mdx index 80c7db6b0..af25a711f 100644 --- a/v2/mfa/totp/totp-for-opt-in-users.mdx +++ b/v2/mfa/totp/totp-for-opt-in-users.mdx @@ -114,9 +114,66 @@ Coming soon. -:::note -Coming soon. -::: +```python +from supertokens_python import init, InputAppInfo, SupertokensConfig +from supertokens_python.recipe import multifactorauth, totp +from supertokens_python.recipe.multifactorauth.types import ( + FactorIds, + OverrideConfig, + MFARequirementList, +) +from supertokens_python.recipe.multifactorauth.interfaces import RecipeInterface +from supertokens_python.types import User +from typing import Dict, Any, Callable, Awaitable, List +from supertokens_python.recipe.userroles.asyncio import get_roles_for_user + + +def override_functions(original_implementation: RecipeInterface): + async def get_mfa_requirements_for_auth( + tenant_id: str, + access_token_payload: Dict[str, Any], + completed_factors: Dict[str, int], + user: Callable[[], Awaitable[User]], + factors_set_up_for_user: Callable[[], Awaitable[List[str]]], + required_secondary_factors_for_user: Callable[[], Awaitable[List[str]]], + required_secondary_factors_for_tenant: Callable[[], Awaitable[List[str]]], + user_context: Dict[str, Any], + ) -> MFARequirementList: + # Get roles for the user + roles = await get_roles_for_user(tenant_id, (await user()).id) + + if "admin" in roles.roles: + # We only want OTP_EMAIL for admins + return [FactorIds.TOTP] + else: + # No MFA for non-admin users + return [] + + original_implementation.get_mfa_requirements_for_auth = ( + get_mfa_requirements_for_auth + ) + return original_implementation + + +init( + app_info=InputAppInfo( + app_name="...", + api_domain="...", + website_domain="...", + ), + supertokens_config=SupertokensConfig( + connection_uri="...", + ), + framework="...", # type: ignore + recipe_list=[ + totp.init(), + multifactorauth.init( + first_factors=[FactorIds.EMAILPASSWORD, FactorIds.THIRDPARTY], + override=OverrideConfig(functions=override_functions), + ), + ], +) +``` @@ -200,9 +257,67 @@ Coming soon. -:::note -Coming soon. -::: +```python +from supertokens_python import init, InputAppInfo, SupertokensConfig +from supertokens_python.recipe import multifactorauth, totp +from supertokens_python.recipe.multifactorauth.types import ( + FactorIds, + OverrideConfig, +) +from typing import Dict, Any +from supertokens_python.recipe.totp.types import ( + TOTPConfig, + OverrideConfig, + VerifyDeviceOkResult, +) +from supertokens_python.recipe.totp.interfaces import APIInterface, APIOptions +from supertokens_python.recipe.session.interfaces import SessionContainer +from supertokens_python.recipe.multifactorauth.asyncio import ( + add_to_required_secondary_factors_for_user, +) + + +def totp_override(original_implementation: APIInterface): + original_verify_device_post = original_implementation.verify_device_post + + async def verify_device_post( + device_name: str, + totp: str, + options: APIOptions, + session: SessionContainer, + user_context: Dict[str, Any], + ): + response = await original_verify_device_post( + device_name, totp, options, session, user_context + ) + if isinstance(response, VerifyDeviceOkResult): + await add_to_required_secondary_factors_for_user( + session.get_user_id(), FactorIds.TOTP + ) + return response + + original_implementation.verify_device_post = verify_device_post + return original_implementation + + +init( + app_info=InputAppInfo( + app_name="...", + api_domain="...", + website_domain="...", + ), + supertokens_config=SupertokensConfig( + connection_uri="...", + ), + framework="...", # type: ignore + recipe_list=[ + totp.init(TOTPConfig(override=OverrideConfig(apis=totp_override))), + multifactorauth.init( + first_factors=[FactorIds.EMAILPASSWORD, FactorIds.THIRDPARTY] + ), + ], +) +``` @@ -281,9 +396,32 @@ Coming soon. In the meantime, checkout the [legacy method](../legacy-method/how- -:::note -Coming soon. In the meantime, checkout the [legacy method](../legacy-method/how-it-works) for adding MFA to your app. -::: + + + +```python +from supertokens_python.recipe.multifactorauth.asyncio import get_required_secondary_factors_for_user +from supertokens_python.recipe.multifactorauth.types import FactorIds + +async def is_totp_factor_enabled_for_user(user_id: str) -> bool: + factors = await get_required_secondary_factors_for_user(user_id, {}) + return FactorIds.TOTP in factors +``` + + + + +```python +from supertokens_python.recipe.multifactorauth.syncio import get_required_secondary_factors_for_user +from supertokens_python.recipe.multifactorauth.types import FactorIds + +def is_totp_factor_enabled_for_user(user_id: str) -> bool: + factors = get_required_secondary_factors_for_user(user_id, {}) + return FactorIds.TOTP in factors +``` + + + @@ -315,9 +453,44 @@ Coming soon. In the meantime, checkout the [legacy method](../legacy-method/how- -:::note -Coming soon. In the meantime, checkout the [legacy method](../legacy-method/how-it-works) for adding MFA to your app. -::: + + + +```python +from supertokens_python.recipe.multifactorauth.asyncio import ( + add_to_required_secondary_factors_for_user, + remove_from_required_secondary_factors_for_user, +) +from supertokens_python.recipe.multifactorauth.types import FactorIds + +async def enable_mfa_for_user(user_id: str) -> None: + await add_to_required_secondary_factors_for_user(user_id, FactorIds.TOTP) + + +async def disable_mfa_for_user(user_id: str) -> None: + await remove_from_required_secondary_factors_for_user(user_id, FactorIds.TOTP) +``` + + + + +```python +from supertokens_python.recipe.multifactorauth.syncio import ( + add_to_required_secondary_factors_for_user, + remove_from_required_secondary_factors_for_user, +) +from supertokens_python.recipe.multifactorauth.types import FactorIds + +def enable_mfa_for_user(user_id: str) -> None: + add_to_required_secondary_factors_for_user(user_id, FactorIds.OTP_EMAIL) + + +def disable_mfa_for_user(user_id: str) -> None: + remove_from_required_secondary_factors_for_user(user_id, FactorIds.OTP_EMAIL) +``` + + + @@ -665,9 +838,69 @@ Coming soon. -:::note -Coming soon. -::: +```python +from supertokens_python import init, InputAppInfo, SupertokensConfig +from supertokens_python.recipe import multifactorauth, totp +from supertokens_python.recipe.multifactorauth.types import ( + FactorIds, + OverrideConfig, + MFARequirementList, +) +from supertokens_python.recipe.multifactorauth.interfaces import RecipeInterface +from supertokens_python.types import User +from typing import Dict, Any, Callable, Awaitable, List +from supertokens_python.recipe.userroles.asyncio import get_roles_for_user + + +def override_functions(original_implementation: RecipeInterface): + async def get_mfa_requirements_for_auth( + tenant_id: str, + access_token_payload: Dict[str, Any], + completed_factors: Dict[str, int], + user: Callable[[], Awaitable[User]], + factors_set_up_for_user: Callable[[], Awaitable[List[str]]], + required_secondary_factors_for_user: Callable[[], Awaitable[List[str]]], + required_secondary_factors_for_tenant: Callable[[], Awaitable[List[str]]], + user_context: Dict[str, Any], + ) -> MFARequirementList: + # Get roles for the user + roles = await get_roles_for_user(tenant_id, (await user()).id) + + if ( + "admin" in roles.roles + and FactorIds.TOTP in await required_secondary_factors_for_tenant() + ): + # We only want OTP_EMAIL for admins + return [FactorIds.TOTP] + else: + # No MFA for non-admin users + return [] + + original_implementation.get_mfa_requirements_for_auth = ( + get_mfa_requirements_for_auth + ) + return original_implementation + + +init( + app_info=InputAppInfo( + app_name="...", + api_domain="...", + website_domain="...", + ), + supertokens_config=SupertokensConfig( + connection_uri="...", + ), + framework="...", # type: ignore + recipe_list=[ + multifactorauth.init( + first_factors=[FactorIds.EMAILPASSWORD, FactorIds.THIRDPARTY], + override=OverrideConfig(functions=override_functions), + ), + totp.init(), + ], +) +``` @@ -765,9 +998,94 @@ Coming soon. -:::note -Coming soon. -::: +```python +from supertokens_python import init, InputAppInfo, SupertokensConfig +from supertokens_python.recipe import multifactorauth, totp +from supertokens_python.recipe.multifactorauth.types import ( + FactorIds, + OverrideConfig as MFAOverrideConfig, + MFARequirementList, +) +from supertokens_python.recipe.multifactorauth.asyncio import ( + add_to_required_secondary_factors_for_user, +) +from supertokens_python.recipe.multifactorauth.interfaces import RecipeInterface +from supertokens_python.recipe.session.interfaces import SessionContainer +from supertokens_python.types import User +from typing import Dict, Any, Callable, Awaitable, List +from supertokens_python.recipe.totp.interfaces import APIInterface, APIOptions +from supertokens_python.recipe.totp.types import ( + TOTPConfig, + OverrideConfig, + VerifyDeviceOkResult, +) + + +def override_functions(original_implementation: RecipeInterface): + async def get_mfa_requirements_for_auth( + tenant_id: str, + access_token_payload: Dict[str, Any], + completed_factors: Dict[str, int], + user: Callable[[], Awaitable[User]], + factors_set_up_for_user: Callable[[], Awaitable[List[str]]], + required_secondary_factors_for_user: Callable[[], Awaitable[List[str]]], + required_secondary_factors_for_tenant: Callable[[], Awaitable[List[str]]], + user_context: Dict[str, Any], + ) -> MFARequirementList: + if FactorIds.TOTP in await required_secondary_factors_for_user(): + if FactorIds.TOTP in await required_secondary_factors_for_tenant(): + return [FactorIds.TOTP] + # no otp-email required for input.user, with the input.tenant. + return [] + + original_implementation.get_mfa_requirements_for_auth = ( + get_mfa_requirements_for_auth + ) + return original_implementation + + +def totp_override(original_implementation: APIInterface): + original_verify_device_post = original_implementation.verify_device_post + + async def verify_device_post( + device_name: str, + totp: str, + options: APIOptions, + session: SessionContainer, + user_context: Dict[str, Any], + ): + response = await original_verify_device_post( + device_name, totp, options, session, user_context + ) + if isinstance(response, VerifyDeviceOkResult): + await add_to_required_secondary_factors_for_user( + session.get_user_id(), FactorIds.TOTP + ) + return response + + original_implementation.verify_device_post = verify_device_post + return original_implementation + + +init( + app_info=InputAppInfo( + app_name="...", + api_domain="...", + website_domain="...", + ), + supertokens_config=SupertokensConfig( + connection_uri="...", + ), + framework="...", # type: ignore + recipe_list=[ + totp.init(TOTPConfig(override=OverrideConfig(apis=totp_override))), + multifactorauth.init( + first_factors=[FactorIds.EMAILPASSWORD, FactorIds.THIRDPARTY], + override=MFAOverrideConfig(functions=override_functions), + ), + ], +) +``` From 22bb5120b9066530c7d72ed23c809034703b75d9 Mon Sep 17 00:00:00 2001 From: rishabhpoddar Date: Sun, 20 Oct 2024 12:05:02 +0530 Subject: [PATCH 21/34] more changes --- v2/multitenancy/list-tenants-and-apps.mdx | 56 ++++++++--------------- v2/multitenancy/new-tenant.mdx | 29 ++++++------ v2/multitenancy/users-and-tenants.mdx | 46 ++++++++++++------- 3 files changed, 61 insertions(+), 70 deletions(-) diff --git a/v2/multitenancy/list-tenants-and-apps.mdx b/v2/multitenancy/list-tenants-and-apps.mdx index b3847cd73..153ae19e0 100644 --- a/v2/multitenancy/list-tenants-and-apps.mdx +++ b/v2/multitenancy/list-tenants-and-apps.mdx @@ -111,25 +111,15 @@ async def some_func(): return for tenant in response.tenants: - current_config = tenant.core_config + core_config = tenant.core_config - print(current_config) + first_factors = tenant.first_factors - is_email_password_enabled = tenant.emailpassword.enabled - is_third_party_enabled = tenant.third_party.enabled - is_passwordless_enabled = tenant.passwordless.enabled + configured_third_party_providers = tenant.third_party_providers - configured_providers = tenant.third_party.providers - - if is_email_password_enabled: - print("Email password is enabled") - - if is_third_party_enabled: - print(configured_providers) - print("Third party is enabled") - - if is_passwordless_enabled: - print("Passwordless is enabled") + print(core_config) + print(f"First factors: {first_factors}") + print(f"Configured third party providers: {configured_third_party_providers}") ``` @@ -139,31 +129,23 @@ async def some_func(): ```python from supertokens_python.recipe.multitenancy.syncio import list_all_tenants -response = list_all_tenants() - -if response.status != "OK": - print("Handle error") - -for tenant in response.tenants: - current_config = tenant.core_config - - print(current_config) +def some_func(): + response = list_all_tenants() - is_email_password_enabled = tenant.emailpassword.enabled - is_third_party_enabled = tenant.third_party.enabled - is_passwordless_enabled = tenant.passwordless.enabled - - configured_providers = tenant.third_party.providers + if response.status != "OK": + print("Handle error") + return + + for tenant in response.tenants: + core_config = tenant.core_config - if is_email_password_enabled: - print("Email password is enabled") + first_factors = tenant.first_factors - if is_third_party_enabled: - print(configured_providers) - print("Third party is enabled") + configured_third_party_providers = tenant.third_party_providers - if is_passwordless_enabled: - print("Passwordless is enabled") + print(core_config) + print(f"First factors: {first_factors}") + print(f"Configured third party providers: {configured_third_party_providers}") ``` diff --git a/v2/multitenancy/new-tenant.mdx b/v2/multitenancy/new-tenant.mdx index 6fcc24acc..bad22119b 100644 --- a/v2/multitenancy/new-tenant.mdx +++ b/v2/multitenancy/new-tenant.mdx @@ -91,13 +91,11 @@ func main() { ```python from supertokens_python.recipe.multitenancy.asyncio import create_or_update_tenant -from supertokens_python.recipe.multitenancy.interfaces import TenantConfig +from supertokens_python.recipe.multitenancy.interfaces import TenantConfigCreateOrUpdate async def some_func(): - response = await create_or_update_tenant("customer1", TenantConfig( - email_password_enabled=True, - third_party_enabled=True, - passwordless_enabled=True, + response = await create_or_update_tenant("customer1", TenantConfigCreateOrUpdate( + first_factors=["emailpassword", "thirdparty", "otp-email", "otp-phone", "link-phone", "link-email"] )) if response.status != "OK": @@ -114,20 +112,19 @@ async def some_func(): ```python from supertokens_python.recipe.multitenancy.syncio import create_or_update_tenant -from supertokens_python.recipe.multitenancy.interfaces import TenantConfig +from supertokens_python.recipe.multitenancy.interfaces import TenantConfigCreateOrUpdate -response = create_or_update_tenant("customer1", TenantConfig( - email_password_enabled=True, - third_party_enabled=True, - passwordless_enabled=True, +def some_func(): + response = create_or_update_tenant("customer1", TenantConfigCreateOrUpdate( + first_factors=["emailpassword", "thirdparty", "otp-email", "otp-phone", "link-phone", "link-email"] )) -if response.status != "OK": - print("Handle error") -elif response.created_new: - print("New tenant was created") -else: - print("Existing tenant's config was updated") + if response.status != "OK": + print("Handle error") + elif response.created_new: + print("New tenant was created") + else: + print("Existing tenant's config was updated") ``` diff --git a/v2/multitenancy/users-and-tenants.mdx b/v2/multitenancy/users-and-tenants.mdx index 9f50f412c..36310370b 100644 --- a/v2/multitenancy/users-and-tenants.mdx +++ b/v2/multitenancy/users-and-tenants.mdx @@ -91,12 +91,13 @@ func main() { ```python from supertokens_python.recipe.multitenancy.asyncio import associate_user_to_tenant -from supertokens_python.recipe.multitenancy.interfaces import AssociateUserToTenantUnknownUserIdError, AssociateUserToTenantEmailAlreadyExistsError, AssociateUserToTenantPhoneNumberAlreadyExistsError +from supertokens_python.recipe.multitenancy.interfaces import AssociateUserToTenantUnknownUserIdError, AssociateUserToTenantEmailAlreadyExistsError, AssociateUserToTenantPhoneNumberAlreadyExistsError, AssociateUserToTenantNotAllowedError, AssociateUserToTenantOkResult +from supertokens_python.types import RecipeUserId async def some_func(): - response = await associate_user_to_tenant("customer1", "user1") + response = await associate_user_to_tenant("customer1", RecipeUserId("user1")) - if response.status == "OK": + if isinstance(response, AssociateUserToTenantOkResult): print("User is now associated with tenant") elif isinstance(response, AssociateUserToTenantUnknownUserIdError): print("The provided user ID was not one that signed up using one of SuperTokens' auth recipes.") @@ -104,6 +105,11 @@ async def some_func(): print("This means that the input user is one of passwordless or email password logins, and the new tenant already has a user with the same email for that login method.") elif isinstance(response, AssociateUserToTenantPhoneNumberAlreadyExistsError): print("This means that the input user is a passwordless user and the new tenant already has a user with the same phone number, for passwordless login.") + elif isinstance(response, AssociateUserToTenantNotAllowedError): + # This can happen if using account linking along with multi tenancy. One example of when this + # happens if if the target tenant has a primary user with the same email / phone numbers + # as the current user. + print("The new tenant does not allow associating users to it.") else: print("status is ThirdPartyUserAlreadyExistsError") print("This means that the input user had already previously signed in with the same third party provider (e.g. Google) for the new tenant.") @@ -115,11 +121,12 @@ async def some_func(): ```python from supertokens_python.recipe.multitenancy.syncio import associate_user_to_tenant -from supertokens_python.recipe.multitenancy.interfaces import AssociateUserToTenantUnknownUserIdError, AssociateUserToTenantEmailAlreadyExistsError, AssociateUserToTenantPhoneNumberAlreadyExistsError +from supertokens_python.recipe.multitenancy.interfaces import AssociateUserToTenantUnknownUserIdError, AssociateUserToTenantEmailAlreadyExistsError, AssociateUserToTenantPhoneNumberAlreadyExistsError, AssociateUserToTenantNotAllowedError, AssociateUserToTenantOkResult +from supertokens_python.types import RecipeUserId -response = associate_user_to_tenant("customer1", "user1") +response = associate_user_to_tenant("customer1", RecipeUserId("user1")) -if response.status == "OK": +if isinstance(response, AssociateUserToTenantOkResult): print("User is now associated with tenant") elif isinstance(response, AssociateUserToTenantUnknownUserIdError): print("The provided user ID was not one that signed up using one of SuperTokens' auth recipes.") @@ -127,6 +134,11 @@ elif isinstance(response, AssociateUserToTenantEmailAlreadyExistsError): print("This means that the input user is one of passwordless or email password logins, and the new tenant already has a user with the same email for that login method.") elif isinstance(response, AssociateUserToTenantPhoneNumberAlreadyExistsError): print("This means that the input user is a passwordless user and the new tenant already has a user with the same phone number, for passwordless login.") +elif isinstance(response, AssociateUserToTenantNotAllowedError): + # This can happen if using account linking along with multi tenancy. One example of when this + # happens if if the target tenant has a primary user with the same email / phone numbers + # as the current user. + print("The new tenant does not allow associating users to it.") else: print("status is ThirdPartyUserAlreadyExistsError") print("This means that the input user had already previously signed in with the same third party provider (e.g. Google) for the new tenant.") @@ -220,10 +232,11 @@ func main() { ```python -from supertokens_python.recipe.multitenancy.asyncio import dissociate_user_from_tenant +from supertokens_python.recipe.multitenancy.asyncio import disassociate_user_from_tenant +from supertokens_python.types import RecipeUserId async def some_func(): - response = await dissociate_user_from_tenant("customer1", "user1") + response = await disassociate_user_from_tenant("customer1", RecipeUserId("user1")) if response.was_associated: print("User was removed from tenant") @@ -236,17 +249,16 @@ async def some_func(): ```python -from supertokens_python.recipe.multitenancy.syncio import dissociate_user_from_tenant +from supertokens_python.recipe.multitenancy.syncio import disassociate_user_from_tenant +from supertokens_python.types import RecipeUserId -response = dissociate_user_from_tenant("customer1", "user1") +def some_func(): + response = disassociate_user_from_tenant("customer1", RecipeUserId("user1")) -if response.status != "OK": - print("Handle error") - -if response.was_associated: - print("User was removed from tenant") -else: - print("User was never a part of the tenant anyway") + if response.was_associated: + print("User was removed from tenant") + else: + print("User was never a part of the tenant anyway") ``` From 24341a39d3412a93b4a6014586fd1961825a6f53 Mon Sep 17 00:00:00 2001 From: rishabhpoddar Date: Sun, 20 Oct 2024 13:57:32 +0530 Subject: [PATCH 22/34] fixes more docs --- .../custom-response/general-error.mdx | 51 ++++-- .../apis-override/usage.mdx | 52 +++--- .../backend-functions-override/usage.mdx | 58 ++++--- .../advanced-customizations/user-context.mdx | 154 +++++++++++++----- 4 files changed, 217 insertions(+), 98 deletions(-) diff --git a/v2/passwordless/advanced-customizations/apis-override/custom-response/general-error.mdx b/v2/passwordless/advanced-customizations/apis-override/custom-response/general-error.mdx index 9b6a0db25..dce6c4fdf 100644 --- a/v2/passwordless/advanced-customizations/apis-override/custom-response/general-error.mdx +++ b/v2/passwordless/advanced-customizations/apis-override/custom-response/general-error.mdx @@ -118,10 +118,13 @@ func emailNotAllowed(email string) bool { ```python from supertokens_python.recipe import passwordless -from supertokens_python.recipe.passwordless.interfaces import APIOptions, ConsumeCodePostOkResult, ConsumeCodePostRestartFlowError, ConsumeCodePostIncorrectUserInputCodeError, ConsumeCodePostExpiredUserInputCodeError -from typing import Dict, Any, Union +from supertokens_python.recipe.passwordless.interfaces import APIOptions +from supertokens_python.recipe.session.interfaces import SessionContainer +from typing import Dict, Any, Union, Optional from supertokens_python.recipe.passwordless.interfaces import APIInterface -from supertokens_python.recipe.passwordless.asyncio import list_codes_by_pre_auth_session_id +from supertokens_python.recipe.passwordless.asyncio import ( + list_codes_by_pre_auth_session_id, +) from supertokens_python.types import GeneralErrorResponse # typecheck-only, removed from output @@ -131,19 +134,42 @@ def override_passwordless_apis(original_implementation: APIInterface): original_consume_code_post = original_implementation.consume_code_post - async def consume_code_post(pre_auth_session_id: str, user_input_code: Union[str, None], device_id: Union[str, None], link_code: Union[str, None], tenant_id: str, api_options: APIOptions, - user_context: Dict[str, Any]) -> Union[ConsumeCodePostOkResult, ConsumeCodePostRestartFlowError, GeneralErrorResponse, ConsumeCodePostIncorrectUserInputCodeError, ConsumeCodePostExpiredUserInputCodeError]: - code_info = await list_codes_by_pre_auth_session_id(tenant_id, pre_auth_session_id) + async def consume_code_post( + pre_auth_session_id: str, + user_input_code: Union[str, None], + device_id: Union[str, None], + link_code: Union[str, None], + session: Optional[SessionContainer], + should_try_linking_with_session_user: Union[bool, None], + tenant_id: str, + api_options: APIOptions, + user_context: Dict[str, Any], + ): + code_info = await list_codes_by_pre_auth_session_id( + tenant_id, pre_auth_session_id + ) if code_info is None: raise Exception("Should never come here") email = code_info.email if email is None: # this example is focused on login via email raise Exception("Should never come here") - if (is_not_allowed(email)): + if is_not_allowed(email): # highlight-next-line - return GeneralErrorResponse("You are not allowed to sign up. Please contact the app's admin to get permission") - return await original_consume_code_post(pre_auth_session_id, user_input_code, device_id, link_code, tenant_id, api_options, user_context) + return GeneralErrorResponse( + "You are not allowed to sign up. Please contact the app's admin to get permission" + ) + return await original_consume_code_post( + pre_auth_session_id, + user_input_code, + device_id, + link_code, + session, + should_try_linking_with_session_user, + tenant_id, + api_options, + user_context, + ) original_implementation.consume_code_post = consume_code_post return original_implementation @@ -155,10 +181,9 @@ def is_not_allowed(email: str): passwordless.init( - ^{pythonRecipeInitDefault} # typecheck-only, removed from output - override=passwordless.InputOverrideConfig( - apis=override_passwordless_apis - ) + contact_config="", # type: ignore # typecheck-only, removed from output + flow_type="USER_INPUT_CODE", # typecheck-only, removed from output + override=passwordless.InputOverrideConfig(apis=override_passwordless_apis), ) ``` diff --git a/v2/passwordless/advanced-customizations/apis-override/usage.mdx b/v2/passwordless/advanced-customizations/apis-override/usage.mdx index 58d851ab2..c4131f6d5 100644 --- a/v2/passwordless/advanced-customizations/apis-override/usage.mdx +++ b/v2/passwordless/advanced-customizations/apis-override/usage.mdx @@ -119,29 +119,40 @@ See all the [functions that can be overrided here](https://supertokens.com/docs/ ```python from supertokens_python import init, InputAppInfo from supertokens_python.recipe import passwordless -from supertokens_python.recipe.passwordless.interfaces import ( - ConsumeCodePostOkResult, ConsumeCodePostRestartFlowError, ConsumeCodePostIncorrectUserInputCodeError, ConsumeCodePostExpiredUserInputCodeError, - APIInterface, APIOptions) -from typing import Union, Dict, Any -from supertokens_python.types import GeneralErrorResponse - -# highlight-start +from supertokens_python.recipe.passwordless.interfaces import APIInterface, APIOptions +from typing import Union, Dict, Any, Optional +from supertokens_python.recipe.session.interfaces import SessionContainer +# highlight-start def override_passwordless_apis(original_implementation: APIInterface): original_consume_code_post = original_implementation.consume_code_post - async def consume_code_post(pre_auth_session_id: str, - user_input_code: Union[str, None], - device_id: Union[str, None], - link_code: Union[str, None], - tenant_id: str, - api_options: APIOptions, - user_context: Dict[str, Any]) -> Union[ConsumeCodePostOkResult, ConsumeCodePostRestartFlowError, GeneralErrorResponse, ConsumeCodePostIncorrectUserInputCodeError, ConsumeCodePostExpiredUserInputCodeError]: + async def consume_code_post( + pre_auth_session_id: str, + user_input_code: Union[str, None], + device_id: Union[str, None], + link_code: Union[str, None], + session: Optional[SessionContainer], + should_try_linking_with_session_user: Union[bool, None], + tenant_id: str, + api_options: APIOptions, + user_context: Dict[str, Any], + ): # TODO: some custom logic # or call the default behaviour as show below - return await original_consume_code_post(pre_auth_session_id, user_input_code, device_id, link_code, tenant_id, api_options, user_context) + return await original_consume_code_post( + pre_auth_session_id, + user_input_code, + device_id, + link_code, + session, + should_try_linking_with_session_user, + tenant_id, + api_options, + user_context, + ) original_implementation.consume_code_post = consume_code_post return original_implementation @@ -149,20 +160,17 @@ def override_passwordless_apis(original_implementation: APIInterface): init( - app_info=InputAppInfo( - api_domain="...", app_name="...", website_domain="..."), - framework='...', # type: ignore + app_info=InputAppInfo(api_domain="...", app_name="...", website_domain="..."), + framework="...", # type: ignore recipe_list=[ passwordless.init( contact_config=..., # type: ignore flow_type="...", # type: ignore # highlight-start - override=passwordless.InputOverrideConfig( - apis=override_passwordless_apis - ) + override=passwordless.InputOverrideConfig(apis=override_passwordless_apis), # highlight-end ) - ] + ], ) ``` diff --git a/v2/passwordless/advanced-customizations/backend-functions-override/usage.mdx b/v2/passwordless/advanced-customizations/backend-functions-override/usage.mdx index cb01b9ff4..3904f5fda 100644 --- a/v2/passwordless/advanced-customizations/backend-functions-override/usage.mdx +++ b/v2/passwordless/advanced-customizations/backend-functions-override/usage.mdx @@ -119,43 +119,59 @@ See all the [functions that can be overrided here](https://supertokens.com/docs/ ```python from supertokens_python import init, InputAppInfo from supertokens_python.recipe import passwordless -from supertokens_python.recipe.passwordless.interfaces import ConsumeCodeOkResult, ConsumeCodeIncorrectUserInputCodeError, ConsumeCodeExpiredUserInputCodeError, ConsumeCodeRestartFlowError +from supertokens_python.recipe.session.interfaces import SessionContainer from supertokens_python.recipe.passwordless.interfaces import RecipeInterface -from typing import Union, Dict, Any +from typing import Union, Dict, Any, Optional + # highlight-start def override_passwordless_functions(original_implementation: RecipeInterface): - original_consume_code = original_implementation.consume_code - async def consume_code(pre_auth_session_id: str, - user_input_code: Union[str, None], - device_id: Union[str, None], - link_code: Union[str, None], - tenant_id: str, - user_context: Dict[str, Any]) -> Union[ConsumeCodeOkResult, ConsumeCodeIncorrectUserInputCodeError, ConsumeCodeExpiredUserInputCodeError, ConsumeCodeRestartFlowError]: + original_consume_code = original_implementation.consume_code + + async def consume_code( + pre_auth_session_id: str, + user_input_code: Union[str, None], + device_id: Union[str, None], + link_code: Union[str, None], + session: Optional[SessionContainer], + should_try_linking_with_session_user: Union[bool, None], + tenant_id: str, + user_context: Dict[str, Any], + ): # TODO: some custom logic - - # or call the default behaviour as show below - return await original_consume_code(pre_auth_session_id, user_input_code, device_id, link_code, tenant_id, user_context) - - original_implementation.consume_code = consume_code - return original_implementation + + # or call the default behaviour as show below + return await original_consume_code( + pre_auth_session_id, + user_input_code, + device_id, + link_code, + session, + should_try_linking_with_session_user, + tenant_id, + user_context, + ) + + original_implementation.consume_code = consume_code + return original_implementation + + # highlight-end init( app_info=InputAppInfo(api_domain="...", app_name="...", website_domain="..."), - - framework='...', # type: ignore + framework="...", # type: ignore recipe_list=[ passwordless.init( - contact_config=..., # type: ignore - flow_type="...", # type: ignore + contact_config=..., # type: ignore + flow_type="...", # type: ignore # highlight-start override=passwordless.InputOverrideConfig( functions=override_passwordless_functions - ) + ), # highlight-end ) - ] + ], ) ``` diff --git a/v2/passwordless/advanced-customizations/user-context.mdx b/v2/passwordless/advanced-customizations/user-context.mdx index e0d374130..d76179768 100644 --- a/v2/passwordless/advanced-customizations/user-context.mdx +++ b/v2/passwordless/advanced-customizations/user-context.mdx @@ -149,8 +149,12 @@ func main() { ```python from supertokens_python import init, InputAppInfo from supertokens_python.recipe import passwordless -from supertokens_python.recipe.passwordless.interfaces import RecipeInterface, ConsumeCodeOkResult -from typing import Dict, Any, Union +from supertokens_python.recipe.passwordless.interfaces import ( + RecipeInterface, + ConsumeCodeOkResult, +) +from typing import Dict, Any, Union, Optional +from supertokens_python.recipe.session.interfaces import SessionContainer # highlight-start @@ -158,15 +162,28 @@ from typing import Dict, Any, Union def override_passwordless_functions(original_implementation: RecipeInterface): original_consume_code = original_implementation.consume_code - async def consume_code(pre_auth_session_id: str, - user_input_code: Union[str, None], - device_id: Union[str, None], - link_code: Union[str, None], - tenant_id: str, - user_context: Dict[str, Any]): - response = await original_consume_code(pre_auth_session_id, user_input_code, device_id, link_code, tenant_id, user_context) + async def consume_code( + pre_auth_session_id: str, + user_input_code: Union[str, None], + device_id: Union[str, None], + link_code: Union[str, None], + session: Optional[SessionContainer], + should_try_linking_with_session_user: Union[bool, None], + tenant_id: str, + user_context: Dict[str, Any], + ): + response = await original_consume_code( + pre_auth_session_id, + user_input_code, + device_id, + link_code, + session, + should_try_linking_with_session_user, + tenant_id, + user_context, + ) - if isinstance(response, ConsumeCodeOkResult): + if isinstance(response, ConsumeCodeOkResult) and len(response.user.login_methods) == 1: # This is called during the consume_code API , # but before calling the create_new_session function. # At the start of the API, we do not know if it will result in a @@ -179,29 +196,30 @@ def override_passwordless_functions(original_implementation: RecipeInterface): # sign up API, and this will tell the create_new_session function # (which will be called next) # to not create a new session in case userContext["isSignUp"] is True - if response.created_new_user: + if response.created_new_recipe_user: user_context["isSignUp"] = True return response original_implementation.consume_code = consume_code return original_implementation + + # highlight-end init( - app_info=InputAppInfo( - api_domain="...", app_name="...", website_domain="..."), - framework='...', # type: ignore + app_info=InputAppInfo(api_domain="...", app_name="...", website_domain="..."), + framework="...", # type: ignore recipe_list=[ passwordless.init( contact_config=..., # type: ignore flow_type="...", # type: ignore override=passwordless.InputOverrideConfig( functions=override_passwordless_functions - ) + ), ) - ] + ], ) ``` @@ -373,25 +391,35 @@ func main() { ```python from supertokens_python import init, InputAppInfo from supertokens_python.recipe import passwordless -from supertokens_python.recipe.session.interfaces import RecipeInterface, SessionClaimValidator, SessionClaim, GetSessionTokensDangerouslyDict +from supertokens_python.recipe.session.interfaces import ( + RecipeInterface, + SessionClaimValidator, + SessionClaim, + GetSessionTokensDangerouslyDict, +) from supertokens_python.recipe.session.recipe_implementation import RecipeImplementation from typing import Dict, Any, Union, List, TypeVar, Optional from supertokens_python.recipe import session from supertokens_python.framework import BaseRequest from supertokens_python.recipe.session.utils import TokenTransferMethod +from supertokens_python.types import RecipeUserId _T = TypeVar("_T") + # highlight-start def override_session_functions(original_implementation: RecipeInterface): original_create_new_session = original_implementation.create_new_session - async def create_new_session(user_id: str, - access_token_payload: Optional[Dict[str, Any]], - session_data_in_database: Optional[Dict[str, Any]], - disable_anti_csrf: Optional[bool], - tenant_id: str, - user_context: Dict[str, Any]): + async def create_new_session( + user_id: str, + recipe_user_id: RecipeUserId, + access_token_payload: Optional[Dict[str, Any]], + session_data_in_database: Optional[Dict[str, Any]], + disable_anti_csrf: Optional[bool], + tenant_id: str, + user_context: Dict[str, Any], + ): if user_context["isSignUp"] is True: # The execution will come here only in case # a sign up API is calling this function. This is because @@ -399,49 +427,82 @@ def override_session_functions(original_implementation: RecipeInterface): # (see above code). return EmptySession(original_implementation) - return await original_create_new_session(user_id, access_token_payload, session_data_in_database, disable_anti_csrf, tenant_id, user_context) + return await original_create_new_session( + user_id, + recipe_user_id, + access_token_payload, + session_data_in_database, + disable_anti_csrf, + tenant_id, + user_context, + ) + original_implementation.create_new_session = create_new_session return original_implementation + + # highlight-end init( - app_info=InputAppInfo( - api_domain="...", app_name="...", website_domain="..."), - framework='...', # type: ignore + app_info=InputAppInfo(api_domain="...", app_name="...", website_domain="..."), + framework="...", # type: ignore recipe_list=[ passwordless.init( # type: ignore # see previous step... ), session.init( - override=session.InputOverrideConfig( - functions=override_session_functions - ) - ) - ] + override=session.InputOverrideConfig(functions=override_session_functions) + ), + ], ) class EmptySession(session.SessionContainer): def __init__(self, recipe_implementation: RecipeInterface): assert isinstance(recipe_implementation, RecipeImplementation) - super().__init__(recipe_implementation, - recipe_implementation.config, "", "", None, "", "", "", {}, None, False, "") + super().__init__( + recipe_implementation, + recipe_implementation.config, + "", + "", + None, + "", + "", + "", + RecipeUserId(""), + {}, + None, + False, + "", + ) async def revoke_session(self, user_context: Union[Any, None] = None) -> None: pass - async def get_session_data_from_database(self, user_context: Union[Dict[str, Any], None] = None) -> Dict[str, Any]: + async def get_session_data_from_database( + self, user_context: Union[Dict[str, Any], None] = None + ) -> Dict[str, Any]: return {} - async def update_session_data_in_database(self, new_session_data: Dict[str, Any], user_context: Union[Dict[str, Any], None] = None) -> None: + async def update_session_data_in_database( + self, + new_session_data: Dict[str, Any], + user_context: Union[Dict[str, Any], None] = None, + ) -> None: pass def get_user_id(self, user_context: Union[Dict[str, Any], None] = None) -> str: return "" + def get_recipe_user_id( + self, user_context: Union[Dict[str, Any], None] = None + ) -> RecipeUserId: + return RecipeUserId("") + def get_access_token_payload( - self, user_context: Union[Dict[str, Any], None] = None) -> Dict[str, Any]: + self, user_context: Union[Dict[str, Any], None] = None + ) -> Dict[str, Any]: return {} def get_handle(self, user_context: Union[Dict[str, Any], None] = None) -> str: @@ -450,13 +511,20 @@ class EmptySession(session.SessionContainer): def get_access_token(self, user_context: Union[Dict[str, Any], None] = None) -> str: return "" - async def get_time_created(self, user_context: Union[Dict[str, Any], None] = None) -> int: + async def get_time_created( + self, user_context: Union[Dict[str, Any], None] = None + ) -> int: return -1 async def get_expiry(self, user_context: Union[Dict[str, Any], None] = None) -> int: return -1 - async def attach_to_request_response(self, request: BaseRequest, transfer_method: TokenTransferMethod, user_context: Union[Dict[str, Any], None] = None): + async def attach_to_request_response( + self, + request: BaseRequest, + transfer_method: TokenTransferMethod, + user_context: Union[Dict[str, Any], None] = None, + ): pass async def assert_claims( @@ -492,7 +560,9 @@ class EmptySession(session.SessionContainer): pass async def merge_into_access_token_payload( - self, access_token_payload_update: Dict[str, Any], user_context: Union[Dict[str, Any], None] = None + self, + access_token_payload_update: Dict[str, Any], + user_context: Union[Dict[str, Any], None] = None, ) -> None: pass @@ -502,9 +572,9 @@ class EmptySession(session.SessionContainer): "accessToken": "", "antiCsrfToken": None, "frontToken": "", - "refreshToken": None + "refreshToken": None, } - + def get_tenant_id(self, user_context: Union[Dict[str, Any], None] = None) -> str: return "" ``` From d842a96e7dffb78f15cdf46eefcb93a800e525e5 Mon Sep 17 00:00:00 2001 From: rishabhpoddar Date: Sun, 20 Oct 2024 17:05:24 +0530 Subject: [PATCH 23/34] more changes --- .../common-customizations/change-email.mdx | 136 ++++++++++++------ .../passwordless-via-allow-list.mdx | 97 ++++++++++--- .../passwordless-via-invite-link.mdx | 78 +++++++--- .../common-customizations/get-user-info.mdx | 15 +- .../handling-signinup-success.mdx | 73 ++++++---- .../multi-tenancy/new-tenant-config.mdx | 49 +++++-- .../common-customizations/userid-format.mdx | 75 ++++++---- .../custom-ui/multitenant-login.mdx | 53 ++++--- .../pre-built-ui/multitenant-login.mdx | 53 ++++--- .../passwordless-via-allow-list.mdx | 97 ++++++++++--- .../passwordless-via-invite-link.mdx | 78 +++++++--- .../common-customizations/get-user-info.mdx | 15 +- 12 files changed, 589 insertions(+), 230 deletions(-) diff --git a/v2/passwordless/common-customizations/change-email.mdx b/v2/passwordless/common-customizations/change-email.mdx index 69209b626..b5b9ee2ab 100644 --- a/v2/passwordless/common-customizations/change-email.mdx +++ b/v2/passwordless/common-customizations/change-email.mdx @@ -240,14 +240,18 @@ func isValidEmail(email string) bool { from supertokens_python.recipe.session.framework.flask import verify_session from supertokens_python.recipe.session import SessionContainer from supertokens_python.recipe.passwordless.syncio import update_user -from supertokens_python.recipe.passwordless.interfaces import UpdateUserOkResult, UpdateUserEmailAlreadyExistsError +from supertokens_python.recipe.passwordless.interfaces import ( + UpdateUserOkResult, + UpdateUserEmailAlreadyExistsError, + EmailChangeNotAllowedError, +) from flask import g, request, Flask from re import fullmatch app = Flask(__name__) -@app.route('/change-email', methods=['POST']) # type: ignore +@app.route("/change-email", methods=["POST"]) # type: ignore @verify_session() def change_email(): # highlight-start @@ -266,8 +270,7 @@ def change_email(): return # update the users email - user_id = session.get_user_id() - update_response = update_user(user_id, email=email) + update_response = update_user(session.get_recipe_user_id(), email=email) if isinstance(update_response, UpdateUserOkResult): # TODO send successful email update response @@ -277,12 +280,18 @@ def change_email(): # TODO handle error, email already exists return + if isinstance(update_response, EmailChangeNotAllowedError): + # This is possible if you have enabled account linking. + # See our docs for account linking to know more about this. + # TODO: tell the user to contact support. + return + raise Exception("Should never reach here") # highlight-end -async def is_valid_email(value: str) -> bool: +def is_valid_email(value: str) -> bool: return ( fullmatch( r'^(([^<>()\[\]\\.,;:\s@"]+(\.[^<>()\[\]\\.,;:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,' @@ -582,9 +591,7 @@ func isValidEmail(email string) bool { ```python from supertokens_python.recipe.session.framework.flask import verify_session from supertokens_python.recipe.session import SessionContainer -from supertokens_python.recipe.passwordless.syncio import ( - update_user -) +from supertokens_python.recipe.passwordless.syncio import update_user from supertokens_python.recipe.passwordless.interfaces import ( UpdateUserOkResult, UpdateUserEmailAlreadyExistsError, @@ -597,14 +604,18 @@ from supertokens_python.recipe.emailverification.syncio import ( from flask import g, request, Flask from re import fullmatch +from supertokens_python.recipe.accountlinking.syncio import is_email_change_allowed +from supertokens_python.syncio import get_user, list_users_by_account_info +from supertokens_python.types import AccountInfo app = Flask(__name__) -@app.route('/change-email', methods=['POST']) # type: ignore + +@app.route("/change-email", methods=["POST"]) # type: ignore @verify_session() def change_email(): # highlight-start - session: SessionContainer = g.supertokens # type: ignore + session: SessionContainer = g.supertokens # type: ignore request_body = request.get_json() @@ -622,17 +633,45 @@ def change_email(): # Then, we check if the email is verified for this user ID or not. # It is important to understand that SuperTokens stores email verification # status based on the user ID AND the email, and not just the email. - is_verified = is_email_verified(user_id, request_body["email"]) - + is_verified = is_email_verified(session.get_recipe_user_id(), request_body["email"]) + if not is_verified: + if not is_email_change_allowed( + session.get_recipe_user_id(), request_body["email"], False + ): + # Email change is not allowed, send a 400 error + return {"error": "Email change not allowed"}, 400 + # Before sending a verification email, we check if the email is already + # being used by another user. If it is, we throw an error. + user = get_user(user_id) + + if user is not None: + for tenant_id in user.tenant_ids: + users_with_same_email = list_users_by_account_info( + tenant_id, AccountInfo(email=request_body["email"]) + ) + for curr_user in users_with_same_email: + # Since one user can be shared across many tenants, we need to check if + # the email already exists in any of the tenants that belongs to this user. + if curr_user.id != user_id: + # TODO handle error, email already exists with another user. + return + # Create and send the email verification link to the user for the new email. - send_email_verification_email(session.get_tenant_id(), user_id, request_body["email"]) - + send_email_verification_email( + session.get_tenant_id(), + user_id, + session.get_recipe_user_id(), + request_body["email"], + ) + # TODO send successful email verification response return - + # update the users email - update_response = update_user(user_id, email=request_body["email"]) + update_response = update_user( + session.get_recipe_user_id(), email=request_body["email"] + ) if isinstance(update_response, UpdateUserOkResult): # TODO send successful email update response @@ -647,7 +686,7 @@ def change_email(): raise Exception("Should never reach here") -async def is_valid_email(value: str) -> bool: +def is_valid_email(value: str) -> bool: return ( fullmatch( r'^(([^<>()\[\]\\.,;:\s@"]+(\.[^<>()\[\]\\.,;:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,' @@ -797,27 +836,22 @@ func main() { ```python - from supertokens_python import init, InputAppInfo, SupertokensConfig from supertokens_python.recipe import passwordless, emailverification from supertokens_python.recipe.emailverification.interfaces import ( APIInterface, - APIOptions + APIOptions, ) -from supertokens_python.recipe.passwordless.asyncio import ( - update_user -) +from supertokens_python.recipe.passwordless.asyncio import update_user from supertokens_python.recipe.passwordless import ContactEmailOrPhoneConfig from supertokens_python.recipe.emailverification.interfaces import ( - EmailVerifyPostOkResult + EmailVerifyPostOkResult, ) -from supertokens_python.recipe.session.interfaces import ( - SessionContainer -) +from supertokens_python.recipe.session.interfaces import SessionContainer from supertokens_python import ( InputAppInfo, SupertokensConfig, @@ -827,44 +861,54 @@ from typing import Optional, Dict, Any def override_email_verification_apis(original_implementation: APIInterface): - original_email_verification_verify_email_post = original_implementation.email_verify_post - - async def email_verify_post(token: str, session: Optional[SessionContainer], tenant_id: str, api_options: APIOptions, user_context: Dict[str, Any] ): + original_email_verification_verify_email_post = ( + original_implementation.email_verify_post + ) + + async def email_verify_post( + token: str, + session: Optional[SessionContainer], + tenant_id: str, + api_options: APIOptions, + user_context: Dict[str, Any], + ): # highlight-start - verification_response = await original_email_verification_verify_email_post(token, session, tenant_id, api_options, user_context) - + verification_response = await original_email_verification_verify_email_post( + token, session, tenant_id, api_options, user_context + ) + if isinstance(verification_response, EmailVerifyPostOkResult): - await update_user(verification_response.user.user_id, verification_response.user.email) - + await update_user( + verification_response.user.recipe_user_id, + verification_response.user.email, + ) + return verification_response # highlight-end - + original_implementation.email_verify_post = email_verify_post return original_implementation init( - supertokens_config=SupertokensConfig( - connection_uri="..." - ), + supertokens_config=SupertokensConfig(connection_uri="..."), app_info=InputAppInfo(api_domain="...", app_name="...", website_domain="..."), framework="flask", recipe_list=[ passwordless.init( flow_type="USER_INPUT_CODE_AND_MAGIC_LINK", - contact_config=ContactEmailOrPhoneConfig() - ), + contact_config=ContactEmailOrPhoneConfig(), + ), emailverification.init( - "REQUIRED", - override=emailverification.InputOverrideConfig( - # highlight-start - apis=override_email_verification_apis - # highlight-end + "REQUIRED", + override=emailverification.InputOverrideConfig( + # highlight-start + apis=override_email_verification_apis + # highlight-end + ), ), - ), ], ) - ``` diff --git a/v2/passwordless/common-customizations/disable-sign-up/passwordless-via-allow-list.mdx b/v2/passwordless/common-customizations/disable-sign-up/passwordless-via-allow-list.mdx index 6eb68bed9..98d320380 100644 --- a/v2/passwordless/common-customizations/disable-sign-up/passwordless-via-allow-list.mdx +++ b/v2/passwordless/common-customizations/disable-sign-up/passwordless-via-allow-list.mdx @@ -318,9 +318,14 @@ func main() { from supertokens_python import init, InputAppInfo from supertokens_python.types import GeneralErrorResponse from supertokens_python.recipe import passwordless -from supertokens_python.recipe.passwordless.asyncio import get_user_by_email, get_user_by_phone_number -from supertokens_python.recipe.passwordless.interfaces import APIInterface, CreateCodePostOkResult, APIOptions -from typing import Union, Dict, Any +from supertokens_python.asyncio import list_users_by_account_info +from supertokens_python.types import AccountInfo +from supertokens_python.recipe.passwordless.interfaces import ( + APIInterface, + APIOptions, +) +from typing import Union, Dict, Any, Optional +from supertokens_python.recipe.session.interfaces import SessionContainer async def is_email_allowed(email: str): @@ -336,39 +341,89 @@ async def is_phone_number_allowed(phone_number: str): def override_passwordless_apis(original_implementation: APIInterface): original_create_code_post = original_implementation.create_code_post - async def create_code_post(email: Union[str, None], phone_number: Union[str, None], tenant_id: str, api_options: APIOptions, user_context: Dict[str, Any], - ) -> Union[CreateCodePostOkResult, GeneralErrorResponse]: - if (email is not None): - existing_user = await get_user_by_email(tenant_id, email) - if existing_user is None: + async def create_code_post( + email: Union[str, None], + phone_number: Union[str, None], + session: Optional[SessionContainer], + should_try_linking_with_session_user: Union[bool, None], + tenant_id: str, + api_options: APIOptions, + user_context: Dict[str, Any], + ): + if email is not None: + existing_user = await list_users_by_account_info( + tenant_id, AccountInfo(email=email) + ) + user_with_passwordless = next( + ( + user + for user in existing_user + if any( + login_method.recipe_id == "passwordless" + and login_method.has_same_email_as(email) + for login_method in user.login_methods + ) + ), + None, + ) + + if user_with_passwordless is None: # sign up attempt - if (not (await is_email_allowed(email))): - return GeneralErrorResponse("Sign ups disabled. Please contact admin.") + if not (await is_email_allowed(email)): + return GeneralErrorResponse( + "Sign ups disabled. Please contact admin." + ) else: assert phone_number is not None - existing_user = await get_user_by_phone_number(tenant_id, phone_number) - if existing_user is None: + existing_user = await list_users_by_account_info( + tenant_id, AccountInfo(phone_number=phone_number) + ) + user_with_passwordless = next( + ( + user + for user in existing_user + if any( + login_method.recipe_id == "passwordless" + and login_method.has_same_phone_number_as(phone_number) + for login_method in user.login_methods + ) + ), + None, + ) + + if user_with_passwordless is None: # sign up attempt - if (not (await is_phone_number_allowed(phone_number))): - return GeneralErrorResponse("Sign ups disabled. Please contact admin.") - - return await original_create_code_post(email, phone_number, tenant_id, api_options, user_context) + if not (await is_phone_number_allowed(phone_number)): + return GeneralErrorResponse( + "Sign ups disabled. Please contact admin." + ) + + return await original_create_code_post( + email, + phone_number, + session, + should_try_linking_with_session_user, + tenant_id, + api_options, + user_context, + ) original_implementation.create_code_post = create_code_post return original_implementation + init( - app_info=InputAppInfo( - api_domain="...", app_name="...", website_domain="..."), - framework='...', # type: ignore + app_info=InputAppInfo(api_domain="...", app_name="...", website_domain="..."), + framework="...", # type: ignore recipe_list=[ passwordless.init( - ^{pythonRecipeInitDefault} + contact_config="", # type: ignore # typecheck-only, removed from output + flow_type="USER_INPUT_CODE", override=passwordless.InputOverrideConfig( apis=override_passwordless_apis, ), ) - ] + ], ) ``` diff --git a/v2/passwordless/common-customizations/disable-sign-up/passwordless-via-invite-link.mdx b/v2/passwordless/common-customizations/disable-sign-up/passwordless-via-invite-link.mdx index a4af0a4aa..1a2bdaa7f 100644 --- a/v2/passwordless/common-customizations/disable-sign-up/passwordless-via-invite-link.mdx +++ b/v2/passwordless/common-customizations/disable-sign-up/passwordless-via-invite-link.mdx @@ -129,45 +129,91 @@ func main() { from supertokens_python import init, InputAppInfo from supertokens_python.types import GeneralErrorResponse from supertokens_python.recipe import passwordless -from supertokens_python.recipe.passwordless.asyncio import get_user_by_email, get_user_by_phone_number -from supertokens_python.recipe.passwordless.interfaces import APIInterface, CreateCodePostOkResult, APIOptions -from typing import Union, Dict, Any +from supertokens_python.recipe.passwordless.interfaces import APIInterface, APIOptions +from typing import Union, Dict, Any, Optional +from supertokens_python.recipe.session.interfaces import SessionContainer +from supertokens_python.asyncio import list_users_by_account_info +from supertokens_python.types import AccountInfo + def override_passwordless_apis(original_implementation: APIInterface): original_create_code_post = original_implementation.create_code_post - async def create_code_post(email: Union[str, None], phone_number: Union[str, None], tenant_id: str, api_options: APIOptions, user_context: Dict[str, Any], - ) -> Union[CreateCodePostOkResult, GeneralErrorResponse]: - if (email is not None): - existing_user = await get_user_by_email(tenant_id, email) - if existing_user is None: + async def create_code_post( + email: Union[str, None], + phone_number: Union[str, None], + session: Optional[SessionContainer], + should_try_linking_with_session_user: Union[bool, None], + tenant_id: str, + api_options: APIOptions, + user_context: Dict[str, Any], + ): + if email is not None: + existing_user = await list_users_by_account_info( + tenant_id, AccountInfo(email=email) + ) + user_with_passwordless = next( + ( + user + for user in existing_user + if any( + login_method.recipe_id == "passwordless" + and login_method.has_same_email_as(email) + for login_method in user.login_methods + ) + ), + None, + ) + if user_with_passwordless is None: # sign up attempt return GeneralErrorResponse("Sign ups disabled. Please contact admin.") else: assert phone_number is not None - existing_user = await get_user_by_phone_number(tenant_id, phone_number) - if existing_user is None: + existing_user = await list_users_by_account_info( + tenant_id, AccountInfo(phone_number=phone_number) + ) + user_with_passwordless = next( + ( + user + for user in existing_user + if any( + login_method.recipe_id == "passwordless" + and login_method.has_same_phone_number_as(phone_number) + for login_method in user.login_methods + ) + ), + None, + ) + if user_with_passwordless is None: # sign up attempt return GeneralErrorResponse("Sign ups disabled. Please contact admin.") - return await original_create_code_post(email, phone_number, tenant_id, api_options, user_context) + return await original_create_code_post( + email, + phone_number, + session, + should_try_linking_with_session_user, + tenant_id, + api_options, + user_context, + ) original_implementation.create_code_post = create_code_post return original_implementation init( - app_info=InputAppInfo( - api_domain="...", app_name="...", website_domain="..."), - framework='...', # type: ignore + app_info=InputAppInfo(api_domain="...", app_name="...", website_domain="..."), + framework="...", # type: ignore recipe_list=[ passwordless.init( - ^{pythonRecipeInitDefault} + contact_config="", # type: ignore # typecheck-only, removed from output + flow_type="USER_INPUT_CODE", override=passwordless.InputOverrideConfig( apis=override_passwordless_apis, ), ) - ] + ], ) ``` diff --git a/v2/passwordless/common-customizations/get-user-info.mdx b/v2/passwordless/common-customizations/get-user-info.mdx index d7dc80c4e..7e8410c60 100644 --- a/v2/passwordless/common-customizations/get-user-info.mdx +++ b/v2/passwordless/common-customizations/get-user-info.mdx @@ -245,19 +245,26 @@ func main() { ```python -from supertokens_python.recipe.passwordless.asyncio import get_user_by_phone_number +from supertokens_python.asyncio import list_users_by_account_info +from supertokens_python.types import AccountInfo async def some_func(): - _ = await get_user_by_phone_number("public", "+1234567890") + _ = await list_users_by_account_info( + "public", AccountInfo(phone_number="+1234567890") + ) ``` ```python -from supertokens_python.recipe.passwordless.syncio import get_user_by_phone_number +from supertokens_python.syncio import list_users_by_account_info +from supertokens_python.types import AccountInfo -user_info = get_user_by_phone_number("public", "+1234567890") +def some_func(): + _ = list_users_by_account_info( + "public", AccountInfo(phone_number="+1234567890") + ) ``` diff --git a/v2/passwordless/common-customizations/handling-signinup-success.mdx b/v2/passwordless/common-customizations/handling-signinup-success.mdx index 37f92a880..c05a462b7 100644 --- a/v2/passwordless/common-customizations/handling-signinup-success.mdx +++ b/v2/passwordless/common-customizations/handling-signinup-success.mdx @@ -256,64 +256,81 @@ func main() { ```python from supertokens_python import init, InputAppInfo from supertokens_python.recipe import session, passwordless -from supertokens_python.recipe.passwordless.interfaces import RecipeInterface, ConsumeCodeOkResult -from typing import Dict, Any, Union +from supertokens_python.recipe.passwordless.interfaces import ( + RecipeInterface, + ConsumeCodeOkResult, +) +from typing import Dict, Any, Union, Optional +from supertokens_python.recipe.session.interfaces import SessionContainer + # highlight-start -def override_passwordless_functions(original_implementation: RecipeInterface) -> RecipeInterface: +def override_passwordless_functions( + original_implementation: RecipeInterface, +) -> RecipeInterface: original_consume_code = original_implementation.consume_code - + async def consume_code( pre_auth_session_id: str, user_input_code: Union[str, None], device_id: Union[str, None], link_code: Union[str, None], + session: Optional[SessionContainer], + should_try_linking_with_session_user: Union[bool, None], tenant_id: str, - user_context: Dict[str, Any] + user_context: Dict[str, Any], ): # First we call the original implementation of consume_code. - result = await original_consume_code(pre_auth_session_id, user_input_code, device_id, link_code, tenant_id, user_context) + result = await original_consume_code( + pre_auth_session_id, + user_input_code, + device_id, + link_code, + session, + should_try_linking_with_session_user, + tenant_id, + user_context, + ) # Post sign up response, we check if it was successful - if isinstance(result, ConsumeCodeOkResult): - id = result.user.user_id - phone_number = result.user.phone_number - email = result.user.email - print(id) - print(phone_number) - print(email) - - if result.created_new_user: - # TODO: post sign up logic - pass - else: - # TODO: post sign in logic - pass - + if session is None: + if ( + isinstance(result, ConsumeCodeOkResult) + and len(result.user.login_methods) == 1 + ): + if result.created_new_recipe_user: + # TODO: post sign up logic + pass + else: + # TODO: post sign in logic + pass + return result - + original_implementation.consume_code = consume_code return original_implementation + + # highlight-end init( app_info=InputAppInfo(api_domain="...", app_name="...", website_domain="..."), - framework='...', # type: ignore + framework="...", # type: ignore recipe_list=[ passwordless.init( contact_config=passwordless.ContactConfig( - contact_method="EMAIL", # This example will work with any contactMethod + contact_method="EMAIL", # This example will work with any contactMethod ), - flow_type="USER_INPUT_CODE_AND_MAGIC_LINK", # This example will work with any flowType - # highlight-start + flow_type="USER_INPUT_CODE_AND_MAGIC_LINK", # This example will work with any flowType + # highlight-start override=passwordless.InputOverrideConfig( functions=override_passwordless_functions ), - # highlight-end + # highlight-end ), session.init(), - ] + ], ) ``` diff --git a/v2/passwordless/common-customizations/multi-tenancy/new-tenant-config.mdx b/v2/passwordless/common-customizations/multi-tenancy/new-tenant-config.mdx index 7e2950877..0e4b072a3 100644 --- a/v2/passwordless/common-customizations/multi-tenancy/new-tenant-config.mdx +++ b/v2/passwordless/common-customizations/multi-tenancy/new-tenant-config.mdx @@ -98,12 +98,22 @@ func main() { ```python from supertokens_python.recipe.multitenancy.asyncio import create_or_update_tenant -from supertokens_python.recipe.multitenancy.interfaces import TenantConfig +from supertokens_python.recipe.multitenancy.interfaces import TenantConfigCreateOrUpdate +from supertokens_python.recipe.multifactorauth.types import FactorIds + async def some_func(): - response = await create_or_update_tenant("customer1", TenantConfig( - passwordless_enabled=True, - )) + response = await create_or_update_tenant( + "customer1", + TenantConfigCreateOrUpdate( + first_factors=[ + FactorIds.OTP_PHONE, + FactorIds.OTP_EMAIL, + FactorIds.LINK_PHONE, + FactorIds.LINK_EMAIL, + ] + ), + ) if response.status != "OK": print("Handle error") @@ -119,18 +129,29 @@ async def some_func(): ```python from supertokens_python.recipe.multitenancy.syncio import create_or_update_tenant -from supertokens_python.recipe.multitenancy.interfaces import TenantConfig +from supertokens_python.recipe.multitenancy.interfaces import TenantConfigCreateOrUpdate +from supertokens_python.recipe.multifactorauth.types import FactorIds -response = create_or_update_tenant("customer1", TenantConfig( - passwordless_enabled=True, -)) -if response.status != "OK": - print("Handle error") -elif response.created_new: - print("new tenant was created") -else: - print("existing tenant's config was modified.") +async def some_func(): + response = create_or_update_tenant( + "customer1", + TenantConfigCreateOrUpdate( + first_factors=[ + FactorIds.OTP_PHONE, + FactorIds.OTP_EMAIL, + FactorIds.LINK_PHONE, + FactorIds.LINK_EMAIL, + ] + ), + ) + + if response.status != "OK": + print("Handle error") + elif response.created_new: + print("new tenant was created") + else: + print("existing tenant's config was modified.") ``` diff --git a/v2/passwordless/common-customizations/userid-format.mdx b/v2/passwordless/common-customizations/userid-format.mdx index 6cb39399b..d82651b9e 100644 --- a/v2/passwordless/common-customizations/userid-format.mdx +++ b/v2/passwordless/common-customizations/userid-format.mdx @@ -141,62 +141,81 @@ func main() { from supertokens_python import init, InputAppInfo from supertokens_python.asyncio import create_user_id_mapping from supertokens_python.recipe import session, passwordless -from supertokens_python.recipe.passwordless.interfaces import RecipeInterface, ConsumeCodeOkResult -from typing import Dict, Any, Union +from supertokens_python.recipe.passwordless.interfaces import ( + RecipeInterface, + ConsumeCodeOkResult, +) +from typing import Dict, Any, Union, Optional +from supertokens_python.recipe.session.interfaces import SessionContainer +from supertokens_python.types import RecipeUserId + -def override_passwordless_functions(original_implementation: RecipeInterface) -> RecipeInterface: +def override_passwordless_functions( + original_implementation: RecipeInterface, +) -> RecipeInterface: original_consume_code = original_implementation.consume_code - + async def consume_code( pre_auth_session_id: str, user_input_code: Union[str, None], device_id: Union[str, None], link_code: Union[str, None], + session: Optional[SessionContainer], + should_try_linking_with_session_user: Union[bool, None], tenant_id: str, - user_context: Dict[str, Any] + user_context: Dict[str, Any], ): # First we call the original implementation of consume_code. - result = await original_consume_code(pre_auth_session_id, user_input_code, device_id, link_code, tenant_id, user_context) + result = await original_consume_code( + pre_auth_session_id, + user_input_code, + device_id, + link_code, + session, + should_try_linking_with_session_user, + tenant_id, + user_context, + ) # Post sign up response, we check if it was successful - if isinstance(result, ConsumeCodeOkResult): - id = result.user.user_id - phone_number = result.user.phone_number - email = result.user.email - print(id) - print(phone_number) - print(email) - - if result.created_new_user: - # highlight-start - external_user_id = "" - await create_user_id_mapping(result.user.user_id, external_user_id) - result.user.user_id = external_user_id - # highlight-end - + if ( + isinstance(result, ConsumeCodeOkResult) + and len(result.user.login_methods) == 1 + and session is None + and result.created_new_recipe_user + ): + # highlight-start + external_user_id = "" + await create_user_id_mapping(result.user.id, external_user_id) + result.user.id = external_user_id + result.user.login_methods[0].recipe_user_id = RecipeUserId(external_user_id) + result.recipe_user_id = RecipeUserId(external_user_id) + # highlight-end + return result - + original_implementation.consume_code = consume_code return original_implementation + init( app_info=InputAppInfo(api_domain="...", app_name="...", website_domain="..."), - framework='...', # type: ignore + framework="...", # type: ignore recipe_list=[ passwordless.init( contact_config=passwordless.ContactConfig( - contact_method="EMAIL", # This example will work with any contactMethod + contact_method="EMAIL", # This example will work with any contactMethod ), - flow_type="USER_INPUT_CODE_AND_MAGIC_LINK", # This example will work with any flowType - # highlight-start + flow_type="USER_INPUT_CODE_AND_MAGIC_LINK", # This example will work with any flowType + # highlight-start override=passwordless.InputOverrideConfig( functions=override_passwordless_functions ), - # highlight-end + # highlight-end ), session.init(), - ] + ], ) ``` diff --git a/v2/passwordless/custom-ui/multitenant-login.mdx b/v2/passwordless/custom-ui/multitenant-login.mdx index 2da19ed08..c201a008f 100644 --- a/v2/passwordless/custom-ui/multitenant-login.mdx +++ b/v2/passwordless/custom-ui/multitenant-login.mdx @@ -115,12 +115,22 @@ func main() { ```python from supertokens_python.recipe.multitenancy.asyncio import create_or_update_tenant -from supertokens_python.recipe.multitenancy.interfaces import TenantConfig +from supertokens_python.recipe.multitenancy.interfaces import TenantConfigCreateOrUpdate +from supertokens_python.recipe.multifactorauth.types import FactorIds + async def some_func(): - response = await create_or_update_tenant("customer1", TenantConfig( - passwordless_enabled=True, - )) + response = await create_or_update_tenant( + "customer1", + TenantConfigCreateOrUpdate( + first_factors=[ + FactorIds.OTP_PHONE, + FactorIds.OTP_EMAIL, + FactorIds.LINK_PHONE, + FactorIds.LINK_EMAIL, + ] + ), + ) if response.status != "OK": print("Handle error") @@ -136,18 +146,29 @@ async def some_func(): ```python from supertokens_python.recipe.multitenancy.syncio import create_or_update_tenant -from supertokens_python.recipe.multitenancy.interfaces import TenantConfig - -response = create_or_update_tenant("customer1", TenantConfig( - passwordless_enabled=True, -)) - -if response.status != "OK": - print("Handle error") -elif response.created_new: - print("new tenant was created") -else: - print("existing tenant's config was modified.") +from supertokens_python.recipe.multitenancy.interfaces import TenantConfigCreateOrUpdate +from supertokens_python.recipe.multifactorauth.types import FactorIds + + +async def some_func(): + response = create_or_update_tenant( + "customer1", + TenantConfigCreateOrUpdate( + first_factors=[ + FactorIds.OTP_PHONE, + FactorIds.OTP_EMAIL, + FactorIds.LINK_PHONE, + FactorIds.LINK_EMAIL, + ] + ), + ) + + if response.status != "OK": + print("Handle error") + elif response.created_new: + print("new tenant was created") + else: + print("existing tenant's config was modified.") ``` diff --git a/v2/passwordless/pre-built-ui/multitenant-login.mdx b/v2/passwordless/pre-built-ui/multitenant-login.mdx index 9f1943dd3..19b41a103 100644 --- a/v2/passwordless/pre-built-ui/multitenant-login.mdx +++ b/v2/passwordless/pre-built-ui/multitenant-login.mdx @@ -112,12 +112,22 @@ func main() { ```python from supertokens_python.recipe.multitenancy.asyncio import create_or_update_tenant -from supertokens_python.recipe.multitenancy.interfaces import TenantConfig +from supertokens_python.recipe.multitenancy.interfaces import TenantConfigCreateOrUpdate +from supertokens_python.recipe.multifactorauth.types import FactorIds + async def some_func(): - response = await create_or_update_tenant("customer1", TenantConfig( - passwordless_enabled=True, - )) + response = await create_or_update_tenant( + "customer1", + TenantConfigCreateOrUpdate( + first_factors=[ + FactorIds.OTP_PHONE, + FactorIds.OTP_EMAIL, + FactorIds.LINK_PHONE, + FactorIds.LINK_EMAIL, + ] + ), + ) if response.status != "OK": print("Handle error") @@ -133,18 +143,29 @@ async def some_func(): ```python from supertokens_python.recipe.multitenancy.syncio import create_or_update_tenant -from supertokens_python.recipe.multitenancy.interfaces import TenantConfig - -response = create_or_update_tenant("customer1", TenantConfig( - passwordless_enabled=True, -)) - -if response.status != "OK": - print("Handle error") -elif response.created_new: - print("new tenant was created") -else: - print("existing tenant's config was modified.") +from supertokens_python.recipe.multitenancy.interfaces import TenantConfigCreateOrUpdate +from supertokens_python.recipe.multifactorauth.types import FactorIds + + +async def some_func(): + response = create_or_update_tenant( + "customer1", + TenantConfigCreateOrUpdate( + first_factors=[ + FactorIds.OTP_PHONE, + FactorIds.OTP_EMAIL, + FactorIds.LINK_PHONE, + FactorIds.LINK_EMAIL, + ] + ), + ) + + if response.status != "OK": + print("Handle error") + elif response.created_new: + print("new tenant was created") + else: + print("existing tenant's config was modified.") ``` diff --git a/v2/thirdpartypasswordless/common-customizations/disable-sign-up/passwordless-via-allow-list.mdx b/v2/thirdpartypasswordless/common-customizations/disable-sign-up/passwordless-via-allow-list.mdx index 61ea0d5db..45cc6bd0a 100644 --- a/v2/thirdpartypasswordless/common-customizations/disable-sign-up/passwordless-via-allow-list.mdx +++ b/v2/thirdpartypasswordless/common-customizations/disable-sign-up/passwordless-via-allow-list.mdx @@ -318,9 +318,14 @@ func main() { from supertokens_python import init, InputAppInfo from supertokens_python.types import GeneralErrorResponse from supertokens_python.recipe import passwordless -from supertokens_python.recipe.passwordless.asyncio import get_user_by_email, get_user_by_phone_number -from supertokens_python.recipe.passwordless.interfaces import APIInterface, CreateCodePostOkResult, APIOptions -from typing import Union, Dict, Any +from supertokens_python.asyncio import list_users_by_account_info +from supertokens_python.types import AccountInfo +from supertokens_python.recipe.passwordless.interfaces import ( + APIInterface, + APIOptions, +) +from typing import Union, Dict, Any, Optional +from supertokens_python.recipe.session.interfaces import SessionContainer async def is_email_allowed(email: str): @@ -336,39 +341,89 @@ async def is_phone_number_allowed(phone_number: str): def override_passwordless_apis(original_implementation: APIInterface): original_create_code_post = original_implementation.create_code_post - async def create_code_post(email: Union[str, None], phone_number: Union[str, None], tenant_id: str, api_options: APIOptions, user_context: Dict[str, Any], - ) -> Union[CreateCodePostOkResult, GeneralErrorResponse]: - if (email is not None): - existing_user = await get_user_by_email(tenant_id, email) - if existing_user is None: + async def create_code_post( + email: Union[str, None], + phone_number: Union[str, None], + session: Optional[SessionContainer], + should_try_linking_with_session_user: Union[bool, None], + tenant_id: str, + api_options: APIOptions, + user_context: Dict[str, Any], + ): + if email is not None: + existing_user = await list_users_by_account_info( + tenant_id, AccountInfo(email=email) + ) + user_with_passwordless = next( + ( + user + for user in existing_user + if any( + login_method.recipe_id == "passwordless" + and login_method.has_same_email_as(email) + for login_method in user.login_methods + ) + ), + None, + ) + + if user_with_passwordless is None: # sign up attempt - if (not (await is_email_allowed(email))): - return GeneralErrorResponse("Sign ups disabled. Please contact admin.") + if not (await is_email_allowed(email)): + return GeneralErrorResponse( + "Sign ups disabled. Please contact admin." + ) else: assert phone_number is not None - existing_user = await get_user_by_phone_number(tenant_id, phone_number) - if existing_user is None: + existing_user = await list_users_by_account_info( + tenant_id, AccountInfo(phone_number=phone_number) + ) + user_with_passwordless = next( + ( + user + for user in existing_user + if any( + login_method.recipe_id == "passwordless" + and login_method.has_same_phone_number_as(phone_number) + for login_method in user.login_methods + ) + ), + None, + ) + + if user_with_passwordless is None: # sign up attempt - if (not (await is_phone_number_allowed(phone_number))): - return GeneralErrorResponse("Sign ups disabled. Please contact admin.") - - return await original_create_code_post(email, phone_number, tenant_id, api_options, user_context) + if not (await is_phone_number_allowed(phone_number)): + return GeneralErrorResponse( + "Sign ups disabled. Please contact admin." + ) + + return await original_create_code_post( + email, + phone_number, + session, + should_try_linking_with_session_user, + tenant_id, + api_options, + user_context, + ) original_implementation.create_code_post = create_code_post return original_implementation + init( - app_info=InputAppInfo( - api_domain="...", app_name="...", website_domain="..."), - framework='...', # type: ignore + app_info=InputAppInfo(api_domain="...", app_name="...", website_domain="..."), + framework="...", # type: ignore recipe_list=[ passwordless.init( - ^{pythonRecipeInitDefault} + contact_config="", # type: ignore # typecheck-only, removed from output + flow_type="USER_INPUT_CODE", override=passwordless.InputOverrideConfig( apis=override_passwordless_apis, ), ) - ] + ], ) ``` diff --git a/v2/thirdpartypasswordless/common-customizations/disable-sign-up/passwordless-via-invite-link.mdx b/v2/thirdpartypasswordless/common-customizations/disable-sign-up/passwordless-via-invite-link.mdx index d03a35d6a..8a27dfa43 100644 --- a/v2/thirdpartypasswordless/common-customizations/disable-sign-up/passwordless-via-invite-link.mdx +++ b/v2/thirdpartypasswordless/common-customizations/disable-sign-up/passwordless-via-invite-link.mdx @@ -129,45 +129,91 @@ func main() { from supertokens_python import init, InputAppInfo from supertokens_python.types import GeneralErrorResponse from supertokens_python.recipe import passwordless -from supertokens_python.recipe.passwordless.asyncio import get_user_by_email, get_user_by_phone_number -from supertokens_python.recipe.passwordless.interfaces import APIInterface, CreateCodePostOkResult, APIOptions -from typing import Union, Dict, Any +from supertokens_python.recipe.passwordless.interfaces import APIInterface, APIOptions +from typing import Union, Dict, Any, Optional +from supertokens_python.recipe.session.interfaces import SessionContainer +from supertokens_python.asyncio import list_users_by_account_info +from supertokens_python.types import AccountInfo + def override_passwordless_apis(original_implementation: APIInterface): original_create_code_post = original_implementation.create_code_post - async def create_code_post(email: Union[str, None], phone_number: Union[str, None], tenant_id: str, api_options: APIOptions, user_context: Dict[str, Any], - ) -> Union[CreateCodePostOkResult, GeneralErrorResponse]: - if (email is not None): - existing_user = await get_user_by_email(tenant_id, email) - if existing_user is None: + async def create_code_post( + email: Union[str, None], + phone_number: Union[str, None], + session: Optional[SessionContainer], + should_try_linking_with_session_user: Union[bool, None], + tenant_id: str, + api_options: APIOptions, + user_context: Dict[str, Any], + ): + if email is not None: + existing_user = await list_users_by_account_info( + tenant_id, AccountInfo(email=email) + ) + user_with_passwordless = next( + ( + user + for user in existing_user + if any( + login_method.recipe_id == "passwordless" + and login_method.has_same_email_as(email) + for login_method in user.login_methods + ) + ), + None, + ) + if user_with_passwordless is None: # sign up attempt return GeneralErrorResponse("Sign ups disabled. Please contact admin.") else: assert phone_number is not None - existing_user = await get_user_by_phone_number(tenant_id, phone_number) - if existing_user is None: + existing_user = await list_users_by_account_info( + tenant_id, AccountInfo(phone_number=phone_number) + ) + user_with_passwordless = next( + ( + user + for user in existing_user + if any( + login_method.recipe_id == "passwordless" + and login_method.has_same_phone_number_as(phone_number) + for login_method in user.login_methods + ) + ), + None, + ) + if user_with_passwordless is None: # sign up attempt return GeneralErrorResponse("Sign ups disabled. Please contact admin.") - return await original_create_code_post(email, phone_number, tenant_id, api_options, user_context) + return await original_create_code_post( + email, + phone_number, + session, + should_try_linking_with_session_user, + tenant_id, + api_options, + user_context, + ) original_implementation.create_code_post = create_code_post return original_implementation init( - app_info=InputAppInfo( - api_domain="...", app_name="...", website_domain="..."), - framework='...', # type: ignore + app_info=InputAppInfo(api_domain="...", app_name="...", website_domain="..."), + framework="...", # type: ignore recipe_list=[ passwordless.init( - ^{pythonRecipeInitDefault} + contact_config="", # type: ignore # typecheck-only, removed from output + flow_type="USER_INPUT_CODE", override=passwordless.InputOverrideConfig( apis=override_passwordless_apis, ), ) - ] + ], ) ``` diff --git a/v2/thirdpartypasswordless/common-customizations/get-user-info.mdx b/v2/thirdpartypasswordless/common-customizations/get-user-info.mdx index b1b48e6c3..e0b37b704 100644 --- a/v2/thirdpartypasswordless/common-customizations/get-user-info.mdx +++ b/v2/thirdpartypasswordless/common-customizations/get-user-info.mdx @@ -258,19 +258,26 @@ func main() { ```python -from supertokens_python.recipe.passwordless.asyncio import get_user_by_phone_number +from supertokens_python.asyncio import list_users_by_account_info +from supertokens_python.types import AccountInfo async def some_func(): - _ = await get_user_by_phone_number("public", "+1234567890") + _ = await list_users_by_account_info( + "public", AccountInfo(phone_number="+1234567890") + ) ``` ```python -from supertokens_python.recipe.passwordless.syncio import get_user_by_phone_number +from supertokens_python.syncio import list_users_by_account_info +from supertokens_python.types import AccountInfo -user_info = get_user_by_phone_number("public", "+1234567890") +def some_func(): + _ = list_users_by_account_info( + "public", AccountInfo(phone_number="+1234567890") + ) ``` From b28aeb4add47bf077f233834b0c4d50f8140b1f8 Mon Sep 17 00:00:00 2001 From: rishabhpoddar Date: Sun, 20 Oct 2024 17:11:49 +0530 Subject: [PATCH 24/34] more fixes --- .../apis-override/usage.mdx | 22 +++++----- .../backend-functions-override/usage.mdx | 40 ++++++++++++------- .../sessions/new-session.mdx | 34 ++++++++++------ 3 files changed, 61 insertions(+), 35 deletions(-) diff --git a/v2/session/advanced-customizations/apis-override/usage.mdx b/v2/session/advanced-customizations/apis-override/usage.mdx index 1785c222e..bba13d746 100644 --- a/v2/session/advanced-customizations/apis-override/usage.mdx +++ b/v2/session/advanced-customizations/apis-override/usage.mdx @@ -120,13 +120,18 @@ See all the [functions that can be overrided here](https://supertokens.com/docs/ from supertokens_python import init, InputAppInfo from supertokens_python.recipe import session from supertokens_python.recipe.session.interfaces import APIInterface, APIOptions -from typing import Dict, Any, Optional +from typing import Dict, Any + # highlight-start def override_session_apis(original_implementation: APIInterface): original_signout_post = original_implementation.signout_post - async def signout_post(session: Optional[session.SessionContainer], api_options: APIOptions, user_context: Dict[str, Any]): + async def signout_post( + session: session.SessionContainer, + api_options: APIOptions, + user_context: Dict[str, Any], + ): # TODO: custom logic # or call the default behaviour as show below @@ -134,22 +139,21 @@ def override_session_apis(original_implementation: APIInterface): original_implementation.signout_post = signout_post return original_implementation + + # highlight-end init( - app_info=InputAppInfo( - api_domain="...", app_name="...", website_domain="..."), - framework='...', # type: ignore + app_info=InputAppInfo(api_domain="...", app_name="...", website_domain="..."), + framework="...", # type: ignore recipe_list=[ session.init( # highlight-start - override=session.InputOverrideConfig( - apis=override_session_apis - ) + override=session.InputOverrideConfig(apis=override_session_apis) # highlight-end ) - ] + ], ) ``` diff --git a/v2/session/advanced-customizations/backend-functions-override/usage.mdx b/v2/session/advanced-customizations/backend-functions-override/usage.mdx index b0a74b747..c9fa6e285 100644 --- a/v2/session/advanced-customizations/backend-functions-override/usage.mdx +++ b/v2/session/advanced-customizations/backend-functions-override/usage.mdx @@ -114,40 +114,52 @@ from supertokens_python import init, InputAppInfo from supertokens_python.recipe import session from supertokens_python.recipe.session.interfaces import RecipeInterface from typing import Any, Dict, Optional +from supertokens_python.types import RecipeUserId + # highlight-start def override_session_functions(original_implementation: RecipeInterface): original_create_new_session = original_implementation.create_new_session - async def create_new_session(user_id: str, - access_token_payload: Optional[Dict[str, Any]], - session_data_in_database: Optional[Dict[str, Any]], - disable_anti_csrf: Optional[bool], - tenant_id: str, - user_context: Dict[str, Any],): + async def create_new_session( + user_id: str, + recipe_user_id: RecipeUserId, + access_token_payload: Optional[Dict[str, Any]], + session_data_in_database: Optional[Dict[str, Any]], + disable_anti_csrf: Optional[bool], + tenant_id: str, + user_context: Dict[str, Any], + ): # TODO: custom logic # or call the default behaviour as show below - return await original_create_new_session(user_id, access_token_payload, session_data_in_database, disable_anti_csrf, tenant_id, user_context) + return await original_create_new_session( + user_id, + recipe_user_id, + access_token_payload, + session_data_in_database, + disable_anti_csrf, + tenant_id, + user_context, + ) original_implementation.create_new_session = create_new_session return original_implementation + + # highlight-end init( - app_info=InputAppInfo( - api_domain="...", app_name="...", website_domain="..."), - framework='...', # type: ignore + app_info=InputAppInfo(api_domain="...", app_name="...", website_domain="..."), + framework="...", # type: ignore recipe_list=[ session.init( # highlight-start - override=session.InputOverrideConfig( - functions=override_session_functions - ) + override=session.InputOverrideConfig(functions=override_session_functions) # highlight-end ) - ] + ], ) ``` diff --git a/v2/session/common-customizations/sessions/new-session.mdx b/v2/session/common-customizations/sessions/new-session.mdx index 64825488e..b24663b75 100644 --- a/v2/session/common-customizations/sessions/new-session.mdx +++ b/v2/session/common-customizations/sessions/new-session.mdx @@ -325,15 +325,17 @@ func login(w http.ResponseWriter, r *http.Request) { from supertokens_python.recipe.session.asyncio import create_new_session from fastapi import Request from fastapi.responses import JSONResponse +from supertokens_python.types import RecipeUserId -@app.post('/login') # type: ignore + +@app.post("/login") # type: ignore async def login(request: Request): # verify user's credentials... - user_id = "..." # get from db + user_id = "..." # get from db # highlight-next-line - await create_new_session(request, "public", user_id) + await create_new_session(request, "public", RecipeUserId(user_id)) # a new session has been created. # - an access & refresh token has been attached to the response's cookie @@ -350,16 +352,17 @@ async def login(request: Request): from supertokens_python.recipe.session.syncio import create_new_session from flask import jsonify from flask.wrappers import Request +from supertokens_python.types import RecipeUserId -@app.route('/login', methods=['POST']) # type: ignore +@app.route("/login", methods=["POST"]) # type: ignore def login(request: Request): # verify user's credentials... - user_id = "..." # get from db + user_id = "..." # get from db # highlight-next-line - create_new_session(request, "public", user_id) + create_new_session(request, "public", RecipeUserId(user_id)) # a new session has been created. # - an access & refresh token has been attached to the response's cookie @@ -367,6 +370,7 @@ def login(request: Request): # return jsonify({"message": "User logged in!"}) + ``` @@ -375,14 +379,16 @@ def login(request: Request): ```python from supertokens_python.recipe.session.asyncio import create_new_session from django.http import HttpRequest, JsonResponse +from supertokens_python.types import RecipeUserId + async def login(request: HttpRequest): # verify user's credentials... - user_id = "..." # get from db + user_id = "..." # get from db # highlight-next-line - await create_new_session(request, "public", user_id) + await create_new_session(request, "public", RecipeUserId(user_id)) # a new session has been created. # - an access & refresh token has been attached to the response's cookie @@ -487,18 +493,22 @@ func login(w http.ResponseWriter, r *http.Request) { ```python -from supertokens_python.recipe.session.asyncio import create_new_session_without_request_response +from supertokens_python.recipe.session.asyncio import ( + create_new_session_without_request_response, +) from fastapi import Request from fastapi.responses import JSONResponse +from supertokens_python.types import RecipeUserId - -@app.post('/login') # type: ignore +@app.post("/login") # type: ignore async def login(request: Request): # verify user's credentials... user_id = "..." # get from db - session = await create_new_session_without_request_response("public", user_id) + session = await create_new_session_without_request_response( + "public", RecipeUserId(user_id) + ) tokens = session.get_all_session_tokens_dangerously() if tokens["accessAndFrontTokenUpdated"]: From c65d0404e97358e42217ee09cd3adceea540ffca Mon Sep 17 00:00:00 2001 From: rishabhpoddar Date: Sun, 20 Oct 2024 18:50:54 +0530 Subject: [PATCH 25/34] more fixes --- .../custom-response/general-error.mdx | 64 ++++++-- .../apis-override/usage.mdx | 44 ++++-- .../backend-functions-override/usage.mdx | 54 +++++-- .../advanced-customizations/user-context.mdx | 146 +++++++++++++----- .../backend-functions-override/usage.mdx | 54 +++++-- 5 files changed, 261 insertions(+), 101 deletions(-) diff --git a/v2/thirdparty/advanced-customizations/apis-override/custom-response/general-error.mdx b/v2/thirdparty/advanced-customizations/apis-override/custom-response/general-error.mdx index 94650e6c8..b4bc6d7f4 100644 --- a/v2/thirdparty/advanced-customizations/apis-override/custom-response/general-error.mdx +++ b/v2/thirdparty/advanced-customizations/apis-override/custom-response/general-error.mdx @@ -138,11 +138,16 @@ func emailNotAllowed(email string) bool { ```python from supertokens_python.recipe import thirdparty -from supertokens_python.recipe.thirdparty.interfaces import APIInterface, RecipeInterface, APIOptions, SignInUpPostOkResult, SignInUpPostNoEmailGivenByProviderResponse, SignInUpOkResult +from supertokens_python.recipe.thirdparty.interfaces import ( + APIInterface, + RecipeInterface, + APIOptions, +) from supertokens_python.recipe.thirdparty.provider import Provider, RedirectUriInfo from supertokens_python.recipe.thirdparty.types import RawUserInfoFromProvider -from typing import Any, Dict, Union +from typing import Any, Dict, Union, Optional from supertokens_python.types import GeneralErrorResponse +from supertokens_python.recipe.session.interfaces import SessionContainer def override_functions(original_implementation: RecipeInterface): @@ -152,15 +157,30 @@ def override_functions(original_implementation: RecipeInterface): third_party_id: str, third_party_user_id: str, email: str, + is_verified: bool, oauth_tokens: Dict[str, Any], raw_user_info_from_provider: RawUserInfoFromProvider, + session: Optional[SessionContainer], + should_try_linking_with_session_user: Union[bool, None], tenant_id: str, - user_context: Dict[str, Any] - ) -> SignInUpOkResult: - if (is_not_allowed(email)): + user_context: Dict[str, Any], + ): + if is_not_allowed(email): # this error will signal to the API to send a GENERAL_ERROR message raise Exception("Email not allowed") - return await original_sign_in_up(third_party_id, third_party_user_id, email, oauth_tokens, raw_user_info_from_provider, tenant_id, user_context) + return await original_sign_in_up( + third_party_id, + third_party_user_id, + email, + is_verified, + oauth_tokens, + raw_user_info_from_provider, + session, + should_try_linking_with_session_user, + tenant_id, + user_context, + ) + original_implementation.sign_in_up = sign_in_up return original_implementation @@ -171,18 +191,31 @@ def override_apis(original_implementation: APIInterface): async def sign_in_up_post( provider: Provider, - redirect_uri_info: Union[RedirectUriInfo, None], - oauth_tokens: Union[Dict[str, Any], None], + redirect_uri_info: Optional[RedirectUriInfo], + oauth_tokens: Optional[Dict[str, Any]], + session: Optional[SessionContainer], + should_try_linking_with_session_user: Union[bool, None], tenant_id: str, api_options: APIOptions, - user_context: Dict[str, Any] - ) -> Union[SignInUpPostOkResult, SignInUpPostNoEmailGivenByProviderResponse, GeneralErrorResponse]: + user_context: Dict[str, Any], + ): try: - return await original_sign_in_up_post(provider, redirect_uri_info, oauth_tokens, tenant_id, api_options, user_context) + return await original_sign_in_up_post( + provider, + redirect_uri_info, + oauth_tokens, + session, + should_try_linking_with_session_user, + tenant_id, + api_options, + user_context, + ) except Exception as e: - if(str(e) == "Email not allowed"): + if str(e) == "Email not allowed": # highlight-start - return GeneralErrorResponse(message="You are not allowed to sign up. Please contact the app's admin to get permission") + return GeneralErrorResponse( + message="You are not allowed to sign up. Please contact the app's admin to get permission" + ) # highlight-end raise e @@ -199,9 +232,8 @@ def is_not_allowed(email: str): thirdparty.init( sign_in_and_up_feature=thirdparty.SignInAndUpFeature([]), override=thirdparty.InputOverrideConfig( - apis=override_apis, - functions=override_functions - ) + apis=override_apis, functions=override_functions + ), ) ``` diff --git a/v2/thirdparty/advanced-customizations/apis-override/usage.mdx b/v2/thirdparty/advanced-customizations/apis-override/usage.mdx index 37e6ea620..dcafc0e72 100644 --- a/v2/thirdparty/advanced-customizations/apis-override/usage.mdx +++ b/v2/thirdparty/advanced-customizations/apis-override/usage.mdx @@ -127,8 +127,10 @@ See all the [functions that can be overrided here](https://supertokens.com/docs/ from supertokens_python import init, InputAppInfo from supertokens_python.recipe import thirdparty from supertokens_python.recipe.thirdparty.interfaces import APIInterface, APIOptions -from typing import Union, Dict, Any +from typing import Union, Dict, Any, Optional from supertokens_python.recipe.thirdparty.provider import Provider, RedirectUriInfo +from supertokens_python.recipe.session.interfaces import SessionContainer + # highlight-start def override_thirdparty_apis(original_implementation: APIInterface): @@ -136,37 +138,49 @@ def override_thirdparty_apis(original_implementation: APIInterface): async def sign_in_up_post( provider: Provider, - redirect_uri_info: Union[RedirectUriInfo, None], - oauth_tokens: Union[Dict[str, Any], None], + redirect_uri_info: Optional[RedirectUriInfo], + oauth_tokens: Optional[Dict[str, Any]], + session: Optional[SessionContainer], + should_try_linking_with_session_user: Union[bool, None], tenant_id: str, api_options: APIOptions, - user_context: Dict[str, Any] + user_context: Dict[str, Any], ): # TODO: custom logic # or call the default behaviour as show below - return await original_sign_in_up_post(provider, redirect_uri_info, oauth_tokens, tenant_id, api_options, user_context) + return await original_sign_in_up_post( + provider, + redirect_uri_info, + oauth_tokens, + session, + should_try_linking_with_session_user, + tenant_id, + api_options, + user_context, + ) original_implementation.sign_in_up_post = sign_in_up_post return original_implementation + + # highlight-end init( - app_info=InputAppInfo( - api_domain="...", app_name="...", website_domain="..."), - framework='...', # type: ignore + app_info=InputAppInfo(api_domain="...", app_name="...", website_domain="..."), + framework="...", # type: ignore recipe_list=[ thirdparty.init( # highlight-start - override=thirdparty.InputOverrideConfig( - apis=override_thirdparty_apis - ), + override=thirdparty.InputOverrideConfig(apis=override_thirdparty_apis), # highlight-end - sign_in_and_up_feature=thirdparty.SignInAndUpFeature(providers=[ - # ... - ]) + sign_in_and_up_feature=thirdparty.SignInAndUpFeature( + providers=[ + # ... + ] + ), ) - ] + ], ) ``` diff --git a/v2/thirdparty/advanced-customizations/backend-functions-override/usage.mdx b/v2/thirdparty/advanced-customizations/backend-functions-override/usage.mdx index ea682e1aa..3a0d5a013 100644 --- a/v2/thirdparty/advanced-customizations/backend-functions-override/usage.mdx +++ b/v2/thirdparty/advanced-customizations/backend-functions-override/usage.mdx @@ -127,45 +127,65 @@ from supertokens_python import init, InputAppInfo from supertokens_python.recipe import thirdparty from supertokens_python.recipe.thirdparty.interfaces import RecipeInterface from supertokens_python.recipe.thirdparty.types import RawUserInfoFromProvider -from typing import Dict, Any +from typing import Dict, Any, Optional, Union +from supertokens_python.recipe.session.interfaces import SessionContainer + # highlight-start def override_thirdparty_functions(original_implementation: RecipeInterface): - original_sign_in_up = original_implementation.sign_in_up + original_sign_in_up = original_implementation.sign_in_up - async def sign_in_up( + async def sign_in_up( third_party_id: str, third_party_user_id: str, email: str, + is_verified: bool, oauth_tokens: Dict[str, Any], raw_user_info_from_provider: RawUserInfoFromProvider, + session: Optional[SessionContainer], + should_try_linking_with_session_user: Union[bool, None], tenant_id: str, - user_context: Dict[str, Any] + user_context: Dict[str, Any], ): - # TODO: custom logic + # TODO: custom logic + + # or call the default behaviour as show below + return await original_sign_in_up( + third_party_id, + third_party_user_id, + email, + is_verified, + oauth_tokens, + raw_user_info_from_provider, + session, + should_try_linking_with_session_user, + tenant_id, + user_context, + ) + + original_implementation.sign_in_up = sign_in_up + return original_implementation + - # or call the default behaviour as show below - return await original_sign_in_up(third_party_id, third_party_user_id, email, oauth_tokens, raw_user_info_from_provider, tenant_id, user_context) - - original_implementation.sign_in_up = sign_in_up - return original_implementation # highlight-end init( app_info=InputAppInfo(api_domain="...", app_name="...", website_domain="..."), - framework='...', # type: ignore + framework="...", # type: ignore recipe_list=[ thirdparty.init( - # highlight-start + # highlight-start override=thirdparty.InputOverrideConfig( functions=override_thirdparty_functions ), - # highlight-end - sign_in_and_up_feature=thirdparty.SignInAndUpFeature(providers=[ - #... - ]) + # highlight-end + sign_in_and_up_feature=thirdparty.SignInAndUpFeature( + providers=[ + # ... + ] + ), ) - ] + ], ) ``` diff --git a/v2/thirdparty/advanced-customizations/user-context.mdx b/v2/thirdparty/advanced-customizations/user-context.mdx index a8e820728..45ba55812 100644 --- a/v2/thirdparty/advanced-customizations/user-context.mdx +++ b/v2/thirdparty/advanced-customizations/user-context.mdx @@ -150,9 +150,14 @@ func main() { ```python from supertokens_python import init, InputAppInfo from supertokens_python.recipe import thirdparty -from supertokens_python.recipe.thirdparty.interfaces import RecipeInterface +from supertokens_python.recipe.thirdparty.interfaces import ( + RecipeInterface, + SignInUpOkResult, +) from supertokens_python.recipe.thirdparty.types import RawUserInfoFromProvider -from typing import Dict, Any +from typing import Dict, Any, Optional, Union +from supertokens_python.recipe.session.interfaces import SessionContainer + # highlight-start def override_thirdparty_functions(original_implementation: RecipeInterface): @@ -162,13 +167,26 @@ def override_thirdparty_functions(original_implementation: RecipeInterface): third_party_id: str, third_party_user_id: str, email: str, + is_verified: bool, oauth_tokens: Dict[str, Any], raw_user_info_from_provider: RawUserInfoFromProvider, + session: Optional[SessionContainer], + should_try_linking_with_session_user: Union[bool, None], tenant_id: str, - user_context: Dict[str, Any] + user_context: Dict[str, Any], ): response = await original_sign_in_up( - third_party_id, third_party_user_id, email, oauth_tokens, raw_user_info_from_provider, tenant_id, user_context) + third_party_id, + third_party_user_id, + email, + is_verified, + oauth_tokens, + raw_user_info_from_provider, + session, + should_try_linking_with_session_user, + tenant_id, + user_context, + ) # This is called during the sign_in_up API for third party login, # but before calling the create_new_session function. @@ -182,28 +200,33 @@ def override_thirdparty_functions(original_implementation: RecipeInterface): # sign up API, and this will tell the create_new_session function # (which will be called next) # to not create a new session in case userContext["isSignUp"] is True - if response.created_new_user: + if ( + isinstance(response, SignInUpOkResult) + and response.created_new_recipe_user + and len(response.user.login_methods) == 1 + ): user_context["isSignUp"] = True return response original_implementation.sign_in_up = sign_in_up return original_implementation + + # highlight-end init( - app_info=InputAppInfo( - api_domain="...", app_name="...", website_domain="..."), - framework='...', # type: ignore + app_info=InputAppInfo(api_domain="...", app_name="...", website_domain="..."), + framework="...", # type: ignore recipe_list=[ thirdparty.init( sign_in_and_up_feature=..., # type: ignore override=thirdparty.InputOverrideConfig( functions=override_thirdparty_functions - ) + ), ) - ] + ], ) ``` @@ -376,12 +399,18 @@ func main() { ```python from supertokens_python import init, InputAppInfo from supertokens_python.recipe import thirdparty -from supertokens_python.recipe.session.interfaces import RecipeInterface, SessionClaimValidator, SessionClaim, GetSessionTokensDangerouslyDict +from supertokens_python.recipe.session.interfaces import ( + RecipeInterface, + SessionClaimValidator, + SessionClaim, + GetSessionTokensDangerouslyDict, +) from supertokens_python.recipe.session.recipe_implementation import RecipeImplementation from typing import Dict, Any, Union, List, TypeVar, Optional from supertokens_python.recipe import session from supertokens_python.framework import BaseRequest from supertokens_python.recipe.session.utils import TokenTransferMethod +from supertokens_python.types import RecipeUserId _T = TypeVar("_T") @@ -391,12 +420,15 @@ _T = TypeVar("_T") def override_session_functions(original_implementation: RecipeInterface): original_create_new_session = original_implementation.create_new_session - async def create_new_session(user_id: str, - access_token_payload: Optional[Dict[str, Any]], - session_data_in_database: Optional[Dict[str, Any]], - disable_anti_csrf: Optional[bool], - tenant_id: str, - user_context: Dict[str, Any]): + async def create_new_session( + user_id: str, + recipe_user_id: RecipeUserId, + access_token_payload: Optional[Dict[str, Any]], + session_data_in_database: Optional[Dict[str, Any]], + disable_anti_csrf: Optional[bool], + tenant_id: str, + user_context: Dict[str, Any], + ): if user_context["isSignUp"] is True: # The execution will come here only in case # a sign up API is calling this function. This is because @@ -404,49 +436,82 @@ def override_session_functions(original_implementation: RecipeInterface): # (see above code). return EmptySession(original_implementation) - return await original_create_new_session(user_id, access_token_payload, session_data_in_database, disable_anti_csrf, tenant_id, user_context) + return await original_create_new_session( + user_id, + recipe_user_id, + access_token_payload, + session_data_in_database, + disable_anti_csrf, + tenant_id, + user_context, + ) + original_implementation.create_new_session = create_new_session return original_implementation + + # highlight-end init( - app_info=InputAppInfo( - api_domain="...", app_name="...", website_domain="..."), - framework='...', # type: ignore + app_info=InputAppInfo(api_domain="...", app_name="...", website_domain="..."), + framework="...", # type: ignore recipe_list=[ thirdparty.init( # type: ignore # see previous step... ), session.init( - override=session.InputOverrideConfig( - functions=override_session_functions - ) - ) - ] + override=session.InputOverrideConfig(functions=override_session_functions) + ), + ], ) class EmptySession(session.SessionContainer): def __init__(self, recipe_implementation: RecipeInterface): assert isinstance(recipe_implementation, RecipeImplementation) - super().__init__(recipe_implementation, - recipe_implementation.config, "", "", None, "", "", "", {}, None, False, "") + super().__init__( + recipe_implementation, + recipe_implementation.config, + "", + "", + None, + "", + "", + "", + RecipeUserId(""), + {}, + None, + False, + "", + ) async def revoke_session(self, user_context: Union[Any, None] = None) -> None: pass - async def get_session_data_from_database(self, user_context: Union[Dict[str, Any], None] = None) -> Dict[str, Any]: + async def get_session_data_from_database( + self, user_context: Union[Dict[str, Any], None] = None + ) -> Dict[str, Any]: return {} - async def update_session_data_in_database(self, new_session_data: Dict[str, Any], user_context: Union[Dict[str, Any], None] = None) -> None: + async def update_session_data_in_database( + self, + new_session_data: Dict[str, Any], + user_context: Union[Dict[str, Any], None] = None, + ) -> None: pass def get_user_id(self, user_context: Union[Dict[str, Any], None] = None) -> str: return "" + def get_recipe_user_id( + self, user_context: Union[Dict[str, Any], None] = None + ) -> RecipeUserId: + return RecipeUserId("") + def get_access_token_payload( - self, user_context: Union[Dict[str, Any], None] = None) -> Dict[str, Any]: + self, user_context: Union[Dict[str, Any], None] = None + ) -> Dict[str, Any]: return {} def get_handle(self, user_context: Union[Dict[str, Any], None] = None) -> str: @@ -455,13 +520,20 @@ class EmptySession(session.SessionContainer): def get_access_token(self, user_context: Union[Dict[str, Any], None] = None) -> str: return "" - async def get_time_created(self, user_context: Union[Dict[str, Any], None] = None) -> int: + async def get_time_created( + self, user_context: Union[Dict[str, Any], None] = None + ) -> int: return -1 async def get_expiry(self, user_context: Union[Dict[str, Any], None] = None) -> int: return -1 - async def attach_to_request_response(self, request: BaseRequest, transfer_method: TokenTransferMethod, user_context: Union[Dict[str, Any], None] = None): + async def attach_to_request_response( + self, + request: BaseRequest, + transfer_method: TokenTransferMethod, + user_context: Union[Dict[str, Any], None] = None, + ): pass async def assert_claims( @@ -497,7 +569,9 @@ class EmptySession(session.SessionContainer): pass async def merge_into_access_token_payload( - self, access_token_payload_update: Dict[str, Any], user_context: Union[Dict[str, Any], None] = None + self, + access_token_payload_update: Dict[str, Any], + user_context: Union[Dict[str, Any], None] = None, ) -> None: pass @@ -507,9 +581,9 @@ class EmptySession(session.SessionContainer): "accessToken": "", "antiCsrfToken": None, "frontToken": "", - "refreshToken": None + "refreshToken": None, } - + def get_tenant_id(self, user_context: Union[Dict[str, Any], None] = None) -> str: return "" ``` diff --git a/v2/thirdpartyemailpassword/advanced-customizations/backend-functions-override/usage.mdx b/v2/thirdpartyemailpassword/advanced-customizations/backend-functions-override/usage.mdx index 3938f7cdf..fdfc30ad9 100644 --- a/v2/thirdpartyemailpassword/advanced-customizations/backend-functions-override/usage.mdx +++ b/v2/thirdpartyemailpassword/advanced-customizations/backend-functions-override/usage.mdx @@ -127,45 +127,65 @@ from supertokens_python import init, InputAppInfo from supertokens_python.recipe import thirdparty from supertokens_python.recipe.thirdparty.interfaces import RecipeInterface from supertokens_python.recipe.thirdparty.types import RawUserInfoFromProvider -from typing import Dict, Any +from typing import Dict, Any, Optional, Union +from supertokens_python.recipe.session.interfaces import SessionContainer + # highlight-start def override_thirdparty_functions(original_implementation: RecipeInterface): - original_sign_in_up = original_implementation.sign_in_up + original_sign_in_up = original_implementation.sign_in_up - async def sign_in_up( + async def sign_in_up( third_party_id: str, third_party_user_id: str, email: str, + is_verified: bool, oauth_tokens: Dict[str, Any], raw_user_info_from_provider: RawUserInfoFromProvider, + session: Optional[SessionContainer], + should_try_linking_with_session_user: Union[bool, None], tenant_id: str, - user_context: Dict[str, Any] + user_context: Dict[str, Any], ): - # TODO: custom logic + # TODO: custom logic + + # or call the default behaviour as show below + return await original_sign_in_up( + third_party_id, + third_party_user_id, + email, + is_verified, + oauth_tokens, + raw_user_info_from_provider, + session, + should_try_linking_with_session_user, + tenant_id, + user_context, + ) + + original_implementation.sign_in_up = sign_in_up + return original_implementation + - # or call the default behaviour as show below - return await original_sign_in_up(third_party_id, third_party_user_id, email, oauth_tokens, raw_user_info_from_provider, tenant_id, user_context) - - original_implementation.sign_in_up = sign_in_up - return original_implementation # highlight-end init( app_info=InputAppInfo(api_domain="...", app_name="...", website_domain="..."), - framework='...', # type: ignore + framework="...", # type: ignore recipe_list=[ thirdparty.init( - # highlight-start + # highlight-start override=thirdparty.InputOverrideConfig( functions=override_thirdparty_functions ), - # highlight-end - sign_in_and_up_feature=thirdparty.SignInAndUpFeature(providers=[ - #... - ]) + # highlight-end + sign_in_and_up_feature=thirdparty.SignInAndUpFeature( + providers=[ + # ... + ] + ), ) - ] + ], ) ``` From 17eccd2191d314bc5129823ae29e9324bb1599ad Mon Sep 17 00:00:00 2001 From: rishabhpoddar Date: Sun, 20 Oct 2024 19:26:54 +0530 Subject: [PATCH 26/34] more fixes --- .../implementing-deduplication.mdx | 108 +++++++++++++----- 1 file changed, 81 insertions(+), 27 deletions(-) diff --git a/v2/thirdparty/common-customizations/deduplication/implementing-deduplication.mdx b/v2/thirdparty/common-customizations/deduplication/implementing-deduplication.mdx index eff7bb1ad..da8db1729 100644 --- a/v2/thirdparty/common-customizations/deduplication/implementing-deduplication.mdx +++ b/v2/thirdparty/common-customizations/deduplication/implementing-deduplication.mdx @@ -156,11 +156,18 @@ func main() { from supertokens_python import init, InputAppInfo from supertokens_python.types import GeneralErrorResponse from supertokens_python.recipe import thirdparty -from supertokens_python.recipe.thirdparty.asyncio import get_users_by_email -from supertokens_python.recipe.thirdparty.interfaces import APIInterface, APIOptions, RecipeInterface, SignInUpOkResult -from typing import Union, Dict, Any +from supertokens_python.asyncio import list_users_by_account_info +from supertokens_python.types import AccountInfo +from supertokens_python.recipe.thirdparty.interfaces import ( + APIInterface, + APIOptions, + RecipeInterface, +) +from supertokens_python.recipe.thirdparty.types import ThirdPartyInfo +from typing import Union, Dict, Any, Optional from supertokens_python.recipe.thirdparty.provider import Provider, RedirectUriInfo from supertokens_python.recipe.thirdparty.types import RawUserInfoFromProvider +from supertokens_python.recipe.session.interfaces import SessionContainer def override_thirdparty_functions(original_implementation: RecipeInterface): @@ -170,21 +177,55 @@ def override_thirdparty_functions(original_implementation: RecipeInterface): third_party_id: str, third_party_user_id: str, email: str, + is_verified: bool, oauth_tokens: Dict[str, Any], raw_user_info_from_provider: RawUserInfoFromProvider, + session: Optional[SessionContainer], + should_try_linking_with_session_user: Union[bool, None], tenant_id: str, - user_context: Dict[str, Any] - ) -> SignInUpOkResult: - existing_users = await get_users_by_email(tenant_id, email, user_context) - if (len(existing_users) == 0): + user_context: Dict[str, Any], + ): + existing_users = await list_users_by_account_info( + tenant_id, AccountInfo(email=email) + ) + if len(existing_users) == 0: # this means this email is new so we allow sign up - return await original_sign_in_up(third_party_id, third_party_user_id, email, oauth_tokens, raw_user_info_from_provider, tenant_id, user_context) - - if (len([x for x in existing_users if - x.third_party_info.id == third_party_id and - x.third_party_info.user_id == third_party_user_id]) > 0): + return await original_sign_in_up( + third_party_id, + third_party_user_id, + email, + is_verified, + oauth_tokens, + raw_user_info_from_provider, + session, + should_try_linking_with_session_user, + tenant_id, + user_context, + ) + + if any( + any( + lm.recipe_id == "thirdparty" + and lm.has_same_third_party_info_as( + ThirdPartyInfo(third_party_user_id, third_party_id) + ) + for lm in user.login_methods + ) + for user in existing_users + ): # this means we are trying to sign in with the same social login. So we allow it - return await original_sign_in_up(third_party_id, third_party_user_id, email, oauth_tokens, raw_user_info_from_provider, tenant_id, user_context) + return await original_sign_in_up( + third_party_id, + third_party_user_id, + email, + is_verified, + oauth_tokens, + raw_user_info_from_provider, + session, + should_try_linking_with_session_user, + tenant_id, + user_context, + ) # this means that the email already exists with another social login method. # so we throw an error. @@ -200,17 +241,30 @@ def override_thirdparty_apis(original_implementation: APIInterface): async def sign_in_up_post( provider: Provider, - redirect_uri_info: Union[RedirectUriInfo, None], - oauth_tokens: Union[Dict[str, Any], None], + redirect_uri_info: Optional[RedirectUriInfo], + oauth_tokens: Optional[Dict[str, Any]], + session: Optional[SessionContainer], + should_try_linking_with_session_user: Union[bool, None], tenant_id: str, api_options: APIOptions, - user_context: Dict[str, Any] + user_context: Dict[str, Any], ): try: - return await original_sign_in_up_post(provider, redirect_uri_info, oauth_tokens, tenant_id, api_options, user_context) + return await original_sign_in_up_post( + provider, + redirect_uri_info, + oauth_tokens, + session, + should_try_linking_with_session_user, + tenant_id, + api_options, + user_context, + ) except Exception as e: if str(e) == "Cannot sign up as email already exists": - return GeneralErrorResponse("Seems like you already have an account with another social login provider. Please use that instead.") + return GeneralErrorResponse( + "Seems like you already have an account with another social login provider. Please use that instead." + ) raise e original_implementation.sign_in_up_post = sign_in_up_post @@ -218,20 +272,20 @@ def override_thirdparty_apis(original_implementation: APIInterface): init( - app_info=InputAppInfo( - api_domain="...", app_name="...", website_domain="..."), - framework='...', # type: ignore + app_info=InputAppInfo(api_domain="...", app_name="...", website_domain="..."), + framework="...", # type: ignore recipe_list=[ thirdparty.init( override=thirdparty.InputOverrideConfig( - apis=override_thirdparty_apis, - functions=override_thirdparty_functions + apis=override_thirdparty_apis, functions=override_thirdparty_functions + ), + sign_in_and_up_feature=thirdparty.SignInAndUpFeature( + providers=[ + # ... + ] ), - sign_in_and_up_feature=thirdparty.SignInAndUpFeature(providers=[ - # ... - ]) ) - ] + ], ) ``` From 396bf8f50394273b29fa802138e1976adcc9922c Mon Sep 17 00:00:00 2001 From: rishabhpoddar Date: Mon, 21 Oct 2024 18:41:19 +0530 Subject: [PATCH 27/34] more changes --- .../tenant-management/overview.mdx | 2 +- .../tenant-management/overview.mdx | 2 +- .../tenant-management/overview.mdx | 2 +- .../tenant-management/overview.mdx | 2 +- .../automatic-account-linking.mdx | 45 +++- .../manual-account-linking.mdx | 234 ++++++++++++++++-- .../saml/with-boxyhq/integration-steps.mdx | 2 +- .../tenant-management/overview.mdx | 2 +- .../tenant-management/overview.mdx | 2 +- .../automatic-account-linking.mdx | 45 +++- .../manual-account-linking.mdx | 234 ++++++++++++++++-- .../saml/with-boxyhq/integration-steps.mdx | 2 +- .../tenant-management/overview.mdx | 2 +- .../tenant-management/overview.mdx | 2 +- .../automatic-account-linking.mdx | 45 +++- .../manual-account-linking.mdx | 234 ++++++++++++++++-- .../saml/with-boxyhq/integration-steps.mdx | 2 +- .../tenant-management/overview.mdx | 2 +- .../tenant-management/overview.mdx | 2 +- .../tenant-management/overview.mdx | 2 +- 20 files changed, 776 insertions(+), 89 deletions(-) diff --git a/v2/emailpassword/custom-ui/init/user-management-dashboard/tenant-management/overview.mdx b/v2/emailpassword/custom-ui/init/user-management-dashboard/tenant-management/overview.mdx index c0fe4e604..32c5447bd 100644 --- a/v2/emailpassword/custom-ui/init/user-management-dashboard/tenant-management/overview.mdx +++ b/v2/emailpassword/custom-ui/init/user-management-dashboard/tenant-management/overview.mdx @@ -16,7 +16,7 @@ Once the dashboard recipe is initialised, the tenant management should be availa :::caution -Currently this is only available with the Node SDK version >= 20.0.1 and will be available for python and golang SDKs soon. +Currently, this is only available with our Node and Python SDKs. ::: diff --git a/v2/emailpassword/pre-built-ui/setup/user-management-dashboard/tenant-management/overview.mdx b/v2/emailpassword/pre-built-ui/setup/user-management-dashboard/tenant-management/overview.mdx index c0fe4e604..32c5447bd 100644 --- a/v2/emailpassword/pre-built-ui/setup/user-management-dashboard/tenant-management/overview.mdx +++ b/v2/emailpassword/pre-built-ui/setup/user-management-dashboard/tenant-management/overview.mdx @@ -16,7 +16,7 @@ Once the dashboard recipe is initialised, the tenant management should be availa :::caution -Currently this is only available with the Node SDK version >= 20.0.1 and will be available for python and golang SDKs soon. +Currently, this is only available with our Node and Python SDKs. ::: diff --git a/v2/passwordless/custom-ui/init/user-management-dashboard/tenant-management/overview.mdx b/v2/passwordless/custom-ui/init/user-management-dashboard/tenant-management/overview.mdx index c0fe4e604..32c5447bd 100644 --- a/v2/passwordless/custom-ui/init/user-management-dashboard/tenant-management/overview.mdx +++ b/v2/passwordless/custom-ui/init/user-management-dashboard/tenant-management/overview.mdx @@ -16,7 +16,7 @@ Once the dashboard recipe is initialised, the tenant management should be availa :::caution -Currently this is only available with the Node SDK version >= 20.0.1 and will be available for python and golang SDKs soon. +Currently, this is only available with our Node and Python SDKs. ::: diff --git a/v2/passwordless/pre-built-ui/setup/user-management-dashboard/tenant-management/overview.mdx b/v2/passwordless/pre-built-ui/setup/user-management-dashboard/tenant-management/overview.mdx index c0fe4e604..32c5447bd 100644 --- a/v2/passwordless/pre-built-ui/setup/user-management-dashboard/tenant-management/overview.mdx +++ b/v2/passwordless/pre-built-ui/setup/user-management-dashboard/tenant-management/overview.mdx @@ -16,7 +16,7 @@ Once the dashboard recipe is initialised, the tenant management should be availa :::caution -Currently this is only available with the Node SDK version >= 20.0.1 and will be available for python and golang SDKs soon. +Currently, this is only available with our Node and Python SDKs. ::: diff --git a/v2/thirdparty/common-customizations/account-linking/automatic-account-linking.mdx b/v2/thirdparty/common-customizations/account-linking/automatic-account-linking.mdx index c4651bf62..74785918e 100644 --- a/v2/thirdparty/common-customizations/account-linking/automatic-account-linking.mdx +++ b/v2/thirdparty/common-customizations/account-linking/automatic-account-linking.mdx @@ -84,9 +84,48 @@ Coming Soon -:::note -Coming Soon -::: +```python +from supertokens_python import init, InputAppInfo, SupertokensConfig +from supertokens_python.recipe import accountlinking +from supertokens_python.types import User +from supertokens_python.recipe.session.interfaces import SessionContainer +from supertokens_python.recipe.accountlinking.types import AccountInfoWithRecipeIdAndUserId, ShouldNotAutomaticallyLink, ShouldAutomaticallyLink +from typing import Dict, Any, Optional, Union + +async def should_do_automatic_account_linking( + new_account_info: AccountInfoWithRecipeIdAndUserId, + user: Optional[User], + session: Optional[SessionContainer], + tenant_id: str, + user_context: Dict[str, Any] +) -> Union[ShouldNotAutomaticallyLink, ShouldAutomaticallyLink]: + if session is not None: + return ShouldNotAutomaticallyLink() + + if new_account_info.recipe_user_id is not None and user is not None: + _user_id = new_account_info.recipe_user_id.get_as_string() + has_info_associated_with_user_id = False # TODO: add your own implementation here. + if has_info_associated_with_user_id: + return ShouldNotAutomaticallyLink() + + return ShouldAutomaticallyLink(should_require_verification=True) + + +init( + app_info=InputAppInfo( + app_name="...", + api_domain="...", + website_domain="...", + ), + supertokens_config=SupertokensConfig( + connection_uri="...", + ), + framework='...', # type: ignore + recipe_list=[ + accountlinking.init(should_do_automatic_account_linking=should_do_automatic_account_linking) + ], +) +``` diff --git a/v2/thirdparty/common-customizations/account-linking/manual-account-linking.mdx b/v2/thirdparty/common-customizations/account-linking/manual-account-linking.mdx index 23efb0e63..1efedbddc 100644 --- a/v2/thirdparty/common-customizations/account-linking/manual-account-linking.mdx +++ b/v2/thirdparty/common-customizations/account-linking/manual-account-linking.mdx @@ -10,6 +10,7 @@ hide_title: true import AccountLinkingPaidBanner from '../../../community/reusableMD/accountlinking/AccountLinkingPaidBanner.mdx' import BackendSDKTabs from "/src/components/tabs/BackendSDKTabs"; import TabItem from '@theme/TabItem'; +import PythonSyncAsyncSubTabs from "/src/components/tabs/PythonSyncAsyncSubTabs"; @@ -43,13 +44,7 @@ supertokens.init({ }, recipeList: [ // highlight-start - AccountLinking.init({ - shouldDoAutomaticAccountLinking: async (newAccountInfo: AccountInfoWithRecipeId & { recipeUserId?: RecipeUserId }, user: User | undefined, session: SessionContainerInterface | undefined, tenantId: string, userContext: any) => { - return { - shouldAutomaticallyLink: false - } - } - }) + AccountLinking.init() // highlight-end ] }); @@ -65,9 +60,26 @@ Coming Soon -:::note -Coming Soon -::: +```python +from supertokens_python import init, InputAppInfo, SupertokensConfig +from supertokens_python.recipe import accountlinking + + +init( + app_info=InputAppInfo( + app_name="...", + api_domain="...", + website_domain="...", + ), + supertokens_config=SupertokensConfig( + connection_uri="...", + ), + framework='...', # type: ignore + recipe_list=[ + accountlinking.init() + ], +) +``` @@ -116,9 +128,62 @@ Coming Soon -:::note -Coming Soon -::: + + + +```python +from supertokens_python.recipe.accountlinking.asyncio import create_primary_user +from supertokens_python.types import RecipeUserId + +async def make_user_primary(recipe_user_id: RecipeUserId): + response = await create_primary_user(recipe_user_id) + if response.status == "OK": + if response.was_already_a_primary_user: + # The input user was already a primary user and accounts can be linked to it. + pass + else: + # User is now primary and accounts can be linked to it. + pass + modified_user = response.user + print(modified_user.is_primary_user) # will print True + elif response.status == "ACCOUNT_INFO_ALREADY_ASSOCIATED_WITH_ANOTHER_PRIMARY_USER_ID_ERROR": + # This happens if there already exists another primary user with the same email or phone number + # in at least one of the tenants that this user belongs to. + pass + elif response.status == "RECIPE_USER_ID_ALREADY_LINKED_WITH_PRIMARY_USER_ID_ERROR": + # This happens if this user is already linked to another primary user. + pass +``` + + + + +```python +from supertokens_python.recipe.accountlinking.syncio import create_primary_user +from supertokens_python.types import RecipeUserId + +def make_user_primary(recipe_user_id: RecipeUserId): + response = create_primary_user(recipe_user_id) + if response.status == "OK": + if response.was_already_a_primary_user: + # The input user was already a primary user and accounts can be linked to it. + pass + else: + # User is now primary and accounts can be linked to it. + pass + modified_user = response.user + print(modified_user.is_primary_user) # will print True + elif response.status == "ACCOUNT_INFO_ALREADY_ASSOCIATED_WITH_ANOTHER_PRIMARY_USER_ID_ERROR": + # This happens if there already exists another primary user with the same email or phone number + # in at least one of the tenants that this user belongs to. + pass + elif response.status == "RECIPE_USER_ID_ALREADY_LINKED_WITH_PRIMARY_USER_ID_ERROR": + # This happens if this user is already linked to another primary user. + pass +``` + + + @@ -168,9 +233,72 @@ Coming Soon -:::note -Coming Soon -::: + + + +```python +from supertokens_python.recipe.accountlinking.asyncio import link_accounts +from supertokens_python.types import RecipeUserId + +async def link_accounts_helper(primary_user_id: str, recipe_user_id: RecipeUserId): + response = await link_accounts(recipe_user_id, primary_user_id) + if response.status == "OK": + if response.accounts_already_linked: + # The input users were already linked + pass + else: + # The two users are now linked + pass + modified_user = response.user + print(modified_user.login_methods) # this will now contain the login method of the recipeUserId as well. + elif response.status == "ACCOUNT_INFO_ALREADY_ASSOCIATED_WITH_ANOTHER_PRIMARY_USER_ID_ERROR": + # This happens if there already exists another primary user with the same email or phone number + # as the recipeUserId's account. + pass + elif response.status == "INPUT_USER_IS_NOT_A_PRIMARY_USER": + # This happens if the input primaryUserId is not actually a primary user ID. + # You can call create_primary_user and call link_accounts again + pass + elif response.status == "RECIPE_USER_ID_ALREADY_LINKED_WITH_ANOTHER_PRIMARY_USER_ID_ERROR": + # This happens if the input recipe user ID is already linked to another primary user. + # You can call unlink_accounts on the recipe user ID and then try linking again. + pass +``` + + + + +```python +from supertokens_python.recipe.accountlinking.syncio import link_accounts +from supertokens_python.types import RecipeUserId + +def link_accounts_helper(primary_user_id: str, recipe_user_id: RecipeUserId): + response = link_accounts(recipe_user_id, primary_user_id) + if response.status == "OK": + if response.accounts_already_linked: + # The input users were already linked + pass + else: + # The two users are now linked + pass + modified_user = response.user + print(modified_user.login_methods) # this will now contain the login method of the recipeUserId as well. + elif response.status == "ACCOUNT_INFO_ALREADY_ASSOCIATED_WITH_ANOTHER_PRIMARY_USER_ID_ERROR": + # This happens if there already exists another primary user with the same email or phone number + # as the recipeUserId's account. + pass + elif response.status == "INPUT_USER_IS_NOT_A_PRIMARY_USER": + # This happens if the input primaryUserId is not actually a primary user ID. + # You can call create_primary_user and call link_accounts again + pass + elif response.status == "RECIPE_USER_ID_ALREADY_LINKED_WITH_ANOTHER_PRIMARY_USER_ID_ERROR": + # This happens if the input recipe user ID is already linked to another primary user. + # You can call unlink_accounts on the recipe user ID and then try linking again. + pass +``` + + + @@ -221,9 +349,68 @@ Coming Soon -:::note -Coming Soon -::: + + + +```python +from supertokens_python.recipe.accountlinking.asyncio import unlink_account +from supertokens_python.types import RecipeUserId + +async def unlink_account_helper(recipe_user_id: RecipeUserId): + response = await unlink_account(recipe_user_id) + if response.was_linked: + # This means that we unlinked the account from its primary user ID + pass + else: + # This means that the user was never linked in the first place + pass + + if response.was_recipe_user_deleted: + # This is true if we call unlink_account on the recipe user ID of the primary user ID user. + # We delete this user because if we don't and we call get_user_by_id() on this user's ID, SuperTokens + # won't know which user info to return - the primary user, or the recipe user. + # Note that even though the recipe user is deleted, the session, metadata, roles etc for this + # primary user is still intact, and calling get_user_by_id(primary_user_id) will still return + # the user object with the other login methods. + pass + else: + # There now exists a user account which is not a primary user, with the recipe_user_id equal to the + # input recipe_user_id. + pass +``` + + + + +```python +from supertokens_python.recipe.accountlinking.syncio import unlink_account +from supertokens_python.types import RecipeUserId + +def unlink_account_helper(recipe_user_id: RecipeUserId): + response = unlink_account(recipe_user_id) + if response.was_linked: + # This means that we unlinked the account from its primary user ID + pass + else: + # This means that the user was never linked in the first place + pass + + if response.was_recipe_user_deleted: + # This is true if we call unlink_account on the recipe user ID of the primary user ID user. + # We delete this user because if we don't and we call get_user_by_id() on this user's ID, SuperTokens + # won't know which user info to return - the primary user, or the recipe user. + # Note that even though the recipe user is deleted, the session, metadata, roles etc for this + # primary user is still intact, and calling get_user_by_id(primary_user_id) will still return + # the user object with the other login methods. + pass + else: + # There now exists a user account which is not a primary user, with the recipe_user_id equal to the + # input recipe_user_id. + pass +``` + + + @@ -252,9 +439,12 @@ Coming Soon -:::note -Coming Soon -::: +```python +from supertokens_python.types import RecipeUserId + +user_id = "some_user_id"; +recipe_user_id = RecipeUserId(user_id) +``` diff --git a/v2/thirdparty/common-customizations/saml/with-boxyhq/integration-steps.mdx b/v2/thirdparty/common-customizations/saml/with-boxyhq/integration-steps.mdx index 3b0c42dda..c1fdabbbd 100644 --- a/v2/thirdparty/common-customizations/saml/with-boxyhq/integration-steps.mdx +++ b/v2/thirdparty/common-customizations/saml/with-boxyhq/integration-steps.mdx @@ -23,7 +23,7 @@ import CoreInjector from "/src/components/coreInjector" :::caution -Currently this is only available with the Node SDK version >= 20.0.1 and will be available for python and golang SDKs soon. +Currently, this is only available with our Node and Python SDKs. ::: diff --git a/v2/thirdparty/custom-ui/init/user-management-dashboard/tenant-management/overview.mdx b/v2/thirdparty/custom-ui/init/user-management-dashboard/tenant-management/overview.mdx index c0fe4e604..32c5447bd 100644 --- a/v2/thirdparty/custom-ui/init/user-management-dashboard/tenant-management/overview.mdx +++ b/v2/thirdparty/custom-ui/init/user-management-dashboard/tenant-management/overview.mdx @@ -16,7 +16,7 @@ Once the dashboard recipe is initialised, the tenant management should be availa :::caution -Currently this is only available with the Node SDK version >= 20.0.1 and will be available for python and golang SDKs soon. +Currently, this is only available with our Node and Python SDKs. ::: diff --git a/v2/thirdparty/pre-built-ui/setup/user-management-dashboard/tenant-management/overview.mdx b/v2/thirdparty/pre-built-ui/setup/user-management-dashboard/tenant-management/overview.mdx index c0fe4e604..32c5447bd 100644 --- a/v2/thirdparty/pre-built-ui/setup/user-management-dashboard/tenant-management/overview.mdx +++ b/v2/thirdparty/pre-built-ui/setup/user-management-dashboard/tenant-management/overview.mdx @@ -16,7 +16,7 @@ Once the dashboard recipe is initialised, the tenant management should be availa :::caution -Currently this is only available with the Node SDK version >= 20.0.1 and will be available for python and golang SDKs soon. +Currently, this is only available with our Node and Python SDKs. ::: diff --git a/v2/thirdpartyemailpassword/common-customizations/account-linking/automatic-account-linking.mdx b/v2/thirdpartyemailpassword/common-customizations/account-linking/automatic-account-linking.mdx index 66928222e..c51d114c7 100644 --- a/v2/thirdpartyemailpassword/common-customizations/account-linking/automatic-account-linking.mdx +++ b/v2/thirdpartyemailpassword/common-customizations/account-linking/automatic-account-linking.mdx @@ -84,9 +84,48 @@ Coming Soon -:::note -Coming Soon -::: +```python +from supertokens_python import init, InputAppInfo, SupertokensConfig +from supertokens_python.recipe import accountlinking +from supertokens_python.types import User +from supertokens_python.recipe.session.interfaces import SessionContainer +from supertokens_python.recipe.accountlinking.types import AccountInfoWithRecipeIdAndUserId, ShouldNotAutomaticallyLink, ShouldAutomaticallyLink +from typing import Dict, Any, Optional, Union + +async def should_do_automatic_account_linking( + new_account_info: AccountInfoWithRecipeIdAndUserId, + user: Optional[User], + session: Optional[SessionContainer], + tenant_id: str, + user_context: Dict[str, Any] +) -> Union[ShouldNotAutomaticallyLink, ShouldAutomaticallyLink]: + if session is not None: + return ShouldNotAutomaticallyLink() + + if new_account_info.recipe_user_id is not None and user is not None: + _user_id = new_account_info.recipe_user_id.get_as_string() + has_info_associated_with_user_id = False # TODO: add your own implementation here. + if has_info_associated_with_user_id: + return ShouldNotAutomaticallyLink() + + return ShouldAutomaticallyLink(should_require_verification=True) + + +init( + app_info=InputAppInfo( + app_name="...", + api_domain="...", + website_domain="...", + ), + supertokens_config=SupertokensConfig( + connection_uri="...", + ), + framework='...', # type: ignore + recipe_list=[ + accountlinking.init(should_do_automatic_account_linking=should_do_automatic_account_linking) + ], +) +``` diff --git a/v2/thirdpartyemailpassword/common-customizations/account-linking/manual-account-linking.mdx b/v2/thirdpartyemailpassword/common-customizations/account-linking/manual-account-linking.mdx index b73408c8a..e0e20ec04 100644 --- a/v2/thirdpartyemailpassword/common-customizations/account-linking/manual-account-linking.mdx +++ b/v2/thirdpartyemailpassword/common-customizations/account-linking/manual-account-linking.mdx @@ -10,6 +10,7 @@ hide_title: true import AccountLinkingPaidBanner from '../../../community/reusableMD/accountlinking/AccountLinkingPaidBanner.mdx' import BackendSDKTabs from "/src/components/tabs/BackendSDKTabs"; import TabItem from '@theme/TabItem'; +import PythonSyncAsyncSubTabs from "/src/components/tabs/PythonSyncAsyncSubTabs"; @@ -43,13 +44,7 @@ supertokens.init({ }, recipeList: [ // highlight-start - AccountLinking.init({ - shouldDoAutomaticAccountLinking: async (newAccountInfo: AccountInfoWithRecipeId & { recipeUserId?: RecipeUserId }, user: User | undefined, session: SessionContainerInterface | undefined, tenantId: string, userContext: any) => { - return { - shouldAutomaticallyLink: false - } - } - }) + AccountLinking.init() // highlight-end ] }); @@ -65,9 +60,26 @@ Coming Soon -:::note -Coming Soon -::: +```python +from supertokens_python import init, InputAppInfo, SupertokensConfig +from supertokens_python.recipe import accountlinking + + +init( + app_info=InputAppInfo( + app_name="...", + api_domain="...", + website_domain="...", + ), + supertokens_config=SupertokensConfig( + connection_uri="...", + ), + framework='...', # type: ignore + recipe_list=[ + accountlinking.init() + ], +) +``` @@ -116,9 +128,62 @@ Coming Soon -:::note -Coming Soon -::: + + + +```python +from supertokens_python.recipe.accountlinking.asyncio import create_primary_user +from supertokens_python.types import RecipeUserId + +async def make_user_primary(recipe_user_id: RecipeUserId): + response = await create_primary_user(recipe_user_id) + if response.status == "OK": + if response.was_already_a_primary_user: + # The input user was already a primary user and accounts can be linked to it. + pass + else: + # User is now primary and accounts can be linked to it. + pass + modified_user = response.user + print(modified_user.is_primary_user) # will print True + elif response.status == "ACCOUNT_INFO_ALREADY_ASSOCIATED_WITH_ANOTHER_PRIMARY_USER_ID_ERROR": + # This happens if there already exists another primary user with the same email or phone number + # in at least one of the tenants that this user belongs to. + pass + elif response.status == "RECIPE_USER_ID_ALREADY_LINKED_WITH_PRIMARY_USER_ID_ERROR": + # This happens if this user is already linked to another primary user. + pass +``` + + + + +```python +from supertokens_python.recipe.accountlinking.syncio import create_primary_user +from supertokens_python.types import RecipeUserId + +def make_user_primary(recipe_user_id: RecipeUserId): + response = create_primary_user(recipe_user_id) + if response.status == "OK": + if response.was_already_a_primary_user: + # The input user was already a primary user and accounts can be linked to it. + pass + else: + # User is now primary and accounts can be linked to it. + pass + modified_user = response.user + print(modified_user.is_primary_user) # will print True + elif response.status == "ACCOUNT_INFO_ALREADY_ASSOCIATED_WITH_ANOTHER_PRIMARY_USER_ID_ERROR": + # This happens if there already exists another primary user with the same email or phone number + # in at least one of the tenants that this user belongs to. + pass + elif response.status == "RECIPE_USER_ID_ALREADY_LINKED_WITH_PRIMARY_USER_ID_ERROR": + # This happens if this user is already linked to another primary user. + pass +``` + + + @@ -168,9 +233,72 @@ Coming Soon -:::note -Coming Soon -::: + + + +```python +from supertokens_python.recipe.accountlinking.asyncio import link_accounts +from supertokens_python.types import RecipeUserId + +async def link_accounts_helper(primary_user_id: str, recipe_user_id: RecipeUserId): + response = await link_accounts(recipe_user_id, primary_user_id) + if response.status == "OK": + if response.accounts_already_linked: + # The input users were already linked + pass + else: + # The two users are now linked + pass + modified_user = response.user + print(modified_user.login_methods) # this will now contain the login method of the recipeUserId as well. + elif response.status == "ACCOUNT_INFO_ALREADY_ASSOCIATED_WITH_ANOTHER_PRIMARY_USER_ID_ERROR": + # This happens if there already exists another primary user with the same email or phone number + # as the recipeUserId's account. + pass + elif response.status == "INPUT_USER_IS_NOT_A_PRIMARY_USER": + # This happens if the input primaryUserId is not actually a primary user ID. + # You can call create_primary_user and call link_accounts again + pass + elif response.status == "RECIPE_USER_ID_ALREADY_LINKED_WITH_ANOTHER_PRIMARY_USER_ID_ERROR": + # This happens if the input recipe user ID is already linked to another primary user. + # You can call unlink_accounts on the recipe user ID and then try linking again. + pass +``` + + + + +```python +from supertokens_python.recipe.accountlinking.syncio import link_accounts +from supertokens_python.types import RecipeUserId + +def link_accounts_helper(primary_user_id: str, recipe_user_id: RecipeUserId): + response = link_accounts(recipe_user_id, primary_user_id) + if response.status == "OK": + if response.accounts_already_linked: + # The input users were already linked + pass + else: + # The two users are now linked + pass + modified_user = response.user + print(modified_user.login_methods) # this will now contain the login method of the recipeUserId as well. + elif response.status == "ACCOUNT_INFO_ALREADY_ASSOCIATED_WITH_ANOTHER_PRIMARY_USER_ID_ERROR": + # This happens if there already exists another primary user with the same email or phone number + # as the recipeUserId's account. + pass + elif response.status == "INPUT_USER_IS_NOT_A_PRIMARY_USER": + # This happens if the input primaryUserId is not actually a primary user ID. + # You can call create_primary_user and call link_accounts again + pass + elif response.status == "RECIPE_USER_ID_ALREADY_LINKED_WITH_ANOTHER_PRIMARY_USER_ID_ERROR": + # This happens if the input recipe user ID is already linked to another primary user. + # You can call unlink_accounts on the recipe user ID and then try linking again. + pass +``` + + + @@ -221,9 +349,68 @@ Coming Soon -:::note -Coming Soon -::: + + + +```python +from supertokens_python.recipe.accountlinking.asyncio import unlink_account +from supertokens_python.types import RecipeUserId + +async def unlink_account_helper(recipe_user_id: RecipeUserId): + response = await unlink_account(recipe_user_id) + if response.was_linked: + # This means that we unlinked the account from its primary user ID + pass + else: + # This means that the user was never linked in the first place + pass + + if response.was_recipe_user_deleted: + # This is true if we call unlink_account on the recipe user ID of the primary user ID user. + # We delete this user because if we don't and we call get_user_by_id() on this user's ID, SuperTokens + # won't know which user info to return - the primary user, or the recipe user. + # Note that even though the recipe user is deleted, the session, metadata, roles etc for this + # primary user is still intact, and calling get_user_by_id(primary_user_id) will still return + # the user object with the other login methods. + pass + else: + # There now exists a user account which is not a primary user, with the recipe_user_id equal to the + # input recipe_user_id. + pass +``` + + + + +```python +from supertokens_python.recipe.accountlinking.syncio import unlink_account +from supertokens_python.types import RecipeUserId + +def unlink_account_helper(recipe_user_id: RecipeUserId): + response = unlink_account(recipe_user_id) + if response.was_linked: + # This means that we unlinked the account from its primary user ID + pass + else: + # This means that the user was never linked in the first place + pass + + if response.was_recipe_user_deleted: + # This is true if we call unlink_account on the recipe user ID of the primary user ID user. + # We delete this user because if we don't and we call get_user_by_id() on this user's ID, SuperTokens + # won't know which user info to return - the primary user, or the recipe user. + # Note that even though the recipe user is deleted, the session, metadata, roles etc for this + # primary user is still intact, and calling get_user_by_id(primary_user_id) will still return + # the user object with the other login methods. + pass + else: + # There now exists a user account which is not a primary user, with the recipe_user_id equal to the + # input recipe_user_id. + pass +``` + + + @@ -252,9 +439,12 @@ Coming Soon -:::note -Coming Soon -::: +```python +from supertokens_python.types import RecipeUserId + +user_id = "some_user_id"; +recipe_user_id = RecipeUserId(user_id) +``` diff --git a/v2/thirdpartyemailpassword/common-customizations/saml/with-boxyhq/integration-steps.mdx b/v2/thirdpartyemailpassword/common-customizations/saml/with-boxyhq/integration-steps.mdx index 5fdffecab..d39b92cde 100644 --- a/v2/thirdpartyemailpassword/common-customizations/saml/with-boxyhq/integration-steps.mdx +++ b/v2/thirdpartyemailpassword/common-customizations/saml/with-boxyhq/integration-steps.mdx @@ -23,7 +23,7 @@ import CoreInjector from "/src/components/coreInjector" :::caution -Currently this is only available with the Node SDK version >= 20.0.1 and will be available for python and golang SDKs soon. +Currently, this is only available with our Node and Python SDKs. ::: diff --git a/v2/thirdpartyemailpassword/custom-ui/init/user-management-dashboard/tenant-management/overview.mdx b/v2/thirdpartyemailpassword/custom-ui/init/user-management-dashboard/tenant-management/overview.mdx index c0fe4e604..32c5447bd 100644 --- a/v2/thirdpartyemailpassword/custom-ui/init/user-management-dashboard/tenant-management/overview.mdx +++ b/v2/thirdpartyemailpassword/custom-ui/init/user-management-dashboard/tenant-management/overview.mdx @@ -16,7 +16,7 @@ Once the dashboard recipe is initialised, the tenant management should be availa :::caution -Currently this is only available with the Node SDK version >= 20.0.1 and will be available for python and golang SDKs soon. +Currently, this is only available with our Node and Python SDKs. ::: diff --git a/v2/thirdpartyemailpassword/pre-built-ui/setup/user-management-dashboard/tenant-management/overview.mdx b/v2/thirdpartyemailpassword/pre-built-ui/setup/user-management-dashboard/tenant-management/overview.mdx index c0fe4e604..32c5447bd 100644 --- a/v2/thirdpartyemailpassword/pre-built-ui/setup/user-management-dashboard/tenant-management/overview.mdx +++ b/v2/thirdpartyemailpassword/pre-built-ui/setup/user-management-dashboard/tenant-management/overview.mdx @@ -16,7 +16,7 @@ Once the dashboard recipe is initialised, the tenant management should be availa :::caution -Currently this is only available with the Node SDK version >= 20.0.1 and will be available for python and golang SDKs soon. +Currently, this is only available with our Node and Python SDKs. ::: diff --git a/v2/thirdpartypasswordless/common-customizations/account-linking/automatic-account-linking.mdx b/v2/thirdpartypasswordless/common-customizations/account-linking/automatic-account-linking.mdx index c4651bf62..74785918e 100644 --- a/v2/thirdpartypasswordless/common-customizations/account-linking/automatic-account-linking.mdx +++ b/v2/thirdpartypasswordless/common-customizations/account-linking/automatic-account-linking.mdx @@ -84,9 +84,48 @@ Coming Soon -:::note -Coming Soon -::: +```python +from supertokens_python import init, InputAppInfo, SupertokensConfig +from supertokens_python.recipe import accountlinking +from supertokens_python.types import User +from supertokens_python.recipe.session.interfaces import SessionContainer +from supertokens_python.recipe.accountlinking.types import AccountInfoWithRecipeIdAndUserId, ShouldNotAutomaticallyLink, ShouldAutomaticallyLink +from typing import Dict, Any, Optional, Union + +async def should_do_automatic_account_linking( + new_account_info: AccountInfoWithRecipeIdAndUserId, + user: Optional[User], + session: Optional[SessionContainer], + tenant_id: str, + user_context: Dict[str, Any] +) -> Union[ShouldNotAutomaticallyLink, ShouldAutomaticallyLink]: + if session is not None: + return ShouldNotAutomaticallyLink() + + if new_account_info.recipe_user_id is not None and user is not None: + _user_id = new_account_info.recipe_user_id.get_as_string() + has_info_associated_with_user_id = False # TODO: add your own implementation here. + if has_info_associated_with_user_id: + return ShouldNotAutomaticallyLink() + + return ShouldAutomaticallyLink(should_require_verification=True) + + +init( + app_info=InputAppInfo( + app_name="...", + api_domain="...", + website_domain="...", + ), + supertokens_config=SupertokensConfig( + connection_uri="...", + ), + framework='...', # type: ignore + recipe_list=[ + accountlinking.init(should_do_automatic_account_linking=should_do_automatic_account_linking) + ], +) +``` diff --git a/v2/thirdpartypasswordless/common-customizations/account-linking/manual-account-linking.mdx b/v2/thirdpartypasswordless/common-customizations/account-linking/manual-account-linking.mdx index 23efb0e63..1efedbddc 100644 --- a/v2/thirdpartypasswordless/common-customizations/account-linking/manual-account-linking.mdx +++ b/v2/thirdpartypasswordless/common-customizations/account-linking/manual-account-linking.mdx @@ -10,6 +10,7 @@ hide_title: true import AccountLinkingPaidBanner from '../../../community/reusableMD/accountlinking/AccountLinkingPaidBanner.mdx' import BackendSDKTabs from "/src/components/tabs/BackendSDKTabs"; import TabItem from '@theme/TabItem'; +import PythonSyncAsyncSubTabs from "/src/components/tabs/PythonSyncAsyncSubTabs"; @@ -43,13 +44,7 @@ supertokens.init({ }, recipeList: [ // highlight-start - AccountLinking.init({ - shouldDoAutomaticAccountLinking: async (newAccountInfo: AccountInfoWithRecipeId & { recipeUserId?: RecipeUserId }, user: User | undefined, session: SessionContainerInterface | undefined, tenantId: string, userContext: any) => { - return { - shouldAutomaticallyLink: false - } - } - }) + AccountLinking.init() // highlight-end ] }); @@ -65,9 +60,26 @@ Coming Soon -:::note -Coming Soon -::: +```python +from supertokens_python import init, InputAppInfo, SupertokensConfig +from supertokens_python.recipe import accountlinking + + +init( + app_info=InputAppInfo( + app_name="...", + api_domain="...", + website_domain="...", + ), + supertokens_config=SupertokensConfig( + connection_uri="...", + ), + framework='...', # type: ignore + recipe_list=[ + accountlinking.init() + ], +) +``` @@ -116,9 +128,62 @@ Coming Soon -:::note -Coming Soon -::: + + + +```python +from supertokens_python.recipe.accountlinking.asyncio import create_primary_user +from supertokens_python.types import RecipeUserId + +async def make_user_primary(recipe_user_id: RecipeUserId): + response = await create_primary_user(recipe_user_id) + if response.status == "OK": + if response.was_already_a_primary_user: + # The input user was already a primary user and accounts can be linked to it. + pass + else: + # User is now primary and accounts can be linked to it. + pass + modified_user = response.user + print(modified_user.is_primary_user) # will print True + elif response.status == "ACCOUNT_INFO_ALREADY_ASSOCIATED_WITH_ANOTHER_PRIMARY_USER_ID_ERROR": + # This happens if there already exists another primary user with the same email or phone number + # in at least one of the tenants that this user belongs to. + pass + elif response.status == "RECIPE_USER_ID_ALREADY_LINKED_WITH_PRIMARY_USER_ID_ERROR": + # This happens if this user is already linked to another primary user. + pass +``` + + + + +```python +from supertokens_python.recipe.accountlinking.syncio import create_primary_user +from supertokens_python.types import RecipeUserId + +def make_user_primary(recipe_user_id: RecipeUserId): + response = create_primary_user(recipe_user_id) + if response.status == "OK": + if response.was_already_a_primary_user: + # The input user was already a primary user and accounts can be linked to it. + pass + else: + # User is now primary and accounts can be linked to it. + pass + modified_user = response.user + print(modified_user.is_primary_user) # will print True + elif response.status == "ACCOUNT_INFO_ALREADY_ASSOCIATED_WITH_ANOTHER_PRIMARY_USER_ID_ERROR": + # This happens if there already exists another primary user with the same email or phone number + # in at least one of the tenants that this user belongs to. + pass + elif response.status == "RECIPE_USER_ID_ALREADY_LINKED_WITH_PRIMARY_USER_ID_ERROR": + # This happens if this user is already linked to another primary user. + pass +``` + + + @@ -168,9 +233,72 @@ Coming Soon -:::note -Coming Soon -::: + + + +```python +from supertokens_python.recipe.accountlinking.asyncio import link_accounts +from supertokens_python.types import RecipeUserId + +async def link_accounts_helper(primary_user_id: str, recipe_user_id: RecipeUserId): + response = await link_accounts(recipe_user_id, primary_user_id) + if response.status == "OK": + if response.accounts_already_linked: + # The input users were already linked + pass + else: + # The two users are now linked + pass + modified_user = response.user + print(modified_user.login_methods) # this will now contain the login method of the recipeUserId as well. + elif response.status == "ACCOUNT_INFO_ALREADY_ASSOCIATED_WITH_ANOTHER_PRIMARY_USER_ID_ERROR": + # This happens if there already exists another primary user with the same email or phone number + # as the recipeUserId's account. + pass + elif response.status == "INPUT_USER_IS_NOT_A_PRIMARY_USER": + # This happens if the input primaryUserId is not actually a primary user ID. + # You can call create_primary_user and call link_accounts again + pass + elif response.status == "RECIPE_USER_ID_ALREADY_LINKED_WITH_ANOTHER_PRIMARY_USER_ID_ERROR": + # This happens if the input recipe user ID is already linked to another primary user. + # You can call unlink_accounts on the recipe user ID and then try linking again. + pass +``` + + + + +```python +from supertokens_python.recipe.accountlinking.syncio import link_accounts +from supertokens_python.types import RecipeUserId + +def link_accounts_helper(primary_user_id: str, recipe_user_id: RecipeUserId): + response = link_accounts(recipe_user_id, primary_user_id) + if response.status == "OK": + if response.accounts_already_linked: + # The input users were already linked + pass + else: + # The two users are now linked + pass + modified_user = response.user + print(modified_user.login_methods) # this will now contain the login method of the recipeUserId as well. + elif response.status == "ACCOUNT_INFO_ALREADY_ASSOCIATED_WITH_ANOTHER_PRIMARY_USER_ID_ERROR": + # This happens if there already exists another primary user with the same email or phone number + # as the recipeUserId's account. + pass + elif response.status == "INPUT_USER_IS_NOT_A_PRIMARY_USER": + # This happens if the input primaryUserId is not actually a primary user ID. + # You can call create_primary_user and call link_accounts again + pass + elif response.status == "RECIPE_USER_ID_ALREADY_LINKED_WITH_ANOTHER_PRIMARY_USER_ID_ERROR": + # This happens if the input recipe user ID is already linked to another primary user. + # You can call unlink_accounts on the recipe user ID and then try linking again. + pass +``` + + + @@ -221,9 +349,68 @@ Coming Soon -:::note -Coming Soon -::: + + + +```python +from supertokens_python.recipe.accountlinking.asyncio import unlink_account +from supertokens_python.types import RecipeUserId + +async def unlink_account_helper(recipe_user_id: RecipeUserId): + response = await unlink_account(recipe_user_id) + if response.was_linked: + # This means that we unlinked the account from its primary user ID + pass + else: + # This means that the user was never linked in the first place + pass + + if response.was_recipe_user_deleted: + # This is true if we call unlink_account on the recipe user ID of the primary user ID user. + # We delete this user because if we don't and we call get_user_by_id() on this user's ID, SuperTokens + # won't know which user info to return - the primary user, or the recipe user. + # Note that even though the recipe user is deleted, the session, metadata, roles etc for this + # primary user is still intact, and calling get_user_by_id(primary_user_id) will still return + # the user object with the other login methods. + pass + else: + # There now exists a user account which is not a primary user, with the recipe_user_id equal to the + # input recipe_user_id. + pass +``` + + + + +```python +from supertokens_python.recipe.accountlinking.syncio import unlink_account +from supertokens_python.types import RecipeUserId + +def unlink_account_helper(recipe_user_id: RecipeUserId): + response = unlink_account(recipe_user_id) + if response.was_linked: + # This means that we unlinked the account from its primary user ID + pass + else: + # This means that the user was never linked in the first place + pass + + if response.was_recipe_user_deleted: + # This is true if we call unlink_account on the recipe user ID of the primary user ID user. + # We delete this user because if we don't and we call get_user_by_id() on this user's ID, SuperTokens + # won't know which user info to return - the primary user, or the recipe user. + # Note that even though the recipe user is deleted, the session, metadata, roles etc for this + # primary user is still intact, and calling get_user_by_id(primary_user_id) will still return + # the user object with the other login methods. + pass + else: + # There now exists a user account which is not a primary user, with the recipe_user_id equal to the + # input recipe_user_id. + pass +``` + + + @@ -252,9 +439,12 @@ Coming Soon -:::note -Coming Soon -::: +```python +from supertokens_python.types import RecipeUserId + +user_id = "some_user_id"; +recipe_user_id = RecipeUserId(user_id) +``` diff --git a/v2/thirdpartypasswordless/common-customizations/saml/with-boxyhq/integration-steps.mdx b/v2/thirdpartypasswordless/common-customizations/saml/with-boxyhq/integration-steps.mdx index 3b0c42dda..c1fdabbbd 100644 --- a/v2/thirdpartypasswordless/common-customizations/saml/with-boxyhq/integration-steps.mdx +++ b/v2/thirdpartypasswordless/common-customizations/saml/with-boxyhq/integration-steps.mdx @@ -23,7 +23,7 @@ import CoreInjector from "/src/components/coreInjector" :::caution -Currently this is only available with the Node SDK version >= 20.0.1 and will be available for python and golang SDKs soon. +Currently, this is only available with our Node and Python SDKs. ::: diff --git a/v2/thirdpartypasswordless/custom-ui/init/user-management-dashboard/tenant-management/overview.mdx b/v2/thirdpartypasswordless/custom-ui/init/user-management-dashboard/tenant-management/overview.mdx index c0fe4e604..32c5447bd 100644 --- a/v2/thirdpartypasswordless/custom-ui/init/user-management-dashboard/tenant-management/overview.mdx +++ b/v2/thirdpartypasswordless/custom-ui/init/user-management-dashboard/tenant-management/overview.mdx @@ -16,7 +16,7 @@ Once the dashboard recipe is initialised, the tenant management should be availa :::caution -Currently this is only available with the Node SDK version >= 20.0.1 and will be available for python and golang SDKs soon. +Currently, this is only available with our Node and Python SDKs. ::: diff --git a/v2/thirdpartypasswordless/pre-built-ui/setup/user-management-dashboard/tenant-management/overview.mdx b/v2/thirdpartypasswordless/pre-built-ui/setup/user-management-dashboard/tenant-management/overview.mdx index c0fe4e604..32c5447bd 100644 --- a/v2/thirdpartypasswordless/pre-built-ui/setup/user-management-dashboard/tenant-management/overview.mdx +++ b/v2/thirdpartypasswordless/pre-built-ui/setup/user-management-dashboard/tenant-management/overview.mdx @@ -16,7 +16,7 @@ Once the dashboard recipe is initialised, the tenant management should be availa :::caution -Currently this is only available with the Node SDK version >= 20.0.1 and will be available for python and golang SDKs soon. +Currently, this is only available with our Node and Python SDKs. ::: diff --git a/v2/userdashboard/tenant-management/overview.mdx b/v2/userdashboard/tenant-management/overview.mdx index c0fe4e604..32c5447bd 100644 --- a/v2/userdashboard/tenant-management/overview.mdx +++ b/v2/userdashboard/tenant-management/overview.mdx @@ -16,7 +16,7 @@ Once the dashboard recipe is initialised, the tenant management should be availa :::caution -Currently this is only available with the Node SDK version >= 20.0.1 and will be available for python and golang SDKs soon. +Currently, this is only available with our Node and Python SDKs. ::: From 8434a6e1cdd75564385a2c58e27b2b2c6b64fbec Mon Sep 17 00:00:00 2001 From: rishabhpoddar Date: Mon, 21 Oct 2024 19:00:11 +0530 Subject: [PATCH 28/34] more fixes --- .../disable-sign-up/thirdparty-changes.mdx | 74 +++++++++++---- .../common-customizations/get-user-info.mdx | 65 ++++++++----- .../handling-signinup-success.mdx | 91 ++++++++++++------- .../multi-tenancy/new-tenant-config.mdx | 35 ++++--- .../saml/with-boxyhq/integration-steps.mdx | 29 +++--- .../common-customizations/userid-format.mdx | 60 +++++++----- v2/thirdparty/custom-ui/multitenant-login.mdx | 35 ++++--- .../pre-built-ui/multitenant-login.mdx | 35 ++++--- .../disable-sign-up/thirdparty-changes.mdx | 74 +++++++++++---- .../common-customizations/get-user-info.mdx | 65 ++++++++----- .../saml/with-boxyhq/integration-steps.mdx | 29 +++--- .../disable-sign-up/thirdparty-changes.mdx | 74 +++++++++++---- .../common-customizations/get-user-info.mdx | 65 ++++++++----- .../saml/with-boxyhq/integration-steps.mdx | 29 +++--- 14 files changed, 491 insertions(+), 269 deletions(-) diff --git a/v2/thirdparty/common-customizations/disable-sign-up/thirdparty-changes.mdx b/v2/thirdparty/common-customizations/disable-sign-up/thirdparty-changes.mdx index 2037e891e..e0cd3769a 100644 --- a/v2/thirdparty/common-customizations/disable-sign-up/thirdparty-changes.mdx +++ b/v2/thirdparty/common-customizations/disable-sign-up/thirdparty-changes.mdx @@ -258,35 +258,59 @@ func main() { from supertokens_python import init, InputAppInfo from supertokens_python.types import GeneralErrorResponse from supertokens_python.recipe import thirdparty -from supertokens_python.recipe.thirdparty.asyncio import get_users_by_email -from supertokens_python.recipe.thirdparty.interfaces import APIInterface, RecipeInterface, SignInUpOkResult, APIOptions -from typing import Optional, Dict, Any +from supertokens_python.recipe.thirdparty.interfaces import ( + APIInterface, + RecipeInterface, + APIOptions, +) +from typing import Optional, Dict, Any, Union +from supertokens_python.asyncio import list_users_by_account_info +from supertokens_python.types import AccountInfo from supertokens_python.recipe.thirdparty.provider import Provider, RedirectUriInfo from supertokens_python.recipe.thirdparty.types import RawUserInfoFromProvider +from supertokens_python.recipe.session.interfaces import SessionContainer + async def is_email_allowed(email: str): # from previous code snippet.. return False -def override_thirdpartyemailpassword_functions(original_implementation: RecipeInterface): + +def override_thirdparty_functions(original_implementation: RecipeInterface): original_thirdparty_sign_in_up = original_implementation.sign_in_up async def thirdparty_sign_in_up( third_party_id: str, third_party_user_id: str, email: str, + is_verified: bool, oauth_tokens: Dict[str, Any], raw_user_info_from_provider: RawUserInfoFromProvider, + session: Optional[SessionContainer], + should_try_linking_with_session_user: Union[bool, None], tenant_id: str, - user_context: Dict[str, Any] - ) -> SignInUpOkResult: - existing_users = await get_users_by_email(tenant_id, email, user_context) - if (len(existing_users) == 0): + user_context: Dict[str, Any], + ): + existing_users = await list_users_by_account_info( + tenant_id, AccountInfo(email=email) + ) + if len(existing_users) == 0: if not await is_email_allowed(email): raise Exception("No sign up") # this means this email is new so we allow sign up - return await original_thirdparty_sign_in_up(third_party_id, third_party_user_id, email, oauth_tokens, raw_user_info_from_provider, tenant_id, user_context) + return await original_thirdparty_sign_in_up( + third_party_id, + third_party_user_id, + email, + is_verified, + oauth_tokens, + raw_user_info_from_provider, + session, + should_try_linking_with_session_user, + tenant_id, + user_context, + ) raise Exception("No sign up") original_implementation.sign_in_up = thirdparty_sign_in_up @@ -294,22 +318,35 @@ def override_thirdpartyemailpassword_functions(original_implementation: RecipeIn return original_implementation -def override_thirdpartyemailpassword_apis(original_implementation: APIInterface): +def override_thirdparty_apis(original_implementation: APIInterface): original_sign_in_up_post = original_implementation.sign_in_up_post async def thirdparty_sign_in_up_post( provider: Provider, redirect_uri_info: Optional[RedirectUriInfo], oauth_tokens: Optional[Dict[str, Any]], + session: Optional[SessionContainer], + should_try_linking_with_session_user: Union[bool, None], tenant_id: str, api_options: APIOptions, - user_context: Dict[str, Any] + user_context: Dict[str, Any], ): try: - return await original_sign_in_up_post(provider, redirect_uri_info, oauth_tokens, tenant_id, api_options, user_context) + return await original_sign_in_up_post( + provider, + redirect_uri_info, + oauth_tokens, + session, + should_try_linking_with_session_user, + tenant_id, + api_options, + user_context, + ) except Exception as e: if str(e) == "No sign up": - return GeneralErrorResponse("Seems like you already have an account with another method. Please use that instead.") + return GeneralErrorResponse( + "Seems like you already have an account with another method. Please use that instead." + ) raise e original_implementation.sign_in_up_post = thirdparty_sign_in_up_post @@ -317,18 +354,15 @@ def override_thirdpartyemailpassword_apis(original_implementation: APIInterface) init( - app_info=InputAppInfo( - api_domain="...", app_name="...", website_domain="..."), - framework='...', # type: ignore + app_info=InputAppInfo(api_domain="...", app_name="...", website_domain="..."), + framework="...", # type: ignore recipe_list=[ thirdparty.init( - override=thirdparty.InputOverrideConfig( - apis=override_thirdpartyemailpassword_apis, - functions=override_thirdpartyemailpassword_functions + apis=override_thirdparty_apis, functions=override_thirdparty_functions ), ) - ] + ], ) ``` diff --git a/v2/thirdparty/common-customizations/get-user-info.mdx b/v2/thirdparty/common-customizations/get-user-info.mdx index f863795a3..fb43476a8 100644 --- a/v2/thirdparty/common-customizations/get-user-info.mdx +++ b/v2/thirdparty/common-customizations/get-user-info.mdx @@ -902,67 +902,84 @@ func main() { ```python from supertokens_python import init, InputAppInfo -from supertokens_python.types import GeneralErrorResponse from supertokens_python.recipe import thirdparty -from supertokens_python.recipe.thirdparty.interfaces import APIInterface, APIOptions, SignInUpPostOkResult, SignInUpPostNoEmailGivenByProviderResponse +from supertokens_python.recipe.thirdparty.interfaces import ( + APIInterface, + APIOptions, + SignInUpPostOkResult, +) from typing import Optional, Union, Dict, Any +from supertokens_python.recipe.session.interfaces import SessionContainer from supertokens_python.recipe.thirdparty.provider import Provider, RedirectUriInfo + # highlight-start def override_thirdparty_apis(original_implementation: APIInterface): original_thirdparty_sign_in_up_post = original_implementation.sign_in_up_post - + async def thirdparty_sign_in_up_post( provider: Provider, redirect_uri_info: Optional[RedirectUriInfo], oauth_tokens: Optional[Dict[str, Any]], + session: Optional[SessionContainer], + should_try_linking_with_session_user: Union[bool, None], tenant_id: str, api_options: APIOptions, - user_context: Dict[str, Any] - ) -> Union[ - SignInUpPostOkResult, - SignInUpPostNoEmailGivenByProviderResponse, - GeneralErrorResponse, - ]: + user_context: Dict[str, Any], + ): # call the default behaviour as show below - result = await original_thirdparty_sign_in_up_post(provider, redirect_uri_info, oauth_tokens, tenant_id, api_options, user_context) - + result = await original_thirdparty_sign_in_up_post( + provider, + redirect_uri_info, + oauth_tokens, + session, + should_try_linking_with_session_user, + tenant_id, + api_options, + user_context, + ) + if isinstance(result, SignInUpPostOkResult): print(result.user) # This is the response from the OAuth tokens provided by the third party provider print(result.oauth_tokens["access_token"]) - # other tokens like the refresh_token or id_token are also + # other tokens like the refresh_token or id_token are also # available in the OAuthTokens object. # This gives the user's info as returned by the provider's user profile endpoint. if result.raw_user_info_from_provider.from_user_info_api is not None: - print(result.raw_user_info_from_provider.from_user_info_api["first_name"]) + print( + result.raw_user_info_from_provider.from_user_info_api["first_name"] + ) - # This gives the user's info from the returned ID token + # This gives the user's info from the returned ID token # if the provider gave us an ID token if result.raw_user_info_from_provider.from_id_token_payload is not None: - print(result.raw_user_info_from_provider.from_id_token_payload["first_name"]) - + print( + result.raw_user_info_from_provider.from_id_token_payload[ + "first_name" + ] + ) + return result original_implementation.sign_in_up_post = thirdparty_sign_in_up_post return original_implementation + + # highlight-end init( app_info=InputAppInfo(api_domain="...", app_name="...", website_domain="..."), - framework='...', # type: ignore + framework="...", # type: ignore recipe_list=[ thirdparty.init( - - # highlight-start - override=thirdparty.InputOverrideConfig( - apis=override_thirdparty_apis - ) - # highlight-end + # highlight-start + override=thirdparty.InputOverrideConfig(apis=override_thirdparty_apis) + # highlight-end ) - ] + ], ) ``` diff --git a/v2/thirdparty/common-customizations/handling-signinup-success.mdx b/v2/thirdparty/common-customizations/handling-signinup-success.mdx index c421f17fe..c55744784 100644 --- a/v2/thirdparty/common-customizations/handling-signinup-success.mdx +++ b/v2/thirdparty/common-customizations/handling-signinup-success.mdx @@ -250,65 +250,94 @@ func main() { ```python from supertokens_python import init, InputAppInfo from supertokens_python.recipe import thirdparty -from supertokens_python.recipe.thirdparty.interfaces import RecipeInterface +from supertokens_python.recipe.thirdparty.interfaces import ( + RecipeInterface, + SignInUpOkResult, +) from supertokens_python.recipe.thirdparty.types import RawUserInfoFromProvider -from typing import Dict, Any +from typing import Dict, Any, Optional, Union +from supertokens_python.recipe.session.interfaces import SessionContainer + # highlight-start -def override_thirdparty_functions(original_implementation: RecipeInterface) -> RecipeInterface: +def override_thirdparty_functions( + original_implementation: RecipeInterface, +) -> RecipeInterface: original_sign_in_up = original_implementation.sign_in_up async def sign_in_up( third_party_id: str, third_party_user_id: str, email: str, + is_verified: bool, oauth_tokens: Dict[str, Any], raw_user_info_from_provider: RawUserInfoFromProvider, + session: Optional[SessionContainer], + should_try_linking_with_session_user: Union[bool, None], tenant_id: str, - user_context: Dict[str, Any] + user_context: Dict[str, Any], ): - result = await original_sign_in_up(third_party_id, third_party_user_id, email, oauth_tokens, raw_user_info_from_provider, tenant_id, user_context) - - # user object contains the ID and email of the user - user = result.user - print(user) - - # This is the response from the OAuth 2 provider that contains their tokens or user info. - provider_access_token = result.oauth_tokens["access_token"] - print(provider_access_token) - - if result.raw_user_info_from_provider.from_user_info_api is not None: - first_name = result.raw_user_info_from_provider.from_user_info_api["first_name"] - print(first_name) - - if result.created_new_user: - print("New user was created") - # TODO: Post sign up logic - else: - print("User already existed and was signed in") - # TODO: Post sign in logic - + result = await original_sign_in_up( + third_party_id, + third_party_user_id, + email, + is_verified, + oauth_tokens, + raw_user_info_from_provider, + session, + should_try_linking_with_session_user, + tenant_id, + user_context, + ) + + if isinstance(result, SignInUpOkResult): + # user object contains the ID and email of the user + user = result.user + print(user) + + # This is the response from the OAuth 2 provider that contains their tokens or user info. + provider_access_token = result.oauth_tokens["access_token"] + print(provider_access_token) + + if result.raw_user_info_from_provider.from_user_info_api is not None: + first_name = result.raw_user_info_from_provider.from_user_info_api[ + "first_name" + ] + print(first_name) + + if session is None: + if ( + result.created_new_recipe_user + and len(result.user.login_methods) == 1 + ): + print("New user was created") + # TODO: Post sign up logic + else: + print("User already existed and was signed in") + # TODO: Post sign in logic + return result - original_implementation.sign_in_up = sign_in_up return original_implementation + + # highlight-end init( app_info=InputAppInfo(api_domain="...", app_name="...", website_domain="..."), - framework='...', # type: ignore + framework="...", # type: ignore recipe_list=[ thirdparty.init( - # highlight-start + # highlight-start override=thirdparty.InputOverrideConfig( functions=override_thirdparty_functions ), - # highlight-end - sign_in_and_up_feature=thirdparty.SignInAndUpFeature(providers=[]) + # highlight-end + sign_in_and_up_feature=thirdparty.SignInAndUpFeature(providers=[]), ) - ] + ], ) ``` diff --git a/v2/thirdparty/common-customizations/multi-tenancy/new-tenant-config.mdx b/v2/thirdparty/common-customizations/multi-tenancy/new-tenant-config.mdx index d86b89261..fa3e82cae 100644 --- a/v2/thirdparty/common-customizations/multi-tenancy/new-tenant-config.mdx +++ b/v2/thirdparty/common-customizations/multi-tenancy/new-tenant-config.mdx @@ -84,12 +84,15 @@ func main() { ```python from supertokens_python.recipe.multitenancy.asyncio import create_or_update_tenant -from supertokens_python.recipe.multitenancy.interfaces import TenantConfig +from supertokens_python.recipe.multitenancy.interfaces import TenantConfigCreateOrUpdate async def some_func(): - result = await create_or_update_tenant("public", TenantConfig( - third_party_enabled=True, - )) + result = await create_or_update_tenant( + "public", + TenantConfigCreateOrUpdate( + first_factors=["thirdparty"], + ), + ) if result.status != "OK": print("handle error") @@ -105,18 +108,22 @@ async def some_func(): ```python from supertokens_python.recipe.multitenancy.syncio import create_or_update_tenant -from supertokens_python.recipe.multitenancy.interfaces import TenantConfig +from supertokens_python.recipe.multitenancy.interfaces import TenantConfigCreateOrUpdate -result = create_or_update_tenant("public", TenantConfig( - third_party_enabled=True, - )) +def some_func(): + result = create_or_update_tenant( + "public", + TenantConfigCreateOrUpdate( + first_factors=["thirdparty"], + ), + ) -if result.status != "OK": - print("handle error") -elif result.created_new: - print("new tenant was created") -else: - print("existing tenant's config was modified.") + if result.status != "OK": + print("handle error") + elif result.created_new: + print("new tenant was created") + else: + print("existing tenant's config was modified.") ``` diff --git a/v2/thirdparty/common-customizations/saml/with-boxyhq/integration-steps.mdx b/v2/thirdparty/common-customizations/saml/with-boxyhq/integration-steps.mdx index c1fdabbbd..b42f4ece4 100644 --- a/v2/thirdparty/common-customizations/saml/with-boxyhq/integration-steps.mdx +++ b/v2/thirdparty/common-customizations/saml/with-boxyhq/integration-steps.mdx @@ -244,12 +244,12 @@ func main() { ```python from supertokens_python.recipe.multitenancy.asyncio import create_or_update_tenant -from supertokens_python.recipe.multitenancy.interfaces import TenantConfig +from supertokens_python.recipe.multitenancy.interfaces import TenantConfigCreateOrUpdate async def some_func(): - result = await create_or_update_tenant("public", TenantConfig( - third_party_enabled=True, - )) + result = await create_or_update_tenant( + "public", TenantConfigCreateOrUpdate(first_factors=["thirdparty"]) + ) if result.status != "OK": print("handle error") @@ -265,18 +265,19 @@ async def some_func(): ```python from supertokens_python.recipe.multitenancy.syncio import create_or_update_tenant -from supertokens_python.recipe.multitenancy.interfaces import TenantConfig +from supertokens_python.recipe.multitenancy.interfaces import TenantConfigCreateOrUpdate -result = create_or_update_tenant("public", TenantConfig( - third_party_enabled=True, - )) +def some_func(): + result = create_or_update_tenant( + "public", TenantConfigCreateOrUpdate(first_factors=["thirdparty"]) + ) -if result.status != "OK": - print("handle error") -elif result.created_new: - print("new tenant was created") -else: - print("existing tenant's config was modified.") + if result.status != "OK": + print("handle error") + elif result.created_new: + print("new tenant was created") + else: + print("existing tenant's config was modified.") ``` diff --git a/v2/thirdparty/common-customizations/userid-format.mdx b/v2/thirdparty/common-customizations/userid-format.mdx index 628b6ba2f..aa937f5ee 100644 --- a/v2/thirdparty/common-customizations/userid-format.mdx +++ b/v2/thirdparty/common-customizations/userid-format.mdx @@ -141,9 +141,14 @@ func main() { from supertokens_python import init, InputAppInfo from supertokens_python.asyncio import create_user_id_mapping from supertokens_python.recipe import thirdparty, session -from supertokens_python.recipe.thirdparty.interfaces import RecipeInterface +from supertokens_python.recipe.thirdparty.interfaces import ( + RecipeInterface, + SignInUpOkResult, +) from supertokens_python.recipe.thirdparty.types import RawUserInfoFromProvider -from typing import Dict, Any +from typing import Dict, Any, Optional, Union +from supertokens_python.recipe.session.interfaces import SessionContainer +from supertokens_python.types import RecipeUserId def override_thirdparty_functions( @@ -155,8 +160,11 @@ def override_thirdparty_functions( third_party_id: str, third_party_user_id: str, email: str, + is_verified: bool, oauth_tokens: Dict[str, Any], raw_user_info_from_provider: RawUserInfoFromProvider, + session: Optional[SessionContainer], + should_try_linking_with_session_user: Union[bool, None], tenant_id: str, user_context: Dict[str, Any], ): @@ -164,32 +172,40 @@ def override_thirdparty_functions( third_party_id, third_party_user_id, email, + is_verified, oauth_tokens, raw_user_info_from_provider, + session, + should_try_linking_with_session_user, tenant_id, user_context, ) - # user object contains the ID and email of the user - user = result.user - print(user) - - # This is the response from the OAuth 2 provider that contains their tokens or user info. - provider_access_token = result.oauth_tokens["access_token"] - print(provider_access_token) - - if result.raw_user_info_from_provider.from_user_info_api is not None: - first_name = result.raw_user_info_from_provider.from_user_info_api[ - "first_name" - ] - print(first_name) - - if result.created_new_user: - # highlight-start - external_user_id = "" - await create_user_id_mapping(result.user.user_id, external_user_id) - result.user.user_id = external_user_id - # highlight-end + if isinstance(result, SignInUpOkResult): + # user object contains the ID and email of the user + user = result.user + print(user) + + # This is the response from the OAuth 2 provider that contains their tokens or user info. + provider_access_token = result.oauth_tokens["access_token"] + print(provider_access_token) + + if result.raw_user_info_from_provider.from_user_info_api is not None: + first_name = result.raw_user_info_from_provider.from_user_info_api[ + "first_name" + ] + print(first_name) + + if result.created_new_recipe_user and len(result.user.login_methods) == 1: + # highlight-start + external_user_id = "" + await create_user_id_mapping(result.user.id, external_user_id) + result.user.id = external_user_id + result.user.login_methods[0].recipe_user_id = RecipeUserId( + external_user_id + ) + result.recipe_user_id = RecipeUserId(external_user_id) + # highlight-end return result diff --git a/v2/thirdparty/custom-ui/multitenant-login.mdx b/v2/thirdparty/custom-ui/multitenant-login.mdx index 22304d85d..113ce0212 100644 --- a/v2/thirdparty/custom-ui/multitenant-login.mdx +++ b/v2/thirdparty/custom-ui/multitenant-login.mdx @@ -113,12 +113,15 @@ func main() { ```python from supertokens_python.recipe.multitenancy.asyncio import create_or_update_tenant -from supertokens_python.recipe.multitenancy.interfaces import TenantConfig +from supertokens_python.recipe.multitenancy.interfaces import TenantConfigCreateOrUpdate async def some_func(): - result = await create_or_update_tenant("public", TenantConfig( - third_party_enabled=True, - )) + result = await create_or_update_tenant( + "public", + TenantConfigCreateOrUpdate( + first_factors=["thirdparty"], + ), + ) if result.status != "OK": print("handle error") @@ -134,18 +137,22 @@ async def some_func(): ```python from supertokens_python.recipe.multitenancy.syncio import create_or_update_tenant -from supertokens_python.recipe.multitenancy.interfaces import TenantConfig +from supertokens_python.recipe.multitenancy.interfaces import TenantConfigCreateOrUpdate -result = create_or_update_tenant("public", TenantConfig( - third_party_enabled=True, - )) +def some_func(): + result = create_or_update_tenant( + "public", + TenantConfigCreateOrUpdate( + first_factors=["thirdparty"], + ), + ) -if result.status != "OK": - print("handle error") -elif result.created_new: - print("new tenant was created") -else: - print("existing tenant's config was modified.") + if result.status != "OK": + print("handle error") + elif result.created_new: + print("new tenant was created") + else: + print("existing tenant's config was modified.") ``` diff --git a/v2/thirdparty/pre-built-ui/multitenant-login.mdx b/v2/thirdparty/pre-built-ui/multitenant-login.mdx index 4e46be243..1b42a09aa 100644 --- a/v2/thirdparty/pre-built-ui/multitenant-login.mdx +++ b/v2/thirdparty/pre-built-ui/multitenant-login.mdx @@ -110,12 +110,15 @@ func main() { ```python from supertokens_python.recipe.multitenancy.asyncio import create_or_update_tenant -from supertokens_python.recipe.multitenancy.interfaces import TenantConfig +from supertokens_python.recipe.multitenancy.interfaces import TenantConfigCreateOrUpdate async def some_func(): - result = await create_or_update_tenant("public", TenantConfig( - third_party_enabled=True, - )) + result = await create_or_update_tenant( + "public", + TenantConfigCreateOrUpdate( + first_factors=["thirdparty"], + ), + ) if result.status != "OK": print("handle error") @@ -131,18 +134,22 @@ async def some_func(): ```python from supertokens_python.recipe.multitenancy.syncio import create_or_update_tenant -from supertokens_python.recipe.multitenancy.interfaces import TenantConfig +from supertokens_python.recipe.multitenancy.interfaces import TenantConfigCreateOrUpdate -result = create_or_update_tenant("public", TenantConfig( - third_party_enabled=True, - )) +def some_func(): + result = create_or_update_tenant( + "public", + TenantConfigCreateOrUpdate( + first_factors=["thirdparty"], + ), + ) -if result.status != "OK": - print("handle error") -elif result.created_new: - print("new tenant was created") -else: - print("existing tenant's config was modified.") + if result.status != "OK": + print("handle error") + elif result.created_new: + print("new tenant was created") + else: + print("existing tenant's config was modified.") ``` diff --git a/v2/thirdpartyemailpassword/common-customizations/disable-sign-up/thirdparty-changes.mdx b/v2/thirdpartyemailpassword/common-customizations/disable-sign-up/thirdparty-changes.mdx index 17246e030..f8ac792d5 100644 --- a/v2/thirdpartyemailpassword/common-customizations/disable-sign-up/thirdparty-changes.mdx +++ b/v2/thirdpartyemailpassword/common-customizations/disable-sign-up/thirdparty-changes.mdx @@ -259,35 +259,59 @@ func main() { from supertokens_python import init, InputAppInfo from supertokens_python.types import GeneralErrorResponse from supertokens_python.recipe import thirdparty -from supertokens_python.recipe.thirdparty.asyncio import get_users_by_email -from supertokens_python.recipe.thirdparty.interfaces import APIInterface, RecipeInterface, SignInUpOkResult, APIOptions -from typing import Optional, Dict, Any +from supertokens_python.recipe.thirdparty.interfaces import ( + APIInterface, + RecipeInterface, + APIOptions, +) +from typing import Optional, Dict, Any, Union +from supertokens_python.asyncio import list_users_by_account_info +from supertokens_python.types import AccountInfo from supertokens_python.recipe.thirdparty.provider import Provider, RedirectUriInfo from supertokens_python.recipe.thirdparty.types import RawUserInfoFromProvider +from supertokens_python.recipe.session.interfaces import SessionContainer + async def is_email_allowed(email: str): # from previous code snippet.. return False -def override_thirdpartyemailpassword_functions(original_implementation: RecipeInterface): + +def override_thirdparty_functions(original_implementation: RecipeInterface): original_thirdparty_sign_in_up = original_implementation.sign_in_up async def thirdparty_sign_in_up( third_party_id: str, third_party_user_id: str, email: str, + is_verified: bool, oauth_tokens: Dict[str, Any], raw_user_info_from_provider: RawUserInfoFromProvider, + session: Optional[SessionContainer], + should_try_linking_with_session_user: Union[bool, None], tenant_id: str, - user_context: Dict[str, Any] - ) -> SignInUpOkResult: - existing_users = await get_users_by_email(tenant_id, email, user_context) - if (len(existing_users) == 0): + user_context: Dict[str, Any], + ): + existing_users = await list_users_by_account_info( + tenant_id, AccountInfo(email=email) + ) + if len(existing_users) == 0: if not await is_email_allowed(email): raise Exception("No sign up") # this means this email is new so we allow sign up - return await original_thirdparty_sign_in_up(third_party_id, third_party_user_id, email, oauth_tokens, raw_user_info_from_provider, tenant_id, user_context) + return await original_thirdparty_sign_in_up( + third_party_id, + third_party_user_id, + email, + is_verified, + oauth_tokens, + raw_user_info_from_provider, + session, + should_try_linking_with_session_user, + tenant_id, + user_context, + ) raise Exception("No sign up") original_implementation.sign_in_up = thirdparty_sign_in_up @@ -295,22 +319,35 @@ def override_thirdpartyemailpassword_functions(original_implementation: RecipeIn return original_implementation -def override_thirdpartyemailpassword_apis(original_implementation: APIInterface): +def override_thirdparty_apis(original_implementation: APIInterface): original_sign_in_up_post = original_implementation.sign_in_up_post async def thirdparty_sign_in_up_post( provider: Provider, redirect_uri_info: Optional[RedirectUriInfo], oauth_tokens: Optional[Dict[str, Any]], + session: Optional[SessionContainer], + should_try_linking_with_session_user: Union[bool, None], tenant_id: str, api_options: APIOptions, - user_context: Dict[str, Any] + user_context: Dict[str, Any], ): try: - return await original_sign_in_up_post(provider, redirect_uri_info, oauth_tokens, tenant_id, api_options, user_context) + return await original_sign_in_up_post( + provider, + redirect_uri_info, + oauth_tokens, + session, + should_try_linking_with_session_user, + tenant_id, + api_options, + user_context, + ) except Exception as e: if str(e) == "No sign up": - return GeneralErrorResponse("Seems like you already have an account with another method. Please use that instead.") + return GeneralErrorResponse( + "Seems like you already have an account with another method. Please use that instead." + ) raise e original_implementation.sign_in_up_post = thirdparty_sign_in_up_post @@ -318,18 +355,15 @@ def override_thirdpartyemailpassword_apis(original_implementation: APIInterface) init( - app_info=InputAppInfo( - api_domain="...", app_name="...", website_domain="..."), - framework='...', # type: ignore + app_info=InputAppInfo(api_domain="...", app_name="...", website_domain="..."), + framework="...", # type: ignore recipe_list=[ thirdparty.init( - override=thirdparty.InputOverrideConfig( - apis=override_thirdpartyemailpassword_apis, - functions=override_thirdpartyemailpassword_functions + apis=override_thirdparty_apis, functions=override_thirdparty_functions ), ) - ] + ], ) ``` diff --git a/v2/thirdpartyemailpassword/common-customizations/get-user-info.mdx b/v2/thirdpartyemailpassword/common-customizations/get-user-info.mdx index 23d6435e5..c5abb9ca1 100644 --- a/v2/thirdpartyemailpassword/common-customizations/get-user-info.mdx +++ b/v2/thirdpartyemailpassword/common-customizations/get-user-info.mdx @@ -990,67 +990,84 @@ func main() { ```python from supertokens_python import init, InputAppInfo -from supertokens_python.types import GeneralErrorResponse from supertokens_python.recipe import thirdparty -from supertokens_python.recipe.thirdparty.interfaces import APIInterface, APIOptions, SignInUpPostOkResult, SignInUpPostNoEmailGivenByProviderResponse +from supertokens_python.recipe.thirdparty.interfaces import ( + APIInterface, + APIOptions, + SignInUpPostOkResult, +) from typing import Optional, Union, Dict, Any +from supertokens_python.recipe.session.interfaces import SessionContainer from supertokens_python.recipe.thirdparty.provider import Provider, RedirectUriInfo + # highlight-start def override_thirdparty_apis(original_implementation: APIInterface): original_thirdparty_sign_in_up_post = original_implementation.sign_in_up_post - + async def thirdparty_sign_in_up_post( provider: Provider, redirect_uri_info: Optional[RedirectUriInfo], oauth_tokens: Optional[Dict[str, Any]], + session: Optional[SessionContainer], + should_try_linking_with_session_user: Union[bool, None], tenant_id: str, api_options: APIOptions, - user_context: Dict[str, Any] - ) -> Union[ - SignInUpPostOkResult, - SignInUpPostNoEmailGivenByProviderResponse, - GeneralErrorResponse, - ]: + user_context: Dict[str, Any], + ): # call the default behaviour as show below - result = await original_thirdparty_sign_in_up_post(provider, redirect_uri_info, oauth_tokens, tenant_id, api_options, user_context) - + result = await original_thirdparty_sign_in_up_post( + provider, + redirect_uri_info, + oauth_tokens, + session, + should_try_linking_with_session_user, + tenant_id, + api_options, + user_context, + ) + if isinstance(result, SignInUpPostOkResult): print(result.user) # This is the response from the OAuth tokens provided by the third party provider print(result.oauth_tokens["access_token"]) - # other tokens like the refresh_token or id_token are also + # other tokens like the refresh_token or id_token are also # available in the OAuthTokens object. # This gives the user's info as returned by the provider's user profile endpoint. if result.raw_user_info_from_provider.from_user_info_api is not None: - print(result.raw_user_info_from_provider.from_user_info_api["first_name"]) + print( + result.raw_user_info_from_provider.from_user_info_api["first_name"] + ) - # This gives the user's info from the returned ID token + # This gives the user's info from the returned ID token # if the provider gave us an ID token if result.raw_user_info_from_provider.from_id_token_payload is not None: - print(result.raw_user_info_from_provider.from_id_token_payload["first_name"]) - + print( + result.raw_user_info_from_provider.from_id_token_payload[ + "first_name" + ] + ) + return result original_implementation.sign_in_up_post = thirdparty_sign_in_up_post return original_implementation + + # highlight-end init( app_info=InputAppInfo(api_domain="...", app_name="...", website_domain="..."), - framework='...', # type: ignore + framework="...", # type: ignore recipe_list=[ thirdparty.init( - - # highlight-start - override=thirdparty.InputOverrideConfig( - apis=override_thirdparty_apis - ) - # highlight-end + # highlight-start + override=thirdparty.InputOverrideConfig(apis=override_thirdparty_apis) + # highlight-end ) - ] + ], ) ``` diff --git a/v2/thirdpartyemailpassword/common-customizations/saml/with-boxyhq/integration-steps.mdx b/v2/thirdpartyemailpassword/common-customizations/saml/with-boxyhq/integration-steps.mdx index d39b92cde..79f518a27 100644 --- a/v2/thirdpartyemailpassword/common-customizations/saml/with-boxyhq/integration-steps.mdx +++ b/v2/thirdpartyemailpassword/common-customizations/saml/with-boxyhq/integration-steps.mdx @@ -244,12 +244,12 @@ func main() { ```python from supertokens_python.recipe.multitenancy.asyncio import create_or_update_tenant -from supertokens_python.recipe.multitenancy.interfaces import TenantConfig +from supertokens_python.recipe.multitenancy.interfaces import TenantConfigCreateOrUpdate async def some_func(): - result = await create_or_update_tenant("public", TenantConfig( - third_party_enabled=True, - )) + result = await create_or_update_tenant( + "public", TenantConfigCreateOrUpdate(first_factors=["thirdparty"]) + ) if result.status != "OK": print("handle error") @@ -265,18 +265,19 @@ async def some_func(): ```python from supertokens_python.recipe.multitenancy.syncio import create_or_update_tenant -from supertokens_python.recipe.multitenancy.interfaces import TenantConfig +from supertokens_python.recipe.multitenancy.interfaces import TenantConfigCreateOrUpdate -result = create_or_update_tenant("public", TenantConfig( - third_party_enabled=True, - )) +def some_func(): + result = create_or_update_tenant( + "public", TenantConfigCreateOrUpdate(first_factors=["thirdparty"]) + ) -if result.status != "OK": - print("handle error") -elif result.created_new: - print("new tenant was created") -else: - print("existing tenant's config was modified.") + if result.status != "OK": + print("handle error") + elif result.created_new: + print("new tenant was created") + else: + print("existing tenant's config was modified.") ``` diff --git a/v2/thirdpartypasswordless/common-customizations/disable-sign-up/thirdparty-changes.mdx b/v2/thirdpartypasswordless/common-customizations/disable-sign-up/thirdparty-changes.mdx index a9836998e..11fddf057 100644 --- a/v2/thirdpartypasswordless/common-customizations/disable-sign-up/thirdparty-changes.mdx +++ b/v2/thirdpartypasswordless/common-customizations/disable-sign-up/thirdparty-changes.mdx @@ -259,35 +259,59 @@ func main() { from supertokens_python import init, InputAppInfo from supertokens_python.types import GeneralErrorResponse from supertokens_python.recipe import thirdparty -from supertokens_python.recipe.thirdparty.asyncio import get_users_by_email -from supertokens_python.recipe.thirdparty.interfaces import APIInterface, RecipeInterface, SignInUpOkResult, APIOptions -from typing import Optional, Dict, Any +from supertokens_python.recipe.thirdparty.interfaces import ( + APIInterface, + RecipeInterface, + APIOptions, +) +from typing import Optional, Dict, Any, Union +from supertokens_python.asyncio import list_users_by_account_info +from supertokens_python.types import AccountInfo from supertokens_python.recipe.thirdparty.provider import Provider, RedirectUriInfo from supertokens_python.recipe.thirdparty.types import RawUserInfoFromProvider +from supertokens_python.recipe.session.interfaces import SessionContainer + async def is_email_allowed(email: str): # from previous code snippet.. return False -def override_thirdpartyemailpassword_functions(original_implementation: RecipeInterface): + +def override_thirdparty_functions(original_implementation: RecipeInterface): original_thirdparty_sign_in_up = original_implementation.sign_in_up async def thirdparty_sign_in_up( third_party_id: str, third_party_user_id: str, email: str, + is_verified: bool, oauth_tokens: Dict[str, Any], raw_user_info_from_provider: RawUserInfoFromProvider, + session: Optional[SessionContainer], + should_try_linking_with_session_user: Union[bool, None], tenant_id: str, - user_context: Dict[str, Any] - ) -> SignInUpOkResult: - existing_users = await get_users_by_email(tenant_id, email, user_context) - if (len(existing_users) == 0): + user_context: Dict[str, Any], + ): + existing_users = await list_users_by_account_info( + tenant_id, AccountInfo(email=email) + ) + if len(existing_users) == 0: if not await is_email_allowed(email): raise Exception("No sign up") # this means this email is new so we allow sign up - return await original_thirdparty_sign_in_up(third_party_id, third_party_user_id, email, oauth_tokens, raw_user_info_from_provider, tenant_id, user_context) + return await original_thirdparty_sign_in_up( + third_party_id, + third_party_user_id, + email, + is_verified, + oauth_tokens, + raw_user_info_from_provider, + session, + should_try_linking_with_session_user, + tenant_id, + user_context, + ) raise Exception("No sign up") original_implementation.sign_in_up = thirdparty_sign_in_up @@ -295,22 +319,35 @@ def override_thirdpartyemailpassword_functions(original_implementation: RecipeIn return original_implementation -def override_thirdpartyemailpassword_apis(original_implementation: APIInterface): +def override_thirdparty_apis(original_implementation: APIInterface): original_sign_in_up_post = original_implementation.sign_in_up_post async def thirdparty_sign_in_up_post( provider: Provider, redirect_uri_info: Optional[RedirectUriInfo], oauth_tokens: Optional[Dict[str, Any]], + session: Optional[SessionContainer], + should_try_linking_with_session_user: Union[bool, None], tenant_id: str, api_options: APIOptions, - user_context: Dict[str, Any] + user_context: Dict[str, Any], ): try: - return await original_sign_in_up_post(provider, redirect_uri_info, oauth_tokens, tenant_id, api_options, user_context) + return await original_sign_in_up_post( + provider, + redirect_uri_info, + oauth_tokens, + session, + should_try_linking_with_session_user, + tenant_id, + api_options, + user_context, + ) except Exception as e: if str(e) == "No sign up": - return GeneralErrorResponse("Seems like you already have an account with another method. Please use that instead.") + return GeneralErrorResponse( + "Seems like you already have an account with another method. Please use that instead." + ) raise e original_implementation.sign_in_up_post = thirdparty_sign_in_up_post @@ -318,18 +355,15 @@ def override_thirdpartyemailpassword_apis(original_implementation: APIInterface) init( - app_info=InputAppInfo( - api_domain="...", app_name="...", website_domain="..."), - framework='...', # type: ignore + app_info=InputAppInfo(api_domain="...", app_name="...", website_domain="..."), + framework="...", # type: ignore recipe_list=[ thirdparty.init( - override=thirdparty.InputOverrideConfig( - apis=override_thirdpartyemailpassword_apis, - functions=override_thirdpartyemailpassword_functions + apis=override_thirdparty_apis, functions=override_thirdparty_functions ), ) - ] + ], ) ``` diff --git a/v2/thirdpartypasswordless/common-customizations/get-user-info.mdx b/v2/thirdpartypasswordless/common-customizations/get-user-info.mdx index e0b37b704..1f3d8f7e0 100644 --- a/v2/thirdpartypasswordless/common-customizations/get-user-info.mdx +++ b/v2/thirdpartypasswordless/common-customizations/get-user-info.mdx @@ -1092,67 +1092,84 @@ func main() { ```python from supertokens_python import init, InputAppInfo -from supertokens_python.types import GeneralErrorResponse from supertokens_python.recipe import thirdparty -from supertokens_python.recipe.thirdparty.interfaces import APIInterface, APIOptions, SignInUpPostOkResult, SignInUpPostNoEmailGivenByProviderResponse +from supertokens_python.recipe.thirdparty.interfaces import ( + APIInterface, + APIOptions, + SignInUpPostOkResult, +) from typing import Optional, Union, Dict, Any +from supertokens_python.recipe.session.interfaces import SessionContainer from supertokens_python.recipe.thirdparty.provider import Provider, RedirectUriInfo + # highlight-start def override_thirdparty_apis(original_implementation: APIInterface): original_thirdparty_sign_in_up_post = original_implementation.sign_in_up_post - + async def thirdparty_sign_in_up_post( provider: Provider, redirect_uri_info: Optional[RedirectUriInfo], oauth_tokens: Optional[Dict[str, Any]], + session: Optional[SessionContainer], + should_try_linking_with_session_user: Union[bool, None], tenant_id: str, api_options: APIOptions, - user_context: Dict[str, Any] - ) -> Union[ - SignInUpPostOkResult, - SignInUpPostNoEmailGivenByProviderResponse, - GeneralErrorResponse, - ]: + user_context: Dict[str, Any], + ): # call the default behaviour as show below - result = await original_thirdparty_sign_in_up_post(provider, redirect_uri_info, oauth_tokens, tenant_id, api_options, user_context) - + result = await original_thirdparty_sign_in_up_post( + provider, + redirect_uri_info, + oauth_tokens, + session, + should_try_linking_with_session_user, + tenant_id, + api_options, + user_context, + ) + if isinstance(result, SignInUpPostOkResult): print(result.user) # This is the response from the OAuth tokens provided by the third party provider print(result.oauth_tokens["access_token"]) - # other tokens like the refresh_token or id_token are also + # other tokens like the refresh_token or id_token are also # available in the OAuthTokens object. # This gives the user's info as returned by the provider's user profile endpoint. if result.raw_user_info_from_provider.from_user_info_api is not None: - print(result.raw_user_info_from_provider.from_user_info_api["first_name"]) + print( + result.raw_user_info_from_provider.from_user_info_api["first_name"] + ) - # This gives the user's info from the returned ID token + # This gives the user's info from the returned ID token # if the provider gave us an ID token if result.raw_user_info_from_provider.from_id_token_payload is not None: - print(result.raw_user_info_from_provider.from_id_token_payload["first_name"]) - + print( + result.raw_user_info_from_provider.from_id_token_payload[ + "first_name" + ] + ) + return result original_implementation.sign_in_up_post = thirdparty_sign_in_up_post return original_implementation + + # highlight-end init( app_info=InputAppInfo(api_domain="...", app_name="...", website_domain="..."), - framework='...', # type: ignore + framework="...", # type: ignore recipe_list=[ thirdparty.init( - - # highlight-start - override=thirdparty.InputOverrideConfig( - apis=override_thirdparty_apis - ) - # highlight-end + # highlight-start + override=thirdparty.InputOverrideConfig(apis=override_thirdparty_apis) + # highlight-end ) - ] + ], ) ``` diff --git a/v2/thirdpartypasswordless/common-customizations/saml/with-boxyhq/integration-steps.mdx b/v2/thirdpartypasswordless/common-customizations/saml/with-boxyhq/integration-steps.mdx index c1fdabbbd..b42f4ece4 100644 --- a/v2/thirdpartypasswordless/common-customizations/saml/with-boxyhq/integration-steps.mdx +++ b/v2/thirdpartypasswordless/common-customizations/saml/with-boxyhq/integration-steps.mdx @@ -244,12 +244,12 @@ func main() { ```python from supertokens_python.recipe.multitenancy.asyncio import create_or_update_tenant -from supertokens_python.recipe.multitenancy.interfaces import TenantConfig +from supertokens_python.recipe.multitenancy.interfaces import TenantConfigCreateOrUpdate async def some_func(): - result = await create_or_update_tenant("public", TenantConfig( - third_party_enabled=True, - )) + result = await create_or_update_tenant( + "public", TenantConfigCreateOrUpdate(first_factors=["thirdparty"]) + ) if result.status != "OK": print("handle error") @@ -265,18 +265,19 @@ async def some_func(): ```python from supertokens_python.recipe.multitenancy.syncio import create_or_update_tenant -from supertokens_python.recipe.multitenancy.interfaces import TenantConfig +from supertokens_python.recipe.multitenancy.interfaces import TenantConfigCreateOrUpdate -result = create_or_update_tenant("public", TenantConfig( - third_party_enabled=True, - )) +def some_func(): + result = create_or_update_tenant( + "public", TenantConfigCreateOrUpdate(first_factors=["thirdparty"]) + ) -if result.status != "OK": - print("handle error") -elif result.created_new: - print("new tenant was created") -else: - print("existing tenant's config was modified.") + if result.status != "OK": + print("handle error") + elif result.created_new: + print("new tenant was created") + else: + print("existing tenant's config was modified.") ``` From bcaf5c35c2369f6f3cbfcd13095f8df3d794f53e Mon Sep 17 00:00:00 2001 From: rishabhpoddar Date: Thu, 24 Oct 2024 16:39:03 +0530 Subject: [PATCH 29/34] more docs --- .../advanced-customizations/user-context.mdx | 133 ++++++++++++++---- 1 file changed, 104 insertions(+), 29 deletions(-) diff --git a/v2/thirdpartyemailpassword/advanced-customizations/user-context.mdx b/v2/thirdpartyemailpassword/advanced-customizations/user-context.mdx index 9b8cd31ec..0d9cfc87e 100644 --- a/v2/thirdpartyemailpassword/advanced-customizations/user-context.mdx +++ b/v2/thirdpartyemailpassword/advanced-customizations/user-context.mdx @@ -178,11 +178,15 @@ func main() { ```python from supertokens_python import init, InputAppInfo from supertokens_python.recipe import thirdparty, emailpassword -from supertokens_python.recipe.thirdparty.interfaces import RecipeInterface +from supertokens_python.recipe.thirdparty.interfaces import ( + RecipeInterface, + SignInUpOkResult, +) from supertokens_python.recipe.emailpassword.interfaces import APIOptions, APIInterface from supertokens_python.recipe.emailpassword.types import FormField -from typing import List, Dict, Any +from typing import List, Dict, Any, Union, Optional from supertokens_python.recipe.thirdparty.types import RawUserInfoFromProvider +from supertokens_python.recipe.session.interfaces import SessionContainer # highlight-start @@ -192,6 +196,8 @@ def override_emailpassword_apis(original_implementation: APIInterface): async def sign_up_post( form_fields: List[FormField], tenant_id: str, + session: Union[SessionContainer, None], + should_try_linking_with_session_user: Union[bool, None], api_options: APIOptions, user_context: Dict[str, Any], ): @@ -203,7 +209,12 @@ def override_emailpassword_apis(original_implementation: APIInterface): # to not create a new session in case userContext["isSignUp"] is True user_context["isSignUp"] = True return await original_sign_up_post( - form_fields, tenant_id, api_options, user_context + form_fields, + tenant_id, + session, + should_try_linking_with_session_user, + api_options, + user_context, ) original_implementation.sign_up_post = sign_up_post @@ -217,8 +228,11 @@ def override_thirdparty_functions(original_implementation: RecipeInterface): third_party_id: str, third_party_user_id: str, email: str, + is_verified: bool, oauth_tokens: Dict[str, Any], raw_user_info_from_provider: RawUserInfoFromProvider, + session: Optional[SessionContainer], + should_try_linking_with_session_user: Union[bool, None], tenant_id: str, user_context: Dict[str, Any], ): @@ -226,8 +240,11 @@ def override_thirdparty_functions(original_implementation: RecipeInterface): third_party_id, third_party_user_id, email, + is_verified, oauth_tokens, raw_user_info_from_provider, + session, + should_try_linking_with_session_user, tenant_id, user_context, ) @@ -238,13 +255,20 @@ def override_thirdparty_functions(original_implementation: RecipeInterface): # sign in or a sign up, so we cannot override the API function. # Instead, we override the recipe function as shown here, # and then set the relevant context only if it's a new user. - if response.created_new_user: + if ( + isinstance(response, SignInUpOkResult) + and response.created_new_recipe_user + and len(response.user.login_methods) == 1 + and session is None + ): user_context["isSignUp"] = True return response original_implementation.sign_in_up = thirdparty_sign_in_up return original_implementation + + # highlight-end @@ -433,12 +457,18 @@ func main() { ```python from supertokens_python import init, InputAppInfo -from supertokens_python.recipe.session.interfaces import RecipeInterface, SessionClaimValidator, SessionClaim, GetSessionTokensDangerouslyDict +from supertokens_python.recipe.session.interfaces import ( + RecipeInterface, + SessionClaimValidator, + SessionClaim, + GetSessionTokensDangerouslyDict, +) from supertokens_python.recipe.session.recipe_implementation import RecipeImplementation from typing import Dict, Any, Union, List, TypeVar, Optional from supertokens_python.recipe import session from supertokens_python.framework import BaseRequest from supertokens_python.recipe.session.utils import TokenTransferMethod +from supertokens_python.types import RecipeUserId _T = TypeVar("_T") @@ -448,12 +478,15 @@ _T = TypeVar("_T") def override_session_functions(original_implementation: RecipeInterface): original_create_new_session = original_implementation.create_new_session - async def create_new_session(user_id: str, - access_token_payload: Optional[Dict[str, Any]], - session_data_in_database: Optional[Dict[str, Any]], - disable_anti_csrf: Optional[bool], - tenant_id: str, - user_context: Dict[str, Any]): + async def create_new_session( + user_id: str, + recipe_user_id: RecipeUserId, + access_token_payload: Optional[Dict[str, Any]], + session_data_in_database: Optional[Dict[str, Any]], + disable_anti_csrf: Optional[bool], + tenant_id: str, + user_context: Dict[str, Any], + ): if user_context["isSignUp"] is True: # The execution will come here only in case # a sign up API is calling this function. This is because @@ -461,46 +494,79 @@ def override_session_functions(original_implementation: RecipeInterface): # (see above code). return EmptySession(original_implementation) - return await original_create_new_session(user_id, access_token_payload, session_data_in_database, disable_anti_csrf, tenant_id, user_context) + return await original_create_new_session( + user_id, + recipe_user_id, + access_token_payload, + session_data_in_database, + disable_anti_csrf, + tenant_id, + user_context, + ) + original_implementation.create_new_session = create_new_session return original_implementation + + # highlight-end init( - app_info=InputAppInfo( - api_domain="...", app_name="...", website_domain="..."), - framework='...', # type: ignore + app_info=InputAppInfo(api_domain="...", app_name="...", website_domain="..."), + framework="...", # type: ignore recipe_list=[ session.init( - override=session.InputOverrideConfig( - functions=override_session_functions - ) + override=session.InputOverrideConfig(functions=override_session_functions) ) - ] + ], ) class EmptySession(session.SessionContainer): def __init__(self, recipe_implementation: RecipeInterface): assert isinstance(recipe_implementation, RecipeImplementation) - super().__init__(recipe_implementation, - recipe_implementation.config, "", "", None, "", "", "", {}, None, False, "") + super().__init__( + recipe_implementation, + recipe_implementation.config, + "", + "", + None, + "", + "", + "", + RecipeUserId(""), + {}, + None, + False, + "", + ) async def revoke_session(self, user_context: Union[Any, None] = None) -> None: pass - async def get_session_data_from_database(self, user_context: Union[Dict[str, Any], None] = None) -> Dict[str, Any]: + async def get_session_data_from_database( + self, user_context: Union[Dict[str, Any], None] = None + ) -> Dict[str, Any]: return {} - async def update_session_data_in_database(self, new_session_data: Dict[str, Any], user_context: Union[Dict[str, Any], None] = None) -> None: + async def update_session_data_in_database( + self, + new_session_data: Dict[str, Any], + user_context: Union[Dict[str, Any], None] = None, + ) -> None: pass def get_user_id(self, user_context: Union[Dict[str, Any], None] = None) -> str: return "" + def get_recipe_user_id( + self, user_context: Union[Dict[str, Any], None] = None + ) -> RecipeUserId: + return RecipeUserId("") + def get_access_token_payload( - self, user_context: Union[Dict[str, Any], None] = None) -> Dict[str, Any]: + self, user_context: Union[Dict[str, Any], None] = None + ) -> Dict[str, Any]: return {} def get_handle(self, user_context: Union[Dict[str, Any], None] = None) -> str: @@ -509,13 +575,20 @@ class EmptySession(session.SessionContainer): def get_access_token(self, user_context: Union[Dict[str, Any], None] = None) -> str: return "" - async def get_time_created(self, user_context: Union[Dict[str, Any], None] = None) -> int: + async def get_time_created( + self, user_context: Union[Dict[str, Any], None] = None + ) -> int: return -1 async def get_expiry(self, user_context: Union[Dict[str, Any], None] = None) -> int: return -1 - async def attach_to_request_response(self, request: BaseRequest, transfer_method: TokenTransferMethod, user_context: Union[Dict[str, Any], None] = None): + async def attach_to_request_response( + self, + request: BaseRequest, + transfer_method: TokenTransferMethod, + user_context: Union[Dict[str, Any], None] = None, + ): pass async def assert_claims( @@ -551,7 +624,9 @@ class EmptySession(session.SessionContainer): pass async def merge_into_access_token_payload( - self, access_token_payload_update: Dict[str, Any], user_context: Union[Dict[str, Any], None] = None + self, + access_token_payload_update: Dict[str, Any], + user_context: Union[Dict[str, Any], None] = None, ) -> None: pass @@ -561,9 +636,9 @@ class EmptySession(session.SessionContainer): "accessToken": "", "antiCsrfToken": None, "frontToken": "", - "refreshToken": None + "refreshToken": None, } - + def get_tenant_id(self, user_context: Union[Dict[str, Any], None] = None) -> str: return "" ``` From 1c19520d1f140d65fb0b9f9920c864055e14c151 Mon Sep 17 00:00:00 2001 From: rishabhpoddar Date: Thu, 24 Oct 2024 18:02:27 +0530 Subject: [PATCH 30/34] more changes --- .../implementing-deduplication.mdx | 163 ++++++++----- .../common-customizations/get-user-info.mdx | 48 +--- .../handling-signinup-success.mdx | 217 ++++++++++++------ .../multi-tenancy/new-tenant-config.mdx | 35 +-- .../common-customizations/userid-format.mdx | 124 +++++++--- .../custom-ui/multitenant-login.mdx | 35 +-- .../pre-built-ui/multitenant-login.mdx | 35 +-- 7 files changed, 410 insertions(+), 247 deletions(-) diff --git a/v2/thirdpartyemailpassword/common-customizations/deduplication/implementing-deduplication.mdx b/v2/thirdpartyemailpassword/common-customizations/deduplication/implementing-deduplication.mdx index edf18a975..206af3767 100644 --- a/v2/thirdpartyemailpassword/common-customizations/deduplication/implementing-deduplication.mdx +++ b/v2/thirdpartyemailpassword/common-customizations/deduplication/implementing-deduplication.mdx @@ -221,71 +221,123 @@ func main() { from supertokens_python import init, InputAppInfo from supertokens_python.types import GeneralErrorResponse from supertokens_python.recipe import thirdparty, emailpassword -from supertokens_python.recipe.thirdparty.asyncio import get_users_by_email as get_thirdparty_users_by_email -from supertokens_python.recipe.emailpassword.asyncio import get_user_by_email as get_emailpassword_user_by_email -from supertokens_python.recipe.thirdparty.interfaces import APIInterface, RecipeInterface as ThirdPartyrecipeInterface, SignInUpOkResult, APIOptions, SignInUpPostOkResult, SignInUpPostNoEmailGivenByProviderResponse -from supertokens_python.recipe.emailpassword.interfaces import RecipeInterface as EmailPasswordRecipeInterface, SignUpOkResult, SignUpEmailAlreadyExistsError +from supertokens_python.asyncio import list_users_by_account_info +from supertokens_python.types import AccountInfo +from supertokens_python.recipe.thirdparty.interfaces import ( + APIInterface, + APIOptions, + RecipeInterface as ThirdPartyRecipeInterface, +) +from supertokens_python.recipe.emailpassword.interfaces import ( + RecipeInterface as EmailPasswordRecipeInterface, + EmailAlreadyExistsError, +) +from supertokens_python.recipe.thirdparty.types import ThirdPartyInfo from typing import Union, Dict, Any, Optional from supertokens_python.recipe.thirdparty.provider import Provider, RedirectUriInfo from supertokens_python.recipe.thirdparty.types import RawUserInfoFromProvider +from supertokens_python.recipe.session.interfaces import SessionContainer -def override_emailpassword_functions(original_implementation: EmailPasswordRecipeInterface): +def override_emailpassword_functions( + original_implementation: EmailPasswordRecipeInterface, +): original_email_password_sign_up = original_implementation.sign_up async def emailpassword_sign_up( - email: str, password: str, tenant_id: str, user_context: Dict[str, Any] - ) -> Union[SignUpOkResult, SignUpEmailAlreadyExistsError]: - existing_user = await get_emailpassword_user_by_email(tenant_id, email, user_context) - - third_party_existing_users = await get_thirdparty_users_by_email(tenant_id, email, user_context) + email: str, + password: str, + tenant_id: str, + session: Union[SessionContainer, None], + should_try_linking_with_session_user: Union[bool, None], + user_context: Dict[str, Any], + ): + existing_users = await list_users_by_account_info( + tenant_id, AccountInfo(email=email) + ) - if (existing_user is None and len(third_party_existing_users) == 0): + if len(existing_users) == 0: # this means this email is new so we allow sign up - return await original_email_password_sign_up(email, password, tenant_id, user_context) + return await original_email_password_sign_up( + email, + password, + tenant_id, + session, + should_try_linking_with_session_user, + user_context, + ) - return SignUpEmailAlreadyExistsError() + return EmailAlreadyExistsError() original_implementation.sign_up = emailpassword_sign_up return original_implementation -def override_thirdparty_functions(original_implementation: ThirdPartyrecipeInterface): + +def override_thirdparty_functions(original_implementation: ThirdPartyRecipeInterface): original_thirdparty_sign_in_up = original_implementation.sign_in_up - async def thirdparty_sign_in_up( + async def sign_in_up( third_party_id: str, third_party_user_id: str, email: str, + is_verified: bool, oauth_tokens: Dict[str, Any], raw_user_info_from_provider: RawUserInfoFromProvider, + session: Optional[SessionContainer], + should_try_linking_with_session_user: Union[bool, None], tenant_id: str, - user_context: Dict[str, Any] - ) -> SignInUpOkResult: - existing_users = await get_thirdparty_users_by_email(tenant_id, email, user_context) - email_password_user = await get_emailpassword_user_by_email(tenant_id, email, user_context) - - if email_password_user is not None: - raise Exception("Cannot sign up as email already exists") - - if (len(existing_users) == 0): + user_context: Dict[str, Any], + ): + existing_users = await list_users_by_account_info( + tenant_id, AccountInfo(email=email) + ) + if len(existing_users) == 0: # this means this email is new so we allow sign up - return await original_thirdparty_sign_in_up(third_party_id, third_party_user_id, email, oauth_tokens, raw_user_info_from_provider, tenant_id, user_context) - - is_sign_in = False - for user in existing_users: - if user.third_party_info.id == third_party_id and user.third_party_info.user_id == third_party_user_id: - is_sign_in = True - break - - if is_sign_in: - return await original_thirdparty_sign_in_up(third_party_id, third_party_user_id, email, oauth_tokens, raw_user_info_from_provider, tenant_id, user_context) - - # this means that the email already exists with another social or email password login method. + return await original_thirdparty_sign_in_up( + third_party_id, + third_party_user_id, + email, + is_verified, + oauth_tokens, + raw_user_info_from_provider, + session, + should_try_linking_with_session_user, + tenant_id, + user_context, + ) + + if any( + any( + lm.recipe_id == "thirdparty" + and lm.has_same_third_party_info_as( + ThirdPartyInfo(third_party_user_id, third_party_id) + ) + for lm in user.login_methods + ) + for user in existing_users + ): + # this means we are trying to sign in with the same social login. So we allow it + return await original_thirdparty_sign_in_up( + third_party_id, + third_party_user_id, + email, + is_verified, + oauth_tokens, + raw_user_info_from_provider, + session, + should_try_linking_with_session_user, + tenant_id, + user_context, + ) + + # this means that the email already exists with another social login method. # so we throw an error. raise Exception("Cannot sign up as email already exists") - original_implementation.sign_in_up = thirdparty_sign_in_up + original_implementation.sign_in_up = sign_in_up + + return original_implementation return original_implementation @@ -297,19 +349,28 @@ def override_thirdparty_apis(original_implementation: APIInterface): provider: Provider, redirect_uri_info: Optional[RedirectUriInfo], oauth_tokens: Optional[Dict[str, Any]], + session: Optional[SessionContainer], + should_try_linking_with_session_user: Union[bool, None], tenant_id: str, api_options: APIOptions, - user_context: Dict[str, Any] - ) -> Union[ - SignInUpPostOkResult, - SignInUpPostNoEmailGivenByProviderResponse, - GeneralErrorResponse, - ]: + user_context: Dict[str, Any], + ): try: - return await original_sign_in_up_post(provider, redirect_uri_info, oauth_tokens, tenant_id, api_options, user_context) + return await original_sign_in_up_post( + provider, + redirect_uri_info, + oauth_tokens, + session, + should_try_linking_with_session_user, + tenant_id, + api_options, + user_context, + ) except Exception as e: if str(e) == "Cannot sign up as email already exists": - return GeneralErrorResponse("Seems like you already have an account with another method. Please use that instead.") + return GeneralErrorResponse( + "Seems like you already have an account with another social login provider. Please use that instead." + ) raise e original_implementation.sign_in_up_post = sign_in_up_post @@ -317,22 +378,20 @@ def override_thirdparty_apis(original_implementation: APIInterface): init( - app_info=InputAppInfo( - api_domain="...", app_name="...", website_domain="..."), - framework='...', # type: ignore + app_info=InputAppInfo(api_domain="...", app_name="...", website_domain="..."), + framework="...", # type: ignore recipe_list=[ thirdparty.init( override=thirdparty.InputOverrideConfig( - apis=override_thirdparty_apis, - functions=override_thirdparty_functions + apis=override_thirdparty_apis, functions=override_thirdparty_functions ), ), emailpassword.init( override=emailpassword.InputOverrideConfig( functions=override_emailpassword_functions ), - ) - ] + ), + ], ) ``` diff --git a/v2/thirdpartyemailpassword/common-customizations/get-user-info.mdx b/v2/thirdpartyemailpassword/common-customizations/get-user-info.mdx index c5abb9ca1..7c5063375 100644 --- a/v2/thirdpartyemailpassword/common-customizations/get-user-info.mdx +++ b/v2/thirdpartyemailpassword/common-customizations/get-user-info.mdx @@ -760,12 +760,7 @@ func getUserInfoAPI(w http.ResponseWriter, r *http.Request) { ```python from supertokens_python.recipe.session.framework.fastapi import verify_session -from supertokens_python.recipe.thirdparty.asyncio import ( - get_user_by_id as get_user_by_id_thirdparty, -) -from supertokens_python.recipe.emailpassword.asyncio import ( - get_user_by_id as get_user_by_id_emailpassword, -) +from supertokens_python.asyncio import get_user from supertokens_python.recipe.session import SessionContainer from fastapi import FastAPI, Depends @@ -775,13 +770,8 @@ app = FastAPI() async def get_user_info_api(session: SessionContainer = Depends(verify_session())): user_id = session.get_user_id() - thirdparty_user = await get_user_by_id_thirdparty(user_id) - if thirdparty_user is None: - emailpassword_user = await get_user_by_id_emailpassword(user_id) - if emailpassword_user is not None: - print(emailpassword_user) - else: - print(thirdparty_user) + user = await get_user(user_id) + print(user) ``` @@ -789,12 +779,7 @@ async def get_user_info_api(session: SessionContainer = Depends(verify_session() ```python from supertokens_python.recipe.session.framework.flask import verify_session -from supertokens_python.recipe.thirdparty.syncio import ( - get_user_by_id as get_user_by_id_thirdparty, -) -from supertokens_python.recipe.emailpassword.syncio import ( - get_user_by_id as get_user_by_id_emailpassword, -) +from supertokens_python.syncio import get_user from flask import Flask, g from supertokens_python.recipe.session import SessionContainer @@ -807,13 +792,8 @@ def get_user_info_api(): user_id = session.get_user_id() - thirdparty_user = get_user_by_id_thirdparty(user_id) - if thirdparty_user is None: - emailpassword_user = get_user_by_id_emailpassword(user_id) - if emailpassword_user is not None: - print(emailpassword_user) - else: - print(thirdparty_user) + user = get_user(user_id) + print(user) ``` @@ -821,12 +801,7 @@ def get_user_info_api(): ```python from supertokens_python.recipe.session.framework.django.asyncio import verify_session -from supertokens_python.recipe.thirdparty.asyncio import ( - get_user_by_id as get_user_by_id_thirdparty, -) -from supertokens_python.recipe.emailpassword.asyncio import ( - get_user_by_id as get_user_by_id_emailpassword, -) +from supertokens_python.asyncio import get_user from django.http import HttpRequest from supertokens_python.recipe.session import SessionContainer @@ -836,13 +811,8 @@ async def get_user_info_api(request: HttpRequest): user_id = session.get_user_id() - thirdparty_user = await get_user_by_id_thirdparty(user_id) - if thirdparty_user is None: - emailpassword_user = await get_user_by_id_emailpassword(user_id) - if emailpassword_user is not None: - print(emailpassword_user) - else: - print(thirdparty_user) + user = await get_user(user_id) + print(user) ``` diff --git a/v2/thirdpartyemailpassword/common-customizations/handling-signinup-success.mdx b/v2/thirdpartyemailpassword/common-customizations/handling-signinup-success.mdx index ddf3f14f2..4a7ea85ed 100644 --- a/v2/thirdpartyemailpassword/common-customizations/handling-signinup-success.mdx +++ b/v2/thirdpartyemailpassword/common-customizations/handling-signinup-success.mdx @@ -373,52 +373,87 @@ func main() { ```python from supertokens_python import init, InputAppInfo from supertokens_python.recipe import thirdparty, emailpassword, session -from supertokens_python.recipe.thirdparty.interfaces import RecipeInterface as ThirdPartyRecipeInterface -from supertokens_python.recipe.emailpassword.interfaces import RecipeInterface as EmailPasswordRecipeInterface, SignInOkResult, SignUpOkResult +from supertokens_python.recipe.thirdparty.interfaces import ( + RecipeInterface as ThirdPartyRecipeInterface, + SignInUpOkResult, +) +from supertokens_python.recipe.emailpassword.interfaces import ( + RecipeInterface as EmailPasswordRecipeInterface, + SignInOkResult, + SignUpOkResult, +) from supertokens_python.recipe.thirdparty.types import RawUserInfoFromProvider -from typing import Dict, Any +from typing import Dict, Any, Optional, Union +from supertokens_python.recipe.session import SessionContainer + # highlight-start -def override_thirdparty_functions(original_implementation: ThirdPartyRecipeInterface) -> ThirdPartyRecipeInterface: +def override_thirdparty_functions( + original_implementation: ThirdPartyRecipeInterface, +) -> ThirdPartyRecipeInterface: original_thirdparty_sign_in_up = original_implementation.sign_in_up async def thirdparty_sign_in_up( third_party_id: str, third_party_user_id: str, email: str, + is_verified: bool, oauth_tokens: Dict[str, Any], raw_user_info_from_provider: RawUserInfoFromProvider, + session: Optional[SessionContainer], + should_try_linking_with_session_user: Union[bool, None], tenant_id: str, - user_context: Dict[str, Any] + user_context: Dict[str, Any], ): - result = await original_thirdparty_sign_in_up(third_party_id, third_party_user_id, email, oauth_tokens, raw_user_info_from_provider, tenant_id, user_context) - - # user object contains the ID and email of the user - user = result.user - print(user) - - # This is the response from the OAuth 2 provider that contains their tokens or user info. - provider_access_token = result.oauth_tokens["access_token"] - print(provider_access_token) - - if result.raw_user_info_from_provider.from_user_info_api is not None: - first_name = result.raw_user_info_from_provider.from_user_info_api["first_name"] - print(first_name) - - if result.created_new_user: - print("New user was created") - # TODO: Post sign up logic - else: - print("User already existed and was signed in") - # TODO: Post sign in logic - + result = await original_thirdparty_sign_in_up( + third_party_id, + third_party_user_id, + email, + is_verified, + oauth_tokens, + raw_user_info_from_provider, + session, + should_try_linking_with_session_user, + tenant_id, + user_context, + ) + + if isinstance(result, SignInUpOkResult): + # user object contains the ID and email of the user + user = result.user + print(user) + + # This is the response from the OAuth 2 provider that contains their tokens or user info. + provider_access_token = result.oauth_tokens["access_token"] + print(provider_access_token) + + if result.raw_user_info_from_provider.from_user_info_api is not None: + first_name = result.raw_user_info_from_provider.from_user_info_api[ + "first_name" + ] + print(first_name) + + if session is None: + if ( + result.created_new_recipe_user + and len(result.user.login_methods) == 1 + ): + print("New user was created") + # TODO: Post sign up logic + else: + print("User already existed and was signed in") + # TODO: Post sign in logic + return result original_implementation.sign_in_up = thirdparty_sign_in_up return original_implementation -def override_emailpassword_functions(original_implementation: EmailPasswordRecipeInterface) -> EmailPasswordRecipeInterface: + +def override_emailpassword_functions( + original_implementation: EmailPasswordRecipeInterface, +) -> EmailPasswordRecipeInterface: original_emailpassword_sign_up = original_implementation.sign_up original_emailpassword_sign_in = original_implementation.sign_in @@ -426,12 +461,25 @@ def override_emailpassword_functions(original_implementation: EmailPasswordRecip email: str, password: str, tenant_id: str, - user_context: Dict[str, Any] + session: Union[SessionContainer, None], + should_try_linking_with_session_user: Union[bool, None], + user_context: Dict[str, Any], ): # TODO: some pre sign up logic - result = await original_emailpassword_sign_up(email, password, tenant_id, user_context) + result = await original_emailpassword_sign_up( + email, + password, + tenant_id, + session, + should_try_linking_with_session_user, + user_context, + ) - if isinstance(result, SignUpOkResult): + if ( + isinstance(result, SignUpOkResult) + and len(result.user.login_methods) == 1 + and session is None + ): # TODO: some post sign up logic pass @@ -441,12 +489,25 @@ def override_emailpassword_functions(original_implementation: EmailPasswordRecip email: str, password: str, tenant_id: str, - user_context: Dict[str, Any] + session: Union[SessionContainer, None], + should_try_linking_with_session_user: Union[bool, None], + user_context: Dict[str, Any], ): # TODO: some pre sign in logic - result = await original_emailpassword_sign_in(email, password, tenant_id, user_context) + result = await original_emailpassword_sign_in( + email, + password, + tenant_id, + session, + should_try_linking_with_session_user, + user_context, + ) - if isinstance(result, SignInOkResult): + if ( + isinstance(result, SignInOkResult) + and len(result.user.login_methods) == 1 + and session is None + ): # TODO: some post sign in logic pass @@ -456,28 +517,30 @@ def override_emailpassword_functions(original_implementation: EmailPasswordRecip original_implementation.sign_in = emailpassword_sign_in return original_implementation + + # highlight-end init( app_info=InputAppInfo(api_domain="...", app_name="...", website_domain="..."), - framework='...', # type: ignore + framework="...", # type: ignore recipe_list=[ thirdparty.init( - # highlight-start + # highlight-start override=thirdparty.InputOverrideConfig( functions=override_thirdparty_functions ), - # highlight-end + # highlight-end ), emailpassword.init( - # highlight-start + # highlight-start override=emailpassword.InputOverrideConfig( functions=override_emailpassword_functions ), - # highlight-end + # highlight-end ), session.init(), - ] + ], ) ``` @@ -631,61 +694,77 @@ func main() { ```python from supertokens_python import init, InputAppInfo from supertokens_python.recipe import emailpassword -from supertokens_python.recipe.emailpassword.interfaces import APIInterface, APIOptions, SignUpPostOkResult, RecipeInterface -from typing import Dict, Any, List +from supertokens_python.recipe.emailpassword.interfaces import ( + APIInterface, + APIOptions, + SignUpPostOkResult, +) from supertokens_python.recipe.emailpassword.types import FormField +from typing import Dict, Any, List, Union +from supertokens_python.recipe.session.interfaces import SessionContainer + # highlight-start -def override_emailpassword_apis(original_implementation: APIInterface): - original_emailpassword_sign_up_post = original_implementation.sign_up_post +def override_email_password_apis(original_implementation: APIInterface): + original_sign_up_post = original_implementation.sign_up_post - async def emailpassword_sign_up_post( + async def sign_up_post( form_fields: List[FormField], tenant_id: str, + session: Union[SessionContainer, None], + should_try_linking_with_session_user: Union[bool, None], api_options: APIOptions, - user_context: Dict[str, Any] + user_context: Dict[str, Any], ): - # call the default behaviour as show below - result = await original_emailpassword_sign_up_post(form_fields, tenant_id, api_options, user_context) + # First we call the original implementation of sign_up_post. + response = await original_sign_up_post( + form_fields, + tenant_id, + session, + should_try_linking_with_session_user, + api_options, + user_context, + ) - if isinstance(result, SignUpPostOkResult): - # TODO: sign up successful + # Post sign up response, we check if it was successful + if ( + isinstance(response, SignUpPostOkResult) + and len(response.user.login_methods) == 1 + and session is None + ): + _id = response.user.id + emails = response.user.emails + print(_id) + print(emails) - # here we fetch a custom form field for the user's name. - # Note that for this to be available, you need to define - # this custom form field. - - # loop result formfields name = "" - for form_field in form_fields: - if form_field.id == "name": - name = form_field.value - break + + for field in form_fields: + if field.id == "name": + name = field.value print(name) - - return result - original_implementation.sign_up_post = emailpassword_sign_up_post - return original_implementation + return response -def override_thirdpartyemailpassword_functions(original_implementation: RecipeInterface): - # TODO: from previous code snippets + original_implementation.sign_up_post = sign_up_post return original_implementation + + # highlight-end init( app_info=InputAppInfo(api_domain="...", app_name="...", website_domain="..."), - framework='...', # type: ignore + framework="...", # type: ignore recipe_list=[ emailpassword.init( - # highlight-start + # highlight-start override=emailpassword.InputOverrideConfig( - apis=override_emailpassword_apis + apis=override_email_password_apis ) - # highlight-end + # highlight-end ) - ] + ], ) ``` diff --git a/v2/thirdpartyemailpassword/common-customizations/multi-tenancy/new-tenant-config.mdx b/v2/thirdpartyemailpassword/common-customizations/multi-tenancy/new-tenant-config.mdx index 83e4d7c1d..02b501981 100644 --- a/v2/thirdpartyemailpassword/common-customizations/multi-tenancy/new-tenant-config.mdx +++ b/v2/thirdpartyemailpassword/common-customizations/multi-tenancy/new-tenant-config.mdx @@ -89,14 +89,14 @@ func main() { ```python from supertokens_python.recipe.multitenancy.asyncio import create_or_update_tenant -from supertokens_python.recipe.multitenancy.interfaces import TenantConfig +from supertokens_python.recipe.multitenancy.interfaces import TenantConfigCreateOrUpdate + async def update_tenant(): result = await create_or_update_tenant( - "customer1", - config=TenantConfig( - third_party_enabled=True, - email_password_enabled=True, + "customer1", + config=TenantConfigCreateOrUpdate( + first_factors=["thirdparty", "emailpassword"], ), ) @@ -114,22 +114,23 @@ async def update_tenant(): ```python from supertokens_python.recipe.multitenancy.syncio import create_or_update_tenant -from supertokens_python.recipe.multitenancy.interfaces import TenantConfig +from supertokens_python.recipe.multitenancy.interfaces import TenantConfigCreateOrUpdate -result = create_or_update_tenant( - "customer1", - config=TenantConfig( - third_party_enabled=True, - email_password_enabled=True, + +def update_tenant(): + result = create_or_update_tenant( + "customer1", + config=TenantConfigCreateOrUpdate( + first_factors=["thirdparty", "emailpassword"], ), ) -if result.status != "OK": - print("Error creating or updating tenant") -elif result.created_new: - print("New tenant was created") -else: - print("Existing tenant's config was modified") + if result.status != "OK": + print("Error creating or updating tenant") + elif result.created_new: + print("New tenant was created") + else: + print("Existing tenant's config was modified") ``` diff --git a/v2/thirdpartyemailpassword/common-customizations/userid-format.mdx b/v2/thirdpartyemailpassword/common-customizations/userid-format.mdx index 7d518d472..ccf1a2fc9 100644 --- a/v2/thirdpartyemailpassword/common-customizations/userid-format.mdx +++ b/v2/thirdpartyemailpassword/common-customizations/userid-format.mdx @@ -205,44 +205,79 @@ func main() { from supertokens_python import init, InputAppInfo from supertokens_python.asyncio import create_user_id_mapping from supertokens_python.recipe import thirdparty, emailpassword, session -from supertokens_python.recipe.thirdparty.interfaces import RecipeInterface as ThirdPartyRecipeInterface -from supertokens_python.recipe.emailpassword.interfaces import RecipeInterface as EmailPasswordRecipeInterface, SignUpOkResult +from supertokens_python.recipe.thirdparty.interfaces import ( + RecipeInterface as ThirdPartyRecipeInterface, + SignInUpOkResult, +) +from supertokens_python.recipe.emailpassword.interfaces import ( + RecipeInterface as EmailPasswordRecipeInterface, + SignUpOkResult, +) from supertokens_python.recipe.thirdparty.types import RawUserInfoFromProvider -from typing import Dict, Any +from typing import Dict, Any, Optional, Union +from supertokens_python.recipe.session.interfaces import SessionContainer +from supertokens_python.types import RecipeUserId -def override_thirdparty_functions(original_implementation: ThirdPartyRecipeInterface) -> ThirdPartyRecipeInterface: +def override_thirdparty_functions( + original_implementation: ThirdPartyRecipeInterface, +) -> ThirdPartyRecipeInterface: original_thirdparty_sign_in_up = original_implementation.sign_in_up async def thirdparty_sign_in_up( third_party_id: str, third_party_user_id: str, email: str, + is_verified: bool, oauth_tokens: Dict[str, Any], raw_user_info_from_provider: RawUserInfoFromProvider, + session: Optional[SessionContainer], + should_try_linking_with_session_user: Union[bool, None], tenant_id: str, - user_context: Dict[str, Any] + user_context: Dict[str, Any], ): - result = await original_thirdparty_sign_in_up(third_party_id, third_party_user_id, email, oauth_tokens, raw_user_info_from_provider, tenant_id, user_context) - - # user object contains the ID and email of the user - user = result.user - print(user) - - # This is the response from the OAuth 2 provider that contains their tokens or user info. - provider_access_token = result.oauth_tokens["access_token"] - print(provider_access_token) - - if result.raw_user_info_from_provider.from_user_info_api is not None: - first_name = result.raw_user_info_from_provider.from_user_info_api["first_name"] - print(first_name) - - if result.created_new_user: - # highlight-start - external_user_id = "" - await create_user_id_mapping(result.user.user_id, external_user_id) - result.user.user_id = external_user_id - # highlight-end + result = await original_thirdparty_sign_in_up( + third_party_id, + third_party_user_id, + email, + is_verified, + oauth_tokens, + raw_user_info_from_provider, + session, + should_try_linking_with_session_user, + tenant_id, + user_context, + ) + + if isinstance(result, SignInUpOkResult): + # user object contains the ID and email of the user + user = result.user + print(user) + + # This is the response from the OAuth 2 provider that contains their tokens or user info. + provider_access_token = result.oauth_tokens["access_token"] + print(provider_access_token) + + if result.raw_user_info_from_provider.from_user_info_api is not None: + first_name = result.raw_user_info_from_provider.from_user_info_api[ + "first_name" + ] + print(first_name) + + if ( + result.created_new_recipe_user + and len(result.user.login_methods) == 1 + and session is None + ): + # highlight-start + external_user_id = "" + await create_user_id_mapping(result.user.id, external_user_id) + result.user.id = external_user_id + result.user.login_methods[0].recipe_user_id = RecipeUserId( + external_user_id + ) + result.recipe_user_id = RecipeUserId(external_user_id) + # highlight-end return result @@ -250,22 +285,40 @@ def override_thirdparty_functions(original_implementation: ThirdPartyRecipeInter return original_implementation -def override_emailpassword_functions(original_implementation: EmailPasswordRecipeInterface) -> EmailPasswordRecipeInterface: + +def override_emailpassword_functions( + original_implementation: EmailPasswordRecipeInterface, +) -> EmailPasswordRecipeInterface: original_emailpassword_sign_up = original_implementation.sign_up async def emailpassword_sign_up( email: str, password: str, tenant_id: str, - user_context: Dict[str, Any] + session: Union[SessionContainer, None], + should_try_linking_with_session_user: Union[bool, None], + user_context: Dict[str, Any], ): - result = await original_emailpassword_sign_up(email, password, tenant_id, user_context) - - if isinstance(result, SignUpOkResult): + result = await original_emailpassword_sign_up( + email, + password, + tenant_id, + session, + should_try_linking_with_session_user, + user_context, + ) + + if ( + isinstance(result, SignUpOkResult) + and len(result.user.login_methods) == 1 + and session is None + ): # highlight-start external_user_id = "" - await create_user_id_mapping(result.user.user_id, external_user_id) - result.user.user_id = external_user_id + await create_user_id_mapping(result.user.id, external_user_id) + result.user.id = external_user_id + result.user.login_methods[0].recipe_user_id = RecipeUserId(external_user_id) + result.recipe_user_id = RecipeUserId(external_user_id) # highlight-end return result @@ -276,9 +329,8 @@ def override_emailpassword_functions(original_implementation: EmailPasswordRecip init( - app_info=InputAppInfo( - api_domain="...", app_name="...", website_domain="..."), - framework='...', # type: ignore + app_info=InputAppInfo(api_domain="...", app_name="...", website_domain="..."), + framework="...", # type: ignore recipe_list=[ thirdparty.init( # highlight-start @@ -295,7 +347,7 @@ init( # highlight-end ), session.init(), - ] + ], ) ``` diff --git a/v2/thirdpartyemailpassword/custom-ui/multitenant-login.mdx b/v2/thirdpartyemailpassword/custom-ui/multitenant-login.mdx index 1bc9f2a39..8e1c60b4c 100644 --- a/v2/thirdpartyemailpassword/custom-ui/multitenant-login.mdx +++ b/v2/thirdpartyemailpassword/custom-ui/multitenant-login.mdx @@ -118,14 +118,14 @@ func main() { ```python from supertokens_python.recipe.multitenancy.asyncio import create_or_update_tenant -from supertokens_python.recipe.multitenancy.interfaces import TenantConfig +from supertokens_python.recipe.multitenancy.interfaces import TenantConfigCreateOrUpdate + async def update_tenant(): result = await create_or_update_tenant( - "customer1", - config=TenantConfig( - third_party_enabled=True, - email_password_enabled=True, + "customer1", + config=TenantConfigCreateOrUpdate( + first_factors=["thirdparty", "emailpassword"], ), ) @@ -143,22 +143,23 @@ async def update_tenant(): ```python from supertokens_python.recipe.multitenancy.syncio import create_or_update_tenant -from supertokens_python.recipe.multitenancy.interfaces import TenantConfig +from supertokens_python.recipe.multitenancy.interfaces import TenantConfigCreateOrUpdate -result = create_or_update_tenant( - "customer1", - config=TenantConfig( - third_party_enabled=True, - email_password_enabled=True, + +def update_tenant(): + result = create_or_update_tenant( + "customer1", + config=TenantConfigCreateOrUpdate( + first_factors=["thirdparty", "emailpassword"], ), ) -if result.status != "OK": - print("Error creating or updating tenant") -elif result.created_new: - print("New tenant was created") -else: - print("Existing tenant's config was modified") + if result.status != "OK": + print("Error creating or updating tenant") + elif result.created_new: + print("New tenant was created") + else: + print("Existing tenant's config was modified") ``` diff --git a/v2/thirdpartyemailpassword/pre-built-ui/multitenant-login.mdx b/v2/thirdpartyemailpassword/pre-built-ui/multitenant-login.mdx index b643b5bbe..9f508a27a 100644 --- a/v2/thirdpartyemailpassword/pre-built-ui/multitenant-login.mdx +++ b/v2/thirdpartyemailpassword/pre-built-ui/multitenant-login.mdx @@ -115,14 +115,14 @@ func main() { ```python from supertokens_python.recipe.multitenancy.asyncio import create_or_update_tenant -from supertokens_python.recipe.multitenancy.interfaces import TenantConfig +from supertokens_python.recipe.multitenancy.interfaces import TenantConfigCreateOrUpdate + async def update_tenant(): result = await create_or_update_tenant( - "customer1", - config=TenantConfig( - third_party_enabled=True, - email_password_enabled=True, + "customer1", + config=TenantConfigCreateOrUpdate( + first_factors=["thirdparty", "emailpassword"], ), ) @@ -140,22 +140,23 @@ async def update_tenant(): ```python from supertokens_python.recipe.multitenancy.syncio import create_or_update_tenant -from supertokens_python.recipe.multitenancy.interfaces import TenantConfig +from supertokens_python.recipe.multitenancy.interfaces import TenantConfigCreateOrUpdate -result = create_or_update_tenant( - "customer1", - config=TenantConfig( - third_party_enabled=True, - email_password_enabled=True, + +def update_tenant(): + result = create_or_update_tenant( + "customer1", + config=TenantConfigCreateOrUpdate( + first_factors=["thirdparty", "emailpassword"], ), ) -if result.status != "OK": - print("Error creating or updating tenant") -elif result.created_new: - print("New tenant was created") -else: - print("Existing tenant's config was modified") + if result.status != "OK": + print("Error creating or updating tenant") + elif result.created_new: + print("New tenant was created") + else: + print("Existing tenant's config was modified") ``` From b469058bed9857c4935963c92047122d642c546a Mon Sep 17 00:00:00 2001 From: rishabhpoddar Date: Thu, 24 Oct 2024 18:14:44 +0530 Subject: [PATCH 31/34] more docs --- .../custom-response/general-error.mdx | 54 +++-- .../apis-override/usage.mdx | 53 +++-- .../backend-functions-override/usage.mdx | 58 +++-- .../advanced-customizations/user-context.mdx | 205 +++++++++++++----- 4 files changed, 259 insertions(+), 111 deletions(-) diff --git a/v2/thirdpartypasswordless/advanced-customizations/apis-override/custom-response/general-error.mdx b/v2/thirdpartypasswordless/advanced-customizations/apis-override/custom-response/general-error.mdx index b112d9b47..9d082c418 100644 --- a/v2/thirdpartypasswordless/advanced-customizations/apis-override/custom-response/general-error.mdx +++ b/v2/thirdpartypasswordless/advanced-customizations/apis-override/custom-response/general-error.mdx @@ -118,32 +118,56 @@ func emailNotAllowed(email string) bool { ```python from supertokens_python.recipe import passwordless -from supertokens_python.recipe.passwordless.interfaces import APIOptions, ConsumeCodePostOkResult, ConsumeCodePostRestartFlowError, ConsumeCodePostIncorrectUserInputCodeError, ConsumeCodePostExpiredUserInputCodeError -from typing import Dict, Any, Union +from supertokens_python.recipe.passwordless.interfaces import APIOptions +from typing import Dict, Any, Union, Optional +from supertokens_python.recipe.session.interfaces import SessionContainer from supertokens_python.recipe.passwordless.interfaces import APIInterface -from supertokens_python.recipe.passwordless.asyncio import list_codes_by_pre_auth_session_id +from supertokens_python.recipe.passwordless.asyncio import ( + list_codes_by_pre_auth_session_id, +) from supertokens_python.types import GeneralErrorResponse -# typecheck-only, removed from output - def override_passwordless_apis(original_implementation: APIInterface): original_consume_code_post = original_implementation.consume_code_post - async def consume_code_post(pre_auth_session_id: str, user_input_code: Union[str, None], device_id: Union[str, None], link_code: Union[str, None], tenant_id: str, api_options: APIOptions, - user_context: Dict[str, Any]) -> Union[ConsumeCodePostOkResult, ConsumeCodePostRestartFlowError, GeneralErrorResponse, ConsumeCodePostIncorrectUserInputCodeError, ConsumeCodePostExpiredUserInputCodeError]: - code_info = await list_codes_by_pre_auth_session_id(tenant_id, pre_auth_session_id) + async def consume_code_post( + pre_auth_session_id: str, + user_input_code: Union[str, None], + device_id: Union[str, None], + link_code: Union[str, None], + session: Optional[SessionContainer], + should_try_linking_with_session_user: Union[bool, None], + tenant_id: str, + api_options: APIOptions, + user_context: Dict[str, Any], + ): + code_info = await list_codes_by_pre_auth_session_id( + tenant_id, pre_auth_session_id + ) if code_info is None: raise Exception("Should never come here") email = code_info.email if email is None: # this example is focused on login via email raise Exception("Should never come here") - if (is_not_allowed(email)): + if is_not_allowed(email): # highlight-next-line - return GeneralErrorResponse("You are not allowed to sign up. Please contact the app's admin to get permission") - return await original_consume_code_post(pre_auth_session_id, user_input_code, device_id, link_code, tenant_id, api_options, user_context) + return GeneralErrorResponse( + "You are not allowed to sign up. Please contact the app's admin to get permission" + ) + return await original_consume_code_post( + pre_auth_session_id, + user_input_code, + device_id, + link_code, + session, + should_try_linking_with_session_user, + tenant_id, + api_options, + user_context, + ) original_implementation.consume_code_post = consume_code_post return original_implementation @@ -155,11 +179,9 @@ def is_not_allowed(email: str): passwordless.init( - contact_config="", # type: ignore # typecheck-only, removed from output - flow_type="USER_INPUT_CODE", # typecheck-only, removed from output - override=passwordless.InputOverrideConfig( - apis=override_passwordless_apis - ) + contact_config="", # type: ignore # typecheck-only, removed from output + flow_type="USER_INPUT_CODE", # typecheck-only, removed from output + override=passwordless.InputOverrideConfig(apis=override_passwordless_apis), ) ``` diff --git a/v2/thirdpartypasswordless/advanced-customizations/apis-override/usage.mdx b/v2/thirdpartypasswordless/advanced-customizations/apis-override/usage.mdx index a3762e714..445997ddb 100644 --- a/v2/thirdpartypasswordless/advanced-customizations/apis-override/usage.mdx +++ b/v2/thirdpartypasswordless/advanced-customizations/apis-override/usage.mdx @@ -119,49 +119,60 @@ See all the [functions that can be overrided here](https://supertokens.com/docs/ ```python from supertokens_python import init, InputAppInfo from supertokens_python.recipe import passwordless -from supertokens_python.recipe.passwordless.interfaces import ( - ConsumeCodePostOkResult, ConsumeCodePostRestartFlowError, ConsumeCodePostIncorrectUserInputCodeError, ConsumeCodePostExpiredUserInputCodeError, - APIInterface, APIOptions) -from typing import Union, Dict, Any -from supertokens_python.types import GeneralErrorResponse +from supertokens_python.recipe.passwordless.interfaces import APIInterface, APIOptions +from typing import Union, Dict, Any, Optional +from supertokens_python.recipe.session.interfaces import SessionContainer + # highlight-start def override_passwordless_apis(original_implementation: APIInterface): original_consume_code_post = original_implementation.consume_code_post - async def consume_code_post(pre_auth_session_id: str, - user_input_code: Union[str, None], - device_id: Union[str, None], - link_code: Union[str, None], - tenant_id: str, - api_options: APIOptions, - user_context: Dict[str, Any]) -> Union[ConsumeCodePostOkResult, ConsumeCodePostRestartFlowError, ConsumeCodePostIncorrectUserInputCodeError, ConsumeCodePostExpiredUserInputCodeError, - GeneralErrorResponse]: + async def consume_code_post( + pre_auth_session_id: str, + user_input_code: Union[str, None], + device_id: Union[str, None], + link_code: Union[str, None], + session: Optional[SessionContainer], + should_try_linking_with_session_user: Union[bool, None], + tenant_id: str, + api_options: APIOptions, + user_context: Dict[str, Any], + ): # TODO: some custom logic # or call the default behaviour as show below - return await original_consume_code_post(pre_auth_session_id, user_input_code, device_id, link_code, tenant_id, api_options, user_context) + return await original_consume_code_post( + pre_auth_session_id, + user_input_code, + device_id, + link_code, + session, + should_try_linking_with_session_user, + tenant_id, + api_options, + user_context, + ) original_implementation.consume_code_post = consume_code_post return original_implementation + + # highlight-end init( - app_info=InputAppInfo( - api_domain="...", app_name="...", website_domain="..."), - framework='...', # type: ignore + app_info=InputAppInfo(api_domain="...", app_name="...", website_domain="..."), + framework="...", # type: ignore recipe_list=[ passwordless.init( contact_config=..., # type: ignore flow_type="...", # type: ignore # highlight-start - override=passwordless.InputOverrideConfig( - apis=override_passwordless_apis - ) + override=passwordless.InputOverrideConfig(apis=override_passwordless_apis), # highlight-end ) - ] + ], ) ``` diff --git a/v2/thirdpartypasswordless/advanced-customizations/backend-functions-override/usage.mdx b/v2/thirdpartypasswordless/advanced-customizations/backend-functions-override/usage.mdx index afd2bd4b4..fdd1c32c3 100644 --- a/v2/thirdpartypasswordless/advanced-customizations/backend-functions-override/usage.mdx +++ b/v2/thirdpartypasswordless/advanced-customizations/backend-functions-override/usage.mdx @@ -119,43 +119,59 @@ See all the [functions that can be overrided here](https://supertokens.com/docs/ ```python from supertokens_python import init, InputAppInfo from supertokens_python.recipe import passwordless -from supertokens_python.recipe.passwordless.interfaces import ConsumeCodeOkResult, ConsumeCodeIncorrectUserInputCodeError, ConsumeCodeExpiredUserInputCodeError, ConsumeCodeRestartFlowError +from supertokens_python.recipe.session.interfaces import SessionContainer from supertokens_python.recipe.passwordless.interfaces import RecipeInterface -from typing import Union, Dict, Any +from typing import Union, Dict, Any, Optional + # highlight-start def override_passwordless_functions(original_implementation: RecipeInterface): - original_consume_code = original_implementation.consume_code - async def consume_code(pre_auth_session_id: str, - user_input_code: Union[str, None], - device_id: Union[str, None], - link_code: Union[str, None], - tenant_id: str, - user_context: Dict[str, Any]) -> Union[ConsumeCodeOkResult, ConsumeCodeIncorrectUserInputCodeError, ConsumeCodeExpiredUserInputCodeError, ConsumeCodeRestartFlowError]: + original_consume_code = original_implementation.consume_code + + async def consume_code( + pre_auth_session_id: str, + user_input_code: Union[str, None], + device_id: Union[str, None], + link_code: Union[str, None], + session: Optional[SessionContainer], + should_try_linking_with_session_user: Union[bool, None], + tenant_id: str, + user_context: Dict[str, Any], + ): # TODO: some custom logic - - # or call the default behaviour as show below - return await original_consume_code(pre_auth_session_id, user_input_code, device_id, link_code, tenant_id, user_context) - - original_implementation.consume_code = consume_code - return original_implementation + + # or call the default behaviour as show below + return await original_consume_code( + pre_auth_session_id, + user_input_code, + device_id, + link_code, + session, + should_try_linking_with_session_user, + tenant_id, + user_context, + ) + + original_implementation.consume_code = consume_code + return original_implementation + + # highlight-end init( app_info=InputAppInfo(api_domain="...", app_name="...", website_domain="..."), - - framework='...', # type: ignore + framework="...", # type: ignore recipe_list=[ passwordless.init( - contact_config=..., # type: ignore - flow_type="...", # type: ignore + contact_config=..., # type: ignore + flow_type="...", # type: ignore # highlight-start override=passwordless.InputOverrideConfig( functions=override_passwordless_functions - ) + ), # highlight-end ) - ] + ], ) ``` diff --git a/v2/thirdpartypasswordless/advanced-customizations/user-context.mdx b/v2/thirdpartypasswordless/advanced-customizations/user-context.mdx index c8198e983..8451b2e7d 100644 --- a/v2/thirdpartypasswordless/advanced-customizations/user-context.mdx +++ b/v2/thirdpartypasswordless/advanced-customizations/user-context.mdx @@ -210,22 +210,45 @@ func main() { ```python from supertokens_python import init, InputAppInfo from supertokens_python.recipe import passwordless, thirdparty -from supertokens_python.recipe.passwordless.interfaces import RecipeInterface as PasswordlessRecipeInterface, ConsumeCodeOkResult -from supertokens_python.recipe.thirdparty.interfaces import RecipeInterface as ThirdPartyRecipeInterface -from typing import Dict, Any, Union +from supertokens_python.recipe.passwordless.interfaces import ( + RecipeInterface as PasswordlessRecipeInterface, + ConsumeCodeOkResult, +) +from supertokens_python.recipe.thirdparty.interfaces import ( + RecipeInterface as ThirdPartyRecipeInterface, + SignInUpOkResult, +) +from typing import Dict, Any, Union, Optional +from supertokens_python.recipe.session.interfaces import SessionContainer from supertokens_python.recipe.thirdparty.types import RawUserInfoFromProvider + # highlight-start -def override_passwordless_functions(original_implementation: PasswordlessRecipeInterface): +def override_passwordless_functions( + original_implementation: PasswordlessRecipeInterface, +): original_consume_code = original_implementation.consume_code - async def consume_code(pre_auth_session_id: str, - user_input_code: Union[str, None], - device_id: Union[str, None], - link_code: Union[str, None], - tenant_id: str, - user_context: Dict[str, Any]): - response = await original_consume_code(pre_auth_session_id, user_input_code, device_id, link_code, tenant_id, user_context) + async def consume_code( + pre_auth_session_id: str, + user_input_code: Union[str, None], + device_id: Union[str, None], + link_code: Union[str, None], + session: Optional[SessionContainer], + should_try_linking_with_session_user: Union[bool, None], + tenant_id: str, + user_context: Dict[str, Any], + ): + response = await original_consume_code( + pre_auth_session_id, + user_input_code, + device_id, + link_code, + session, + should_try_linking_with_session_user, + tenant_id, + user_context, + ) if isinstance(response, ConsumeCodeOkResult): # This is called during the consume_code API , @@ -240,7 +263,11 @@ def override_passwordless_functions(original_implementation: PasswordlessRecipeI # sign up API, and this will tell the create_new_session function # (which will be called next) # to not create a new session in case userContext["isSignUp"] is True - if response.created_new_user: + if ( + response.created_new_recipe_user + and len(response.user.login_methods) == 1 + and session is None + ): user_context["isSignUp"] = True return response @@ -249,58 +276,79 @@ def override_passwordless_functions(original_implementation: PasswordlessRecipeI return original_implementation + def override_thirdparty_functions(original_implementation: ThirdPartyRecipeInterface): original_thirdparty_sign_in_up = original_implementation.sign_in_up + async def thirdparty_sign_in_up( third_party_id: str, third_party_user_id: str, email: str, + is_verified: bool, oauth_tokens: Dict[str, Any], raw_user_info_from_provider: RawUserInfoFromProvider, + session: Optional[SessionContainer], + should_try_linking_with_session_user: Union[bool, None], tenant_id: str, - user_context: Dict[str, Any] + user_context: Dict[str, Any], ): response = await original_thirdparty_sign_in_up( - third_party_id, third_party_user_id, email, oauth_tokens, raw_user_info_from_provider, tenant_id, user_context) + third_party_id, + third_party_user_id, + email, + is_verified, + oauth_tokens, + raw_user_info_from_provider, + session, + should_try_linking_with_session_user, + tenant_id, + user_context, + ) + if isinstance(response, SignInUpOkResult): - # This is called during the sign_in_up API for third party login, - # but before calling the create_new_session function. - # At the start of the API, we do not know if it will result in a - # sign in or a sign up, so we cannot override the API function. - # Instead, we override the recipe function as shown here, - # and then set the relevant context only if it's a new user. - if response.created_new_user: - user_context["isSignUp"] = True + # This is called during the sign_in_up API for third party login, + # but before calling the create_new_session function. + # At the start of the API, we do not know if it will result in a + # sign in or a sign up, so we cannot override the API function. + # Instead, we override the recipe function as shown here, + # and then set the relevant context only if it's a new user. + if ( + response.created_new_recipe_user + and len(response.user.login_methods) == 1 + and session is None + ): + user_context["isSignUp"] = True return response original_implementation.sign_in_up = thirdparty_sign_in_up return original_implementation + + # highlight-end init( - app_info=InputAppInfo( - api_domain="...", app_name="...", website_domain="..."), - framework='...', # type: ignore + app_info=InputAppInfo(api_domain="...", app_name="...", website_domain="..."), + framework="...", # type: ignore recipe_list=[ passwordless.init( contact_config=..., # type: ignore flow_type="...", # type: ignore override=passwordless.InputOverrideConfig( functions=override_passwordless_functions - ) + ), ), thirdparty.init( contact_config=..., # type: ignore flow_type="...", # type: ignore override=thirdparty.InputOverrideConfig( functions=override_thirdparty_functions - ) + ), ), - ] + ], ) ``` @@ -466,12 +514,18 @@ func main() { ```python from supertokens_python import init, InputAppInfo -from supertokens_python.recipe.session.interfaces import RecipeInterface, SessionClaimValidator, SessionClaim, GetSessionTokensDangerouslyDict +from supertokens_python.recipe.session.interfaces import ( + RecipeInterface, + SessionClaimValidator, + SessionClaim, + GetSessionTokensDangerouslyDict, +) from supertokens_python.recipe.session.recipe_implementation import RecipeImplementation from typing import Dict, Any, Union, List, TypeVar, Optional from supertokens_python.recipe import session from supertokens_python.framework import BaseRequest from supertokens_python.recipe.session.utils import TokenTransferMethod +from supertokens_python.types import RecipeUserId _T = TypeVar("_T") @@ -481,12 +535,15 @@ _T = TypeVar("_T") def override_session_functions(original_implementation: RecipeInterface): original_create_new_session = original_implementation.create_new_session - async def create_new_session(user_id: str, - access_token_payload: Optional[Dict[str, Any]], - session_data_in_database: Optional[Dict[str, Any]], - disable_anti_csrf: Optional[bool], - tenant_id: str, - user_context: Dict[str, Any]): + async def create_new_session( + user_id: str, + recipe_user_id: RecipeUserId, + access_token_payload: Optional[Dict[str, Any]], + session_data_in_database: Optional[Dict[str, Any]], + disable_anti_csrf: Optional[bool], + tenant_id: str, + user_context: Dict[str, Any], + ): if user_context["isSignUp"] is True: # The execution will come here only in case # a sign up API is calling this function. This is because @@ -494,61 +551,101 @@ def override_session_functions(original_implementation: RecipeInterface): # (see above code). return EmptySession(original_implementation) - return await original_create_new_session(user_id, access_token_payload, session_data_in_database, disable_anti_csrf, tenant_id, user_context) + return await original_create_new_session( + user_id, + recipe_user_id, + access_token_payload, + session_data_in_database, + disable_anti_csrf, + tenant_id, + user_context, + ) + original_implementation.create_new_session = create_new_session return original_implementation + + # highlight-end init( - app_info=InputAppInfo( - api_domain="...", app_name="...", website_domain="..."), - framework='...', # type: ignore + app_info=InputAppInfo(api_domain="...", app_name="...", website_domain="..."), + framework="...", # type: ignore recipe_list=[ session.init( - override=session.InputOverrideConfig( - functions=override_session_functions - ) + override=session.InputOverrideConfig(functions=override_session_functions) ) - ] + ], ) class EmptySession(session.SessionContainer): def __init__(self, recipe_implementation: RecipeInterface): assert isinstance(recipe_implementation, RecipeImplementation) - super().__init__(recipe_implementation, - recipe_implementation.config, "", "", None, "", "", "", {}, None, False, "") + super().__init__( + recipe_implementation, + recipe_implementation.config, + "", + "", + None, + "", + "", + "", + RecipeUserId(""), + {}, + None, + False, + "", + ) async def revoke_session(self, user_context: Union[Any, None] = None) -> None: pass - async def get_session_data_from_database(self, user_context: Union[Dict[str, Any], None] = None) -> Dict[str, Any]: + async def get_session_data_from_database( + self, user_context: Union[Dict[str, Any], None] = None + ) -> Dict[str, Any]: return {} - async def update_session_data_in_database(self, new_session_data: Dict[str, Any], user_context: Union[Dict[str, Any], None] = None) -> None: + async def update_session_data_in_database( + self, + new_session_data: Dict[str, Any], + user_context: Union[Dict[str, Any], None] = None, + ) -> None: pass def get_user_id(self, user_context: Union[Dict[str, Any], None] = None) -> str: return "" def get_access_token_payload( - self, user_context: Union[Dict[str, Any], None] = None) -> Dict[str, Any]: + self, user_context: Union[Dict[str, Any], None] = None + ) -> Dict[str, Any]: return {} def get_handle(self, user_context: Union[Dict[str, Any], None] = None) -> str: return "" + def get_recipe_user_id( + self, user_context: Union[Dict[str, Any], None] = None + ) -> RecipeUserId: + return RecipeUserId("") + def get_access_token(self, user_context: Union[Dict[str, Any], None] = None) -> str: return "" - async def get_time_created(self, user_context: Union[Dict[str, Any], None] = None) -> int: + async def get_time_created( + self, user_context: Union[Dict[str, Any], None] = None + ) -> int: return -1 async def get_expiry(self, user_context: Union[Dict[str, Any], None] = None) -> int: return -1 - async def attach_to_request_response(self, request: BaseRequest, transfer_method: TokenTransferMethod, user_context: Union[Dict[str, Any], None] = None): + async def attach_to_request_response( + self, + request: BaseRequest, + transfer_method: TokenTransferMethod, + user_context: Union[Dict[str, Any], None] = None, + ): pass async def assert_claims( @@ -584,7 +681,9 @@ class EmptySession(session.SessionContainer): pass async def merge_into_access_token_payload( - self, access_token_payload_update: Dict[str, Any], user_context: Union[Dict[str, Any], None] = None + self, + access_token_payload_update: Dict[str, Any], + user_context: Union[Dict[str, Any], None] = None, ) -> None: pass @@ -594,9 +693,9 @@ class EmptySession(session.SessionContainer): "accessToken": "", "antiCsrfToken": None, "frontToken": "", - "refreshToken": None + "refreshToken": None, } - + def get_tenant_id(self, user_context: Union[Dict[str, Any], None] = None) -> str: return "" ``` From 7917df6067b3e04b705df1e95e0321aebbba1ef5 Mon Sep 17 00:00:00 2001 From: rishabhpoddar Date: Thu, 24 Oct 2024 19:32:50 +0530 Subject: [PATCH 32/34] more fixes --- .../common-customizations/change-email.mdx | 189 +++++++++++++----- 1 file changed, 136 insertions(+), 53 deletions(-) diff --git a/v2/thirdpartypasswordless/common-customizations/change-email.mdx b/v2/thirdpartypasswordless/common-customizations/change-email.mdx index 124124f5f..f820a2b85 100644 --- a/v2/thirdpartypasswordless/common-customizations/change-email.mdx +++ b/v2/thirdpartypasswordless/common-customizations/change-email.mdx @@ -263,15 +263,20 @@ func isValidEmail(email string) bool { ```python from supertokens_python.recipe.session.framework.flask import verify_session from supertokens_python.recipe.session import SessionContainer -from supertokens_python.recipe.passwordless.syncio import update_user, get_user_by_id -from supertokens_python.recipe.passwordless.interfaces import UpdateUserOkResult, UpdateUserEmailAlreadyExistsError +from supertokens_python.recipe.passwordless.syncio import update_user +from supertokens_python.syncio import get_user +from supertokens_python.recipe.passwordless.interfaces import ( + UpdateUserOkResult, + UpdateUserEmailAlreadyExistsError, + EmailChangeNotAllowedError, +) from flask import g, request, Flask from re import fullmatch app = Flask(__name__) -@app.route('/change-email', methods=['POST']) # type: ignore +@app.route("/change-email", methods=["POST"]) # type: ignore @verify_session() def change_email(): # highlight-start @@ -290,15 +295,30 @@ def change_email(): return # check that the account is not a social account - user_id = session.get_user_id() - user_info = get_user_by_id(user_id) + user_info = get_user(session.get_user_id()) if user_info is None: # TODO handle error, cannot update email for social account return + login_method = next( + ( + lm + for lm in user_info.login_methods + if lm.recipe_user_id.get_as_string() + == session.get_recipe_user_id().get_as_string() + ), + None, + ) + + assert login_method is not None + + if login_method.recipe_id == "thirdparty": + # TODO: handle error, cannot update email for third party account + return + # update the users email - update_response = update_user(user_id, email=email) + update_response = update_user(session.get_recipe_user_id(), email=email) if isinstance(update_response, UpdateUserOkResult): # TODO send successful email update response @@ -308,11 +328,17 @@ def change_email(): # TODO handle error, email already exists return + if isinstance(update_response, EmailChangeNotAllowedError): + # This is possible if you have enabled account linking. + # See our docs for account linking to know more about this. + # TODO: tell the user to contact support. + return + raise Exception("Should never reach here") # highlight-end -async def is_valid_email(value: str) -> bool: +def is_valid_email(value: str) -> bool: return ( fullmatch( r'^(([^<>()\[\]\\.,;:\s@"]+(\.[^<>()\[\]\\.,;:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,' @@ -634,29 +660,32 @@ func isValidEmail(email string) bool { ```python from supertokens_python.recipe.session.framework.flask import verify_session from supertokens_python.recipe.session import SessionContainer -from supertokens_python.recipe.passwordless.syncio import ( - update_user -) +from supertokens_python.recipe.passwordless.syncio import update_user from supertokens_python.recipe.passwordless.interfaces import ( UpdateUserEmailAlreadyExistsError, - UpdateUserOkResult + UpdateUserOkResult, ) from supertokens_python.recipe.emailverification.syncio import ( is_email_verified, - send_email_verification_email, + send_email_verification_email, ) from flask import g, request, Flask from re import fullmatch +from supertokens_python.syncio import get_user +from supertokens_python.recipe.accountlinking.syncio import is_email_change_allowed +from supertokens_python.syncio import list_users_by_account_info +from supertokens_python.types import AccountInfo app = Flask(__name__) -@app.route('/change-email', methods=['POST']) # type: ignore + +@app.route("/change-email", methods=["POST"]) # type: ignore @verify_session() def change_email(): # highlight-start - session: SessionContainer = g.supertokens # type: ignore + session: SessionContainer = g.supertokens # type: ignore request_body = request.get_json() @@ -669,23 +698,71 @@ def change_email(): # TODO: handle invalid email error return + # check that the account is not a social account + user_info = get_user(session.get_user_id()) + + if user_info is None: + # TODO handle error, cannot update email for social account + return + + login_method = next( + ( + lm + for lm in user_info.login_methods + if lm.recipe_user_id.get_as_string() + == session.get_recipe_user_id().get_as_string() + ), + None, + ) + + assert login_method is not None + + if login_method.recipe_id == "thirdparty": + # TODO: handle error, cannot update email for third party account + return + # check that the account is not a social account user_id = session.get_user_id() # Then, we check if the email is verified for this user ID or not. # It is important to understand that SuperTokens stores email verification # status based on the user ID AND the email, and not just the email. - is_verified = is_email_verified(user_id, request_body["email"]) - + is_verified = is_email_verified(session.get_recipe_user_id(), request_body["email"]) + if not is_verified: + if not is_email_change_allowed( + session.get_recipe_user_id(), request_body["email"], False + ): + # Email change is not allowed, send a 400 error + return {"error": "Email change not allowed"}, 400 + # Before sending a verification email, we check if the email is already + # being used by another user. If it is, we throw an error. + user = get_user(user_id) + + if user is not None: + for tenant_id in user.tenant_ids: + users_with_same_email = list_users_by_account_info( + tenant_id, AccountInfo(email=request_body["email"]) + ) + for curr_user in users_with_same_email: + # Since one user can be shared across many tenants, we need to check if + # the email already exists in any of the tenants that belongs to this user. + if curr_user.id != user_id: + # TODO handle error, email already exists with another user. + return + # Create and send the email verification link to the user for the new email. - send_email_verification_email(session.get_tenant_id(), user_id, request_body["email"]) - - # TODO send successful email verification response - return - + send_email_verification_email( + session.get_tenant_id(), + user_id, + session.get_recipe_user_id(), + request_body["email"], + ) + # update the users email - update_response = update_user(user_id, email=request_body["email"]) + update_response = update_user( + session.get_recipe_user_id(), email=request_body["email"] + ) if isinstance(update_response, UpdateUserOkResult): # TODO send successful email update response @@ -699,7 +776,7 @@ def change_email(): # highlight-end -async def is_valid_email(value: str) -> bool: +def is_valid_email(value: str) -> bool: return ( fullmatch( r'^(([^<>()\[\]\\.,;:\s@"]+(\.[^<>()\[\]\\.,;:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,' @@ -843,68 +920,74 @@ from supertokens_python import init, InputAppInfo, SupertokensConfig from supertokens_python.recipe import passwordless, emailverification from supertokens_python.recipe.emailverification.interfaces import ( APIInterface, - APIOptions + APIOptions, ) -from supertokens_python.recipe.passwordless.asyncio import ( - update_user -) +from supertokens_python.recipe.passwordless.asyncio import update_user from supertokens_python.recipe.emailverification.interfaces import ( - EmailVerifyPostOkResult + EmailVerifyPostOkResult, ) -from supertokens_python.recipe.session.interfaces import ( - SessionContainer -) +from supertokens_python.recipe.session.interfaces import SessionContainer from supertokens_python import ( InputAppInfo, SupertokensConfig, ) -from supertokens_python.recipe.passwordless import ( - ContactEmailOrPhoneConfig -) +from supertokens_python.recipe.passwordless import ContactEmailOrPhoneConfig from typing import Optional, Dict, Any def override_email_verification_apis(original_implementation: APIInterface): - original_email_verification_verify_email_post = original_implementation.email_verify_post - - async def email_verify_post(token: str, session: Optional[SessionContainer], tenant_id: str, api_options: APIOptions, user_context: Dict[str, Any] ): - + original_email_verification_verify_email_post = ( + original_implementation.email_verify_post + ) + + async def email_verify_post( + token: str, + session: Optional[SessionContainer], + tenant_id: str, + api_options: APIOptions, + user_context: Dict[str, Any], + ): + # highlight-start - verification_response = await original_email_verification_verify_email_post(token, session, tenant_id, api_options, user_context) - + verification_response = await original_email_verification_verify_email_post( + token, session, tenant_id, api_options, user_context + ) + if isinstance(verification_response, EmailVerifyPostOkResult): - await update_user(verification_response.user.user_id, verification_response.user.email) - + await update_user( + verification_response.user.recipe_user_id, + verification_response.user.email, + ) + return verification_response # highlight-end - + original_implementation.email_verify_post = email_verify_post return original_implementation init( - supertokens_config=SupertokensConfig( - connection_uri="..." - ), + supertokens_config=SupertokensConfig(connection_uri="..."), app_info=InputAppInfo(api_domain="...", app_name="...", website_domain="..."), framework="flask", recipe_list=[ passwordless.init( flow_type="USER_INPUT_CODE_AND_MAGIC_LINK", - contact_config=ContactEmailOrPhoneConfig()), + contact_config=ContactEmailOrPhoneConfig(), + ), emailverification.init( - "REQUIRED", - override=emailverification.InputOverrideConfig( - # highlight-start - apis=override_email_verification_apis - # highlight-end + "REQUIRED", + override=emailverification.InputOverrideConfig( + # highlight-start + apis=override_email_verification_apis + # highlight-end + ), ), - ), ], ) ``` From 6ed245e24cd1c9b6009355c4ceb589a850d28608 Mon Sep 17 00:00:00 2001 From: rishabhpoddar Date: Thu, 24 Oct 2024 20:26:52 +0530 Subject: [PATCH 33/34] more fixes --- .../implementing-deduplication.mdx | 218 +++++++++++++----- .../common-customizations/get-user-info.mdx | 52 +---- .../handling-signinup-success.mdx | 149 +++++++----- .../multi-tenancy/new-tenant-config.mdx | 55 +++-- .../common-customizations/userid-format.mdx | 154 +++++++++---- .../custom-ui/multitenant-login.mdx | 55 +++-- .../pre-built-ui/multitenant-login.mdx | 55 +++-- 7 files changed, 477 insertions(+), 261 deletions(-) diff --git a/v2/thirdpartypasswordless/common-customizations/deduplication/implementing-deduplication.mdx b/v2/thirdpartypasswordless/common-customizations/deduplication/implementing-deduplication.mdx index c718ba43b..80d60d3cc 100644 --- a/v2/thirdpartypasswordless/common-customizations/deduplication/implementing-deduplication.mdx +++ b/v2/thirdpartypasswordless/common-customizations/deduplication/implementing-deduplication.mdx @@ -233,103 +233,194 @@ func main() { from supertokens_python import init, InputAppInfo from supertokens_python.types import GeneralErrorResponse from supertokens_python.recipe import passwordless, thirdparty -from supertokens_python.recipe.passwordless.asyncio import get_user_by_email as get_users_by_email_passwordless -from supertokens_python.recipe.thirdparty.asyncio import get_users_by_email as get_users_by_email_thirdparty -from supertokens_python.recipe.passwordless.interfaces import APIInterface as PasswordlessAPIInterface, APIOptions as PasswordlessAPIOptions -from supertokens_python.recipe.thirdparty.interfaces import RecipeInterface, SignInUpOkResult, APIInterface as ThirdPartyAPIInterface, APIOptions as ThirdPartyAPIOptions -from typing import Union, Dict, Any +from supertokens_python.recipe.passwordless.interfaces import ( + APIInterface as PasswordlessAPIInterface, + APIOptions as PasswordlessAPIOptions, +) +from supertokens_python.recipe.thirdparty.interfaces import ( + RecipeInterface, + APIInterface as ThirdPartyAPIInterface, + APIOptions as ThirdPartyAPIOptions, +) +from typing import Union, Dict, Any, Optional +from supertokens_python.recipe.session.interfaces import SessionContainer +from supertokens_python.asyncio import list_users_by_account_info +from supertokens_python.types import AccountInfo from supertokens_python.recipe.thirdparty.provider import Provider, RedirectUriInfo from supertokens_python.recipe.thirdparty.types import RawUserInfoFromProvider +from supertokens_python.recipe.thirdparty.types import ThirdPartyInfo + def override_thirdparty_functions(original_implementation: RecipeInterface): - original_thirdparty_sign_in_up = original_implementation.sign_in_up + original_sign_in_up = original_implementation.sign_in_up - async def thirdparty_sign_in_up( + async def sign_in_up( third_party_id: str, third_party_user_id: str, email: str, + is_verified: bool, oauth_tokens: Dict[str, Any], raw_user_info_from_provider: RawUserInfoFromProvider, + session: Optional[SessionContainer], + should_try_linking_with_session_user: Union[bool, None], tenant_id: str, - user_context: Dict[str, Any] - ) -> SignInUpOkResult: - existing_users = await get_users_by_email_thirdparty(tenant_id, email, user_context) - passwordless_user = await get_users_by_email_passwordless(tenant_id, email, user_context) - - if passwordless_user is not None: - raise Exception("Cannot sign up as email already exists") - - if (len(existing_users) == 0): + user_context: Dict[str, Any], + ): + existing_users = await list_users_by_account_info( + tenant_id, AccountInfo(email=email) + ) + if len(existing_users) == 0: # this means this email is new so we allow sign up - return await original_thirdparty_sign_in_up(third_party_id, third_party_user_id, email, oauth_tokens, raw_user_info_from_provider, tenant_id, user_context) - - is_sign_in = False - for user in existing_users: - if user.third_party_info.id == third_party_id and user.third_party_info.user_id == third_party_user_id: - is_sign_in = True - break - - if is_sign_in: - return await original_thirdparty_sign_in_up(third_party_id, third_party_user_id, email, oauth_tokens, raw_user_info_from_provider, tenant_id, user_context) - - # this means that the email already exists with another social or passwordless login method. + return await original_sign_in_up( + third_party_id, + third_party_user_id, + email, + is_verified, + oauth_tokens, + raw_user_info_from_provider, + session, + should_try_linking_with_session_user, + tenant_id, + user_context, + ) + + if any( + any( + lm.recipe_id == "thirdparty" + and lm.has_same_third_party_info_as( + ThirdPartyInfo(third_party_user_id, third_party_id) + ) + for lm in user.login_methods + ) + for user in existing_users + ): + # this means we are trying to sign in with the same social login. So we allow it + return await original_sign_in_up( + third_party_id, + third_party_user_id, + email, + is_verified, + oauth_tokens, + raw_user_info_from_provider, + session, + should_try_linking_with_session_user, + tenant_id, + user_context, + ) + + # this means that the email already exists with another social login method. # so we throw an error. raise Exception("Cannot sign up as email already exists") - original_implementation.sign_in_up = thirdparty_sign_in_up + original_implementation.sign_in_up = sign_in_up return original_implementation -def override_passwordless_apis(original_implementation: PasswordlessAPIInterface): - original_create_code_post = original_implementation.create_code_post - - async def create_code_post(email: Union[str, None], phone_number: Union[str, None], tenant_id: str, api_options: PasswordlessAPIOptions, - user_context: Dict[str, Any], - ): - if email is not None: - existing_user = await get_users_by_email_passwordless(tenant_id, email, user_context) - - third_party_existing_users = await get_users_by_email_thirdparty(tenant_id, email, user_context) - - if (existing_user is None and len(third_party_existing_users) == 0): - # this means this email is new so we allow sign up - return await original_create_code_post(email, phone_number, tenant_id, api_options, user_context) - - return GeneralErrorResponse("Seems like you already have an account with another social login provider. Please use that instead.") - - # phone number based login, so we allow it. - return await original_create_code_post(email, phone_number, tenant_id, api_options, user_context) - - original_implementation.create_code_post = create_code_post - return original_implementation - def override_thirdparty_apis(original_implementation: ThirdPartyAPIInterface): original_sign_in_up_post = original_implementation.sign_in_up_post async def sign_in_up_post( provider: Provider, - redirect_uri_info: Union[RedirectUriInfo, None], - oauth_tokens: Union[Dict[str, Any], None], + redirect_uri_info: Optional[RedirectUriInfo], + oauth_tokens: Optional[Dict[str, Any]], + session: Optional[SessionContainer], + should_try_linking_with_session_user: Union[bool, None], tenant_id: str, api_options: ThirdPartyAPIOptions, - user_context: Dict[str, Any] + user_context: Dict[str, Any], ): try: - return await original_sign_in_up_post(provider, redirect_uri_info, oauth_tokens, tenant_id, api_options, user_context) + return await original_sign_in_up_post( + provider, + redirect_uri_info, + oauth_tokens, + session, + should_try_linking_with_session_user, + tenant_id, + api_options, + user_context, + ) except Exception as e: if str(e) == "Cannot sign up as email already exists": - return GeneralErrorResponse("Seems like you already have an account with another method. Please use that instead.") + return GeneralErrorResponse( + "Seems like you already have an account with another social login provider. Please use that instead." + ) raise e original_implementation.sign_in_up_post = sign_in_up_post return original_implementation +def override_passwordless_apis(original_implementation: PasswordlessAPIInterface): + original_create_code_post = original_implementation.create_code_post + + async def create_code_post( + email: Union[str, None], + phone_number: Union[str, None], + session: Optional[SessionContainer], + should_try_linking_with_session_user: Union[bool, None], + tenant_id: str, + api_options: PasswordlessAPIOptions, + user_context: Dict[str, Any], + ): + if email is not None: + existing_users = await list_users_by_account_info( + tenant_id, AccountInfo(email=email) + ) + if len(existing_users) == 0: + # this means this email is new so we allow sign up + return await original_create_code_post( + email, + phone_number, + session, + should_try_linking_with_session_user, + tenant_id, + api_options, + user_context, + ) + + if any( + user.login_methods + and any( + lm.recipe_id == "passwordless" and lm.has_same_email_as(email) + for lm in user.login_methods + ) + for user in existing_users + ): + # this means that the existing user is a passwordless login user. So we allow it + return await original_create_code_post( + email, + phone_number, + session, + should_try_linking_with_session_user, + tenant_id, + api_options, + user_context, + ) + + return GeneralErrorResponse( + "Seems like you already have an account with another method. Please use that instead." + ) + + # phone number based login, so we allow it. + return await original_create_code_post( + email, + phone_number, + session, + should_try_linking_with_session_user, + tenant_id, + api_options, + user_context, + ) + + original_implementation.create_code_post = create_code_post + return original_implementation + + init( - app_info=InputAppInfo( - api_domain="...", app_name="...", website_domain="..."), - framework='...', # type: ignore + app_info=InputAppInfo(api_domain="...", app_name="...", website_domain="..."), + framework="...", # type: ignore recipe_list=[ passwordless.init( contact_config=..., # type: ignore @@ -340,11 +431,10 @@ init( ), thirdparty.init( override=thirdparty.InputOverrideConfig( - apis=override_thirdparty_apis, - functions=override_thirdparty_functions + apis=override_thirdparty_apis, functions=override_thirdparty_functions ) - ) - ] + ), + ], ) ``` diff --git a/v2/thirdpartypasswordless/common-customizations/get-user-info.mdx b/v2/thirdpartypasswordless/common-customizations/get-user-info.mdx index 1f3d8f7e0..0f8ec6cea 100644 --- a/v2/thirdpartypasswordless/common-customizations/get-user-info.mdx +++ b/v2/thirdpartypasswordless/common-customizations/get-user-info.mdx @@ -862,28 +862,18 @@ func getUserInfoAPI(w http.ResponseWriter, r *http.Request) { ```python from supertokens_python.recipe.session.framework.fastapi import verify_session -from supertokens_python.recipe.thirdparty.asyncio import ( - get_user_by_id as get_user_by_id_thirdparty, -) -from supertokens_python.recipe.passwordless.asyncio import ( - get_user_by_id as get_user_by_id_passwordless, -) +from supertokens_python.asyncio import get_user from supertokens_python.recipe.session import SessionContainer from fastapi import FastAPI, Depends app = FastAPI() -@app.post('/get_user_info_api') +@app.post('/get_user_info_api') # type: ignore async def get_user_info_api(session: SessionContainer = Depends(verify_session())): user_id = session.get_user_id() - thirdparty_user = await get_user_by_id_thirdparty(user_id) - if thirdparty_user is None: - passwordless_user = await get_user_by_id_passwordless(user_id) - if passwordless_user is not None: - print(passwordless_user) - else: - print(thirdparty_user) + user = await get_user(user_id) + print(user) ``` @@ -891,12 +881,7 @@ async def get_user_info_api(session: SessionContainer = Depends(verify_session() ```python from supertokens_python.recipe.session.framework.flask import verify_session -from supertokens_python.recipe.thirdparty.syncio import ( - get_user_by_id as get_user_by_id_thirdparty, -) -from supertokens_python.recipe.passwordless.syncio import ( - get_user_by_id as get_user_by_id_passwordless, -) +from supertokens_python.syncio import get_user from flask import Flask, g from supertokens_python.recipe.session import SessionContainer @@ -905,17 +890,12 @@ app = Flask(__name__) @app.route('/get_user_info', methods=['GET']) # type: ignore @verify_session() def get_user_info_api(): - session: SessionContainer = g.supertokens + session: SessionContainer = g.supertokens # type: ignore user_id = session.get_user_id() - thirdparty_user = get_user_by_id_thirdparty(user_id) - if thirdparty_user is None: - passwordless_user = get_user_by_id_passwordless(user_id) - if passwordless_user is not None: - print(passwordless_user) - else: - print(thirdparty_user) + user = get_user(user_id) + print(user) ``` @@ -923,12 +903,7 @@ def get_user_info_api(): ```python from supertokens_python.recipe.session.framework.django.asyncio import verify_session -from supertokens_python.recipe.thirdparty.asyncio import ( - get_user_by_id as get_user_by_id_thirdparty, -) -from supertokens_python.recipe.passwordless.asyncio import ( - get_user_by_id as get_user_by_id_passwordless, -) +from supertokens_python.asyncio import get_user from django.http import HttpRequest from supertokens_python.recipe.session import SessionContainer @@ -938,13 +913,8 @@ async def get_user_info_api(request: HttpRequest): user_id = session.get_user_id() - thirdparty_user = await get_user_by_id_thirdparty(user_id) - if thirdparty_user is None: - passwordless_user = await get_user_by_id_passwordless(user_id) - if passwordless_user is not None: - print(passwordless_user) - else: - print(thirdparty_user) + user = await get_user(user_id) + print(user) ``` diff --git a/v2/thirdpartypasswordless/common-customizations/handling-signinup-success.mdx b/v2/thirdpartypasswordless/common-customizations/handling-signinup-success.mdx index 9781093c0..a8ea11fb8 100644 --- a/v2/thirdpartypasswordless/common-customizations/handling-signinup-success.mdx +++ b/v2/thirdpartypasswordless/common-customizations/handling-signinup-success.mdx @@ -369,106 +369,147 @@ func main() { ```python from supertokens_python import init, InputAppInfo from supertokens_python.recipe import passwordless, session, thirdparty -from supertokens_python.recipe.passwordless.interfaces import RecipeInterface as PasswordlessRecipeInterface, ConsumeCodeOkResult -from supertokens_python.recipe.thirdparty.interfaces import RecipeInterface as ThirdPartyRecipeInterface +from supertokens_python.recipe.passwordless.interfaces import ( + RecipeInterface as PasswordlessRecipeInterface, + ConsumeCodeOkResult, +) +from supertokens_python.recipe.thirdparty.interfaces import ( + RecipeInterface as ThirdPartyRecipeInterface, + SignInUpOkResult, +) from supertokens_python.recipe.thirdparty.types import RawUserInfoFromProvider -from typing import Dict, Any, Union +from typing import Dict, Any, Union, Optional +from supertokens_python.recipe.session.interfaces import SessionContainer + # highlight-start -def override_passwordless_functions(original_implementation: PasswordlessRecipeInterface) -> PasswordlessRecipeInterface: +def override_passwordless_functions( + original_implementation: PasswordlessRecipeInterface, +) -> PasswordlessRecipeInterface: original_consume_code = original_implementation.consume_code - + async def consume_code( pre_auth_session_id: str, user_input_code: Union[str, None], device_id: Union[str, None], link_code: Union[str, None], + session: Optional[SessionContainer], + should_try_linking_with_session_user: Union[bool, None], tenant_id: str, - user_context: Dict[str, Any] + user_context: Dict[str, Any], ): # First we call the original implementation of consume_code. - result = await original_consume_code(pre_auth_session_id, user_input_code, device_id, link_code, tenant_id, user_context) + result = await original_consume_code( + pre_auth_session_id, + user_input_code, + device_id, + link_code, + session, + should_try_linking_with_session_user, + tenant_id, + user_context, + ) # Post sign up response, we check if it was successful if isinstance(result, ConsumeCodeOkResult): - id = result.user.user_id + id = result.user.id print(id) - if result.user.phone_number is not None: - phone_number = result.user.phone_number - print(phone_number) - else: - email = result.user.email - print(email) - - if result.created_new_user: - # TODO: post sign up logic - pass - else: - # TODO: post sign in logic - pass - + if session is None: + if ( + result.created_new_recipe_user + and len(result.user.login_methods) == 1 + ): + # TODO: post sign up logic + pass + else: + # TODO: post sign in logic + pass + return result - + original_implementation.consume_code = consume_code return original_implementation -def override_thirdparty_functions(original_implementation: ThirdPartyRecipeInterface) -> ThirdPartyRecipeInterface: + +def override_thirdparty_functions( + original_implementation: ThirdPartyRecipeInterface, +) -> ThirdPartyRecipeInterface: original_thirdparty_sign_in_up = original_implementation.sign_in_up async def thirdparty_sign_in_up( third_party_id: str, third_party_user_id: str, email: str, + is_verified: bool, oauth_tokens: Dict[str, Any], raw_user_info_from_provider: RawUserInfoFromProvider, + session: Optional[SessionContainer], + should_try_linking_with_session_user: Union[bool, None], tenant_id: str, - user_context: Dict[str, Any] + user_context: Dict[str, Any], ): - result = await original_thirdparty_sign_in_up(third_party_id, third_party_user_id, email, oauth_tokens, raw_user_info_from_provider, tenant_id, user_context) - - # user object contains the ID and email of the user - user = result.user - print(user) - - # This is the response from the OAuth 2 provider that contains their tokens or user info. - provider_access_token = result.oauth_tokens["access_token"] - print(provider_access_token) - - if result.raw_user_info_from_provider.from_user_info_api is not None: - first_name = result.raw_user_info_from_provider.from_user_info_api["first_name"] - print(first_name) - - if result.created_new_user: - print("New user was created") - # TODO: Post sign up logic - else: - print("User already existed and was signed in") - # TODO: Post sign in logic - - return result + result = await original_thirdparty_sign_in_up( + third_party_id, + third_party_user_id, + email, + is_verified, + oauth_tokens, + raw_user_info_from_provider, + session, + should_try_linking_with_session_user, + tenant_id, + user_context, + ) + if isinstance(result, SignInUpOkResult): + # user object contains the ID and email of the user + user = result.user + print(user) + + # This is the response from the OAuth 2 provider that contains their tokens or user info. + provider_access_token = result.oauth_tokens["access_token"] + print(provider_access_token) + + if result.raw_user_info_from_provider.from_user_info_api is not None: + first_name = result.raw_user_info_from_provider.from_user_info_api[ + "first_name" + ] + print(first_name) + + if session is None: + if ( + result.created_new_recipe_user + and len(result.user.login_methods) == 1 + ): + # TODO: post sign up logic + pass + else: + # TODO: post sign in logic + pass - + return result original_implementation.sign_in_up = thirdparty_sign_in_up return original_implementation + + # highlight-end init( app_info=InputAppInfo(api_domain="...", app_name="...", website_domain="..."), - framework='...', # type: ignore + framework="...", # type: ignore recipe_list=[ passwordless.init( contact_config=passwordless.ContactConfig( - contact_method="EMAIL", # This example will work with any contactMethod + contact_method="EMAIL", # This example will work with any contactMethod ), - flow_type="USER_INPUT_CODE_AND_MAGIC_LINK", # This example will work with any flowType - # highlight-start + flow_type="USER_INPUT_CODE_AND_MAGIC_LINK", # This example will work with any flowType + # highlight-start override=passwordless.InputOverrideConfig( functions=override_passwordless_functions ), - # highlight-end + # highlight-end ), thirdparty.init( override=thirdparty.InputOverrideConfig( @@ -476,7 +517,7 @@ init( ) ), session.init(), - ] + ], ) ``` diff --git a/v2/thirdpartypasswordless/common-customizations/multi-tenancy/new-tenant-config.mdx b/v2/thirdpartypasswordless/common-customizations/multi-tenancy/new-tenant-config.mdx index e3e7ea548..fe4f3c4d5 100644 --- a/v2/thirdpartypasswordless/common-customizations/multi-tenancy/new-tenant-config.mdx +++ b/v2/thirdpartypasswordless/common-customizations/multi-tenancy/new-tenant-config.mdx @@ -96,13 +96,23 @@ func main() { ```python from supertokens_python.recipe.multitenancy.asyncio import create_or_update_tenant -from supertokens_python.recipe.multitenancy.interfaces import TenantConfig +from supertokens_python.recipe.multitenancy.interfaces import TenantConfigCreateOrUpdate +from supertokens_python.recipe.multifactorauth.types import FactorIds + async def some_func(): - response = await create_or_update_tenant("customer1", TenantConfig( - third_party_enabled=True, - passwordless_enabled=True, - )) + response = await create_or_update_tenant( + "customer1", + TenantConfigCreateOrUpdate( + first_factors=[ + FactorIds.OTP_PHONE, + FactorIds.OTP_EMAIL, + FactorIds.LINK_PHONE, + FactorIds.LINK_EMAIL, + FactorIds.THIRDPARTY, + ] + ), + ) if response.status != "OK": print("Handle error") @@ -118,19 +128,30 @@ async def some_func(): ```python from supertokens_python.recipe.multitenancy.syncio import create_or_update_tenant -from supertokens_python.recipe.multitenancy.interfaces import TenantConfig - -response = create_or_update_tenant("customer1", TenantConfig( - third_party_enabled=True, - passwordless_enabled=True, -)) +from supertokens_python.recipe.multitenancy.interfaces import TenantConfigCreateOrUpdate +from supertokens_python.recipe.multifactorauth.types import FactorIds + + +def some_func(): + response = create_or_update_tenant( + "customer1", + TenantConfigCreateOrUpdate( + first_factors=[ + FactorIds.OTP_PHONE, + FactorIds.OTP_EMAIL, + FactorIds.LINK_PHONE, + FactorIds.LINK_EMAIL, + FactorIds.THIRDPARTY, + ] + ), + ) -if response.status != "OK": - print("Handle error") -elif response.created_new: - print("new tenant was created") -else: - print("existing tenant's config was modified.") + if response.status != "OK": + print("Handle error") + elif response.created_new: + print("new tenant was created") + else: + print("existing tenant's config was modified.") ``` diff --git a/v2/thirdpartypasswordless/common-customizations/userid-format.mdx b/v2/thirdpartypasswordless/common-customizations/userid-format.mdx index f9dc6beeb..89a04dca5 100644 --- a/v2/thirdpartypasswordless/common-customizations/userid-format.mdx +++ b/v2/thirdpartypasswordless/common-customizations/userid-format.mdx @@ -222,113 +222,165 @@ func main() { from supertokens_python import init, InputAppInfo from supertokens_python.asyncio import create_user_id_mapping from supertokens_python.recipe import thirdparty, session, passwordless -from supertokens_python.recipe.thirdparty.interfaces import RecipeInterface as ThirdPartyRecipeInterface -from supertokens_python.recipe.passwordless.interfaces import RecipeInterface as PasswordlessRecipeInterface, ConsumeCodeOkResult +from supertokens_python.recipe.thirdparty.interfaces import ( + RecipeInterface as ThirdPartyRecipeInterface, + SignInUpOkResult, +) +from supertokens_python.recipe.passwordless.interfaces import ( + RecipeInterface as PasswordlessRecipeInterface, + ConsumeCodeOkResult, +) from supertokens_python.recipe.thirdparty.types import RawUserInfoFromProvider -from typing import Dict, Any, Union +from typing import Dict, Any, Union, Optional +from supertokens_python.recipe.session.interfaces import SessionContainer +from supertokens_python.types import RecipeUserId + -def override_thirdparty_functions(original_implementation: ThirdPartyRecipeInterface) -> ThirdPartyRecipeInterface: +def override_thirdparty_functions( + original_implementation: ThirdPartyRecipeInterface, +) -> ThirdPartyRecipeInterface: original_thirdparty_sign_in_up = original_implementation.sign_in_up async def thirdparty_sign_in_up( third_party_id: str, third_party_user_id: str, email: str, + is_verified: bool, oauth_tokens: Dict[str, Any], raw_user_info_from_provider: RawUserInfoFromProvider, + session: Optional[SessionContainer], + should_try_linking_with_session_user: Union[bool, None], tenant_id: str, - user_context: Dict[str, Any] + user_context: Dict[str, Any], ): - result = await original_thirdparty_sign_in_up(third_party_id, third_party_user_id, email, oauth_tokens, raw_user_info_from_provider, tenant_id, user_context) - - # user object contains the ID and email of the user - user = result.user - print(user) - - # This is the response from the OAuth 2 provider that contains their tokens or user info. - provider_access_token = result.oauth_tokens["access_token"] - print(provider_access_token) - - if result.raw_user_info_from_provider.from_user_info_api is not None: - first_name = result.raw_user_info_from_provider.from_user_info_api["first_name"] - print(first_name) + result = await original_thirdparty_sign_in_up( + third_party_id, + third_party_user_id, + email, + is_verified, + oauth_tokens, + raw_user_info_from_provider, + session, + should_try_linking_with_session_user, + tenant_id, + user_context, + ) + + if isinstance(result, SignInUpOkResult): + # user object contains the ID and email of the user + user = result.user + print(user) + + # This is the response from the OAuth 2 provider that contains their tokens or user info. + provider_access_token = result.oauth_tokens["access_token"] + print(provider_access_token) + + if result.raw_user_info_from_provider.from_user_info_api is not None: + first_name = result.raw_user_info_from_provider.from_user_info_api[ + "first_name" + ] + print(first_name) + + if ( + result.created_new_recipe_user + and len(result.user.login_methods) == 1 + and session is None + ): + # highlight-start + external_user_id = "" + await create_user_id_mapping(result.user.id, external_user_id) + result.user.id = external_user_id + result.user.login_methods[0].recipe_user_id = RecipeUserId( + external_user_id + ) + result.recipe_user_id = RecipeUserId(external_user_id) + # highlight-end - if result.created_new_user: - # highlight-start - external_user_id = "" - await create_user_id_mapping(result.user.user_id, external_user_id) - result.user.user_id = external_user_id - # highlight-end - return result - original_implementation.sign_in_up = thirdparty_sign_in_up return original_implementation -def override_passwordless_functions(original_implementation: PasswordlessRecipeInterface) -> PasswordlessRecipeInterface: + +def override_passwordless_functions( + original_implementation: PasswordlessRecipeInterface, +) -> PasswordlessRecipeInterface: original_consume_code = original_implementation.consume_code - + async def consume_code( pre_auth_session_id: str, user_input_code: Union[str, None], device_id: Union[str, None], link_code: Union[str, None], + session: Optional[SessionContainer], + should_try_linking_with_session_user: Union[bool, None], tenant_id: str, - user_context: Dict[str, Any] + user_context: Dict[str, Any], ): # First we call the original implementation of consumeCodePOST. - result = await original_consume_code(pre_auth_session_id, user_input_code, device_id, link_code, tenant_id, user_context) + result = await original_consume_code( + pre_auth_session_id, + user_input_code, + device_id, + link_code, + session, + should_try_linking_with_session_user, + tenant_id, + user_context, + ) # Post sign up response, we check if it was successful if isinstance(result, ConsumeCodeOkResult): - id = result.user.user_id + id = result.user.id print(id) - if result.user.phone_number is not None: - phone_number = result.user.phone_number - print(phone_number) - else: - email = result.user.email - print(email) - - if result.created_new_user: + + if ( + result.created_new_recipe_user + and len(result.user.login_methods) == 1 + and session is None + ): # highlight-start external_user_id = "" - await create_user_id_mapping(result.user.user_id, external_user_id) - result.user.user_id = external_user_id + await create_user_id_mapping(result.user.id, external_user_id) + result.user.id = external_user_id + result.user.login_methods[0].recipe_user_id = RecipeUserId( + external_user_id + ) + result.recipe_user_id = RecipeUserId(external_user_id) # highlight-end - + return result - + original_implementation.consume_code = consume_code return original_implementation + init( app_info=InputAppInfo(api_domain="...", app_name="...", website_domain="..."), - framework='...', # type: ignore + framework="...", # type: ignore recipe_list=[ thirdparty.init( - # highlight-start + # highlight-start override=thirdparty.InputOverrideConfig( functions=override_thirdparty_functions ), - # highlight-end + # highlight-end ), passwordless.init( contact_config=passwordless.ContactConfig( - contact_method="EMAIL", # This example will work with any contactMethod + contact_method="EMAIL", # This example will work with any contactMethod ), - flow_type="USER_INPUT_CODE_AND_MAGIC_LINK", # This example will work with any flowType - # highlight-start + flow_type="USER_INPUT_CODE_AND_MAGIC_LINK", # This example will work with any flowType + # highlight-start override=passwordless.InputOverrideConfig( functions=override_passwordless_functions ), - # highlight-end + # highlight-end ), session.init(), - ] + ], ) ``` diff --git a/v2/thirdpartypasswordless/custom-ui/multitenant-login.mdx b/v2/thirdpartypasswordless/custom-ui/multitenant-login.mdx index 982077759..5505be5f8 100644 --- a/v2/thirdpartypasswordless/custom-ui/multitenant-login.mdx +++ b/v2/thirdpartypasswordless/custom-ui/multitenant-login.mdx @@ -125,13 +125,23 @@ func main() { ```python from supertokens_python.recipe.multitenancy.asyncio import create_or_update_tenant -from supertokens_python.recipe.multitenancy.interfaces import TenantConfig +from supertokens_python.recipe.multitenancy.interfaces import TenantConfigCreateOrUpdate +from supertokens_python.recipe.multifactorauth.types import FactorIds + async def some_func(): - response = await create_or_update_tenant("customer1", TenantConfig( - third_party_enabled=True, - passwordless_enabled=True, - )) + response = await create_or_update_tenant( + "customer1", + TenantConfigCreateOrUpdate( + first_factors=[ + FactorIds.OTP_PHONE, + FactorIds.OTP_EMAIL, + FactorIds.LINK_PHONE, + FactorIds.LINK_EMAIL, + FactorIds.THIRDPARTY, + ] + ), + ) if response.status != "OK": print("Handle error") @@ -147,19 +157,30 @@ async def some_func(): ```python from supertokens_python.recipe.multitenancy.syncio import create_or_update_tenant -from supertokens_python.recipe.multitenancy.interfaces import TenantConfig - -response = create_or_update_tenant("customer1", TenantConfig( - third_party_enabled=True, - passwordless_enabled=True, -)) +from supertokens_python.recipe.multitenancy.interfaces import TenantConfigCreateOrUpdate +from supertokens_python.recipe.multifactorauth.types import FactorIds + + +def some_func(): + response = create_or_update_tenant( + "customer1", + TenantConfigCreateOrUpdate( + first_factors=[ + FactorIds.OTP_PHONE, + FactorIds.OTP_EMAIL, + FactorIds.LINK_PHONE, + FactorIds.LINK_EMAIL, + FactorIds.THIRDPARTY, + ] + ), + ) -if response.status != "OK": - print("Handle error") -elif response.created_new: - print("new tenant was created") -else: - print("existing tenant's config was modified.") + if response.status != "OK": + print("Handle error") + elif response.created_new: + print("new tenant was created") + else: + print("existing tenant's config was modified.") ``` diff --git a/v2/thirdpartypasswordless/pre-built-ui/multitenant-login.mdx b/v2/thirdpartypasswordless/pre-built-ui/multitenant-login.mdx index 1cfebb908..aa9376fc6 100644 --- a/v2/thirdpartypasswordless/pre-built-ui/multitenant-login.mdx +++ b/v2/thirdpartypasswordless/pre-built-ui/multitenant-login.mdx @@ -122,13 +122,23 @@ func main() { ```python from supertokens_python.recipe.multitenancy.asyncio import create_or_update_tenant -from supertokens_python.recipe.multitenancy.interfaces import TenantConfig +from supertokens_python.recipe.multitenancy.interfaces import TenantConfigCreateOrUpdate +from supertokens_python.recipe.multifactorauth.types import FactorIds + async def some_func(): - response = await create_or_update_tenant("customer1", TenantConfig( - third_party_enabled=True, - passwordless_enabled=True, - )) + response = await create_or_update_tenant( + "customer1", + TenantConfigCreateOrUpdate( + first_factors=[ + FactorIds.OTP_PHONE, + FactorIds.OTP_EMAIL, + FactorIds.LINK_PHONE, + FactorIds.LINK_EMAIL, + FactorIds.THIRDPARTY, + ] + ), + ) if response.status != "OK": print("Handle error") @@ -144,19 +154,30 @@ async def some_func(): ```python from supertokens_python.recipe.multitenancy.syncio import create_or_update_tenant -from supertokens_python.recipe.multitenancy.interfaces import TenantConfig - -response = create_or_update_tenant("customer1", TenantConfig( - third_party_enabled=True, - passwordless_enabled=True, -)) +from supertokens_python.recipe.multitenancy.interfaces import TenantConfigCreateOrUpdate +from supertokens_python.recipe.multifactorauth.types import FactorIds + + +def some_func(): + response = create_or_update_tenant( + "customer1", + TenantConfigCreateOrUpdate( + first_factors=[ + FactorIds.OTP_PHONE, + FactorIds.OTP_EMAIL, + FactorIds.LINK_PHONE, + FactorIds.LINK_EMAIL, + FactorIds.THIRDPARTY, + ] + ), + ) -if response.status != "OK": - print("Handle error") -elif response.created_new: - print("new tenant was created") -else: - print("existing tenant's config was modified.") + if response.status != "OK": + print("Handle error") + elif response.created_new: + print("new tenant was created") + else: + print("existing tenant's config was modified.") ``` From dbf604a58de6b3560b7dacc98ca7cab8d01fc6b9 Mon Sep 17 00:00:00 2001 From: rishabhpoddar Date: Thu, 24 Oct 2024 23:29:09 +0530 Subject: [PATCH 34/34] fixes more snippets --- .../account-creation/ep-migration-without-password-hash.mdx | 6 +++--- .../account-creation/ep-migration-without-password-hash.mdx | 6 +++--- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/v2/emailpassword/migration/account-creation/ep-migration-without-password-hash.mdx b/v2/emailpassword/migration/account-creation/ep-migration-without-password-hash.mdx index 1b4162dac..45a5a82f5 100644 --- a/v2/emailpassword/migration/account-creation/ep-migration-without-password-hash.mdx +++ b/v2/emailpassword/migration/account-creation/ep-migration-without-password-hash.mdx @@ -608,9 +608,9 @@ EmailPassword.init({ superTokensUserId: signupResponse.user.id, externalUserId: legacyUserData.user_id }) - signUpResponse.user.id = legacyUserData.user_id - signUpResponse.user.loginMethods[0].recipeUserId = new RecipeUserId(legacyUserData.user_id); - signUpResponse.recipeUserId = new RecipeUserId(legacyUserData.user_id); + signupResponse.user.id = legacyUserData.user_id + signupResponse.user.loginMethods[0].recipeUserId = new RecipeUserId(legacyUserData.user_id); + signupResponse.recipeUserId = new RecipeUserId(legacyUserData.user_id); // We will also need to set the email verification status of the user if (legacyUserData.isEmailVerified) { diff --git a/v2/thirdpartyemailpassword/migration/account-creation/ep-migration-without-password-hash.mdx b/v2/thirdpartyemailpassword/migration/account-creation/ep-migration-without-password-hash.mdx index 9dafd56ac..fc23b4a1a 100644 --- a/v2/thirdpartyemailpassword/migration/account-creation/ep-migration-without-password-hash.mdx +++ b/v2/thirdpartyemailpassword/migration/account-creation/ep-migration-without-password-hash.mdx @@ -608,9 +608,9 @@ EmailPassword.init({ superTokensUserId: signupResponse.user.id, externalUserId: legacyUserData.user_id }) - signUpResponse.user.id = legacyUserData.user_id - signUpResponse.user.loginMethods[0].recipeUserId = new RecipeUserId(legacyUserData.user_id); - signUpResponse.recipeUserId = new RecipeUserId(legacyUserData.user_id); + signupResponse.user.id = legacyUserData.user_id + signupResponse.user.loginMethods[0].recipeUserId = new RecipeUserId(legacyUserData.user_id); + signupResponse.recipeUserId = new RecipeUserId(legacyUserData.user_id); // We will also need to set the email verification status of the user if (legacyUserData.isEmailVerified) {