diff --git a/.github/workflows/auth-react-test-1-django.yml b/.github/workflows/auth-react-test-1-django.yml index ad74b1bcc..3f4a1fb11 100644 --- a/.github/workflows/auth-react-test-1-django.yml +++ b/.github/workflows/auth-react-test-1-django.yml @@ -23,6 +23,8 @@ jobs: fdiVersions: ${{ steps.versions.outputs.fdiVersions }} cdiVersions: ${{ steps.versions.outputs.cdiVersions }} pyVersions: '["3.8", "3.13"]' + nodeFdiVersionMap: ${{ steps.node-versions.outputs.fdiVersions }} + authReactFdiVersionMap: ${{ steps.auth-react-versions.outputs.fdiVersions }} steps: - uses: actions/checkout@v4 @@ -32,6 +34,20 @@ jobs: has-fdi: true has-cdi: true + - uses: supertokens/actions/get-versions-from-repo@main + id: auth-react-versions + with: + repo: supertokens-auth-react + github-token: ${{ secrets.GITHUB_TOKEN }} + fdi-versions: ${{ steps.versions.outputs.fdiVersions }} + + - uses: supertokens/actions/get-versions-from-repo@main + id: node-versions + with: + repo: supertokens-node + github-token: ${{ secrets.GITHUB_TOKEN }} + fdi-versions: ${{ steps.versions.outputs.fdiVersions }} + setup-auth-react: runs-on: ubuntu-latest needs: define-versions @@ -51,19 +67,20 @@ jobs: matrix: ${{ steps.setup-matrix.outputs.matrix }} steps: - - uses: supertokens/get-versions-action@main + - name: Get node and auth-react versions for FDI id: versions - with: - driver-name: python - fdi-version: ${{ matrix.fdi-version }} - env: - SUPERTOKENS_API_KEY: ${{ secrets.SUPERTOKENS_API_KEY }} + run: | + nodeVersion=$( echo '${{ needs.define-versions.outputs.nodeFdiVersionMap }}' | jq -r '.["${{ matrix.fdi-version }}"]' ) + authReactVersion=$( echo '${{ needs.define-versions.outputs.authReactFdiVersionMap }}' | jq -r '.["${{ matrix.fdi-version }}"]' ) + + echo "nodeVersion=${nodeVersion}" >> $GITHUB_OUTPUT + echo "authReactVersion=${authReactVersion}" >> $GITHUB_OUTPUT - uses: supertokens/auth-react-testing-action/setup@main id: envs with: - auth-react-version: ${{ steps.versions.outputs.authReactVersionXy }} - node-sdk-version: ${{ steps.versions.outputs.nodeTag }} + auth-react-version: ${{ steps.versions.outputs.authReactVersion }} + node-sdk-version: ${{ steps.versions.outputs.nodeVersion }} fdi-version: ${{ matrix.fdi-version }} - id: setup-matrix diff --git a/.github/workflows/auth-react-test-1-fastapi.yml b/.github/workflows/auth-react-test-1-fastapi.yml index 5f0e08cc9..1afba68a3 100644 --- a/.github/workflows/auth-react-test-1-fastapi.yml +++ b/.github/workflows/auth-react-test-1-fastapi.yml @@ -23,6 +23,8 @@ jobs: fdiVersions: ${{ steps.versions.outputs.fdiVersions }} cdiVersions: ${{ steps.versions.outputs.cdiVersions }} pyVersions: '["3.8", "3.13"]' + nodeFdiVersionMap: ${{ steps.node-versions.outputs.fdiVersions }} + authReactFdiVersionMap: ${{ steps.auth-react-versions.outputs.fdiVersions }} steps: - uses: actions/checkout@v4 @@ -32,6 +34,20 @@ jobs: has-fdi: true has-cdi: true + - uses: supertokens/actions/get-versions-from-repo@main + id: auth-react-versions + with: + repo: supertokens-auth-react + github-token: ${{ secrets.GITHUB_TOKEN }} + fdi-versions: ${{ steps.versions.outputs.fdiVersions }} + + - uses: supertokens/actions/get-versions-from-repo@main + id: node-versions + with: + repo: supertokens-node + github-token: ${{ secrets.GITHUB_TOKEN }} + fdi-versions: ${{ steps.versions.outputs.fdiVersions }} + setup-auth-react: runs-on: ubuntu-latest needs: define-versions @@ -51,19 +67,20 @@ jobs: matrix: ${{ steps.setup-matrix.outputs.matrix }} steps: - - uses: supertokens/get-versions-action@main + - name: Get node and auth-react versions for FDI id: versions - with: - driver-name: python - fdi-version: ${{ matrix.fdi-version }} - env: - SUPERTOKENS_API_KEY: ${{ secrets.SUPERTOKENS_API_KEY }} + run: | + nodeVersion=$( echo '${{ needs.define-versions.outputs.nodeFdiVersionMap }}' | jq -r '.["${{ matrix.fdi-version }}"]' ) + authReactVersion=$( echo '${{ needs.define-versions.outputs.authReactFdiVersionMap }}' | jq -r '.["${{ matrix.fdi-version }}"]' ) + + echo "nodeVersion=${nodeVersion}" >> $GITHUB_OUTPUT + echo "authReactVersion=${authReactVersion}" >> $GITHUB_OUTPUT - uses: supertokens/auth-react-testing-action/setup@main id: envs with: - auth-react-version: ${{ steps.versions.outputs.authReactVersionXy }} - node-sdk-version: ${{ steps.versions.outputs.nodeTag }} + auth-react-version: ${{ steps.versions.outputs.authReactVersion }} + node-sdk-version: ${{ steps.versions.outputs.nodeVersion }} fdi-version: ${{ matrix.fdi-version }} - id: setup-matrix diff --git a/.github/workflows/auth-react-test-1-flask.yml b/.github/workflows/auth-react-test-1-flask.yml index 991381bb7..241945588 100644 --- a/.github/workflows/auth-react-test-1-flask.yml +++ b/.github/workflows/auth-react-test-1-flask.yml @@ -23,6 +23,8 @@ jobs: fdiVersions: ${{ steps.versions.outputs.fdiVersions }} cdiVersions: ${{ steps.versions.outputs.cdiVersions }} pyVersions: '["3.8", "3.13"]' + nodeFdiVersionMap: ${{ steps.node-versions.outputs.fdiVersions }} + authReactFdiVersionMap: ${{ steps.auth-react-versions.outputs.fdiVersions }} steps: - uses: actions/checkout@v4 @@ -32,6 +34,20 @@ jobs: has-fdi: true has-cdi: true + - uses: supertokens/actions/get-versions-from-repo@main + id: auth-react-versions + with: + repo: supertokens-auth-react + github-token: ${{ secrets.GITHUB_TOKEN }} + fdi-versions: ${{ steps.versions.outputs.fdiVersions }} + + - uses: supertokens/actions/get-versions-from-repo@main + id: node-versions + with: + repo: supertokens-node + github-token: ${{ secrets.GITHUB_TOKEN }} + fdi-versions: ${{ steps.versions.outputs.fdiVersions }} + setup-auth-react: runs-on: ubuntu-latest needs: define-versions @@ -51,19 +67,20 @@ jobs: matrix: ${{ steps.setup-matrix.outputs.matrix }} steps: - - uses: supertokens/get-versions-action@main + - name: Get node and auth-react versions for FDI id: versions - with: - driver-name: python - fdi-version: ${{ matrix.fdi-version }} - env: - SUPERTOKENS_API_KEY: ${{ secrets.SUPERTOKENS_API_KEY }} + run: | + nodeVersion=$( echo '${{ needs.define-versions.outputs.nodeFdiVersionMap }}' | jq -r '.["${{ matrix.fdi-version }}"]' ) + authReactVersion=$( echo '${{ needs.define-versions.outputs.authReactFdiVersionMap }}' | jq -r '.["${{ matrix.fdi-version }}"]' ) + + echo "nodeVersion=${nodeVersion}" >> $GITHUB_OUTPUT + echo "authReactVersion=${authReactVersion}" >> $GITHUB_OUTPUT - uses: supertokens/auth-react-testing-action/setup@main id: envs with: - auth-react-version: ${{ steps.versions.outputs.authReactVersionXy }} - node-sdk-version: ${{ steps.versions.outputs.nodeTag }} + auth-react-version: ${{ steps.versions.outputs.authReactVersion }} + node-sdk-version: ${{ steps.versions.outputs.nodeVersion }} fdi-version: ${{ matrix.fdi-version }} - id: setup-matrix diff --git a/.github/workflows/auth-react-test-3.yml b/.github/workflows/auth-react-test-3.yml index 23a5b108d..0482417e5 100644 --- a/.github/workflows/auth-react-test-3.yml +++ b/.github/workflows/auth-react-test-3.yml @@ -83,8 +83,32 @@ jobs: python3 -m pip install pip setuptools --upgrade make dev-install && rm -rf src + - name: Get supported Python CDI versions + id: cdi-versions + uses: supertokens/get-supported-versions-action@main + with: + has-cdi: true + working-directory: supertokens-python + + - uses: supertokens/actions/get-versions-from-repo@main + id: core-versions + with: + repo: supertokens-core + github-token: ${{ secrets.GITHUB_TOKEN }} + cdi-versions: ${{ steps.cdi-versions.outputs.cdiVersions }} + + - name: Get core version from latest Python CDI version + id: core-version + run: | + lastPythonCdiVersion=$(echo '${{ steps.cdi-versions.outputs.cdiVersions }}' | jq -r '.[-1]') | sed -e 's/"/\\"/g' + coreVersion=$(echo '${{ steps.core-versions.outputs.cdiVersions }}' | jq -r ".[$lastPythonCdiVersion]") + + echo "coreVersion=${coreVersion}" >> $GITHUB_OUTPUT + - name: Start core working-directory: supertokens-python + env: + SUPERTOKENS_CORE_VERSION: ${{ steps.core-version.outputs.coreVersion }} run: docker compose up --wait - name: Start Server (django) diff --git a/.github/workflows/backend-sdk-testing.yml b/.github/workflows/backend-sdk-testing.yml index 94798d06c..41cba1724 100644 --- a/.github/workflows/backend-sdk-testing.yml +++ b/.github/workflows/backend-sdk-testing.yml @@ -24,6 +24,7 @@ jobs: cdiVersions: ${{ steps.versions.outputs.cdiVersions }} pyVersions: '["3.8", "3.9", "3.10", "3.11", "3.12", "3.13"]' nodeVersions: '["20"]' + coreCdiVersionMap: ${{ steps.core-versions.outputs.cdiVersions }} steps: - uses: actions/checkout@v4 @@ -33,6 +34,13 @@ jobs: has-fdi: true has-cdi: true + - uses: supertokens/actions/get-versions-from-repo@main + id: core-versions + with: + repo: supertokens-core + github-token: ${{ secrets.GITHUB_TOKEN }} + cdi-versions: ${{steps.versions.outputs.cdiVersions }} + test: runs-on: ubuntu-latest needs: define-versions @@ -56,14 +64,12 @@ jobs: # Checking out to a custom path since the test repo will also be cloned path: supertokens-python - - uses: supertokens/get-versions-action@main - id: versions - with: - driver-name: python - cdi-version: ${{ matrix.cdi-version }} - fdi-version: ${{ matrix.fdi-version }} - env: - SUPERTOKENS_API_KEY: ${{ secrets.SUPERTOKENS_API_KEY }} + - name: Get core version from current CDI version + id: core-version + run: | + coreVersion=$(echo '${{ needs.define-versions.outputs.coreCdiVersionMap }}' | jq -r '.["${{ matrix.cdi-version }}"]') + + echo "coreVersion=${coreVersion}" >> $GITHUB_OUTPUT - uses: actions/setup-node@v4 with: @@ -82,7 +88,7 @@ jobs: working-directory: supertokens-python env: SUPERTOKENS_ENV: testing - SUPERTOKENS_CORE_VERSION: ${{ steps.versions.outputs.coreVersionXy }} + SUPERTOKENS_CORE_VERSION: ${{ steps.core-version.outputs.coreVersion }} run: | source venv/bin/activate docker compose up --build --wait @@ -91,6 +97,6 @@ jobs: - uses: supertokens/backend-sdk-testing-action@main with: version: ${{ matrix.fdi-version }} - check-name-suffix: '[CDI=${{ matrix.cdi-version }}][Core=${{ steps.versions.outputs.coreVersionXy }}][FDI=${{ matrix.fdi-version }}][Py=${{ matrix.py-version }}][Node=${{ matrix.node-version }}]' + check-name-suffix: '[CDI=${{ matrix.cdi-version }}][Core=${{ steps.core-version.outputs.coreVersion }}][FDI=${{ matrix.fdi-version }}][Py=${{ matrix.py-version }}][Node=${{ matrix.node-version }}]' path: backend-sdk-testing app-server-logs: ${{ github.workspace }}/supertokens-python/python.log diff --git a/.github/workflows/unit-test.yml b/.github/workflows/unit-test.yml index a74f9ec51..5163e2c50 100644 --- a/.github/workflows/unit-test.yml +++ b/.github/workflows/unit-test.yml @@ -24,6 +24,7 @@ jobs: fdiVersions: ${{ steps.versions.outputs.fdiVersions }} cdiVersions: ${{ steps.versions.outputs.cdiVersions }} pyVersions: '["3.8", "3.9", "3.10", "3.11", "3.12", "3.13"]' + coreCdiVersionMap: ${{ steps.core-versions.outputs.cdiVersions }} steps: - uses: actions/checkout@v4 @@ -34,6 +35,13 @@ jobs: has-fdi: true has-cdi: true + - uses: supertokens/actions/get-versions-from-repo@main + id: core-versions + with: + repo: supertokens-core + github-token: ${{ secrets.GITHUB_TOKEN }} + cdi-versions: ${{steps.versions.outputs.cdiVersions }} + test: runs-on: ubuntu-latest needs: define-versions @@ -47,13 +55,12 @@ jobs: steps: - uses: actions/checkout@v4 - - uses: supertokens/get-versions-action@main - id: versions - with: - driver-name: python - cdi-version: ${{ matrix.cdi-version }} - env: - SUPERTOKENS_API_KEY: ${{ secrets.SUPERTOKENS_API_KEY }} + - name: Get core version from current CDI version + id: core-version + run: | + coreVersion=$(echo '${{ needs.define-versions.outputs.coreCdiVersionMap }}' | jq -r '.["${{ matrix.cdi-version }}"]') + + echo "coreVersion=${coreVersion}" >> $GITHUB_OUTPUT - uses: actions/setup-python@v5 with: @@ -72,7 +79,7 @@ jobs: source venv/bin/activate make test env: - SUPERTOKENS_CORE_VERSION: ${{ steps.versions.outputs.coreVersionXy }} + SUPERTOKENS_CORE_VERSION: ${{ steps.core-version.outputs.coreVersion }} - uses: pmeier/pytest-results-action@main name: Surface failing tests @@ -80,4 +87,4 @@ jobs: with: path: test-results/junit.xml summary: true - title: "[Core=${{ steps.versions.outputs.coreVersionXy }}][py=${{ matrix.py-version }}] Unit Test Results" + title: "[Core=${{ steps.core-version.outputs.coreVersion }}][py=${{ matrix.py-version }}] Unit Test Results" diff --git a/.github/workflows/website-test.yml b/.github/workflows/website-test.yml index f63530500..5aba206e6 100644 --- a/.github/workflows/website-test.yml +++ b/.github/workflows/website-test.yml @@ -23,6 +23,9 @@ jobs: fdiVersions: ${{ steps.versions.outputs.fdiVersions }} cdiVersions: ${{ steps.versions.outputs.cdiVersions }} pyVersions: '["3.8", "3.13"]' + nodeFdiVersionMap: ${{ steps.node-versions.outputs.fdiVersions }} + websiteFdiVersionMap: ${{ steps.website-versions.outputs.fdiVersions }} + coreCdiVersionMap: ${{ steps.core-versions.outputs.cdiVersions }} steps: - uses: actions/checkout@v4 @@ -32,6 +35,27 @@ jobs: has-fdi: true has-cdi: true + - uses: supertokens/actions/get-versions-from-repo@main + id: website-versions + with: + repo: supertokens-website + github-token: ${{ secrets.GITHUB_TOKEN }} + fdi-versions: ${{ steps.versions.outputs.fdiVersions }} + + - uses: supertokens/actions/get-versions-from-repo@main + id: node-versions + with: + repo: supertokens-node + github-token: ${{ secrets.GITHUB_TOKEN }} + fdi-versions: ${{ steps.versions.outputs.fdiVersions }} + + - uses: supertokens/actions/get-versions-from-repo@main + id: core-versions + with: + repo: supertokens-core + github-token: ${{ secrets.GITHUB_TOKEN }} + cdi-versions: ${{steps.versions.outputs.cdiVersions }} + test: runs-on: ubuntu-latest needs: define-versions @@ -67,16 +91,22 @@ jobs: with: python-version: ${{ matrix.py-version }} - - uses: supertokens/get-versions-action@main + - name: Get versions from current FDI/CDI version id: versions - with: - driver-name: python - fdi-version: ${{ matrix.fdi-version }} - env: - SUPERTOKENS_API_KEY: ${{ secrets.SUPERTOKENS_API_KEY }} + run: | + latestCdiVersion=$(echo '${{ needs.define-versions.outputs.cdiVersions }}' | jq -r '.[-1]') | sed -e 's/"/\\"/g' + coreVersion=$(echo '${{ needs.define-versions.outputs.coreCdiVersionMap }}' | jq -r ".[$latestCdiVersion]") + nodeVersion=$(echo '${{ needs.define-versions.outputs.nodeFdiVersionMap }}' | jq -r '.["${{ matrix.fdi-version }}"]') + websiteVersion=$(echo '${{ needs.define-versions.outputs.websiteFdiVersionMap }}' | jq -r '.["${{ matrix.fdi-version }}"]') + + echo "coreVersion=${coreVersion}" >> $GITHUB_OUTPUT + echo "nodeVersion=${nodeVersion}" >> $GITHUB_OUTPUT + echo "websiteVersion=${websiteVersion}" >> $GITHUB_OUTPUT - name: Start core working-directory: supertokens-python + env: + SUPERTOKENS_CORE_VERSION: ${{ steps.versions.outputs.coreVersion }} run: docker compose up --wait - name: Setup venv @@ -185,8 +215,8 @@ jobs: - uses: supertokens/website-testing-action@main with: - version: ${{ steps.versions.outputs.frontendVersionXy }} - node-sdk-version: ${{ steps.versions.outputs.nodeTag }} + version: ${{ steps.versions.outputs.websiteVersion }} + node-sdk-version: ${{ steps.versions.outputs.nodeVersion }} path: supertokens-website check-name-suffix: '[Py=${{ matrix.py-version }}][FDI=${{ matrix.fdi-version }}][Framework=${{ matrix.framework }}]' app-server-logs: ${{ steps.envs.outputs.APP_SERVER_LOG_DIR }} diff --git a/CHANGELOG.md b/CHANGELOG.md index 9be579f48..ea0b1fcbc 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -62,6 +62,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - UserMetadata `InputOverrideConfig` -> `UserMetadataOverrideConfig` - UserRoles `InputOverrideConfig` -> `UserRolesOverrideConfig` +## [0.30.2] - 2025-08-14 +- Adds Webauthn user editing support to the Dashboard + ## [0.30.1] - 2025-07-21 - Adds missing register credential endpoint to the Webauthn recipe diff --git a/html/supertokens_python/constants.html b/html/supertokens_python/constants.html index 2058bfbe4..2bc9744ac 100644 --- a/html/supertokens_python/constants.html +++ b/html/supertokens_python/constants.html @@ -43,7 +43,7 @@
supertokens_python.constants
supertokens_python.constants
supertokens_python.recipe.dashboard.api.multitena
"link-email": "Passwordless",
"link-phone": "Passwordless",
"totp": "Totp",
+ "webauthn": "WebAuthn",
}
return factor_id_to_recipe_map.get(factor_id, "")
diff --git a/html/supertokens_python/recipe/dashboard/api/userdetails/user_put.html b/html/supertokens_python/recipe/dashboard/api/userdetails/user_put.html
index a8a1444d6..282c817e5 100644
--- a/html/supertokens_python/recipe/dashboard/api/userdetails/user_put.html
+++ b/html/supertokens_python/recipe/dashboard/api/userdetails/user_put.html
@@ -65,6 +65,11 @@ Module supertokens_python.recipe.dashboard.api.userdetai
)
from supertokens_python.recipe.usermetadata import UserMetadataRecipe
from supertokens_python.recipe.usermetadata.asyncio import update_user_metadata
+from supertokens_python.recipe.webauthn.functions import update_user_email
+from supertokens_python.recipe.webauthn.interfaces.recipe import (
+ UnknownUserIdErrorResponse,
+)
+from supertokens_python.recipe.webauthn.recipe import WebauthnRecipe
from supertokens_python.types import RecipeUserId
from .....types.response import APIResponse
@@ -229,6 +234,31 @@ Module supertokens_python.recipe.dashboard.api.userdetai
return OkResponse()
+ if recipe_id == "webauthn":
+ validation_error = (
+ await WebauthnRecipe.get_instance().config.validate_email_address(
+ email=email,
+ tenant_id=tenant_id,
+ user_context=user_context,
+ )
+ )
+
+ if validation_error is not None:
+ return InvalidEmailErrorResponse(validation_error)
+
+ email_update_response = await update_user_email(
+ email=email,
+ recipe_user_id=recipe_user_id.get_as_string(),
+ tenant_id=tenant_id,
+ user_context=user_context,
+ )
+
+ if isinstance(email_update_response, EmailAlreadyExistsError):
+ return EmailAlreadyExistsErrorResponse()
+
+ if isinstance(email_update_response, UnknownUserIdErrorResponse):
+ raise Exception("Should never come here")
+
# If it comes here then the user is a third party user in which case the UI should not have allowed this
raise Exception("Should never come here")
diff --git a/html/supertokens_python/recipe/dashboard/utils.html b/html/supertokens_python/recipe/dashboard/utils.html
index 8ac5868d5..e546a2e23 100644
--- a/html/supertokens_python/recipe/dashboard/utils.html
+++ b/html/supertokens_python/recipe/dashboard/utils.html
@@ -46,6 +46,7 @@ Module supertokens_python.recipe.dashboard.utils
<
from typing_extensions import Literal
from supertokens_python.recipe.accountlinking.recipe import AccountLinkingRecipe
+from supertokens_python.recipe.webauthn.recipe import WebauthnRecipe
if TYPE_CHECKING:
from supertokens_python.framework.request import BaseRequest
@@ -245,7 +246,9 @@ Module supertokens_python.recipe.dashboard.utils
<
async def _get_user_for_recipe_id(
recipe_user_id: RecipeUserId, recipe_id: str, user_context: Dict[str, Any]
) -> GetUserForRecipeIdHelperResult:
- recipe: Optional[Literal["emailpassword", "thirdparty", "passwordless"]] = None
+ recipe: Optional[
+ Literal["emailpassword", "thirdparty", "passwordless", "webauthn"]
+ ] = None
user = await AccountLinkingRecipe.get_instance().recipe_implementation.get_user(
recipe_user_id.get_as_string(), user_context
@@ -285,6 +288,12 @@ Module supertokens_python.recipe.dashboard.utils
<
recipe = "passwordless"
except Exception:
pass
+ elif recipe_id == WebauthnRecipe.recipe_id:
+ try:
+ WebauthnRecipe.get_instance()
+ recipe = "webauthn"
+ except Exception:
+ pass
return GetUserForRecipeIdHelperResult(user=user, recipe=recipe)
diff --git a/html/supertokens_python/recipe/multitenancy/api/implementation.html b/html/supertokens_python/recipe/multitenancy/api/implementation.html
index 736d1a895..afcb7678c 100644
--- a/html/supertokens_python/recipe/multitenancy/api/implementation.html
+++ b/html/supertokens_python/recipe/multitenancy/api/implementation.html
@@ -53,7 +53,7 @@ Module supertokens_python.recipe.multitenancy.api.implem
from supertokens_python.types.response import GeneralErrorResponse
from ..constants import DEFAULT_TENANT_ID
-from ..interfaces import APIInterface, ThirdPartyProvider
+from ..interfaces import APIInterface, LoginMethodWebauthn, ThirdPartyProvider
class APIImplementation(APIInterface):
@@ -143,6 +143,7 @@ Module supertokens_python.recipe.multitenancy.api.implem
enabled="thirdparty" in valid_first_factors,
providers=final_provider_list,
),
+ webauthn=LoginMethodWebauthn(enabled="webauthn" in valid_first_factors),
first_factors=valid_first_factors,
)
@@ -253,6 +254,7 @@ Classes
enabled="thirdparty" in valid_first_factors,
providers=final_provider_list,
),
+ webauthn=LoginMethodWebauthn(enabled="webauthn" in valid_first_factors),
first_factors=valid_first_factors,
)
diff --git a/html/supertokens_python/recipe/multitenancy/interfaces.html b/html/supertokens_python/recipe/multitenancy/interfaces.html
index badfac4ef..862fce8a3 100644
--- a/html/supertokens_python/recipe/multitenancy/interfaces.html
+++ b/html/supertokens_python/recipe/multitenancy/interfaces.html
@@ -358,6 +358,16 @@ Module supertokens_python.recipe.multitenancy.interfaces
}
+class LoginMethodWebauthn:
+ def __init__(self, enabled: bool):
+ self.enabled = enabled
+
+ def to_json(self) -> Dict[str, Any]:
+ return {
+ "enabled": self.enabled,
+ }
+
+
class LoginMethodThirdParty:
def __init__(self, enabled: bool, providers: List[ThirdPartyProvider]):
self.enabled = enabled
@@ -376,12 +386,14 @@ Module supertokens_python.recipe.multitenancy.interfaces
email_password: LoginMethodEmailPassword,
passwordless: LoginMethodPasswordless,
third_party: LoginMethodThirdParty,
+ webauthn: LoginMethodWebauthn,
first_factors: List[str],
):
self.status = "OK"
self.email_password = email_password
self.passwordless = passwordless
self.third_party = third_party
+ self.webauthn = webauthn
self.first_factors = first_factors
def to_json(self) -> Dict[str, Any]:
@@ -390,6 +402,7 @@ Module supertokens_python.recipe.multitenancy.interfaces
"emailPassword": self.email_password.to_json(),
"passwordless": self.passwordless.to_json(),
"thirdParty": self.third_party.to_json(),
+ "webauthn": self.webauthn.to_json(),
"firstFactors": self.first_factors,
}
@@ -859,9 +872,38 @@ Methods
+
+class LoginMethodWebauthn
+(enabled: bool)
+
+
+
+
+
+Expand source code
+
+class LoginMethodWebauthn:
+ def __init__(self, enabled: bool):
+ self.enabled = enabled
+
+ def to_json(self) -> Dict[str, Any]:
+ return {
+ "enabled": self.enabled,
+ }
+
+Methods
+
+
+def to_json(self) ‑> Dict[str, Any]
+
+-
+
+
+
+
class LoginMethodsGetOkResult
-(email_password: LoginMethodEmailPassword, passwordless: LoginMethodPasswordless, third_party: LoginMethodThirdParty, first_factors: List[str])
+(email_password: LoginMethodEmailPassword, passwordless: LoginMethodPasswordless, third_party: LoginMethodThirdParty, webauthn: LoginMethodWebauthn, first_factors: List[str])
Helper class that provides a standard way to create an ABC using
@@ -876,12 +918,14 @@
Methods
email_password: LoginMethodEmailPassword,
passwordless: LoginMethodPasswordless,
third_party: LoginMethodThirdParty,
+ webauthn: LoginMethodWebauthn,
first_factors: List[str],
):
self.status = "OK"
self.email_password = email_password
self.passwordless = passwordless
self.third_party = third_party
+ self.webauthn = webauthn
self.first_factors = first_factors
def to_json(self) -> Dict[str, Any]:
@@ -890,6 +934,7 @@ Methods
"emailPassword": self.email_password.to_json(),
"passwordless": self.passwordless.to_json(),
"thirdParty": self.third_party.to_json(),
+ "webauthn": self.webauthn.to_json(),
"firstFactors": self.first_factors,
}
@@ -1378,6 +1423,12 @@ LoginMethodWebauthn
+
+to_json
+
+
+
LoginMethodsGetOkResult
to_json
diff --git a/html/supertokens_python/recipe/webauthn/interfaces/api.html b/html/supertokens_python/recipe/webauthn/interfaces/api.html
index 11bfc4d9b..9942c2265 100644
--- a/html/supertokens_python/recipe/webauthn/interfaces/api.html
+++ b/html/supertokens_python/recipe/webauthn/interfaces/api.html
@@ -780,7 +780,7 @@ Inherited members
Ancestors
-- StatusReasonResponseBaseModel[Literal['RECOVER_ACCOUNT_NOT_ALLOWED'], str]
+- supertokens_python.types.response.StatusReasonResponseBaseModel[Literal['RECOVER_ACCOUNT_NOT_ALLOWED'], str]
- StatusReasonResponseBaseModel
- StatusResponseBaseModel
- CamelCaseBaseModel
@@ -823,7 +823,7 @@ Class variables
Ancestors
-- StatusReasonResponseBaseModel[Literal['RECOVER_ACCOUNT_NOT_ALLOWED'], str]
+- supertokens_python.types.response.StatusReasonResponseBaseModel[Literal['RECOVER_ACCOUNT_NOT_ALLOWED'], str]
- StatusReasonResponseBaseModel
- StatusResponseBaseModel
- CamelCaseBaseModel
@@ -845,10 +845,10 @@ Class variables
Inherited members
-StatusReasonResponseBaseModel[Literal['RECOVER_ACCOUNT_NOT_ALLOWED'], str]
:
+StatusReasonResponseBaseModel
:
@@ -967,7 +967,7 @@ Class variables
Ancestors
-- StatusReasonResponseBaseModel[Literal['REGISTER_CREDENTIAL_NOT_ALLOWED'], str]
+- supertokens_python.types.response.StatusReasonResponseBaseModel[Literal['REGISTER_CREDENTIAL_NOT_ALLOWED'], str]
- StatusReasonResponseBaseModel
- StatusResponseBaseModel
- CamelCaseBaseModel
@@ -989,10 +989,10 @@ Class variables
Inherited members
-StatusReasonResponseBaseModel[Literal['REGISTER_CREDENTIAL_NOT_ALLOWED'], str]
:
+StatusReasonResponseBaseModel
:
@@ -1212,7 +1212,7 @@ Inherited members
Ancestors
-- StatusReasonResponseBaseModel[Literal['SIGN_IN_NOT_ALLOWED'], str]
+- supertokens_python.types.response.StatusReasonResponseBaseModel[Literal['SIGN_IN_NOT_ALLOWED'], str]
- StatusReasonResponseBaseModel
- StatusResponseBaseModel
- CamelCaseBaseModel
@@ -1234,10 +1234,10 @@ Class variables
Inherited members
-StatusReasonResponseBaseModel[Literal['SIGN_IN_NOT_ALLOWED'], str]
:
+StatusReasonResponseBaseModel
:
@@ -1399,7 +1399,7 @@ Inherited members
Ancestors
-- StatusReasonResponseBaseModel[Literal['SIGN_UP_NOT_ALLOWED'], str]
+- supertokens_python.types.response.StatusReasonResponseBaseModel[Literal['SIGN_UP_NOT_ALLOWED'], str]
- StatusReasonResponseBaseModel
- StatusResponseBaseModel
- CamelCaseBaseModel
@@ -1421,10 +1421,10 @@ Class variables
Inherited members
-StatusReasonResponseBaseModel[Literal['SIGN_UP_NOT_ALLOWED'], str]
:
+StatusReasonResponseBaseModel
:
diff --git a/html/supertokens_python/recipe/webauthn/interfaces/recipe.html b/html/supertokens_python/recipe/webauthn/interfaces/recipe.html
index 0f260c1a9..dd95b886d 100644
--- a/html/supertokens_python/recipe/webauthn/interfaces/recipe.html
+++ b/html/supertokens_python/recipe/webauthn/interfaces/recipe.html
@@ -1063,7 +1063,7 @@ Inherited members
Ancestors
-- StatusResponseBaseModel[Literal['CREDENTIAL_NOT_FOUND_ERROR']]
+- supertokens_python.types.response.StatusResponseBaseModel[Literal['CREDENTIAL_NOT_FOUND_ERROR']]
- StatusResponseBaseModel
- CamelCaseBaseModel
- APIResponse
@@ -1105,7 +1105,7 @@ Class variables
Ancestors
-- StatusResponseBaseModel[Literal['CREDENTIAL_NOT_FOUND_ERROR']]
+- supertokens_python.types.response.StatusResponseBaseModel[Literal['CREDENTIAL_NOT_FOUND_ERROR']]
- StatusResponseBaseModel
- CamelCaseBaseModel
- APIResponse
@@ -1147,7 +1147,7 @@ Class variables
Ancestors
-- StatusResponseBaseModel[Literal['CREDENTIAL_NOT_FOUND_ERROR']]
+- supertokens_python.types.response.StatusResponseBaseModel[Literal['CREDENTIAL_NOT_FOUND_ERROR']]
- StatusResponseBaseModel
- CamelCaseBaseModel
- APIResponse
@@ -1168,10 +1168,10 @@ Class variables
Inherited members
@@ -1387,7 +1387,7 @@ Class variables
Ancestors
-- StatusResponseBaseModel[Literal['EMAIL_ALREADY_EXISTS_ERROR']]
+- supertokens_python.types.response.StatusResponseBaseModel[Literal['EMAIL_ALREADY_EXISTS_ERROR']]
- StatusResponseBaseModel
- CamelCaseBaseModel
- APIResponse
@@ -1408,10 +1408,10 @@ Class variables
Inherited members
@@ -1747,7 +1747,7 @@ Inherited members
Ancestors
-- StatusReasonResponseBaseModel[Literal['INVALID_AUTHENTICATOR_ERROR'], str]
+- supertokens_python.types.response.StatusReasonResponseBaseModel[Literal['INVALID_AUTHENTICATOR_ERROR'], str]
- StatusReasonResponseBaseModel
- StatusResponseBaseModel
- CamelCaseBaseModel
@@ -1769,10 +1769,10 @@ Class variables
Inherited members
-StatusReasonResponseBaseModel[Literal['INVALID_AUTHENTICATOR_ERROR'], str]
:
+StatusReasonResponseBaseModel
:
@@ -1799,7 +1799,7 @@ Inherited members
Ancestors
-- StatusResponseBaseModel[Literal['INVALID_CREDENTIALS_ERROR']]
+- supertokens_python.types.response.StatusResponseBaseModel[Literal['INVALID_CREDENTIALS_ERROR']]
- StatusResponseBaseModel
- CamelCaseBaseModel
- APIResponse
@@ -1820,10 +1820,10 @@ Class variables
Inherited members
@@ -1850,7 +1850,7 @@ Inherited members
Ancestors
-- StatusErrResponseBaseModel[Literal['INVALID_EMAIL_ERROR']]
+- supertokens_python.types.response.StatusErrResponseBaseModel[Literal['INVALID_EMAIL_ERROR']]
- StatusErrResponseBaseModel
- StatusResponseBaseModel
- CamelCaseBaseModel
@@ -1872,10 +1872,10 @@ Class variables
Inherited members
@@ -1902,7 +1902,7 @@ Inherited members
Ancestors
-- StatusResponseBaseModel[Literal['INVALID_OPTIONS_ERROR']]
+- supertokens_python.types.response.StatusResponseBaseModel[Literal['INVALID_OPTIONS_ERROR']]
- StatusResponseBaseModel
- CamelCaseBaseModel
- APIResponse
@@ -1944,7 +1944,7 @@ Class variables
Ancestors
-- StatusResponseBaseModel[Literal['INVALID_OPTIONS_ERROR']]
+- supertokens_python.types.response.StatusResponseBaseModel[Literal['INVALID_OPTIONS_ERROR']]
- StatusResponseBaseModel
- CamelCaseBaseModel
- APIResponse
@@ -1965,10 +1965,10 @@ Class variables
Inherited members
@@ -2056,7 +2056,7 @@ Inherited members
Ancestors
-- StatusResponseBaseModel[Literal['OPTIONS_NOT_FOUND_ERROR']]
+- supertokens_python.types.response.StatusResponseBaseModel[Literal['OPTIONS_NOT_FOUND_ERROR']]
- StatusResponseBaseModel
- CamelCaseBaseModel
- APIResponse
@@ -2098,7 +2098,7 @@ Class variables
Ancestors
-- StatusResponseBaseModel[Literal['OPTIONS_NOT_FOUND_ERROR']]
+- supertokens_python.types.response.StatusResponseBaseModel[Literal['OPTIONS_NOT_FOUND_ERROR']]
- StatusResponseBaseModel
- CamelCaseBaseModel
- APIResponse
@@ -2140,7 +2140,7 @@ Class variables
Ancestors
-- StatusResponseBaseModel[Literal['OPTIONS_NOT_FOUND_ERROR']]
+- supertokens_python.types.response.StatusResponseBaseModel[Literal['OPTIONS_NOT_FOUND_ERROR']]
- StatusResponseBaseModel
- CamelCaseBaseModel
- APIResponse
@@ -2161,10 +2161,10 @@ Class variables
Inherited members
@@ -2540,7 +2540,7 @@ Class variables
Ancestors
-- StatusResponseBaseModel[Literal['RECOVER_ACCOUNT_TOKEN_INVALID_ERROR']]
+- supertokens_python.types.response.StatusResponseBaseModel[Literal['RECOVER_ACCOUNT_TOKEN_INVALID_ERROR']]
- StatusResponseBaseModel
- CamelCaseBaseModel
- APIResponse
@@ -2584,7 +2584,7 @@ Class variables
Ancestors
-- StatusResponseBaseModel[Literal['RECOVER_ACCOUNT_TOKEN_INVALID_ERROR']]
+- supertokens_python.types.response.StatusResponseBaseModel[Literal['RECOVER_ACCOUNT_TOKEN_INVALID_ERROR']]
- StatusResponseBaseModel
- CamelCaseBaseModel
- APIResponse
@@ -2628,7 +2628,7 @@ Class variables
Ancestors
-- StatusResponseBaseModel[Literal['RECOVER_ACCOUNT_TOKEN_INVALID_ERROR']]
+- supertokens_python.types.response.StatusResponseBaseModel[Literal['RECOVER_ACCOUNT_TOKEN_INVALID_ERROR']]
- StatusResponseBaseModel
- CamelCaseBaseModel
- APIResponse
@@ -2649,10 +2649,10 @@ Class variables
Inherited members
@@ -3163,7 +3163,7 @@ Inherited members
Ancestors
-- StatusResponseBaseModel[Literal['UNKNOWN_USER_ID_ERROR']]
+- supertokens_python.types.response.StatusResponseBaseModel[Literal['UNKNOWN_USER_ID_ERROR']]
- StatusResponseBaseModel
- CamelCaseBaseModel
- APIResponse
@@ -3205,7 +3205,7 @@ Class variables
Ancestors
-- StatusResponseBaseModel[Literal['UNKNOWN_USER_ID_ERROR']]
+- supertokens_python.types.response.StatusResponseBaseModel[Literal['UNKNOWN_USER_ID_ERROR']]
- StatusResponseBaseModel
- CamelCaseBaseModel
- APIResponse
@@ -3226,10 +3226,10 @@ Class variables
Inherited members
diff --git a/html/supertokens_python/types/response.html b/html/supertokens_python/types/response.html
index 39009f2df..cf141b1a8 100644
--- a/html/supertokens_python/types/response.html
+++ b/html/supertokens_python/types/response.html
@@ -689,7 +689,7 @@ Ancestors
Subclasses
-- StatusErrResponseBaseModel[Literal['INVALID_EMAIL_ERROR']]
+- supertokens_python.types.response.StatusErrResponseBaseModel[Literal['INVALID_EMAIL_ERROR']]
Class variables
@@ -712,421 +712,8 @@ Inherited members
-
-class StatusErrResponseBaseModel[Literal['INVALID_EMAIL_ERROR']]
-(**data: Any)
-
-
-Helper class that provides a standard way to create an ABC using
-inheritance.
-Create a new model by parsing and validating input data from keyword arguments.
-Raises [ValidationError
][pydantic_core.ValidationError] if the input data cannot be
-validated to form a valid model.
-self
is explicitly positional-only to allow self
as a field name.
-Ancestors
-
-- StatusErrResponseBaseModel
-- StatusResponseBaseModel
-- CamelCaseBaseModel
-- APIResponse
-- abc.ABC
-- pydantic.main.BaseModel
-- typing.Generic
-
-Subclasses
-
-Class variables
-
-var model_config
--
-
-
-
-Inherited members
-
-StatusErrResponseBaseModel
:
-
-
-
-
-
-class StatusReasonResponseBaseModel
-(**data: Any)
-
-
-Helper class that provides a standard way to create an ABC using
-inheritance.
-Create a new model by parsing and validating input data from keyword arguments.
-Raises [ValidationError
][pydantic_core.ValidationError] if the input data cannot be
-validated to form a valid model.
-self
is explicitly positional-only to allow self
as a field name.
-
-
-Expand source code
-
-class StatusReasonResponseBaseModel(
- StatusResponseBaseModel[Status], Generic[Status, Reason]
-):
- reason: Reason
-
-Ancestors
-
-- StatusResponseBaseModel
-- CamelCaseBaseModel
-- APIResponse
-- abc.ABC
-- pydantic.main.BaseModel
-- typing.Generic
-
-Subclasses
-
-- StatusReasonResponseBaseModel[Literal['INVALID_AUTHENTICATOR_ERROR'], str]
-- StatusReasonResponseBaseModel[Literal['LINKING_TO_SESSION_USER_FAILED'], Literal['EMAIL_VERIFICATION_REQUIRED', 'RECIPE_USER_ID_ALREADY_LINKED_WITH_ANOTHER_PRIMARY_USER_ID_ERROR', 'ACCOUNT_INFO_ALREADY_ASSOCIATED_WITH_ANOTHER_PRIMARY_USER_ID_ERROR', 'SESSION_USER_ACCOUNT_INFO_ALREADY_ASSOCIATED_WITH_ANOTHER_PRIMARY_USER_ID_ERROR', 'INPUT_USER_IS_NOT_A_PRIMARY_USER']]
-- StatusReasonResponseBaseModel[Literal['RECOVER_ACCOUNT_NOT_ALLOWED'], str]
-- StatusReasonResponseBaseModel[Literal['REGISTER_CREDENTIAL_NOT_ALLOWED'], str]
-- StatusReasonResponseBaseModel[Literal['SIGN_IN_NOT_ALLOWED'], str]
-- StatusReasonResponseBaseModel[Literal['SIGN_UP_NOT_ALLOWED'], str]
-
-Class variables
-
-var model_config
--
-
-
-var reason : ~Reason
--
-
-
-
-Inherited members
-
-StatusResponseBaseModel
:
-
-
-
-
-
-class StatusReasonResponseBaseModel[Literal['INVALID_AUTHENTICATOR_ERROR'], str]
-(**data: Any)
-
-
-Helper class that provides a standard way to create an ABC using
-inheritance.
-Create a new model by parsing and validating input data from keyword arguments.
-Raises [ValidationError
][pydantic_core.ValidationError] if the input data cannot be
-validated to form a valid model.
-self
is explicitly positional-only to allow self
as a field name.
-Ancestors
-
-- StatusReasonResponseBaseModel
-- StatusResponseBaseModel
-- CamelCaseBaseModel
-- APIResponse
-- abc.ABC
-- pydantic.main.BaseModel
-- typing.Generic
-
-Subclasses
-
-Class variables
-
-var model_config
--
-
-
-
-Inherited members
-
-StatusReasonResponseBaseModel
:
-
-
-
-
-
-class StatusReasonResponseBaseModel[Literal['LINKING_TO_SESSION_USER_FAILED'], Literal['EMAIL_VERIFICATION_REQUIRED', 'RECIPE_USER_ID_ALREADY_LINKED_WITH_ANOTHER_PRIMARY_USER_ID_ERROR', 'ACCOUNT_INFO_ALREADY_ASSOCIATED_WITH_ANOTHER_PRIMARY_USER_ID_ERROR', 'SESSION_USER_ACCOUNT_INFO_ALREADY_ASSOCIATED_WITH_ANOTHER_PRIMARY_USER_ID_ERROR', 'INPUT_USER_IS_NOT_A_PRIMARY_USER']]
-(**data: Any)
-
-
-Helper class that provides a standard way to create an ABC using
-inheritance.
-Create a new model by parsing and validating input data from keyword arguments.
-Raises [ValidationError
][pydantic_core.ValidationError] if the input data cannot be
-validated to form a valid model.
-self
is explicitly positional-only to allow self
as a field name.
-Ancestors
-
-- StatusReasonResponseBaseModel
-- StatusResponseBaseModel
-- CamelCaseBaseModel
-- APIResponse
-- abc.ABC
-- pydantic.main.BaseModel
-- typing.Generic
-
-Subclasses
-
-Class variables
-
-var model_config
--
-
-
-
-Inherited members
-
-StatusReasonResponseBaseModel
:
-
-
-
-
-
-class StatusReasonResponseBaseModel[Literal['RECOVER_ACCOUNT_NOT_ALLOWED'], str]
-(**data: Any)
-
-
-Helper class that provides a standard way to create an ABC using
-inheritance.
-Create a new model by parsing and validating input data from keyword arguments.
-Raises [ValidationError
][pydantic_core.ValidationError] if the input data cannot be
-validated to form a valid model.
-self
is explicitly positional-only to allow self
as a field name.
-Ancestors
-
-- StatusReasonResponseBaseModel
-- StatusResponseBaseModel
-- CamelCaseBaseModel
-- APIResponse
-- abc.ABC
-- pydantic.main.BaseModel
-- typing.Generic
-
-Subclasses
-
-Class variables
-
-var model_config
--
-
-
-
-Inherited members
-
-StatusReasonResponseBaseModel
:
-
-
-
-
-
-class StatusReasonResponseBaseModel[Literal['REGISTER_CREDENTIAL_NOT_ALLOWED'], str]
-(**data: Any)
-
-
-Helper class that provides a standard way to create an ABC using
-inheritance.
-Create a new model by parsing and validating input data from keyword arguments.
-Raises [ValidationError
][pydantic_core.ValidationError] if the input data cannot be
-validated to form a valid model.
-self
is explicitly positional-only to allow self
as a field name.
-Ancestors
-
-- StatusReasonResponseBaseModel
-- StatusResponseBaseModel
-- CamelCaseBaseModel
-- APIResponse
-- abc.ABC
-- pydantic.main.BaseModel
-- typing.Generic
-
-Subclasses
-
-Class variables
-
-var model_config
--
-
-
-
-Inherited members
-
-StatusReasonResponseBaseModel
:
-
-
-
-
-
-class StatusReasonResponseBaseModel[Literal['SIGN_IN_NOT_ALLOWED'], str]
-(**data: Any)
-
-
-Helper class that provides a standard way to create an ABC using
-inheritance.
-Create a new model by parsing and validating input data from keyword arguments.
-Raises [ValidationError
][pydantic_core.ValidationError] if the input data cannot be
-validated to form a valid model.
-self
is explicitly positional-only to allow self
as a field name.
-Ancestors
-
-- StatusReasonResponseBaseModel
-- StatusResponseBaseModel
-- CamelCaseBaseModel
-- APIResponse
-- abc.ABC
-- pydantic.main.BaseModel
-- typing.Generic
-
-Subclasses
-
-Class variables
-
-var model_config
--
-
-
-
-Inherited members
-
-StatusReasonResponseBaseModel
:
-
-
-
-
-
-class StatusReasonResponseBaseModel[Literal['SIGN_UP_NOT_ALLOWED'], str]
-(**data: Any)
-
-
-Helper class that provides a standard way to create an ABC using
-inheritance.
-Create a new model by parsing and validating input data from keyword arguments.
-Raises [ValidationError
][pydantic_core.ValidationError] if the input data cannot be
-validated to form a valid model.
-self
is explicitly positional-only to allow self
as a field name.
-Ancestors
-
-- StatusReasonResponseBaseModel
-- StatusResponseBaseModel
-- CamelCaseBaseModel
-- APIResponse
-- abc.ABC
-- pydantic.main.BaseModel
-- typing.Generic
-
-Subclasses
-
-Class variables
-
-var model_config
--
-
-
-
-Inherited members
-
-StatusReasonResponseBaseModel
:
-
-
-
-
-
-class StatusResponseBaseModel
-(**data: Any)
-
-
-Helper class that provides a standard way to create an ABC using
-inheritance.
-Create a new model by parsing and validating input data from keyword arguments.
-Raises [ValidationError
][pydantic_core.ValidationError] if the input data cannot be
-validated to form a valid model.
-self
is explicitly positional-only to allow self
as a field name.
-
-
-Expand source code
-
-class StatusResponseBaseModel(CamelCaseBaseModel, Generic[Status]):
- status: Status
-
-Ancestors
-
-- CamelCaseBaseModel
-- APIResponse
-- abc.ABC
-- pydantic.main.BaseModel
-- typing.Generic
-
-Subclasses
-
-- StatusErrResponseBaseModel
-- StatusReasonResponseBaseModel
-- StatusResponseBaseModel[Literal['CREDENTIAL_NOT_FOUND_ERROR']]
-- StatusResponseBaseModel[Literal['EMAIL_ALREADY_EXISTS_ERROR']]
-- StatusResponseBaseModel[Literal['INVALID_CREDENTIALS_ERROR']]
-- StatusResponseBaseModel[Literal['INVALID_OPTIONS_ERROR']]
-- StatusResponseBaseModel[Literal['OK']]
-- StatusResponseBaseModel[Literal['OPTIONS_NOT_FOUND_ERROR']]
-- StatusResponseBaseModel[Literal['RECOVER_ACCOUNT_TOKEN_INVALID_ERROR']]
-- StatusResponseBaseModel[Literal['UNKNOWN_USER_ID_ERROR']]
-
-Class variables
-
-var model_config
--
-
-
-var status : ~Status
--
-
-
-
-Inherited members
-
-CamelCaseBaseModel
:
-
-
-
-
-
-class StatusResponseBaseModel[Literal['CREDENTIAL_NOT_FOUND_ERROR']]
+
+class StatusReasonResponseBaseModel
(**data: Any)
@@ -1136,6 +723,15 @@ Inherited members
Raises [ValidationError
][pydantic_core.ValidationError] if the input data cannot be
validated to form a valid model.
self
is explicitly positional-only to allow self
as a field name.
+
+
+Expand source code
+
+class StatusReasonResponseBaseModel(
+ StatusResponseBaseModel[Status], Generic[Status, Reason]
+):
+ reason: Reason
+
Ancestors
- StatusResponseBaseModel
@@ -1147,52 +743,20 @@ Ancestors
Subclasses
-- CredentialNotFoundErrorResponse
+- supertokens_python.types.response.StatusReasonResponseBaseModel[Literal['INVALID_AUTHENTICATOR_ERROR'], str]
+- StatusReasonResponseBaseModel[Literal['LINKING_TO_SESSION_USER_FAILED'], Literal['EMAIL_VERIFICATION_REQUIRED', 'RECIPE_USER_ID_ALREADY_LINKED_WITH_ANOTHER_PRIMARY_USER_ID_ERROR', 'ACCOUNT_INFO_ALREADY_ASSOCIATED_WITH_ANOTHER_PRIMARY_USER_ID_ERROR', 'SESSION_USER_ACCOUNT_INFO_ALREADY_ASSOCIATED_WITH_ANOTHER_PRIMARY_USER_ID_ERROR', 'INPUT_USER_IS_NOT_A_PRIMARY_USER']]
+- supertokens_python.types.response.StatusReasonResponseBaseModel[Literal['RECOVER_ACCOUNT_NOT_ALLOWED'], str]
+- supertokens_python.types.response.StatusReasonResponseBaseModel[Literal['REGISTER_CREDENTIAL_NOT_ALLOWED'], str]
+- supertokens_python.types.response.StatusReasonResponseBaseModel[Literal['SIGN_IN_NOT_ALLOWED'], str]
+- supertokens_python.types.response.StatusReasonResponseBaseModel[Literal['SIGN_UP_NOT_ALLOWED'], str]
Class variables
-var model_config
+var model_config
-
-
-Inherited members
-
-StatusResponseBaseModel
:
-
-
-
-
-
-class StatusResponseBaseModel[Literal['EMAIL_ALREADY_EXISTS_ERROR']]
-(**data: Any)
-
-
-Helper class that provides a standard way to create an ABC using
-inheritance.
-Create a new model by parsing and validating input data from keyword arguments.
-Raises [ValidationError
][pydantic_core.ValidationError] if the input data cannot be
-validated to form a valid model.
-self
is explicitly positional-only to allow self
as a field name.
-Ancestors
-
-- StatusResponseBaseModel
-- CamelCaseBaseModel
-- APIResponse
-- abc.ABC
-- pydantic.main.BaseModel
-- typing.Generic
-
-Subclasses
-
-Class variables
-
-var model_config
+var reason : ~Reason
-
@@ -1207,8 +771,8 @@ Inherited members
-
-class StatusResponseBaseModel[Literal['INVALID_CREDENTIALS_ERROR']]
+
+class StatusReasonResponseBaseModel[Literal['LINKING_TO_SESSION_USER_FAILED'], Literal['EMAIL_VERIFICATION_REQUIRED', 'RECIPE_USER_ID_ALREADY_LINKED_WITH_ANOTHER_PRIMARY_USER_ID_ERROR', 'ACCOUNT_INFO_ALREADY_ASSOCIATED_WITH_ANOTHER_PRIMARY_USER_ID_ERROR', 'SESSION_USER_ACCOUNT_INFO_ALREADY_ASSOCIATED_WITH_ANOTHER_PRIMARY_USER_ID_ERROR', 'INPUT_USER_IS_NOT_A_PRIMARY_USER']]
(**data: Any)
@@ -1220,6 +784,7 @@ Inherited members
self
is explicitly positional-only to allow self
as a field name.
Ancestors
+- StatusReasonResponseBaseModel
- StatusResponseBaseModel
- CamelCaseBaseModel
- APIResponse
@@ -1229,27 +794,27 @@ Ancestors
Subclasses
Class variables
-var model_config
+var model_config
-
Inherited members
-
-class StatusResponseBaseModel[Literal['INVALID_OPTIONS_ERROR']]
+
+class StatusResponseBaseModel
(**data: Any)
@@ -1259,9 +824,15 @@ Inherited members
Raises [ValidationError
][pydantic_core.ValidationError] if the input data cannot be
validated to form a valid model.
self
is explicitly positional-only to allow self
as a field name.
+
+
+Expand source code
+
+class StatusResponseBaseModel(CamelCaseBaseModel, Generic[Status]):
+ status: Status
+
Ancestors
-- StatusResponseBaseModel
- CamelCaseBaseModel
- APIResponse
- abc.ABC
@@ -1270,21 +841,34 @@ Ancestors
Subclasses
-- InvalidOptionsErrorResponse
+- StatusErrResponseBaseModel
+- StatusReasonResponseBaseModel
+- supertokens_python.types.response.StatusResponseBaseModel[Literal['CREDENTIAL_NOT_FOUND_ERROR']]
+- supertokens_python.types.response.StatusResponseBaseModel[Literal['EMAIL_ALREADY_EXISTS_ERROR']]
+- supertokens_python.types.response.StatusResponseBaseModel[Literal['INVALID_CREDENTIALS_ERROR']]
+- supertokens_python.types.response.StatusResponseBaseModel[Literal['INVALID_OPTIONS_ERROR']]
+- StatusResponseBaseModel[Literal['OK']]
+- supertokens_python.types.response.StatusResponseBaseModel[Literal['OPTIONS_NOT_FOUND_ERROR']]
+- supertokens_python.types.response.StatusResponseBaseModel[Literal['RECOVER_ACCOUNT_TOKEN_INVALID_ERROR']]
+- supertokens_python.types.response.StatusResponseBaseModel[Literal['UNKNOWN_USER_ID_ERROR']]
Class variables
-var model_config
+var model_config
+-
+
+
+var status : ~Status
-
Inherited members
@@ -1330,129 +914,6 @@ Inherited members
-
-class StatusResponseBaseModel[Literal['OPTIONS_NOT_FOUND_ERROR']]
-(**data: Any)
-
-
-Helper class that provides a standard way to create an ABC using
-inheritance.
-Create a new model by parsing and validating input data from keyword arguments.
-Raises [ValidationError
][pydantic_core.ValidationError] if the input data cannot be
-validated to form a valid model.
-self
is explicitly positional-only to allow self
as a field name.
-Ancestors
-
-- StatusResponseBaseModel
-- CamelCaseBaseModel
-- APIResponse
-- abc.ABC
-- pydantic.main.BaseModel
-- typing.Generic
-
-Subclasses
-
-Class variables
-
-var model_config
--
-
-
-
-Inherited members
-
-StatusResponseBaseModel
:
-
-
-
-
-
-class StatusResponseBaseModel[Literal['RECOVER_ACCOUNT_TOKEN_INVALID_ERROR']]
-(**data: Any)
-
-
-Helper class that provides a standard way to create an ABC using
-inheritance.
-Create a new model by parsing and validating input data from keyword arguments.
-Raises [ValidationError
][pydantic_core.ValidationError] if the input data cannot be
-validated to form a valid model.
-self
is explicitly positional-only to allow self
as a field name.
-Ancestors
-
-- StatusResponseBaseModel
-- CamelCaseBaseModel
-- APIResponse
-- abc.ABC
-- pydantic.main.BaseModel
-- typing.Generic
-
-Subclasses
-
-Class variables
-
-var model_config
--
-
-
-
-Inherited members
-
-StatusResponseBaseModel
:
-
-
-
-
-
-class StatusResponseBaseModel[Literal['UNKNOWN_USER_ID_ERROR']]
-(**data: Any)
-
-
-Helper class that provides a standard way to create an ABC using
-inheritance.
-Create a new model by parsing and validating input data from keyword arguments.
-Raises [ValidationError
][pydantic_core.ValidationError] if the input data cannot be
-validated to form a valid model.
-self
is explicitly positional-only to allow self
as a field name.
-Ancestors
-
-- StatusResponseBaseModel
-- CamelCaseBaseModel
-- APIResponse
-- abc.ABC
-- pydantic.main.BaseModel
-- typing.Generic
-
-Subclasses
-
-Class variables
-
-var model_config
--
-
-
-
-Inherited members
-
-StatusResponseBaseModel
:
-
-
-
-
@@ -1522,12 +983,6 @@ StatusErrResponseBaseModel[Literal['INVALID_EMAIL_ERROR']]
-
-model_config
-
-
-
StatusReasonResponseBaseModel
model_config
@@ -1535,42 +990,12 @@ StatusReasonResponseBaseModel[Literal['INVALID_AUTHENTICATOR_ERROR'], str]
-
-model_config
-
-
-
StatusReasonResponseBaseModel[Literal['LINKING_TO_SESSION_USER_FAILED'], Literal['EMAIL_VERIFICATION_REQUIRED', 'RECIPE_USER_ID_ALREADY_LINKED_WITH_ANOTHER_PRIMARY_USER_ID_ERROR', 'ACCOUNT_INFO_ALREADY_ASSOCIATED_WITH_ANOTHER_PRIMARY_USER_ID_ERROR', 'SESSION_USER_ACCOUNT_INFO_ALREADY_ASSOCIATED_WITH_ANOTHER_PRIMARY_USER_ID_ERROR', 'INPUT_USER_IS_NOT_A_PRIMARY_USER']]
-StatusReasonResponseBaseModel[Literal['RECOVER_ACCOUNT_NOT_ALLOWED'], str]
-
-model_config
-
-
-
-StatusReasonResponseBaseModel[Literal['REGISTER_CREDENTIAL_NOT_ALLOWED'], str]
-
-model_config
-
-
-
-StatusReasonResponseBaseModel[Literal['SIGN_IN_NOT_ALLOWED'], str]
-
-model_config
-
-
-
-StatusReasonResponseBaseModel[Literal['SIGN_UP_NOT_ALLOWED'], str]
-
-model_config
-
-
-
StatusResponseBaseModel
model_config
@@ -1578,53 +1003,11 @@
-StatusResponseBaseModel[Literal['CREDENTIAL_NOT_FOUND_ERROR']]
-
-model_config
-
-
-
-StatusResponseBaseModel[Literal['EMAIL_ALREADY_EXISTS_ERROR']]
-
-model_config
-
-
-
-StatusResponseBaseModel[Literal['INVALID_CREDENTIALS_ERROR']]
-
-model_config
-
-
-
-StatusResponseBaseModel[Literal['INVALID_OPTIONS_ERROR']]
-
-model_config
-
-
-
StatusResponseBaseModel[Literal['OK']]
-
-StatusResponseBaseModel[Literal['OPTIONS_NOT_FOUND_ERROR']]
-
-model_config
-
-
-
-StatusResponseBaseModel[Literal['RECOVER_ACCOUNT_TOKEN_INVALID_ERROR']]
-
-model_config
-
-
-
-StatusResponseBaseModel[Literal['UNKNOWN_USER_ID_ERROR']]
-
-model_config
-
-
diff --git a/supertokens_python/constants.py b/supertokens_python/constants.py
index 34c8a4118..16eaaa5d9 100644
--- a/supertokens_python/constants.py
+++ b/supertokens_python/constants.py
@@ -28,6 +28,6 @@
FDI_KEY_HEADER = "fdi-version"
API_VERSION = "/apiversion"
API_VERSION_HEADER = "cdi-version"
-DASHBOARD_VERSION = "0.13"
+DASHBOARD_VERSION = "0.15"
ONE_YEAR_IN_MS = 31536000000
RATE_LIMIT_STATUS_CODE = 429
diff --git a/supertokens_python/recipe/dashboard/api/multitenancy/utils.py b/supertokens_python/recipe/dashboard/api/multitenancy/utils.py
index 199f83c45..24aef6819 100644
--- a/supertokens_python/recipe/dashboard/api/multitenancy/utils.py
+++ b/supertokens_python/recipe/dashboard/api/multitenancy/utils.py
@@ -78,6 +78,7 @@ def factor_id_to_recipe(factor_id: str) -> str:
"link-email": "Passwordless",
"link-phone": "Passwordless",
"totp": "Totp",
+ "webauthn": "WebAuthn",
}
return factor_id_to_recipe_map.get(factor_id, "")
diff --git a/supertokens_python/recipe/dashboard/api/userdetails/user_put.py b/supertokens_python/recipe/dashboard/api/userdetails/user_put.py
index a6123db6a..c348eefe8 100644
--- a/supertokens_python/recipe/dashboard/api/userdetails/user_put.py
+++ b/supertokens_python/recipe/dashboard/api/userdetails/user_put.py
@@ -37,6 +37,11 @@
)
from supertokens_python.recipe.usermetadata import UserMetadataRecipe
from supertokens_python.recipe.usermetadata.asyncio import update_user_metadata
+from supertokens_python.recipe.webauthn.functions import update_user_email
+from supertokens_python.recipe.webauthn.interfaces.recipe import (
+ UnknownUserIdErrorResponse,
+)
+from supertokens_python.recipe.webauthn.recipe import WebauthnRecipe
from supertokens_python.types import RecipeUserId
from .....types.response import APIResponse
@@ -201,6 +206,31 @@ async def update_email_for_recipe_id(
return OkResponse()
+ if recipe_id == "webauthn":
+ validation_error = (
+ await WebauthnRecipe.get_instance().config.validate_email_address(
+ email=email,
+ tenant_id=tenant_id,
+ user_context=user_context,
+ )
+ )
+
+ if validation_error is not None:
+ return InvalidEmailErrorResponse(validation_error)
+
+ email_update_response = await update_user_email(
+ email=email,
+ recipe_user_id=recipe_user_id.get_as_string(),
+ tenant_id=tenant_id,
+ user_context=user_context,
+ )
+
+ if isinstance(email_update_response, EmailAlreadyExistsError):
+ return EmailAlreadyExistsErrorResponse()
+
+ if isinstance(email_update_response, UnknownUserIdErrorResponse):
+ raise Exception("Should never come here")
+
# If it comes here then the user is a third party user in which case the UI should not have allowed this
raise Exception("Should never come here")
diff --git a/supertokens_python/recipe/dashboard/utils.py b/supertokens_python/recipe/dashboard/utils.py
index a49243b44..dc6aac2ac 100644
--- a/supertokens_python/recipe/dashboard/utils.py
+++ b/supertokens_python/recipe/dashboard/utils.py
@@ -18,6 +18,7 @@
from typing_extensions import Literal
from supertokens_python.recipe.accountlinking.recipe import AccountLinkingRecipe
+from supertokens_python.recipe.webauthn.recipe import WebauthnRecipe
from supertokens_python.types.config import (
BaseConfig,
BaseNormalisedConfig,
@@ -231,7 +232,9 @@ async def get_user_for_recipe_id(
async def _get_user_for_recipe_id(
recipe_user_id: RecipeUserId, recipe_id: str, user_context: Dict[str, Any]
) -> GetUserForRecipeIdHelperResult:
- recipe: Optional[Literal["emailpassword", "thirdparty", "passwordless"]] = None
+ recipe: Optional[
+ Literal["emailpassword", "thirdparty", "passwordless", "webauthn"]
+ ] = None
user = await AccountLinkingRecipe.get_instance().recipe_implementation.get_user(
recipe_user_id.get_as_string(), user_context
@@ -271,6 +274,12 @@ async def _get_user_for_recipe_id(
recipe = "passwordless"
except Exception:
pass
+ elif recipe_id == WebauthnRecipe.recipe_id:
+ try:
+ WebauthnRecipe.get_instance()
+ recipe = "webauthn"
+ except Exception:
+ pass
return GetUserForRecipeIdHelperResult(user=user, recipe=recipe)
diff --git a/supertokens_python/recipe/multitenancy/api/implementation.py b/supertokens_python/recipe/multitenancy/api/implementation.py
index f901e07b1..568cf92af 100644
--- a/supertokens_python/recipe/multitenancy/api/implementation.py
+++ b/supertokens_python/recipe/multitenancy/api/implementation.py
@@ -25,7 +25,7 @@
from supertokens_python.types.response import GeneralErrorResponse
from ..constants import DEFAULT_TENANT_ID
-from ..interfaces import APIInterface, ThirdPartyProvider
+from ..interfaces import APIInterface, LoginMethodWebauthn, ThirdPartyProvider
class APIImplementation(APIInterface):
@@ -115,5 +115,6 @@ async def login_methods_get(
enabled="thirdparty" in valid_first_factors,
providers=final_provider_list,
),
+ webauthn=LoginMethodWebauthn(enabled="webauthn" in valid_first_factors),
first_factors=valid_first_factors,
)
diff --git a/supertokens_python/recipe/multitenancy/interfaces.py b/supertokens_python/recipe/multitenancy/interfaces.py
index 0af997881..dd1f03750 100644
--- a/supertokens_python/recipe/multitenancy/interfaces.py
+++ b/supertokens_python/recipe/multitenancy/interfaces.py
@@ -331,6 +331,16 @@ def to_json(self) -> Dict[str, Any]:
}
+class LoginMethodWebauthn:
+ def __init__(self, enabled: bool):
+ self.enabled = enabled
+
+ def to_json(self) -> Dict[str, Any]:
+ return {
+ "enabled": self.enabled,
+ }
+
+
class LoginMethodThirdParty:
def __init__(self, enabled: bool, providers: List[ThirdPartyProvider]):
self.enabled = enabled
@@ -349,12 +359,14 @@ def __init__(
email_password: LoginMethodEmailPassword,
passwordless: LoginMethodPasswordless,
third_party: LoginMethodThirdParty,
+ webauthn: LoginMethodWebauthn,
first_factors: List[str],
):
self.status = "OK"
self.email_password = email_password
self.passwordless = passwordless
self.third_party = third_party
+ self.webauthn = webauthn
self.first_factors = first_factors
def to_json(self) -> Dict[str, Any]:
@@ -363,6 +375,7 @@ def to_json(self) -> Dict[str, Any]:
"emailPassword": self.email_password.to_json(),
"passwordless": self.passwordless.to_json(),
"thirdParty": self.third_party.to_json(),
+ "webauthn": self.webauthn.to_json(),
"firstFactors": self.first_factors,
}
diff --git a/tests/test_utils.py b/tests/test_utils.py
index e5321ccf3..fe832a40c 100644
--- a/tests/test_utils.py
+++ b/tests/test_utils.py
@@ -220,6 +220,10 @@ def test_tldextract_http_toggle(
):
import socket
+ import certifi
+
+ fs.add_real_file(certifi.where())
+
# Disable sockets, will raise errors on HTTP calls
socket_patch = patch.object(socket, "socket", side_effect=RuntimeError)
environ_patch = patch.dict(