From bd405d063e42842a84b475e2bba951c7b284b3c9 Mon Sep 17 00:00:00 2001 From: "promptless[bot]" <179508745+promptless[bot]@users.noreply.github.com> Date: Thu, 26 Jun 2025 16:32:43 +0000 Subject: [PATCH 01/10] Documentation updates from Promptless --- .../mfa/important-concepts.mdx | 1 + .../mfa/introduction.mdx | 4 +- docs/authentication/overview.mdx | 11 +- .../authentication/passkeys/customization.mdx | 186 ++++++++++++++++-- .../authentication/passkeys/initial-setup.mdx | 35 +++- 5 files changed, 216 insertions(+), 21 deletions(-) diff --git a/docs/additional-verification/mfa/important-concepts.mdx b/docs/additional-verification/mfa/important-concepts.mdx index 34eb2e988..271580daf 100644 --- a/docs/additional-verification/mfa/important-concepts.mdx +++ b/docs/additional-verification/mfa/important-concepts.mdx @@ -65,6 +65,7 @@ Each auth challenge has a factor ID in SuperTokens: | Passwordless - Email magic link | `link-email` | | Passwordless - SMS magic link | `link-phone` | | TOTP | `totp` | +| WebAuthn (Passkeys) | `webauthn` | These factor IDs get used to configure the MFA requirements for users (except the `acccess-denied` one). They are also used to indicate which authentication challenges have completed in the current session. diff --git a/docs/additional-verification/mfa/introduction.mdx b/docs/additional-verification/mfa/introduction.mdx index 612760982..9c58047e7 100644 --- a/docs/additional-verification/mfa/introduction.mdx +++ b/docs/additional-verification/mfa/introduction.mdx @@ -4,7 +4,7 @@ title: Introduction hide_title: true skip_llms_txt: true description: >- - Implement multi-factor authentication with email, SMS, or TOTP, and customize + Implement multi-factor authentication with email, SMS, TOTP, or WebAuthn, and customize user authentication preferences. page_type: overview recipe: mfa @@ -17,7 +17,7 @@ category: multi-factor-authentication ## Overview Multi-factor authentication (MFA) is a security process that requires users to verify their identity through multiple forms of credentials before gaining access to a system. -**SuperTokens** allows you to integrate MFA in your application using either Email/SMS One-Time Password (OTP) or Time-based One-Time Password (TOTP). +**SuperTokens** allows you to integrate MFA in your application using Email/SMS One-Time Password (OTP), Time-based One-Time Password (TOTP), or WebAuthn (Passkeys). ## Prerequisites diff --git a/docs/authentication/overview.mdx b/docs/authentication/overview.mdx index 84649d442..f0e5fac0e 100644 --- a/docs/authentication/overview.mdx +++ b/docs/authentication/overview.mdx @@ -45,6 +45,15 @@ Discover all the ways in which you can authenticate your users with **SuperToken Login flow that uses third-party providers for authentication. + + + Passkeys + + + Passwordless authentication using biometrics, security keys, or device-based credentials. + + + Enterprise Login @@ -53,7 +62,6 @@ Discover all the ways in which you can authenticate your users with **SuperToken Instructions on how to configure your application to support multiple tenants and enterprise authentication methods. - Unified Login @@ -62,6 +70,7 @@ Discover all the ways in which you can authenticate your users with **SuperToken Details on how to create a common authentication experience for all your products. + Machine to Machine Authentication diff --git a/docs/authentication/passkeys/customization.mdx b/docs/authentication/passkeys/customization.mdx index 8d2abea47..931dd805f 100644 --- a/docs/authentication/passkeys/customization.mdx +++ b/docs/authentication/passkeys/customization.mdx @@ -87,11 +87,45 @@ At the moment there is no support for using passkeys authentication in the Go SD -:::caution - -At the moment there is no support for using passkeys authentication in the Python SDK. - -::: +```python +from supertokens_python import init, InputAppInfo, SupertokensConfig +from supertokens_python.recipe import webauthn, session + +def get_origin(tenant_id: str, request, user_context): + return "https://example.com" + +def get_relying_party_id(tenant_id: str, request, user_context): + return "example.com" + +def get_relying_party_name(tenant_id: str, request, user_context): + return "example" + +init( + app_info=InputAppInfo( + app_name="", + api_domain="", + website_domain="", + api_base_path="/auth", + website_base_path="/auth" + ), + supertokens_config=SupertokensConfig( + # https://try.supertokens.com is for demo purposes. Replace this with the address of your core instance (sign up on supertokens.com), or self host a core. + connection_uri="https://try.supertokens.com", + # api_key="" + ), + framework='flask', # Replace this with the framework you are using + recipe_list=[ + webauthn.init( + config=webauthn.WebauthnConfig( + get_origin=get_origin, + get_relying_party_id=get_relying_party_id, + get_relying_party_name=get_relying_party_name, + ) + ), + session.init() # initializes session features + ] +) +``` @@ -190,11 +224,84 @@ At the moment there is no support for using passkeys authentication in the Go SD -:::caution +```python +from supertokens_python import init, InputAppInfo, SupertokensConfig +from supertokens_python.recipe import webauthn, session +from typing import Dict, Any, List, Optional, Union +from typing_extensions import Unpack + +def override_webauthn_functions(original_implementation: webauthn.RecipeInterface): + original_register_options = original_implementation.register_options + + async def register_options( + email: Optional[str], + recover_account_token: Optional[str], + tenant_id: str, + user_context: Dict[str, Any], + **kwargs: Unpack[webauthn.RegisterOptionsKwargsInput] + ): + return await original_register_options( + email=email, + recover_account_token=recover_account_token, + tenant_id=tenant_id, + user_context=user_context, + attestation="direct", + resident_key="required", + timeout=10 * 1000, + user_verification="required", + display_name="John Doe", + supported_algorithms=[-257], + relying_party_id='example.com', + relying_party_name='example', + origin='https://example.com', + ) + + original_implementation.register_options = register_options + return original_implementation + +init( + app_info=InputAppInfo( + app_name="", + api_domain="", + website_domain="", + api_base_path="/auth", + website_base_path="/auth" + ), + supertokens_config=SupertokensConfig( + # https://try.supertokens.com is for demo purposes. Replace this with the address of your core instance (sign up on supertokens.com), or self host a core. + connection_uri="https://try.supertokens.com", + # api_key="" + ), + framework='flask', # Replace this with the framework you are using + recipe_list=[ + webauthn.init( + config=webauthn.WebauthnConfig( + override=webauthn.OverrideConfig( + functions=override_webauthn_functions + ) + ) + ), + session.init() # initializes session features + ] +) +``` -At the moment there is no support for using passkeys authentication in the Python SDK. +
+ +#### Input properties + +| Name | Type | Description | Default Value | +|----------|----------|-------------|---------------| +| `relying_party_id` | `str` | The domain name of your application that the system uses for validating the credential. | Uses `get_relying_party_id` from the recipe configuration which defaults to the `api_domain` | +| `relying_party_name` | `str` | The human-readable name of your application. | Uses `get_relying_party_name` from the recipe configuration which defaults to the `app_name` | +| `origin` | `str` | The origin URL where the credential is generated. | Uses `get_origin` from the recipe configuration which defaults to the origin of the request | +| `timeout` | `int` | The time in milliseconds that the user has to complete the credential generation process. | `6000` | +| `attestation` | `"none" \| "indirect" \| "direct" \| "enterprise"` | The amount of information about the authenticator that gets included in the attestation statement. This controls what authenticators support. | `none` | +| `supported_algorithms` | `List[int]` | The cryptographic algorithms that can generate credentials. Different authenticators support different algorithms. | `[-8, -7, -257]` | +| `resident_key` | `"discouraged" \| "preferred" \| "required"` | Whether the credential gest stored on the authenticator device. | `required` | +| `user_verification` | `"discouraged" \| "preferred" \| "required"` | Whether user verification (like `PIN` or biometrics) is necessary. | `preferred` | +| `display_name` | `str` | The display name of the user. | The user's `email` property | -:::
@@ -283,11 +390,68 @@ At the moment there is no support for using passkeys authentication in the Go SD -:::caution +```python +from supertokens_python import init, InputAppInfo, SupertokensConfig +from supertokens_python.recipe import webauthn, session +from typing import Dict, Any, Optional +from typing_extensions import Unpack + +def override_webauthn_functions(original_implementation: webauthn.RecipeInterface): + original_sign_in_options = original_implementation.sign_in_options + + async def sign_in_options( + tenant_id: str, + user_context: Dict[str, Any], + **kwargs: Unpack[webauthn.SignInOptionsKwargsInput] + ): + return await original_sign_in_options( + tenant_id=tenant_id, + user_context=user_context, + timeout=10 * 1000, + user_verification="required", + relying_party_id='example.com', + origin='https://example.com', + ) + + original_implementation.sign_in_options = sign_in_options + return original_implementation + +init( + app_info=InputAppInfo( + app_name="", + api_domain="", + website_domain="", + api_base_path="/auth", + website_base_path="/auth" + ), + supertokens_config=SupertokensConfig( + # https://try.supertokens.com is for demo purposes. Replace this with the address of your core instance (sign up on supertokens.com), or self host a core. + connection_uri="https://try.supertokens.com", + # api_key="" + ), + framework='flask', # Replace this with the framework you are using + recipe_list=[ + webauthn.init( + config=webauthn.WebauthnConfig( + override=webauthn.OverrideConfig( + functions=override_webauthn_functions + ) + ) + ), + session.init() # initializes session features + ] +) +``` -At the moment there is no support for using passkeys authentication in the Python SDK. +#### Input properties -::: +| Name | Type | Description | Default | +|----------|----------|-------------|---------| +| `relying_party_id` | `str` | The domain name of your application that the system uses for validating the credential. | Uses `get_relying_party_id` from the recipe configuration which defaults to the `api_domain` | +| `relying_party_name` | `str` | The human-readable name of your application. | Uses `get_relying_party_name` from the recipe configuration which defaults to the `app_name` | +| `origin` | `str` | The origin URL where the credential is generated. | Uses `get_origin` from the recipe configuration which defaults to the origin of the request | +| `timeout` | `int` | The time in milliseconds that the user has to complete the credential validation process. | `6000` | +| `user_verification` | `"discouraged" \| "preferred" \| "required"` | The parameter controls whether user verification (like `PIN` or biometrics) is necessary. | `preferred` | diff --git a/docs/authentication/passkeys/initial-setup.mdx b/docs/authentication/passkeys/initial-setup.mdx index a81c825c7..2beb9a7a9 100644 --- a/docs/authentication/passkeys/initial-setup.mdx +++ b/docs/authentication/passkeys/initial-setup.mdx @@ -22,8 +22,8 @@ The tutorial creates a login flow, rendered by either the **Prebuilt UI** compon These instructions assume that you already have gone through the main [quickstart guide](/docs/quickstart/introduction). If you have skipped that page please follow the tutorial and return here once you're done. -:::info Restrictions -At the moment, the authentication method is only available in the **`Node.js SDK`**. +:::info SDK Support +WebAuthn (Passkeys) authentication is available in the **Node.js SDK** and **Python SDK**. ::: @@ -739,11 +739,32 @@ At the moment there is no support for using passkeys authentication in the Go SD -:::caution - -At the moment there is no support for using passkeys authentication in the Python SDK. - -::: +```python +from supertokens_python import init, InputAppInfo, SupertokensConfig +from supertokens_python.recipe import webauthn, session + +init( + app_info=InputAppInfo( + app_name="", + api_domain="", + website_domain="", + api_base_path="/auth", + website_base_path="/auth" + ), + supertokens_config=SupertokensConfig( + # We use try.supertokens for demo purposes. + # At the end of the tutorial we will show you how to create + # your own SuperTokens core instance and then update your config. + connection_uri="https://try.supertokens.io", + # api_key="" + ), + framework='flask', # Replace this with the framework you are using + recipe_list=[ + webauthn.init(), + session.init() + ] +) +``` From d78e555a4bce08bf19ba404117e7d2b8b9a22999 Mon Sep 17 00:00:00 2001 From: Namit Nathwani Date: Fri, 27 Jun 2025 12:35:18 +0530 Subject: [PATCH 02/10] update: adds input properties table for Python --- .../mfa/introduction.mdx | 4 +- .../authentication/passkeys/customization.mdx | 37 ++++++++++++------- 2 files changed, 26 insertions(+), 15 deletions(-) diff --git a/docs/additional-verification/mfa/introduction.mdx b/docs/additional-verification/mfa/introduction.mdx index 9c58047e7..0e623f929 100644 --- a/docs/additional-verification/mfa/introduction.mdx +++ b/docs/additional-verification/mfa/introduction.mdx @@ -4,9 +4,9 @@ title: Introduction hide_title: true skip_llms_txt: true description: >- - Implement multi-factor authentication with email, SMS, TOTP, or WebAuthn, and customize + Implement multi-factor authentication with email, SMS, TOTP, or WebAuthn (Passkeys), and customize user authentication preferences. -page_type: overview +page_type: overview recipe: mfa category: multi-factor-authentication --- diff --git a/docs/authentication/passkeys/customization.mdx b/docs/authentication/passkeys/customization.mdx index 931dd805f..53fbdb637 100644 --- a/docs/authentication/passkeys/customization.mdx +++ b/docs/authentication/passkeys/customization.mdx @@ -19,19 +19,6 @@ The following page describes the options that you can change and the different s ## Backend recipe configuration - -The backend recipe accepts the following properties during initialization: - -| Option | Description | Default | -|--------|-------------|---------| -| `getRelyingPartyId` | Sets the domain name associated with the WebAuthn credentials. This helps ensure that only your domain uses the credentials. | The `apiDomain` value that you have set in `appConfig` | -| `getRelyingPartyName` | Sets a human-readable name for your application. The name appears to users during the WebAuthn registration process. | The `appName` value that you have set in `appConfig` | -| `getOrigin` | Configures the origin URL that WebAuthn credentials bind to. This should match your application's domain and protocol. | Origin of the request | -| `emailDelivery` | Configures how the system builds and sends verification emails to users. Read the [email delivery page](/docs/platform-configuration/email-delivery) for more information. | Default email service | -| `validateEmailAddress` | Adds custom validation logic for email addresses. | Basic email format validation | - -All the properties are optional. - @@ -73,6 +60,18 @@ supertokens.init({ }); ``` +The backend recipe accepts the following properties during initialization: + +| Option | Description | Default | +|--------|-------------|---------| +| `getRelyingPartyId` | Sets the domain name associated with the WebAuthn credentials. This helps ensure that only your domain uses the credentials. | The `apiDomain` value that you have set in `appConfig` | +| `getRelyingPartyName` | Sets a human-readable name for your application. The name appears to users during the WebAuthn registration process. | The `appName` value that you have set in `appConfig` | +| `getOrigin` | Configures the origin URL that WebAuthn credentials bind to. This should match your application's domain and protocol. | Origin of the request | +| `emailDelivery` | Configures how the system builds and sends verification emails to users. Read the [email delivery page](/docs/platform-configuration/email-delivery) for more information. | Default email service | +| `validateEmailAddress` | Adds custom validation logic for email addresses. | Basic email format validation | + +All the properties are optional. + @@ -127,6 +126,18 @@ init( ) ``` +The backend recipe accepts the following properties during initialization: + +| Option | Description | Default | +|--------|-------------|---------| +| `get_relying_party_id` | Sets the domain name associated with the WebAuthn credentials. This helps ensure that only your domain uses the credentials. | The `api_domain` value that you have set in `app_config` | +| `get_relying_party_name` | Sets a human-readable name for your application. The name appears to users during the WebAuthn registration process. | The `app_name` value that you have set in `app_config` | +| `get_origin` | Configures the origin URL that WebAuthn credentials bind to. This should match your application's domain and protocol. | Origin of the request | +| `email_delivery` | Configures how the system builds and sends verification emails to users. Read the [email delivery page](/docs/platform-configuration/email-delivery) for more information. | Default email service | +| `validate_email_address` | Adds custom validation logic for email addresses. | Basic email format validation | + +All the properties are optional. + From bdcbe2dfd37a3ea989a7e116e83932e51c2f7028 Mon Sep 17 00:00:00 2001 From: Namit Nathwani Date: Fri, 27 Jun 2025 14:49:02 +0530 Subject: [PATCH 03/10] update: Python example imports, types --- .../authentication/passkeys/customization.mdx | 46 +++++++++++-------- 1 file changed, 28 insertions(+), 18 deletions(-) diff --git a/docs/authentication/passkeys/customization.mdx b/docs/authentication/passkeys/customization.mdx index 53fbdb637..ed887d9a2 100644 --- a/docs/authentication/passkeys/customization.mdx +++ b/docs/authentication/passkeys/customization.mdx @@ -88,15 +88,19 @@ At the moment there is no support for using passkeys authentication in the Go SD ```python from supertokens_python import init, InputAppInfo, SupertokensConfig +from supertokens_python.framework import BaseRequest from supertokens_python.recipe import webauthn, session +from supertokens_python.recipe.webauthn import WebauthnConfig +from supertokens_python.types.base import UserContext +from typing import Optional -def get_origin(tenant_id: str, request, user_context): +def get_origin(*, tenant_id: str, request: Optional[BaseRequest], user_context: UserContext): return "https://example.com" -def get_relying_party_id(tenant_id: str, request, user_context): +def get_relying_party_id(*, tenant_id: str, request: Optional[BaseRequest], user_context: UserContext): return "example.com" -def get_relying_party_name(tenant_id: str, request, user_context): +def get_relying_party_name(*, tenant_id: str, request: Optional[BaseRequest], user_context: UserContext): return "example" init( @@ -115,7 +119,7 @@ init( framework='flask', # Replace this with the framework you are using recipe_list=[ webauthn.init( - config=webauthn.WebauthnConfig( + config=WebauthnConfig( get_origin=get_origin, get_relying_party_id=get_relying_party_id, get_relying_party_name=get_relying_party_name, @@ -238,18 +242,21 @@ At the moment there is no support for using passkeys authentication in the Go SD ```python from supertokens_python import init, InputAppInfo, SupertokensConfig from supertokens_python.recipe import webauthn, session -from typing import Dict, Any, List, Optional, Union -from typing_extensions import Unpack +from supertokens_python.recipe.webauthn import WebauthnConfig, RecipeInterface +from supertokens_python.recipe.webauthn.types.config import OverrideConfig +from supertokens_python.types.base import UserContext +from typing import Any, List, Optional, Union -def override_webauthn_functions(original_implementation: webauthn.RecipeInterface): +def override_webauthn_functions(original_implementation: RecipeInterface): original_register_options = original_implementation.register_options async def register_options( + *, email: Optional[str], recover_account_token: Optional[str], tenant_id: str, - user_context: Dict[str, Any], - **kwargs: Unpack[webauthn.RegisterOptionsKwargsInput] + user_context: UserContext, + **kwargs: Any, ): return await original_register_options( email=email, @@ -286,8 +293,8 @@ init( framework='flask', # Replace this with the framework you are using recipe_list=[ webauthn.init( - config=webauthn.WebauthnConfig( - override=webauthn.OverrideConfig( + config=WebauthnConfig( + override=OverrideConfig( functions=override_webauthn_functions ) ) @@ -404,16 +411,19 @@ At the moment there is no support for using passkeys authentication in the Go SD ```python from supertokens_python import init, InputAppInfo, SupertokensConfig from supertokens_python.recipe import webauthn, session -from typing import Dict, Any, Optional -from typing_extensions import Unpack +from supertokens_python.recipe.webauthn import WebauthnConfig, RecipeInterface +from supertokens_python.recipe.webauthn.types.config import OverrideConfig +from supertokens_python.types.base import UserContext +from typing import Any, Optional -def override_webauthn_functions(original_implementation: webauthn.RecipeInterface): +def override_webauthn_functions(original_implementation: RecipeInterface): original_sign_in_options = original_implementation.sign_in_options async def sign_in_options( + *, tenant_id: str, - user_context: Dict[str, Any], - **kwargs: Unpack[webauthn.SignInOptionsKwargsInput] + user_context: UserContext, + **kwargs: Any ): return await original_sign_in_options( tenant_id=tenant_id, @@ -443,8 +453,8 @@ init( framework='flask', # Replace this with the framework you are using recipe_list=[ webauthn.init( - config=webauthn.WebauthnConfig( - override=webauthn.OverrideConfig( + config=WebauthnConfig( + override=OverrideConfig( functions=override_webauthn_functions ) ) From ede47fd3a7937b7f23b0df6fb9305601c6e14f8d Mon Sep 17 00:00:00 2001 From: Namit Nathwani Date: Fri, 27 Jun 2025 16:02:44 +0530 Subject: [PATCH 04/10] lint: remove unused import --- docs/authentication/passkeys/customization.mdx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/authentication/passkeys/customization.mdx b/docs/authentication/passkeys/customization.mdx index ed887d9a2..d7b794b47 100644 --- a/docs/authentication/passkeys/customization.mdx +++ b/docs/authentication/passkeys/customization.mdx @@ -414,7 +414,7 @@ from supertokens_python.recipe import webauthn, session from supertokens_python.recipe.webauthn import WebauthnConfig, RecipeInterface from supertokens_python.recipe.webauthn.types.config import OverrideConfig from supertokens_python.types.base import UserContext -from typing import Any, Optional +from typing import Any def override_webauthn_functions(original_implementation: RecipeInterface): original_sign_in_options = original_implementation.sign_in_options From 90242274a7cd1a6978c9bec049f1bf99fa8cf27b Mon Sep 17 00:00:00 2001 From: Namit Nathwani Date: Fri, 27 Jun 2025 16:24:03 +0530 Subject: [PATCH 05/10] lint: fix type issues --- docs/authentication/passkeys/customization.mdx | 11 +++++++---- docs/authentication/passkeys/initial-setup.mdx | 4 ++-- 2 files changed, 9 insertions(+), 6 deletions(-) diff --git a/docs/authentication/passkeys/customization.mdx b/docs/authentication/passkeys/customization.mdx index d7b794b47..2f5ff09c8 100644 --- a/docs/authentication/passkeys/customization.mdx +++ b/docs/authentication/passkeys/customization.mdx @@ -409,12 +409,14 @@ At the moment there is no support for using passkeys authentication in the Go SD ```python -from supertokens_python import init, InputAppInfo, SupertokensConfig -from supertokens_python.recipe import webauthn, session -from supertokens_python.recipe.webauthn import WebauthnConfig, RecipeInterface +from typing import Any + +from supertokens_python import InputAppInfo, SupertokensConfig, init +from supertokens_python.recipe import session, webauthn +from supertokens_python.recipe.webauthn import RecipeInterface, WebauthnConfig from supertokens_python.recipe.webauthn.types.config import OverrideConfig from supertokens_python.types.base import UserContext -from typing import Any + def override_webauthn_functions(original_implementation: RecipeInterface): original_sign_in_options = original_implementation.sign_in_options @@ -431,6 +433,7 @@ def override_webauthn_functions(original_implementation: RecipeInterface): timeout=10 * 1000, user_verification="required", relying_party_id='example.com', + relying_party_name='Example', origin='https://example.com', ) diff --git a/docs/authentication/passkeys/initial-setup.mdx b/docs/authentication/passkeys/initial-setup.mdx index 2beb9a7a9..0bcc43cc6 100644 --- a/docs/authentication/passkeys/initial-setup.mdx +++ b/docs/authentication/passkeys/initial-setup.mdx @@ -740,8 +740,8 @@ At the moment there is no support for using passkeys authentication in the Go SD ```python -from supertokens_python import init, InputAppInfo, SupertokensConfig -from supertokens_python.recipe import webauthn, session +from supertokens_python import InputAppInfo, SupertokensConfig, init +from supertokens_python.recipe import session, webauthn init( app_info=InputAppInfo( From 357edbd72e5feef6eafd1dc376e8b222cc74ced3 Mon Sep 17 00:00:00 2001 From: Namit Nathwani Date: Fri, 27 Jun 2025 16:38:28 +0530 Subject: [PATCH 06/10] update: python deps --- scripts/code-type-checking/python/requirements.txt | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/scripts/code-type-checking/python/requirements.txt b/scripts/code-type-checking/python/requirements.txt index 8a8aa7dde..8ce6deb36 100644 --- a/scripts/code-type-checking/python/requirements.txt +++ b/scripts/code-type-checking/python/requirements.txt @@ -22,7 +22,7 @@ django-cors-headers==3.11.0 django-stubs==1.9.0 django-stubs-ext==0.4.0 exceptiongroup==1.1.3 -fastapi==0.68.1 +fastapi==0.115.5 filelock==3.12.2 Flask==2.0.2 Flask-Cors==3.0.10 @@ -60,11 +60,11 @@ py==1.11.0 pycodestyle==2.8.0 pycparser==2.21 pycryptodome==3.10.4 -pydantic==1.9.0 +pydantic==2.10.6 PyJWT==2.8.0 pylint==2.12.2 pyparsing==3.0.7 -pyright==1.1.236 +pyright==1.1.393 pyrsistent==0.18.1 pytest==6.2.5 pytest-asyncio==0.14.0 @@ -79,15 +79,15 @@ rfc3986==1.5.0 six==1.16.0 sniffio==1.3.0 sqlparse==0.4.2 -starlette==0.14.2 -supertokens_python==0.27.0 +starlette==0.41.3 +supertokens_python==0.30.0 tldextract==3.1.0 toml==0.10.2 tomli==2.0.1 twilio==7.9.1 types-pytz==2021.3.6 types-PyYAML==6.0.5 -typing_extensions==4.7.1 +typing_extensions==4.12.2 tzdata==2021.5 urllib3==2.0.4 uvicorn==0.18.2 From e859a83d46dcec3493aca73a9e6ccab64c97103b Mon Sep 17 00:00:00 2001 From: Namit Nathwani Date: Fri, 27 Jun 2025 18:45:56 +0530 Subject: [PATCH 07/10] fix: pyright errors --- .../mfa/protect-routes.mdx | 42 ++-- .../mfa/step-up-auth.mdx | 66 ++++-- .../protect-api-routes.mdx | 217 ++++++++++++------ 3 files changed, 208 insertions(+), 117 deletions(-) diff --git a/docs/additional-verification/mfa/protect-routes.mdx b/docs/additional-verification/mfa/protect-routes.mdx index 652db4494..d1775acf7 100644 --- a/docs/additional-verification/mfa/protect-routes.mdx +++ b/docs/additional-verification/mfa/protect-routes.mdx @@ -367,7 +367,7 @@ async def like_comment(request: HttpRequest): The same modification can be done for `getSession` as well. -### Check MFA claim manually +### Check MFA claim manually To account for a more complex logic when you check the MFA claim (other than checking if `v` is `true`), look over the next code snippet. @@ -869,15 +869,16 @@ At the moment this feature is not supported through the Go SDK. ```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, ) +from supertokens_python.recipe.session import SessionContainer +from supertokens_python.recipe.session.exceptions import ( + ClaimValidationError, + raise_invalid_claims_exception, +) +from supertokens_python.recipe.session.framework.fastapi import verify_session @app.post("/update-blog") # type: ignore @@ -915,10 +916,16 @@ async def update_blog_api(session: SessionContainer = Depends(verify_session())) ```python from flask import Flask, g -from supertokens_python.recipe.session.framework.flask import verify_session + +from supertokens_python.recipe.multifactorauth.multi_factor_auth_claim import ( + MultiFactorAuthClaim, +) 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 +from supertokens_python.recipe.session.exceptions import ( + ClaimValidationError, + raise_invalid_claims_exception, +) +from supertokens_python.recipe.session.framework.flask import verify_session app = Flask(__name__) @@ -952,21 +959,24 @@ def check_mfa_api(): ```python +from typing import cast + from django.http import HttpRequest -from supertokens_python.recipe.session.framework.django.asyncio import verify_session + +from supertokens_python.recipe.multifactorauth.multi_factor_auth_claim import ( + MultiFactorAuthClaim, +) from supertokens_python.recipe.session import SessionContainer from supertokens_python.recipe.session.exceptions import ( - raise_invalid_claims_exception, ClaimValidationError, + raise_invalid_claims_exception, ) -from supertokens_python.recipe.multifactorauth.multi_factor_auth_claim import ( - MultiFactorAuthClaim, -) +from supertokens_python.recipe.session.framework.django.asyncio import verify_session @verify_session() async def get_user_info_api(request: HttpRequest): - session: SessionContainer = request.supertokens # type: ignore + session: SessionContainer = cast(SessionContainer, request.supertokens) # type: ignore # highlight-start mfa_claim_value = await session.get_claim_value(MultiFactorAuthClaim) if mfa_claim_value is None: diff --git a/docs/additional-verification/mfa/step-up-auth.mdx b/docs/additional-verification/mfa/step-up-auth.mdx index cebb502ef..3b2da1518 100644 --- a/docs/additional-verification/mfa/step-up-auth.mdx +++ b/docs/additional-verification/mfa/step-up-auth.mdx @@ -420,17 +420,19 @@ At the moment this feature is not supported through the Go SDK. ```python +import time + 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 +from supertokens_python.recipe.session import SessionContainer +from supertokens_python.recipe.session.exceptions import ( + ClaimValidationError, + raise_invalid_claims_exception, +) +from supertokens_python.recipe.session.framework.fastapi import verify_session @app.post("/update-blog") # type: ignore @@ -462,12 +464,19 @@ async def update_blog_api(session: SessionContainer = Depends(verify_session())) ```python +import time + from flask import Flask, g -from supertokens_python.recipe.session.framework.flask import verify_session + +from supertokens_python.recipe.multifactorauth.multi_factor_auth_claim import ( + MultiFactorAuthClaim, +) 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 +from supertokens_python.recipe.session.exceptions import ( + ClaimValidationError, + raise_invalid_claims_exception, +) +from supertokens_python.recipe.session.framework.flask import verify_session app = Flask(__name__) @@ -502,22 +511,25 @@ def check_mfa_api(): ```python +import time +from typing import cast + from django.http import HttpRequest -from supertokens_python.recipe.session.framework.django.asyncio import verify_session + +from supertokens_python.recipe.multifactorauth.multi_factor_auth_claim import ( + MultiFactorAuthClaim, +) from supertokens_python.recipe.session import SessionContainer from supertokens_python.recipe.session.exceptions import ( - raise_invalid_claims_exception, ClaimValidationError, + raise_invalid_claims_exception, ) -from supertokens_python.recipe.multifactorauth.multi_factor_auth_claim import ( - MultiFactorAuthClaim, -) -import time +from supertokens_python.recipe.session.framework.django.asyncio import verify_session @verify_session() async def get_user_info_api(request: HttpRequest): - session: SessionContainer = request.supertokens # type: ignore + session: SessionContainer = cast(SessionContainer, request.supertokens) # type: ignore # highlight-start mfa_claim_value = await session.get_claim_value(MultiFactorAuthClaim) assert mfa_claim_value is not None @@ -639,20 +651,24 @@ At the moment this feature is not supported through the Go SDK. ```python -from supertokens_python import init, InputAppInfo, SupertokensConfig +import time +from typing import Any, Awaitable, Callable, Dict, List + +from supertokens_python import InputAppInfo, SupertokensConfig, init 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.multifactorauth.types import ( + FactorIds, + MFARequirementList, + OverrideConfig, +) +from supertokens_python.recipe.session import SessionContainer from supertokens_python.recipe.session.exceptions import ( - raise_invalid_claims_exception, ClaimValidationError, + raise_invalid_claims_exception, ) diff --git a/docs/additional-verification/session-verification/protect-api-routes.mdx b/docs/additional-verification/session-verification/protect-api-routes.mdx index 0aa952720..f3687c44b 100644 --- a/docs/additional-verification/session-verification/protect-api-routes.mdx +++ b/docs/additional-verification/session-verification/protect-api-routes.mdx @@ -384,10 +384,12 @@ func likeCommentAPI(w http.ResponseWriter, r *http.Request) { ```python -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.session import SessionContainer +from supertokens_python.recipe.session.framework.fastapi import verify_session + + # highlight-start @app.post('/like_comment') # type: ignore async def like_comment(session: SessionContainer = Depends(verify_session())): @@ -401,10 +403,12 @@ async def like_comment(session: SessionContainer = Depends(verify_session())): ```python -from supertokens_python.recipe.session.framework.flask import verify_session -from supertokens_python.recipe.session import SessionContainer from flask import g +from supertokens_python.recipe.session import SessionContainer +from supertokens_python.recipe.session.framework.flask import verify_session + + # highlight-start @app.route('/update-jwt', methods=['POST']) # type: ignore @verify_session() @@ -421,18 +425,22 @@ def like_comment(): ```python -from supertokens_python.recipe.session.framework.django.asyncio import verify_session +from typing import cast + from django.http import HttpRequest + from supertokens_python.recipe.session import SessionContainer +from supertokens_python.recipe.session.framework.django.asyncio import verify_session + # highlight-start @verify_session() async def like_comment(request: HttpRequest): - session: SessionContainer = request.supertokens # type: ignore + session: SessionContainer = cast(SessionContainer, request.supertokens) # type: ignore user_id = session.get_user_id() # highlight-end - + print(user_id) ``` @@ -863,18 +871,26 @@ func likeCommentAPI(w http.ResponseWriter, r *http.Request) { ```python -from supertokens_python.recipe.session.framework.fastapi import verify_session +from typing import Optional + from fastapi import Depends + from supertokens_python.recipe.session import SessionContainer +from supertokens_python.recipe.session.framework.fastapi import verify_session + # highlight-start -@app.post('/like_comment') # type: ignore -async def like_comment(session: SessionContainer = Depends(verify_session(session_required=False))): +@app.post("/like_comment") # type: ignore +async def like_comment( + session: Optional[SessionContainer] = Depends( + verify_session(session_required=False) + ), +): if session is not None: user_id = session.get_user_id() - print(user_id) # TODO.. + print(user_id) # TODO.. else: - pass # user is not logged in + pass # user is not logged in # highlight-end ``` @@ -882,11 +898,14 @@ async def like_comment(session: SessionContainer = Depends(verify_session(sessio ```python -from supertokens_python.recipe.session.framework.flask import verify_session -from supertokens_python.recipe.session import SessionContainer from typing import Union + from flask import g +from supertokens_python.recipe.session import SessionContainer +from supertokens_python.recipe.session.framework.flask import verify_session + + # highlight-start @app.route('/update-jwt', methods=['POST']) # type: ignore @verify_session(session_required=False) @@ -905,16 +924,19 @@ def like_comment(): ```python -from supertokens_python.recipe.session.framework.django.asyncio import verify_session +from typing import Optional, cast + from django.http import HttpRequest + from supertokens_python.recipe.session import SessionContainer -from typing import Union +from supertokens_python.recipe.session.framework.django.asyncio import verify_session + # highlight-start @verify_session(session_required=False) async def like_comment(request: HttpRequest): - session: Union[None, SessionContainer] = request.supertokens # type: ignore - + session: Optional[SessionContainer] = cast(Optional[SessionContainer], request.supertokens) # type: ignore + if session is not None: user_id = session.get_user_id() print(user_id) # TODO.. @@ -1346,10 +1368,12 @@ func exampleAPI(w http.ResponseWriter, r *http.Request) { ```python +from fastapi import Depends + +from supertokens_python.recipe.session import SessionContainer from supertokens_python.recipe.session.framework.fastapi import verify_session from supertokens_python.recipe.userroles import UserRoleClaim -from supertokens_python.recipe.session import SessionContainer -from fastapi import Depends + @app.post('/like_comment') # type: ignore async def like_comment(session: SessionContainer = Depends( @@ -1372,6 +1396,7 @@ async def like_comment(session: SessionContainer = Depends( from supertokens_python.recipe.session.framework.flask import verify_session from supertokens_python.recipe.userroles import UserRoleClaim + @app.route('/update-jwt', methods=['POST']) # type: ignore @verify_session( # highlight-start @@ -1389,10 +1414,12 @@ def like_comment(): ```python -from supertokens_python.recipe.session.framework.django.asyncio import verify_session from django.http import HttpRequest + +from supertokens_python.recipe.session.framework.django.asyncio import verify_session from supertokens_python.recipe.userroles import UserRoleClaim + @verify_session( # highlight-start # We add the UserRoleClaim's includes validator @@ -1672,9 +1699,11 @@ func likeCommentAPI(w http.ResponseWriter, r *http.Request) { ```python -from supertokens_python.recipe.session.asyncio import get_session from fastapi.requests import Request +from supertokens_python.recipe.session.asyncio import get_session + + @app.post('/like-comment') # type: ignore async def like_comment(request: Request): # highlight-next-line @@ -1693,30 +1722,35 @@ async def like_comment(request: Request): ```python -from supertokens_python.recipe.session.syncio import get_session -from flask.wrappers import Request +from fastapi.requests import Request -@app.route('/like-comment', methods=['POST']) # type: ignore -def like_comment(request: Request): +from supertokens_python.recipe.session.asyncio import get_session + + +@app.post("/like-comment") # type: ignore +async def like_comment(request: Request): # highlight-next-line - session = get_session(request) + session = await get_session(request) if session is None: raise Exception("Should never come here") user_id = session.get_user_id() - print(user_id) + print(user_id) # TODO + ``` ```python -from supertokens_python.recipe.session.asyncio import get_session from django.http import HttpRequest +from supertokens_python.recipe.session.asyncio import get_session + + async def like_comment(request: HttpRequest): # highlight-next-line session = await get_session(request) @@ -1725,7 +1759,8 @@ async def like_comment(request: HttpRequest): user_id = session.get_user_id() - print(user_id) # TODO + print(user_id) # TODO + ``` @@ -2025,38 +2060,44 @@ func likeCommentAPI(w http.ResponseWriter, r *http.Request) { ```python -from supertokens_python.recipe.session.asyncio import get_session from fastapi import Request -@app.post('/like-comment') # type: ignore +from supertokens_python.recipe.session.asyncio import get_session + + +@app.post("/like-comment") # type: ignore async def like_comment(request: Request): # highlight-next-line session = await get_session(request, session_required=False) if session is not None: user_id = session.get_user_id() - print(user_id) # TODO: + print(user_id) # TODO: else: - pass # user is not logged in + pass # user is not logged in + ``` ```python -from supertokens_python.recipe.session.syncio import get_session from flask.wrappers import Request -@app.route('/like-comment', methods=['POST']) # type: ignore +from supertokens_python.recipe.session.syncio import get_session + + +@app.route("/like-comment", methods=["POST"]) # type: ignore def like_comment(request: Request): # highlight-next-line session = get_session(request, session_required=False) if session is not None: user_id = session.get_user_id() - print(user_id) # TODO.. + print(user_id) # TODO.. else: - pass # user is not logged in + pass # user is not logged in + ``` @@ -2064,17 +2105,20 @@ def like_comment(request: Request): ```python from django.http import HttpRequest + from supertokens_python.recipe.session.asyncio import get_session + async def like_comment(request: HttpRequest): # highlight-next-line session = await get_session(request, session_required=False) if session is not None: user_id = session.get_user_id() - print(user_id) # TODO.. + print(user_id) # TODO.. else: - pass # user is not logged in + pass # user is not logged in + ``` @@ -2410,10 +2454,12 @@ func likeCommentAPI(w http.ResponseWriter, r *http.Request) { ```python -from supertokens_python.recipe.session.asyncio import get_session from fastapi.requests import Request + +from supertokens_python.recipe.session.asyncio import get_session from supertokens_python.recipe.userroles import UserRoleClaim + @app.post('/like-comment') # type: ignore async def like_comment(request: Request): session = await get_session(request, @@ -2433,43 +2479,55 @@ async def like_comment(request: Request): ```python -from supertokens_python.recipe.session.syncio import get_session from flask.wrappers import Request + +from supertokens_python.recipe.session.syncio import get_session from supertokens_python.recipe.userroles import UserRoleClaim -@app.route('/like-comment', methods=['POST']) # type: ignore + +@app.route("/like-comment", methods=["POST"]) # type: ignore def like_comment(request: Request): - session = get_session(request, - override_global_claim_validators=lambda global_validators, session, user_context: global_validators + \ - [UserRoleClaim.validators.includes("admin")]) + session = get_session( + request, + override_global_claim_validators=lambda global_validators, + session, + user_context: global_validators + [UserRoleClaim.validators.includes("admin")], + ) if session is None: raise Exception("Should never come here") user_id = session.get_user_id() - print(user_id) + print(user_id) # TODO + ``` ```python -from supertokens_python.recipe.session.asyncio import get_session from django.http import HttpRequest + +from supertokens_python.recipe.session.asyncio import get_session from supertokens_python.recipe.userroles import UserRoleClaim + async def like_comment(request: HttpRequest): - session = await get_session(request, - override_global_claim_validators=lambda global_validators, session, user_context: global_validators + \ - [UserRoleClaim.validators.includes("admin")]) + session = await get_session( + request, + override_global_claim_validators=lambda global_validators, + session, + user_context: global_validators + [UserRoleClaim.validators.includes("admin")], + ) if session is None: raise Exception("Should never come here") user_id = session.get_user_id() - print(user_id) # TODO + print(user_id) # TODO + ``` @@ -2600,18 +2658,18 @@ The `SuperTokens.ErrorHandler` sends a `401` reply to the frontend if the `getSe ```python from functools import wraps -from typing import Any, Callable, Dict, TypeVar, Union, cast, List, Optional +from typing import Any, Callable, Dict, List, Optional, TypeVar, Union, cast from supertokens_python.framework.flask.flask_request import FlaskRequest -from supertokens_python.recipe.session.syncio import get_session from supertokens_python.recipe.session import SessionContainer -from supertokens_python.recipe.session.interfaces import SessionClaimValidator -from supertokens_python.types import MaybeAwaitable from supertokens_python.recipe.session.exceptions import ( - UnauthorisedError, InvalidClaimsError, - TryRefreshTokenError + TryRefreshTokenError, + UnauthorisedError, ) +from supertokens_python.recipe.session.interfaces import SessionClaimValidator +from supertokens_python.recipe.session.syncio import get_session +from supertokens_python.types import MaybeAwaitable _T = TypeVar("_T", bound=Callable[..., Any]) @@ -2627,7 +2685,6 @@ def verify_session( ] ] = None, ) -> Callable[[_T], _T]: - def session_verify(f: _T) -> _T: @wraps(f) def wrapped_function(*args: Any, **kwargs: Any): @@ -2635,11 +2692,13 @@ def verify_session( baseRequest = FlaskRequest(request) try: - session = get_session(baseRequest, - session_required, - anti_csrf_check, - check_database, - override_global_claim_validators) + session = get_session( + baseRequest, + session_required, + anti_csrf_check, + check_database, + override_global_claim_validators, + ) except Exception as e: if isinstance(e, TryRefreshTokenError): # This means that the session exists, but the access token @@ -2674,6 +2733,7 @@ def verify_session( return cast(_T, wrapped_function) return session_verify + ``` If `get_session` throws an error (in case the input access token is invalid or has expired), then the SuperTokens middleware added to your app handles that exception. It sends a `401` to the frontend. @@ -2801,17 +2861,19 @@ func VerifySession(accessToken string, antiCsrfToken *string, options *sessmodel ```python -from typing import Any, Callable, Dict, TypeVar, List, Optional +from typing import Any, Callable, Dict, List, Optional, TypeVar -from supertokens_python.recipe.session.syncio import get_session_without_request_response from supertokens_python.recipe.session import SessionContainer -from supertokens_python.recipe.session.interfaces import SessionClaimValidator -from supertokens_python.types import MaybeAwaitable from supertokens_python.recipe.session.exceptions import ( - UnauthorisedError, InvalidClaimsError, - TryRefreshTokenError + TryRefreshTokenError, + UnauthorisedError, +) +from supertokens_python.recipe.session.interfaces import SessionClaimValidator +from supertokens_python.recipe.session.syncio import ( + get_session_without_request_response, ) +from supertokens_python.types import MaybeAwaitable _T = TypeVar("_T", bound=Callable[..., Any]) @@ -2830,12 +2892,14 @@ def verify_session( ] = None, ): try: - session = get_session_without_request_response(access_token, - anti_csrf_token, - anti_csrf_check, - session_required, - check_database, - override_global_claim_validators) + session = get_session_without_request_response( + access_token, + anti_csrf_token, + anti_csrf_check, + session_required, + check_database, + override_global_claim_validators, + ) except Exception as e: if isinstance(e, TryRefreshTokenError): # This means that the session exists, but the access token @@ -2872,6 +2936,7 @@ def verify_session( if tokens["antiCsrfToken"] is not None: # TODO: set anti-csrf token update in response via tokens["antiCsrfToken"] pass + ``` From 3bb9085f7eace8b8fbef7cb8ae0dc03b33d4fff1 Mon Sep 17 00:00:00 2001 From: Namit Nathwani Date: Fri, 27 Jun 2025 19:20:45 +0530 Subject: [PATCH 08/10] fix: pyright errors --- .../user-roles/protecting-routes.mdx | 63 +++++++--- .../implement-username-login.mdx | 87 ++++++++------ docs/authentication/m2m/legacy-flow.mdx | 14 ++- .../authentication/passkeys/customization.mdx | 79 +++++++----- .../passwordless/allow-list-flow.mdx | 20 ++-- .../passwordless/invite-link-flow.mdx | 32 +++-- .../ep-migration-without-password-hash.mdx | 112 ++++++++++-------- .../access-session-data.mdx | 46 ++++--- 8 files changed, 277 insertions(+), 176 deletions(-) diff --git a/docs/additional-verification/user-roles/protecting-routes.mdx b/docs/additional-verification/user-roles/protecting-routes.mdx index 649552147..51bda315a 100644 --- a/docs/additional-verification/user-roles/protecting-routes.mdx +++ b/docs/additional-verification/user-roles/protecting-routes.mdx @@ -447,10 +447,12 @@ func exampleAPI(w http.ResponseWriter, r *http.Request) { ```python +from fastapi import Depends + +from supertokens_python.recipe.session import SessionContainer from supertokens_python.recipe.session.framework.fastapi import verify_session from supertokens_python.recipe.userroles import UserRoleClaim -from supertokens_python.recipe.session import SessionContainer -from fastapi import Depends + @app.post('/like_comment') # type: ignore async def like_comment(session: SessionContainer = Depends( @@ -473,6 +475,7 @@ async def like_comment(session: SessionContainer = Depends( from supertokens_python.recipe.session.framework.flask import verify_session from supertokens_python.recipe.userroles import UserRoleClaim + @app.route('/update-jwt', methods=['POST']) # type: ignore @verify_session( # highlight-start @@ -490,10 +493,12 @@ def like_comment(): ```python -from supertokens_python.recipe.session.framework.django.asyncio import verify_session from django.http import HttpRequest + +from supertokens_python.recipe.session.framework.django.asyncio import verify_session from supertokens_python.recipe.userroles import UserRoleClaim + @verify_session( # highlight-start # We add the UserRoleClaim's includes validator @@ -1089,19 +1094,26 @@ func contains(s []interface{}, e string) bool { ```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.session.exceptions import ( + ClaimValidationError, + raise_invalid_claims_exception, +) +from supertokens_python.recipe.session.framework.fastapi import verify_session from supertokens_python.recipe.userroles import UserRoleClaim -@app.post('/update-blog') # type: ignore + +@app.post("/update-blog") # type: ignore async def update_blog_api(session: SessionContainer = Depends(verify_session())): # highlight-start roles = await session.get_claim_value(UserRoleClaim) if roles is None or "admin" not in roles: - raise_invalid_claims_exception("User is not an admin", [ - ClaimValidationError(UserRoleClaim.key, None)]) + raise_invalid_claims_exception( + "User is not an admin", [ClaimValidationError(UserRoleClaim.key, None)] + ) # highlight-end + ``` @@ -1109,43 +1121,58 @@ async def update_blog_api(session: SessionContainer = Depends(verify_session())) ```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.session.exceptions import ( + ClaimValidationError, + raise_invalid_claims_exception, +) +from supertokens_python.recipe.session.framework.flask import verify_session from supertokens_python.recipe.userroles import UserRoleClaim app = Flask(__name__) -@app.route('/update-blog', methods=['POST']) # type: ignore + +@app.route("/update-blog", methods=["POST"]) # type: ignore @verify_session() def set_role_api(): session: SessionContainer = g.supertokens # type: ignore # highlight-start roles = session.sync_get_claim_value(UserRoleClaim) if roles is None or "admin" not in roles: - raise_invalid_claims_exception("User is not an admin", [ - ClaimValidationError(UserRoleClaim.key, None)]) + raise_invalid_claims_exception( + "User is not an admin", [ClaimValidationError(UserRoleClaim.key, None)] + ) # highlight-end + ``` ```python +from typing import cast + 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.session.exceptions import ( + ClaimValidationError, + raise_invalid_claims_exception, +) +from supertokens_python.recipe.session.framework.django.asyncio import verify_session from supertokens_python.recipe.userroles import UserRoleClaim + @verify_session() async def get_user_info_api(request: HttpRequest): - session: SessionContainer = request.supertokens # type: ignore + session: SessionContainer = cast(SessionContainer, request.supertokens) # type: ignore # highlight-start roles = await session.get_claim_value(UserRoleClaim) if roles is None or "admin" not in roles: - raise_invalid_claims_exception("User is not an admin", [ - ClaimValidationError(UserRoleClaim.key, None)]) + raise_invalid_claims_exception( + "User is not an admin", [ClaimValidationError(UserRoleClaim.key, None)] + ) # highlight-end ``` diff --git a/docs/authentication/email-password/implement-username-login.mdx b/docs/authentication/email-password/implement-username-login.mdx index 32f2dce58..b3111b6d5 100644 --- a/docs/authentication/email-password/implement-username-login.mdx +++ b/docs/authentication/email-password/implement-username-login.mdx @@ -162,11 +162,13 @@ func main() { ```python -from supertokens_python import init, InputAppInfo +from re import fullmatch + +from supertokens_python import InputAppInfo, init from supertokens_python.recipe import emailpassword -from supertokens_python.recipe.emailpassword.utils import InputSignUpFeature from supertokens_python.recipe.emailpassword.types import InputFormField -from re import fullmatch +from supertokens_python.recipe.emailpassword.utils import InputSignUpFeature + async def validate(value: str, tenant_id: str): # first we check for if it's an email @@ -358,13 +360,14 @@ func main() { ```python -from supertokens_python import init, InputAppInfo -from supertokens_python.recipe import emailpassword -from supertokens_python.recipe.emailpassword.utils import InputSignUpFeature -from supertokens_python.recipe.emailpassword.types import InputFormField from re import fullmatch from typing import Dict +from supertokens_python import InputAppInfo, init +from supertokens_python.recipe import emailpassword +from supertokens_python.recipe.emailpassword.types import InputFormField +from supertokens_python.recipe.emailpassword.utils import InputSignUpFeature + email_user_map: Dict[str, str] = {} # highlight-start @@ -578,19 +581,20 @@ func main() { ```python -from supertokens_python import init, InputAppInfo +from typing import Any, Dict, List, Union + +from supertokens_python import InputAppInfo, init from supertokens_python.recipe import emailpassword -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, ) -from typing import Dict, List, Union, Any +from supertokens_python.recipe.emailpassword.types import FormField +from supertokens_python.recipe.emailpassword.utils import ( + InputOverrideConfig, + InputSignUpFeature, +) from supertokens_python.recipe.session.interfaces import SessionContainer email_user_map: Dict[str, str] = {} @@ -858,17 +862,18 @@ func main() { ```python -from supertokens_python import init, InputAppInfo +from re import fullmatch +from typing import Any, Dict, Union + +from supertokens_python import InputAppInfo, init +from supertokens_python.asyncio import get_user from supertokens_python.recipe import emailpassword +from supertokens_python.recipe.emailpassword.interfaces import RecipeInterface from supertokens_python.recipe.emailpassword.utils import ( - InputSignUpFeature, InputOverrideConfig, + InputSignUpFeature, ) -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] = {} @@ -1263,22 +1268,23 @@ func main() { ```python -from supertokens_python import init, InputAppInfo -from supertokens_python.recipe import emailpassword +from re import fullmatch +from typing import Any, Dict, List + +from supertokens_python import InputAppInfo, init 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 import emailpassword from supertokens_python.recipe.emailpassword.interfaces import ( APIInterface, APIOptions, ) +from supertokens_python.recipe.emailpassword.types import FormField +from supertokens_python.recipe.emailpassword.utils import ( + InputOverrideConfig, + InputSignUpFeature, +) from supertokens_python.types import GeneralErrorResponse -from typing import Dict, List, Any -from re import fullmatch +from supertokens_python.types.base import AccountInfoInput email_user_map: Dict[str, str] = {} @@ -1362,7 +1368,7 @@ def apis_override(original: APIInterface): if field.id == "email": username = field.value supertokens_user = await list_users_by_account_info( - tenant_id, AccountInfo(email=username) + tenant_id, AccountInfoInput(email=username) ) target_user = next( ( @@ -1592,13 +1598,20 @@ 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.ingredients.emaildelivery.types import EmailDeliveryConfig -from typing import Dict, Any from re import fullmatch -from supertokens_python.recipe.emailpassword.types import EmailDeliveryOverrideInput, EmailTemplateVars +from typing import Any, Dict + +from supertokens_python import InputAppInfo, init +from supertokens_python.ingredients.emaildelivery.types import EmailDeliveryConfig +from supertokens_python.recipe import emailpassword +from supertokens_python.recipe.emailpassword.types import ( + EmailDeliveryOverrideInput, + EmailTemplateVars, +) +from supertokens_python.recipe.emailpassword.utils import ( + InputOverrideConfig, + InputSignUpFeature, +) email_user_map: Dict[str, str] = {} diff --git a/docs/authentication/m2m/legacy-flow.mdx b/docs/authentication/m2m/legacy-flow.mdx index f40fe859a..c577d63ba 100644 --- a/docs/authentication/m2m/legacy-flow.mdx +++ b/docs/authentication/m2m/legacy-flow.mdx @@ -134,7 +134,7 @@ func main() { ```python -from supertokens_python import init, InputAppInfo, SupertokensConfig +from supertokens_python import InputAppInfo, SupertokensConfig, init from supertokens_python.recipe import jwt init( @@ -218,6 +218,7 @@ func main() { from supertokens_python.recipe.jwt import asyncio from supertokens_python.recipe.jwt.interfaces import CreateJwtOkResult + async def create_jwt(): jwtResponse = await asyncio.create_jwt({ "source": "microservice", @@ -235,8 +236,8 @@ async def create_jwt(): ```python -from supertokens_python.recipe.jwt.syncio import create_jwt from supertokens_python.recipe.jwt.interfaces import CreateJwtOkResult +from supertokens_python.recipe.jwt.syncio import create_jwt jwtResponse = create_jwt({ "source": "microservice", @@ -595,17 +596,20 @@ app.post("/like-comment", async (req, res, next) => { ```python -from supertokens_python.recipe.session.asyncio import get_session -from django.http import HttpRequest from typing import Union +from django.http import HttpRequest + +from supertokens_python.recipe.session.asyncio import get_session + + async def like_comment(request: HttpRequest): session = await get_session(request, session_required=False) user_id = "" if session is not None: user_id = session.get_user_id() else: - jwt: Union[str, None] = request.headers.get("Authorization") # type: ignore + jwt: Union[str, None] = request.headers.get("Authorization") if jwt is None: # return a 401 unauthorised error... pass diff --git a/docs/authentication/passkeys/customization.mdx b/docs/authentication/passkeys/customization.mdx index 2f5ff09c8..1b50691c2 100644 --- a/docs/authentication/passkeys/customization.mdx +++ b/docs/authentication/passkeys/customization.mdx @@ -87,20 +87,22 @@ At the moment there is no support for using passkeys authentication in the Go SD ```python -from supertokens_python import init, InputAppInfo, SupertokensConfig +from typing import Optional + +from supertokens_python import InputAppInfo, SupertokensConfig, init from supertokens_python.framework import BaseRequest -from supertokens_python.recipe import webauthn, session +from supertokens_python.recipe import session, webauthn from supertokens_python.recipe.webauthn import WebauthnConfig from supertokens_python.types.base import UserContext -from typing import Optional -def get_origin(*, tenant_id: str, request: Optional[BaseRequest], user_context: UserContext): + +async def get_origin(*, tenant_id: str, request: Optional[BaseRequest], user_context: UserContext): return "https://example.com" -def get_relying_party_id(*, tenant_id: str, request: Optional[BaseRequest], user_context: UserContext): +async def get_relying_party_id(*, tenant_id: str, request: Optional[BaseRequest], user_context: UserContext): return "example.com" -def get_relying_party_name(*, tenant_id: str, request: Optional[BaseRequest], user_context: UserContext): +async def get_relying_party_name(*, tenant_id: str, user_context: UserContext): return "example" init( @@ -240,68 +242,85 @@ At the moment there is no support for using passkeys authentication in the Go SD ```python -from supertokens_python import init, InputAppInfo, SupertokensConfig -from supertokens_python.recipe import webauthn, session -from supertokens_python.recipe.webauthn import WebauthnConfig, RecipeInterface +from typing import Any, List, Optional, cast + +from typing_extensions import Unpack + +from supertokens_python import InputAppInfo, SupertokensConfig, init +from supertokens_python.recipe import session, webauthn +from supertokens_python.recipe.webauthn import RecipeInterface, WebauthnConfig +from supertokens_python.recipe.webauthn.interfaces.recipe import ( + Attestation, + RegisterOptionsKwargsInput, + ResidentKey, + UserVerification, +) from supertokens_python.recipe.webauthn.types.config import OverrideConfig from supertokens_python.types.base import UserContext -from typing import Any, List, Optional, Union + def override_webauthn_functions(original_implementation: RecipeInterface): original_register_options = original_implementation.register_options async def register_options( *, - email: Optional[str], - recover_account_token: Optional[str], + relying_party_id: str, + relying_party_name: str, + origin: str, + resident_key: Optional[ResidentKey] = None, + user_verification: Optional[UserVerification] = None, + user_presence: Optional[bool] = None, + attestation: Optional[Attestation] = None, + supported_algorithm_ids: Optional[List[int]] = None, + timeout: Optional[int] = None, tenant_id: str, user_context: UserContext, - **kwargs: Any, + **kwargs: Unpack[RegisterOptionsKwargsInput], ): return await original_register_options( - email=email, - recover_account_token=recover_account_token, - tenant_id=tenant_id, - user_context=user_context, - attestation="direct", + relying_party_id="example.com", + relying_party_name="example", + origin="https://example.com", resident_key="required", - timeout=10 * 1000, user_verification="required", + user_presence=True, + attestation="direct", + timeout=10 * 1000, + tenant_id=tenant_id, + user_context=user_context, + email=cast(str, kwargs.get("email")), + recover_account_token=cast(str, kwargs.get("recover_account_token")), display_name="John Doe", - supported_algorithms=[-257], - relying_party_id='example.com', - relying_party_name='example', - origin='https://example.com', ) original_implementation.register_options = register_options return original_implementation + init( app_info=InputAppInfo( app_name="", api_domain="", website_domain="", api_base_path="/auth", - website_base_path="/auth" + website_base_path="/auth", ), supertokens_config=SupertokensConfig( # https://try.supertokens.com is for demo purposes. Replace this with the address of your core instance (sign up on supertokens.com), or self host a core. connection_uri="https://try.supertokens.com", # api_key="" ), - framework='flask', # Replace this with the framework you are using + framework="flask", # Replace this with the framework you are using recipe_list=[ webauthn.init( config=WebauthnConfig( - override=OverrideConfig( - functions=override_webauthn_functions - ) + override=OverrideConfig(functions=override_webauthn_functions) ) ), - session.init() # initializes session features - ] + session.init(), # initializes session features + ], ) + ```
diff --git a/docs/authentication/passwordless/allow-list-flow.mdx b/docs/authentication/passwordless/allow-list-flow.mdx index 22dfb6172..7cdd137d2 100644 --- a/docs/authentication/passwordless/allow-list-flow.mdx +++ b/docs/authentication/passwordless/allow-list-flow.mdx @@ -157,9 +157,14 @@ func isPhoneNumberAllowed(phoneNumber string) (bool, error) { ```python -from supertokens_python.recipe.usermetadata.asyncio import get_user_metadata, update_user_metadata from typing import List +from supertokens_python.recipe.usermetadata.asyncio import ( + get_user_metadata, + update_user_metadata, +) + + async def add_email_to_allow_list(email: str): metadataResult = await get_user_metadata("emailAllowList") allow_list: List[str] = metadataResult.metadata["allowList"] if "allowList" in metadataResult.metadata else [] @@ -338,17 +343,18 @@ func main() { ```python -from supertokens_python import init, InputAppInfo -from supertokens_python.types import GeneralErrorResponse -from supertokens_python.recipe import passwordless +from typing import Any, Dict, Optional, Union + +from supertokens_python import InputAppInfo, init from supertokens_python.asyncio import list_users_by_account_info -from supertokens_python.types import AccountInfo +from supertokens_python.recipe import passwordless 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.types import AccountInfo, GeneralErrorResponse +from supertokens_python.types.base import AccountInfoInput async def is_email_allowed(email: str): @@ -375,7 +381,7 @@ def override_passwordless_apis(original_implementation: APIInterface): ): if email is not None: existing_user = await list_users_by_account_info( - tenant_id, AccountInfo(email=email) + tenant_id, AccountInfoInput(email=email) ) user_with_passwordless = next( ( diff --git a/docs/authentication/passwordless/invite-link-flow.mdx b/docs/authentication/passwordless/invite-link-flow.mdx index 774f08225..8bc658542 100644 --- a/docs/authentication/passwordless/invite-link-flow.mdx +++ b/docs/authentication/passwordless/invite-link-flow.mdx @@ -618,11 +618,13 @@ func createUserAPI(w http.ResponseWriter, r *http.Request) { ```python -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.passwordless.asyncio import create_magic_link, signinup +from supertokens_python.recipe.session import SessionContainer +from supertokens_python.recipe.session.framework.fastapi import verify_session +from supertokens_python.recipe.userroles import UserRoleClaim + @app.post('/create-user') # type: ignore async def create_user(session: SessionContainer = Depends(verify_session( @@ -645,9 +647,10 @@ async def create_user(session: SessionContainer = Depends(verify_session( ```python +from supertokens_python.recipe.passwordless.syncio import create_magic_link, signinup from supertokens_python.recipe.session.framework.flask import verify_session from supertokens_python.recipe.userroles import UserRoleClaim -from supertokens_python.recipe.passwordless.syncio import create_magic_link, signinup + @app.route('/create_user', methods=['POST']) # type: ignore @verify_session( @@ -671,10 +674,12 @@ def create_user(): ```python -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.passwordless.asyncio import create_magic_link, signinup +from supertokens_python.recipe.session.framework.django.asyncio import verify_session +from supertokens_python.recipe.userroles import UserRoleClaim + @verify_session( override_global_claim_validators=lambda global_validators, session, user_context: global_validators + @@ -818,14 +823,15 @@ func main() { ```python -from supertokens_python import init, InputAppInfo -from supertokens_python.types import GeneralErrorResponse +from typing import Any, Dict, Optional, Union + +from supertokens_python import InputAppInfo, init +from supertokens_python.asyncio import list_users_by_account_info from supertokens_python.recipe import passwordless 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 +from supertokens_python.types import GeneralErrorResponse +from supertokens_python.types.base import AccountInfoInput def override_passwordless_apis(original_implementation: APIInterface): @@ -842,7 +848,7 @@ def override_passwordless_apis(original_implementation: APIInterface): ): if email is not None: existing_user = await list_users_by_account_info( - tenant_id, AccountInfo(email=email) + tenant_id, AccountInfoInput(email=email) ) user_with_passwordless = next( ( @@ -862,7 +868,7 @@ def override_passwordless_apis(original_implementation: APIInterface): else: assert phone_number is not None existing_user = await list_users_by_account_info( - tenant_id, AccountInfo(phone_number=phone_number) + tenant_id, AccountInfoInput(phone_number=phone_number) ) user_with_passwordless = next( ( diff --git a/docs/migration/legacy/account-creation/ep-migration-without-password-hash.mdx b/docs/migration/legacy/account-creation/ep-migration-without-password-hash.mdx index bedaebc6a..4bf774712 100644 --- a/docs/migration/legacy/account-creation/ep-migration-without-password-hash.mdx +++ b/docs/migration/legacy/account-creation/ep-migration-without-password-hash.mdx @@ -74,7 +74,9 @@ async function doesUserExistInExternalProvider(email: string): Promise ```python -from supertokens_python import init, InputAppInfo +from typing import Any, Dict, List, Union + +from supertokens_python import InputAppInfo, init from supertokens_python.recipe import emailpassword from supertokens_python.recipe.emailpassword.interfaces import ( APIInterface, @@ -82,7 +84,6 @@ from supertokens_python.recipe.emailpassword.interfaces import ( EmailAlreadyExistsError, ) from supertokens_python.recipe.emailpassword.types import FormField -from typing import Dict, Any, Union, List from supertokens_python.recipe.session.interfaces import SessionContainer @@ -288,19 +289,15 @@ async function validateAndGetUserInfoFromExternalProvider(email: string, passwor ```python -from supertokens_python import init, InputAppInfo -from supertokens_python.recipe import emailpassword -from supertokens_python.asyncio import create_user_id_mapping -from supertokens_python.recipe.emailverification.asyncio import ( - create_email_verification_token, - verify_email_using_token, -) -from supertokens_python.recipe.emailpassword.asyncio import sign_up -from supertokens_python.recipe.emailverification.interfaces import ( - CreateEmailVerificationTokenOkResult, +from typing import Any, Dict, List, Union + +from supertokens_python import InputAppInfo, init +from supertokens_python.asyncio import ( + create_user_id_mapping, + list_users_by_account_info, ) -from supertokens_python import init, InputAppInfo from supertokens_python.recipe import emailpassword +from supertokens_python.recipe.emailpassword.asyncio import sign_up from supertokens_python.recipe.emailpassword.interfaces import ( APIInterface, APIOptions, @@ -308,10 +305,16 @@ from supertokens_python.recipe.emailpassword.interfaces import ( WrongCredentialsError, ) from supertokens_python.recipe.emailpassword.types import FormField -from typing import Dict, Any, Union, List +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.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.types import RecipeUserId +from supertokens_python.types.base import AccountInfoInput def override_emailpassword_apis(original_implementation: APIInterface): @@ -334,7 +337,7 @@ def override_emailpassword_apis(original_implementation: APIInterface): password = field.value # Check if an email-password user with the input email exists in SuperTokens supertokens_user_with_same_email = await list_users_by_account_info( - tenant_id, AccountInfo(email=email), False, user_context + tenant_id, AccountInfoInput(email=email), False, user_context ) emailpassword_user = next( ( @@ -653,27 +656,31 @@ async function retrieveUserDataFromExternalProvider(email: string): Promise<{ ```python -from supertokens_python import init, InputAppInfo -from supertokens_python.asyncio import create_user_id_mapping -from supertokens_python.recipe import emailpassword -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 typing import Any, Dict, List, Union + +from supertokens_python import InputAppInfo, init +from supertokens_python.asyncio import ( + create_user_id_mapping, + list_users_by_account_info, ) -from supertokens_python.recipe.emailpassword.types import FormField +from supertokens_python.recipe import emailpassword 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, SignUpOkResult, ) +from supertokens_python.recipe.emailpassword.types import FormField +from supertokens_python.recipe.emailverification.asyncio import ( + create_email_verification_token, + verify_email_using_token, +) from supertokens_python.recipe.emailverification.interfaces import ( CreateEmailVerificationTokenOkResult, ) -from typing import Union, Dict, Any, List +from supertokens_python.recipe.usermetadata.asyncio import update_user_metadata +from supertokens_python.types import RecipeUserId +from supertokens_python.types.base import AccountInfoInput def override_emailpassword_apis(original_implementation: APIInterface): @@ -697,7 +704,7 @@ def override_emailpassword_apis(original_implementation: APIInterface): # Check if an email-password user with the input email exists in SuperTokens supertokens_user_with_same_email = await list_users_by_account_info( - tenant_id, AccountInfo(email=email), False, user_context + tenant_id, AccountInfoInput(email=email), False, user_context ) emailpassword_user = next( ( @@ -977,19 +984,20 @@ EmailPassword.init({ ```python -from supertokens_python import init, InputAppInfo +from typing import Any, Dict, List + +from supertokens_python import InputAppInfo, init from supertokens_python.recipe import emailpassword -from supertokens_python.recipe.emailpassword.types import FormField -from supertokens_python.recipe.usermetadata.asyncio import ( - get_user_metadata, - update_user_metadata, -) from supertokens_python.recipe.emailpassword.interfaces import ( APIInterface, APIOptions, PasswordResetPostOkResult, ) -from typing import Dict, Any, List +from supertokens_python.recipe.emailpassword.types import FormField +from supertokens_python.recipe.usermetadata.asyncio import ( + get_user_metadata, + update_user_metadata, +) def override_emailpassword_apis(original_implementation: APIInterface): @@ -1220,22 +1228,18 @@ async function validateAndGetUserInfoFromExternalProvider(email: string, passwor ```python -from supertokens_python import init, InputAppInfo -from supertokens_python.recipe import emailpassword -from supertokens_python.asyncio import create_user_id_mapping -from supertokens_python.recipe.emailverification.asyncio import ( - create_email_verification_token, - verify_email_using_token, +from typing import Any, Dict, List, Union + +from supertokens_python import InputAppInfo, init +from supertokens_python.asyncio import ( + create_user_id_mapping, + list_users_by_account_info, ) +from supertokens_python.recipe import emailpassword from supertokens_python.recipe.emailpassword.asyncio import ( sign_up, update_email_or_password, ) -from supertokens_python.recipe.emailverification.interfaces import ( - CreateEmailVerificationTokenOkResult, -) -from supertokens_python import init, InputAppInfo -from supertokens_python.recipe import emailpassword from supertokens_python.recipe.emailpassword.interfaces import ( APIInterface, APIOptions, @@ -1243,14 +1247,20 @@ from supertokens_python.recipe.emailpassword.interfaces import ( WrongCredentialsError, ) from supertokens_python.recipe.emailpassword.types import FormField -from typing import Dict, Any, Union, List +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.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, ) +from supertokens_python.types import RecipeUserId +from supertokens_python.types.base import AccountInfoInput def override_emailpassword_apis(original_implementation: APIInterface): @@ -1273,7 +1283,7 @@ def override_emailpassword_apis(original_implementation: APIInterface): password = field.value # Check if an email-password user with the input email exists in SuperTokens supertokens_user_with_same_email = await list_users_by_account_info( - tenant_id, AccountInfo(email=email), False, user_context + tenant_id, AccountInfoInput(email=email), False, user_context ) emailpassword_user = next( ( diff --git a/docs/post-authentication/session-management/access-session-data.mdx b/docs/post-authentication/session-management/access-session-data.mdx index ba580e513..4b92890ea 100644 --- a/docs/post-authentication/session-management/access-session-data.mdx +++ b/docs/post-authentication/session-management/access-session-data.mdx @@ -286,9 +286,11 @@ func getJWT(w http.ResponseWriter, r *http.Request) { ```python -from supertokens_python.recipe.session.framework.fastapi import verify_session from fastapi import Depends + from supertokens_python.recipe.session import SessionContainer +from supertokens_python.recipe.session.framework.fastapi import verify_session + @app.get('/getJWT') # type: ignore async def get_jwt(session: SessionContainer = Depends(verify_session())): @@ -302,10 +304,12 @@ async def get_jwt(session: SessionContainer = Depends(verify_session())): ```python -from supertokens_python.recipe.session.framework.flask import verify_session -from supertokens_python.recipe.session import SessionContainer from flask import g +from supertokens_python.recipe.session import SessionContainer +from supertokens_python.recipe.session.framework.flask import verify_session + + @app.route('/getJWT', methods=['GET']) # type: ignore @verify_session() def get_jwt(): @@ -321,13 +325,17 @@ def get_jwt(): ```python -from supertokens_python.recipe.session.framework.django.asyncio import verify_session +from typing import cast + from django.http import HttpRequest + from supertokens_python.recipe.session import SessionContainer +from supertokens_python.recipe.session.framework.django.asyncio import verify_session + @verify_session() async def get_jwt(request: HttpRequest): - session: SessionContainer = request.supertokens # type: ignore + session: SessionContainer = cast(SessionContainer, request.supertokens) # type: ignore # highlight-next-line current_jwt = session.get_access_token() @@ -403,7 +411,7 @@ func main() { ```python -from supertokens_python import init, InputAppInfo +from supertokens_python import InputAppInfo, init from supertokens_python.recipe import session init( @@ -928,26 +936,30 @@ func likeCommentAPI(w http.ResponseWriter, r *http.Request) { ```python -from supertokens_python.recipe.session.framework.fastapi import verify_session from fastapi import Depends + from supertokens_python.recipe.session import SessionContainer +from supertokens_python.recipe.session.framework.fastapi import verify_session + @app.get('/getTenantId') # type: ignore async def get_tenant_id(session: SessionContainer = Depends(verify_session())): # highlight-next-line tenant_id = session.get_tenant_id() - print(tenant_id) + print(tenant_id) ``` ```python -from supertokens_python.recipe.session.framework.flask import verify_session -from supertokens_python.recipe.session import SessionContainer from flask import g +from supertokens_python.recipe.session import SessionContainer +from supertokens_python.recipe.session.framework.flask import verify_session + + @app.route('/getTenantId', methods=['GET']) # type: ignore @verify_session() def get_tenant_id(): @@ -956,25 +968,29 @@ def get_tenant_id(): # highlight-next-line tenant_id = session.get_tenant_id() - print(tenant_id) + print(tenant_id) ``` ```python -from supertokens_python.recipe.session.framework.django.asyncio import verify_session +from typing import cast + from django.http import HttpRequest + from supertokens_python.recipe.session import SessionContainer +from supertokens_python.recipe.session.framework.django.asyncio import verify_session + @verify_session() async def get_tenant_id(request: HttpRequest): - session: SessionContainer = request.supertokens # type: ignore + session: SessionContainer = cast(SessionContainer, request.supertokens) # type: ignore # highlight-next-line tenant_id = session.get_tenant_id() - print(tenant_id) + print(tenant_id) ``` @@ -1059,6 +1075,7 @@ func main() { ```python from supertokens_python.recipe.session.asyncio import get_all_session_handles_for_user + async def some_func(): # session_handles is List[string] # highlight-next-line @@ -1080,7 +1097,6 @@ async def some_func(): ```python from supertokens_python.recipe.session.syncio import get_all_session_handles_for_user - # session_handles is List[string] # highlight-next-line session_handles = get_all_session_handles_for_user("someUserId") From c8b82183c5940812a5c3c9eef5568e7c0e40a17d Mon Sep 17 00:00:00 2001 From: Namit Nathwani Date: Fri, 27 Jun 2025 19:49:25 +0530 Subject: [PATCH 09/10] fix: pyright errors --- docs/authentication/m2m/legacy-flow.mdx | 4 +- .../authentication/passkeys/customization.mdx | 3 +- .../social/custom-invite-flow.mdx | 24 ++-- .../access-token-blacklisting.mdx | 32 +++-- .../advanced-workflows/user-impersonation.mdx | 64 ++++++---- .../user-management/account-deduplication.mdx | 31 +++-- .../allow-users-to-update-their-data.mdx | 109 +++++++++--------- .../user-management/common-actions.mdx | 68 ++++++----- docs/quickstart/backend-setup.mdx | 29 +++-- .../backend-sdks/function-overrides.mdx | 44 ++++--- 10 files changed, 247 insertions(+), 161 deletions(-) diff --git a/docs/authentication/m2m/legacy-flow.mdx b/docs/authentication/m2m/legacy-flow.mdx index c577d63ba..59ccb7b21 100644 --- a/docs/authentication/m2m/legacy-flow.mdx +++ b/docs/authentication/m2m/legacy-flow.mdx @@ -596,7 +596,7 @@ app.post("/like-comment", async (req, res, next) => { ```python -from typing import Union +from typing import Optional, Union, cast from django.http import HttpRequest @@ -609,7 +609,7 @@ async def like_comment(request: HttpRequest): if session is not None: user_id = session.get_user_id() else: - jwt: Union[str, None] = request.headers.get("Authorization") + jwt: Optional[str] = cast(Optional[str], request.headers.get("Authorization")) # type: ignore if jwt is None: # return a 401 unauthorised error... pass diff --git a/docs/authentication/passkeys/customization.mdx b/docs/authentication/passkeys/customization.mdx index 1b50691c2..fe0a2fc0c 100644 --- a/docs/authentication/passkeys/customization.mdx +++ b/docs/authentication/passkeys/customization.mdx @@ -242,7 +242,7 @@ At the moment there is no support for using passkeys authentication in the Go SD ```python -from typing import Any, List, Optional, cast +from typing import List, Optional, cast from typing_extensions import Unpack @@ -320,7 +320,6 @@ init( session.init(), # initializes session features ], ) - ```
diff --git a/docs/authentication/social/custom-invite-flow.mdx b/docs/authentication/social/custom-invite-flow.mdx index f48df09b3..abf6d26cb 100644 --- a/docs/authentication/social/custom-invite-flow.mdx +++ b/docs/authentication/social/custom-invite-flow.mdx @@ -105,9 +105,14 @@ func isEmailAllowed(email string) (bool, error) { ```python -from supertokens_python.recipe.usermetadata.asyncio import get_user_metadata, update_user_metadata from typing import List +from supertokens_python.recipe.usermetadata.asyncio import ( + get_user_metadata, + update_user_metadata, +) + + async def add_email_to_allow_list(email: str): metadataResult = await get_user_metadata("emailAllowList") allow_list: List[str] = metadataResult.metadata["allowList"] if "allowList" in metadataResult.metadata else [] @@ -266,20 +271,21 @@ func main() { ```python -from supertokens_python import init, InputAppInfo -from supertokens_python.types import GeneralErrorResponse +from typing import Any, Dict, Optional, Union + +from supertokens_python import InputAppInfo, init +from supertokens_python.asyncio import list_users_by_account_info from supertokens_python.recipe import thirdparty +from supertokens_python.recipe.session.interfaces import SessionContainer from supertokens_python.recipe.thirdparty.interfaces import ( APIInterface, - RecipeInterface, APIOptions, + RecipeInterface, ) -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 +from supertokens_python.types import GeneralErrorResponse +from supertokens_python.types.base import AccountInfoInput async def is_email_allowed(email: str): @@ -303,7 +309,7 @@ def override_thirdparty_functions(original_implementation: RecipeInterface): user_context: Dict[str, Any], ): existing_users = await list_users_by_account_info( - tenant_id, AccountInfo(email=email) + tenant_id, AccountInfoInput(email=email) ) if len(existing_users) == 0: if not await is_email_allowed(email): diff --git a/docs/post-authentication/session-management/advanced-workflows/access-token-blacklisting.mdx b/docs/post-authentication/session-management/advanced-workflows/access-token-blacklisting.mdx index f3ed0af08..e5fe2711f 100644 --- a/docs/post-authentication/session-management/advanced-workflows/access-token-blacklisting.mdx +++ b/docs/post-authentication/session-management/advanced-workflows/access-token-blacklisting.mdx @@ -396,10 +396,12 @@ func likeCommentAPI(w http.ResponseWriter, r *http.Request) { ```python -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.session import SessionContainer +from supertokens_python.recipe.session.framework.fastapi import verify_session + + # highlight-start @app.post('/like_comment') # type: ignore async def like_comment(session: SessionContainer = Depends(verify_session(check_database=True))): @@ -413,10 +415,12 @@ async def like_comment(session: SessionContainer = Depends(verify_session(check_ ```python -from supertokens_python.recipe.session.framework.flask import verify_session -from supertokens_python.recipe.session import SessionContainer from flask import g +from supertokens_python.recipe.session import SessionContainer +from supertokens_python.recipe.session.framework.flask import verify_session + + # highlight-start @app.route('/update-jwt', methods=['POST']) # type: ignore @verify_session(check_database=True) @@ -433,18 +437,22 @@ def like_comment(): ```python -from supertokens_python.recipe.session.framework.django.asyncio import verify_session +from typing import cast + from django.http import HttpRequest + from supertokens_python.recipe.session import SessionContainer +from supertokens_python.recipe.session.framework.django.asyncio import verify_session + # highlight-start @verify_session(check_database=True) async def like_comment(request: HttpRequest): - session: SessionContainer = request.supertokens # type: ignore + session: SessionContainer = cast(SessionContainer, request.supertokens) # type: ignore user_id = session.get_user_id() # highlight-end - + print(user_id) ``` @@ -734,9 +742,11 @@ func likeCommentAPI(w http.ResponseWriter, r *http.Request) { ```python -from supertokens_python.recipe.session.asyncio import get_session from fastapi import Request +from supertokens_python.recipe.session.asyncio import get_session + + @app.post('/like-comment') # type: ignore async def like_comment(request: Request): # highlight-next-line @@ -753,9 +763,11 @@ async def like_comment(request: Request): ```python -from supertokens_python.recipe.session.syncio import get_session from flask.wrappers import Request +from supertokens_python.recipe.session.syncio import get_session + + @app.route('/like-comment', methods=['POST']) # type: ignore def like_comment(request: Request): # highlight-next-line @@ -773,8 +785,10 @@ def like_comment(request: Request): ```python from django.http import HttpRequest + from supertokens_python.recipe.session.asyncio import get_session + async def like_comment(request: HttpRequest): # highlight-next-line session = await get_session(request, check_database=True) diff --git a/docs/post-authentication/session-management/advanced-workflows/user-impersonation.mdx b/docs/post-authentication/session-management/advanced-workflows/user-impersonation.mdx index 376552e25..e2510465c 100644 --- a/docs/post-authentication/session-management/advanced-workflows/user-impersonation.mdx +++ b/docs/post-authentication/session-management/advanced-workflows/user-impersonation.mdx @@ -488,15 +488,15 @@ func impersonate(w http.ResponseWriter, r *http.Request) { ```python -from supertokens_python.recipe.session.framework.fastapi import verify_session -from supertokens_python.recipe.session.asyncio import create_new_session +from fastapi import Depends, Request from fastapi.responses import JSONResponse + +from supertokens_python.asyncio import list_users_by_account_info from supertokens_python.recipe.session import SessionContainer -from fastapi import Depends +from supertokens_python.recipe.session.asyncio import create_new_session +from supertokens_python.recipe.session.framework.fastapi import verify_session from supertokens_python.recipe.userroles import UserRoleClaim -from supertokens_python.asyncio import list_users_by_account_info -from supertokens_python.types import AccountInfo -from fastapi import Request +from supertokens_python.types.base import AccountInfoInput @app.post("/impersonate") # type: ignore @@ -505,7 +505,9 @@ async def impersonate( session: SessionContainer = Depends( verify_session( # We add the UserRoleClaim's includes validator - override_global_claim_validators=lambda global_validators, session, user_context: global_validators + override_global_claim_validators=lambda global_validators, + session, + user_context: global_validators + [UserRoleClaim.validators.includes("admin")] ) ), @@ -513,7 +515,7 @@ 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 list_users_by_account_info("public", AccountInfo(email=email)) + user = await list_users_by_account_info("public", AccountInfoInput(email=email)) if len(user) == 0: # return a 400 error to the client @@ -534,64 +536,82 @@ async def impersonate( ```python -from supertokens_python.recipe.session.syncio import create_new_session from flask import jsonify from flask.wrappers import Request + from supertokens_python.recipe.session.framework.flask import verify_session +from supertokens_python.recipe.session.syncio import create_new_session from supertokens_python.recipe.userroles import UserRoleClaim from supertokens_python.syncio import list_users_by_account_info -from supertokens_python.types import AccountInfo +from supertokens_python.types.base import AccountInfoInput + @app.route("/impersonate", methods=["POST"]) # type: ignore @verify_session( # We add the UserRoleClaim's includes validator - 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 login(request: Request): email = "..." # get from request body # we use the email password recipe here, but you can use the recipe you use - user = list_users_by_account_info("public", AccountInfo(email=email)) + user = list_users_by_account_info("public", AccountInfoInput(email=email)) if len(user) == 0: # return a 400 error to the client return - create_new_session(request, "public", user[0].login_methods[0].recipe_user_id, {"isImpersonation": True}) + create_new_session( + request, + "public", + user[0].login_methods[0].recipe_user_id, + {"isImpersonation": True}, + ) return jsonify({"message": "Impersonation complete!"}) + ``` ```python -from supertokens_python.recipe.session.framework.django.asyncio import verify_session -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.asyncio import list_users_by_account_info -from supertokens_python.types import AccountInfo +from supertokens_python.recipe.session.asyncio import create_new_session +from supertokens_python.recipe.session.framework.django.asyncio import verify_session +from supertokens_python.recipe.userroles import UserRoleClaim +from supertokens_python.types.base import AccountInfoInput + @verify_session( # We add the UserRoleClaim's includes validator - 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 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 list_users_by_account_info("public", AccountInfo(email=email)) + user = await list_users_by_account_info("public", AccountInfoInput(email=email)) if len(user) == 0: # return a 400 error to the client return - await create_new_session(request, "public", user[0].login_methods[0].recipe_user_id, {"isImpersonation": True}) + await create_new_session( + request, + "public", + user[0].login_methods[0].recipe_user_id, + {"isImpersonation": True}, + ) return JsonResponse({"message": "User logged in!"}) + ``` diff --git a/docs/post-authentication/user-management/account-deduplication.mdx b/docs/post-authentication/user-management/account-deduplication.mdx index 433ef6e6f..7edd9676c 100644 --- a/docs/post-authentication/user-management/account-deduplication.mdx +++ b/docs/post-authentication/user-management/account-deduplication.mdx @@ -248,25 +248,34 @@ func main() { ```python -from supertokens_python import init, InputAppInfo -from supertokens_python.types import GeneralErrorResponse +from typing import Any, Dict, Optional, Union + +from supertokens_python import InputAppInfo, init +from supertokens_python.asyncio import list_users_by_account_info from supertokens_python.recipe import passwordless, thirdparty from supertokens_python.recipe.passwordless.interfaces import ( APIInterface as PasswordlessAPIInterface, +) +from supertokens_python.recipe.passwordless.interfaces import ( APIOptions as PasswordlessAPIOptions, ) +from supertokens_python.recipe.session.interfaces import SessionContainer from supertokens_python.recipe.thirdparty.interfaces import ( - RecipeInterface, APIInterface as ThirdPartyAPIInterface, +) +from supertokens_python.recipe.thirdparty.interfaces import ( 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.interfaces import ( + RecipeInterface, +) 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 +from supertokens_python.recipe.thirdparty.types import ( + RawUserInfoFromProvider, + ThirdPartyInfo, +) +from supertokens_python.types import GeneralErrorResponse +from supertokens_python.types.base import AccountInfoInput def override_thirdparty_functions(original_implementation: RecipeInterface): @@ -285,7 +294,7 @@ def override_thirdparty_functions(original_implementation: RecipeInterface): user_context: Dict[str, Any], ): existing_users = await list_users_by_account_info( - tenant_id, AccountInfo(email=email) + tenant_id, AccountInfoInput(email=email) ) if len(existing_users) == 0: # this means this email is new so we allow sign up @@ -384,7 +393,7 @@ def override_passwordless_apis(original_implementation: PasswordlessAPIInterface ): if email is not None: existing_users = await list_users_by_account_info( - tenant_id, AccountInfo(email=email) + tenant_id, AccountInfoInput(email=email) ) if len(existing_users) == 0: # this means this email is new so we allow sign up diff --git a/docs/post-authentication/user-management/allow-users-to-update-their-data.mdx b/docs/post-authentication/user-management/allow-users-to-update-their-data.mdx index 12bb2e30c..de31b2924 100644 --- a/docs/post-authentication/user-management/allow-users-to-update-their-data.mdx +++ b/docs/post-authentication/user-management/allow-users-to-update-their-data.mdx @@ -94,9 +94,10 @@ func changeEmailAPI(w http.ResponseWriter, r *http.Request) { ```python # the following example uses flask -from supertokens_python.recipe.session.framework.flask import verify_session from flask import Flask +from supertokens_python.recipe.session.framework.flask import verify_session + app = Flask(__name__) # highlight-start @@ -253,16 +254,18 @@ 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 re import fullmatch + +from flask import Flask, g, request + from supertokens_python.recipe.passwordless.interfaces import ( - UpdateUserOkResult, - UpdateUserEmailAlreadyExistsError, EmailChangeNotAllowedError, + UpdateUserEmailAlreadyExistsError, + UpdateUserOkResult, ) -from flask import g, request, Flask -from re import fullmatch +from supertokens_python.recipe.passwordless.syncio import update_user +from supertokens_python.recipe.session import SessionContainer +from supertokens_python.recipe.session.framework.flask import verify_session app = Flask(__name__) @@ -381,9 +384,10 @@ func changeEmailAPI(w http.ResponseWriter, r *http.Request) { ```python # the following example uses flask -from supertokens_python.recipe.session.framework.flask import verify_session from flask import Flask +from supertokens_python.recipe.session.framework.flask import verify_session + app = Flask(__name__) # highlight-start @@ -603,24 +607,24 @@ 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.interfaces import ( - UpdateUserOkResult, - UpdateUserEmailAlreadyExistsError, -) +from re import fullmatch + +from flask import Flask, g, request +from supertokens_python.recipe.accountlinking.syncio import is_email_change_allowed from supertokens_python.recipe.emailverification.syncio import ( is_email_verified, 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 +from supertokens_python.recipe.passwordless.interfaces import ( + UpdateUserEmailAlreadyExistsError, + UpdateUserOkResult, +) +from supertokens_python.recipe.passwordless.syncio import update_user +from supertokens_python.recipe.session import SessionContainer +from supertokens_python.recipe.session.framework.flask import verify_session from supertokens_python.syncio import get_user, list_users_by_account_info -from supertokens_python.types import AccountInfo +from supertokens_python.types.base import AccountInfoInput app = Flask(__name__) @@ -662,7 +666,7 @@ def change_email(): 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"]) + tenant_id, AccountInfoInput(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 @@ -847,28 +851,22 @@ func main() { ```python -from supertokens_python import init, InputAppInfo, SupertokensConfig -from supertokens_python.recipe import passwordless, emailverification +from typing import Any, Dict, Optional + +from supertokens_python import ( + InputAppInfo, + SupertokensConfig, + init, +) +from supertokens_python.recipe import emailverification, passwordless from supertokens_python.recipe.emailverification.interfaces import ( APIInterface, APIOptions, -) - -from supertokens_python.recipe.passwordless.asyncio import update_user - -from supertokens_python.recipe.passwordless import ContactEmailOrPhoneConfig - -from supertokens_python.recipe.emailverification.interfaces import ( EmailVerifyPostOkResult, ) - +from supertokens_python.recipe.passwordless import ContactEmailOrPhoneConfig +from supertokens_python.recipe.passwordless.asyncio import update_user from supertokens_python.recipe.session.interfaces import SessionContainer -from supertokens_python import ( - InputAppInfo, - SupertokensConfig, -) - -from typing import Optional, Dict, Any def override_email_verification_apis(original_implementation: APIInterface): @@ -988,9 +986,10 @@ func changePasswordAPI(w http.ResponseWriter, r *http.Request) { ```python # the following example uses flask -from supertokens_python.recipe.session.framework.flask import verify_session from flask import Flask +from supertokens_python.recipe.session.framework.flask import verify_session + app = Flask(__name__) # highlight-start @@ -1160,18 +1159,19 @@ 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 flask import g, request + +from supertokens_python.recipe.emailpassword.interfaces import ( + PasswordPolicyViolationError, + WrongCredentialsError, +) from supertokens_python.recipe.emailpassword.syncio import ( - verify_credentials, update_email_or_password, + verify_credentials, ) +from supertokens_python.recipe.session import SessionContainer +from supertokens_python.recipe.session.framework.flask import verify_session 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 @@ -1348,18 +1348,21 @@ func changePasswordAPI(w http.ResponseWriter, r *http.Request) { ```python -from supertokens_python.recipe.session.framework.flask import verify_session -from supertokens_python.recipe.session.syncio import revoke_all_sessions_for_user +from typing import cast + from flask import Flask + from supertokens_python.recipe.session import SessionContainer +from supertokens_python.recipe.session.framework.flask import verify_session +from supertokens_python.recipe.session.syncio import revoke_all_sessions_for_user app = Flask(__name__) @app.route('/change-password', methods=['POST']) # type: ignore @verify_session() def change_password(): - - session: SessionContainer = g.supertokens # type: ignore - + + session: SessionContainer = cast(SessionContainer, g.supertokens) # type: ignore + # get the userId from the session object user_id = session.get_user_id() @@ -1368,7 +1371,7 @@ def change_password(): # highlight-start # revoke all sessions for the user revoke_all_sessions_for_user(user_id) - + # revoke the user's current session, we do this to remove the auth cookies, logging out the user on the frontend session.sync_revoke_session() # highlight-end diff --git a/docs/post-authentication/user-management/common-actions.mdx b/docs/post-authentication/user-management/common-actions.mdx index faf53dbbb..7270b1cae 100644 --- a/docs/post-authentication/user-management/common-actions.mdx +++ b/docs/post-authentication/user-management/common-actions.mdx @@ -86,14 +86,15 @@ func main() { ```python from supertokens_python.asyncio import list_users_by_account_info -from supertokens_python.types import AccountInfo +from supertokens_python.types.base import AccountInfoInput + async def some_func(): # Note that users_info has type List[User] - user_info = await list_users_by_account_info("public", AccountInfo(email="test@example.com")) + user_info = await list_users_by_account_info("public", AccountInfoInput(email="test@example.com")) print(user_info) - # + # # user_info contains the following info: # - emails # - id @@ -103,7 +104,7 @@ async def some_func(): # - third party login info # - all the login methods associated with this user. # - information about if the user's email is verified or not. - # + # ```
@@ -112,14 +113,15 @@ async def some_func(): ```python from supertokens_python.syncio import list_users_by_account_info -from supertokens_python.types import AccountInfo +from supertokens_python.types.base import AccountInfoInput + def some_func(): # Note that users_info has type List[User] - user_info = list_users_by_account_info("public", AccountInfo(email="test@example.com")) + user_info = list_users_by_account_info("public", AccountInfoInput(email="test@example.com")) print(user_info) - # + # # user_info contains the following info: # - emails # - id @@ -129,7 +131,7 @@ def some_func(): # - third party login info # - all the login methods associated with this user. # - information about if the user's email is verified or not. - # + # ``` @@ -206,11 +208,12 @@ func main() { ```python from supertokens_python.asyncio import list_users_by_account_info -from supertokens_python.types import AccountInfo +from supertokens_python.types.base import AccountInfoInput + async def some_func(): _ = await list_users_by_account_info( - "public", AccountInfo(phone_number="+1234567890") + "public", AccountInfoInput(phone_number="+1234567890") ) ``` @@ -219,11 +222,12 @@ async def some_func(): ```python from supertokens_python.syncio import list_users_by_account_info -from supertokens_python.types import AccountInfo +from supertokens_python.types.base import AccountInfoInput + def some_func(): _ = list_users_by_account_info( - "public", AccountInfo(phone_number="+1234567890") + "public", AccountInfoInput(phone_number="+1234567890") ) ``` @@ -756,10 +760,11 @@ func getUserInfoAPI(w http.ResponseWriter, r *http.Request) { ```python -from supertokens_python.recipe.session.framework.fastapi import verify_session +from fastapi import Depends, FastAPI + from supertokens_python.asyncio import get_user from supertokens_python.recipe.session import SessionContainer -from fastapi import FastAPI, Depends +from supertokens_python.recipe.session.framework.fastapi import verify_session app = FastAPI() @@ -776,10 +781,11 @@ 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.syncio import get_user from flask import Flask, g + from supertokens_python.recipe.session import SessionContainer +from supertokens_python.recipe.session.framework.flask import verify_session +from supertokens_python.syncio import get_user app = Flask(__name__) @@ -799,14 +805,18 @@ def get_user_info_api(): ```python -from supertokens_python.recipe.session.framework.django.asyncio import verify_session -from supertokens_python.asyncio import get_user +from typing import cast + from django.http import HttpRequest + +from supertokens_python.asyncio import get_user from supertokens_python.recipe.session import SessionContainer +from supertokens_python.recipe.session.framework.django.asyncio import verify_session + @verify_session() async def get_user_info_api(request: HttpRequest): - session: SessionContainer = request.supertokens # type: ignore + session: SessionContainer = cast(SessionContainer, request.supertokens) # type: ignore user_id = session.get_user_id() @@ -869,6 +879,7 @@ func main() { ```python from supertokens_python.asyncio import delete_user + async def do_delete(): user_id = "..." # get the user ID somehow... await delete_user(user_id) # this will succeed even if the userId didn't exist. @@ -880,6 +891,7 @@ async def do_delete(): ```python from supertokens_python.syncio import delete_user + user_id = "..." # get the user ID somehow... delete_user(user_id) # this will succeed even if the userId didn't exist. ``` @@ -982,6 +994,7 @@ func main() { ```python from supertokens_python.asyncio import get_users_newest_first + async def some_func(): # get the latest 100 users users_response = await get_users_newest_first("public") @@ -992,7 +1005,7 @@ async def some_func(): # get for specific recipes users_response = await get_users_newest_first( "public", - 200, + 200, users_response.next_pagination_token, # only get for those users who signed up with ^{recipeNameCapitalLetters} ["^{rid}"] @@ -1003,9 +1016,9 @@ async def some_func(): ```python - from supertokens_python.syncio import get_users_newest_first + # get the latest 100 users users_response = get_users_newest_first("public") @@ -1015,7 +1028,7 @@ users_response = get_users_newest_first("public", 200, users_response.next_pagin # get for specific recipes users_response = get_users_newest_first( "public", - 200, + 200, users_response.next_pagination_token, # only get for those users who signed up with ^{recipeNameCapitalLetters} ["^{rid}"] @@ -1118,9 +1131,9 @@ func main() { ```python - from supertokens_python.asyncio import get_users_oldest_first + async def some_func(): # get the latest 100 users users_response = await get_users_oldest_first("public") @@ -1131,7 +1144,7 @@ async def some_func(): # get for specific recipes users_response = await get_users_oldest_first( "public", - 200, + 200, users_response.next_pagination_token, # only get for those users who signed up with ^{recipeNameCapitalLetters} ["^{rid}"] @@ -1142,9 +1155,9 @@ async def some_func(): ```python - from supertokens_python.syncio import get_users_oldest_first + # get the latest 100 users users_response = get_users_oldest_first("public") @@ -1154,7 +1167,7 @@ users_response = get_users_oldest_first("public", 200, users_response.next_pagin # get for specific recipes users_response = get_users_oldest_first( "public", - 200, + 200, users_response.next_pagination_token, # only get for those users who signed up with ^{recipeNameCapitalLetters} ["^{rid}"] @@ -1219,6 +1232,7 @@ func main() { ```python from supertokens_python.asyncio import get_user_count + async def some_func(): user_count = await get_user_count() @@ -1229,9 +1243,9 @@ async def some_func(): ```python - from supertokens_python.syncio import get_user_count + user_count = get_user_count() ``` diff --git a/docs/quickstart/backend-setup.mdx b/docs/quickstart/backend-setup.mdx index d79fb019d..1070e912c 100644 --- a/docs/quickstart/backend-setup.mdx +++ b/docs/quickstart/backend-setup.mdx @@ -505,9 +505,10 @@ Use the `Middleware` (**BEFORE all your routes**) and the `get_all_cors_headers( ```python -from supertokens_python import get_all_cors_headers from fastapi import FastAPI from starlette.middleware.cors import CORSMiddleware + +from supertokens_python import get_all_cors_headers from supertokens_python.framework.fastapi import get_middleware app = FastAPI() @@ -583,10 +584,12 @@ Use the `Middleware` and the `get_all_cors_headers()` functions as shown below i ```python -from supertokens_python import get_all_cors_headers from typing import List + from corsheaders.defaults import default_headers +from supertokens_python import get_all_cors_headers + CORS_ORIGIN_WHITELIST = [ "^{appInfo.websiteDomain}" ] @@ -607,7 +610,7 @@ INSTALLED_APPS = [ 'supertokens_python' ] -MIDDLEWARE = [ +MIDDLEWARE = [ # type: ignore 'corsheaders.middleware.CorsMiddleware', ..., # highlight-next-line @@ -1009,10 +1012,12 @@ For your APIs that require a user to be logged in, use the `verify_session` midd ```python -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.session import SessionContainer +from supertokens_python.recipe.session.framework.fastapi import verify_session + + # highlight-start @app.post('/like_comment') # type: ignore async def like_comment(session: SessionContainer = Depends(verify_session())): @@ -1026,10 +1031,12 @@ async def like_comment(session: SessionContainer = Depends(verify_session())): ```python -from supertokens_python.recipe.session.framework.flask import verify_session -from supertokens_python.recipe.session import SessionContainer from flask import g +from supertokens_python.recipe.session import SessionContainer +from supertokens_python.recipe.session.framework.flask import verify_session + + # highlight-start @app.route('/update-jwt', methods=['POST']) # type: ignore @verify_session() @@ -1046,14 +1053,18 @@ def like_comment(): ```python -from supertokens_python.recipe.session.framework.django.asyncio import verify_session +from typing import cast + from django.http import HttpRequest + from supertokens_python.recipe.session import SessionContainer +from supertokens_python.recipe.session.framework.django.asyncio import verify_session + # highlight-start @verify_session() async def like_comment(request: HttpRequest): - session: SessionContainer = request.supertokens # type: ignore + session: SessionContainer = cast(SessionContainer, request.supertokens) # type: ignore user_id = session.get_user_id() # highlight-end diff --git a/docs/references/backend-sdks/function-overrides.mdx b/docs/references/backend-sdks/function-overrides.mdx index 905fe444c..137f954b5 100644 --- a/docs/references/backend-sdks/function-overrides.mdx +++ b/docs/references/backend-sdks/function-overrides.mdx @@ -179,15 +179,19 @@ See all the [functions that can be overridden here](https://supertokens.com/docs ::: ```python -from supertokens_python import init, InputAppInfo -from supertokens_python.recipe import thirdparty -from supertokens_python.recipe import session -from supertokens_python.recipe.thirdparty.interfaces import RecipeInterface as ThirdPartyRecipeInterface -from supertokens_python.recipe.session.interfaces import RecipeInterface as SessionRecipeInterface +from typing import Any, Dict, Optional, Union + +from supertokens_python import InputAppInfo, init +from supertokens_python.recipe import session, thirdparty +from supertokens_python.recipe.session.interfaces import ( + RecipeInterface as SessionRecipeInterface, +) +from supertokens_python.recipe.session.interfaces import SessionContainer +from supertokens_python.recipe.thirdparty.interfaces import ( + RecipeInterface as ThirdPartyRecipeInterface, +) from supertokens_python.recipe.thirdparty.types import RawUserInfoFromProvider from supertokens_python.types import RecipeUserId -from typing import Dict, Any, Optional, Union -from supertokens_python.recipe.session.interfaces import SessionContainer # highlight-start @@ -374,10 +378,11 @@ func main() { ```python -from supertokens_python.recipe.session.asyncio import get_all_session_handles_for_user +from typing import Any, Dict, Optional + from supertokens_python.recipe import session +from supertokens_python.recipe.session.asyncio import get_all_session_handles_for_user from supertokens_python.recipe.session.interfaces import RecipeInterface -from typing import Any, Dict, Optional from supertokens_python.types import RecipeUserId @@ -715,7 +720,7 @@ app = FastAPI() async def exception_handler(_, exc: Exception): if str(exc) == "Session already exists on another device": pass # TODO: send custom response - + # TODO: Send generic 500 response ``` @@ -731,7 +736,7 @@ app = Flask(__name__) def all_exception_handler(exc: Exception): if str(exc) == "Session already exists on another device": pass # TODO: send custom response - + # TODO: Send generic 500 response ``` @@ -741,21 +746,24 @@ def all_exception_handler(exc: Exception): ```python # Add this middlware in settings.py +from typing import cast + from django.http import HttpRequest, HttpResponse + class ErrorHandlerMiddleware: def __init__(self, get_response): # type: ignore self.get_response = get_response def __call__(self, request: HttpRequest): - response: HttpResponse = self.get_response(request) + response: HttpResponse = cast(HttpResponse, self.get_response(request)) return response def process_exception(self, request: HttpRequest, exception: Exception) -> HttpResponse: if exception and str(exception) == "Session already exists on another device": pass # TODO: send custom response - + return HttpResponse("Error processing the request.", status=500) ``` @@ -867,10 +875,12 @@ func main() { We use the `get_request_from_user_context` function provided by the SDK to get the request object from the user context. ```python +from typing import Any, Dict + from supertokens_python import get_request_from_user_context from supertokens_python.recipe import session -from supertokens_python.recipe.session.interfaces import RecipeInterface -from typing import Any, Dict +from supertokens_python.recipe.session.interfaces import RecipeInterface + def override_session_functions(original_implementation: RecipeInterface): original_revoke_session = original_implementation.revoke_session @@ -885,12 +895,12 @@ def override_session_functions(original_implementation: RecipeInterface): else: # # This is possible if the function is triggered from the user management dashboard - # + # # In this case set a reasonable default value to use # customHeaderValue="default" # highlight-end - + print(customHeaderValue) # Perform custom logic based on the value of customHeadervalue From 2ce401c92828b2bb4c6b02881b8562f77e48541e Mon Sep 17 00:00:00 2001 From: Namit Nathwani Date: Fri, 27 Jun 2025 19:54:09 +0530 Subject: [PATCH 10/10] fix: pyright errors --- docs/authentication/m2m/legacy-flow.mdx | 2 +- docs/authentication/passwordless/allow-list-flow.mdx | 4 ++-- .../session-management/session-invalidation.mdx | 8 ++++++-- 3 files changed, 9 insertions(+), 5 deletions(-) diff --git a/docs/authentication/m2m/legacy-flow.mdx b/docs/authentication/m2m/legacy-flow.mdx index 59ccb7b21..1e149f2aa 100644 --- a/docs/authentication/m2m/legacy-flow.mdx +++ b/docs/authentication/m2m/legacy-flow.mdx @@ -596,7 +596,7 @@ app.post("/like-comment", async (req, res, next) => { ```python -from typing import Optional, Union, cast +from typing import Optional, cast from django.http import HttpRequest diff --git a/docs/authentication/passwordless/allow-list-flow.mdx b/docs/authentication/passwordless/allow-list-flow.mdx index 7cdd137d2..18692342b 100644 --- a/docs/authentication/passwordless/allow-list-flow.mdx +++ b/docs/authentication/passwordless/allow-list-flow.mdx @@ -353,7 +353,7 @@ from supertokens_python.recipe.passwordless.interfaces import ( APIOptions, ) from supertokens_python.recipe.session.interfaces import SessionContainer -from supertokens_python.types import AccountInfo, GeneralErrorResponse +from supertokens_python.types import GeneralErrorResponse from supertokens_python.types.base import AccountInfoInput @@ -405,7 +405,7 @@ def override_passwordless_apis(original_implementation: APIInterface): else: assert phone_number is not None existing_user = await list_users_by_account_info( - tenant_id, AccountInfo(phone_number=phone_number) + tenant_id, AccountInfoInput(phone_number=phone_number) ) user_with_passwordless = next( ( diff --git a/docs/post-authentication/session-management/session-invalidation.mdx b/docs/post-authentication/session-management/session-invalidation.mdx index c5b3ea34d..f82d4cad6 100644 --- a/docs/post-authentication/session-management/session-invalidation.mdx +++ b/docs/post-authentication/session-management/session-invalidation.mdx @@ -508,14 +508,18 @@ def some_api(): ```python -from supertokens_python.recipe.session.framework.django.asyncio import verify_session +from typing import cast + from django.http import HttpRequest + from supertokens_python.recipe.session import SessionContainer +from supertokens_python.recipe.session.framework.django.asyncio import verify_session + # highlight-start @verify_session() async def some_api(request: HttpRequest): - session: SessionContainer = request.supertokens # type: ignore This will delete the session from the db and from the frontend (cookies) + session: SessionContainer = cast(SessionContainer, request.supertokens) # type: ignore This will delete the session from the db and from the frontend (cookies) # highlight-end await session.revoke_session() ```