Skip to content

Commit 989155e

Browse files
authored
feat: webauthn (supertokens#583)
### Adds Webauthn (Passkeys) support - Adds Webauthn recipe with support for: - Registration, sign-in, and credential verification flows - Account recovery - Adds new API endpoints for WebAuthn operations: - GET `/api/webauthn/email/exists` - Check if email exists in system - POST `/api/webauthn/options/register` - Handle registration options - POST `/api/webauthn/options/signin` - Handle sign-in options - POST `/api/webauthn/signin` - Handle WebAuthn sign-in - POST `/api/webauthn/signup` - Handle WebAuthn sign-up - POST `/api/user/webauthn/reset` - Handle account recovery - POST `/api/user/webauthn/reset/token` - Generate recovery tokens - Adds WebAuthn support to account linking functionality: - Support for linking users based on WebAuthn `credential_id` - Updates `AccountInfo` type to `AccountInfoInput` with WebAuthn fields - Adds `has_same_webauthn_info_as` method for credential comparison - Adds FDI support for version `4.1` - Recipe functions are directly importable from the Webauthn recipe module - ```python from supertokens_python.recipe.webauthn import sign_in await sign_in(...) # Async sign_in.sync(...) # Sync ``` ### Breaking Changes - Updates supported CDI version from `5.2` to `5.3` - Changes `AccountInfo` to `AccountInfoInput` in various methods - This is required to allow querying by a single Webauthn `credential_id`, while the Webauthn login method contains an array of `credential_ids` - Affected functions: - `supertokens_python.asyncio.list_users_by_account_info` - `supertokens_python.syncio.list_users_by_account_info` - `supertokens_python.recipe.accountlinking.interface.RecipeInterface.list_users_by_account_info` - `supertokens_python.recipe.accountlinking.recipe_implementation.RecipeImplementation.list_users_by_account_info`
1 parent 1d431dc commit 989155e

File tree

130 files changed

+7913
-265
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

130 files changed

+7913
-265
lines changed

.github/workflows/backend-sdk-testing.yml

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -82,10 +82,11 @@ jobs:
8282
run: |
8383
source venv/bin/activate
8484
docker compose up --build --wait
85-
python3 tests/test-server/app.py &
85+
python3 tests/test-server/app.py &> python.log &
8686
8787
- uses: supertokens/backend-sdk-testing-action@main
8888
with:
8989
version: ${{ matrix.fdi-version }}
9090
check-name-suffix: '[CDI=${{ matrix.cdi-version }}][Core=${{ steps.versions.outputs.coreVersionXy }}][FDI=${{ matrix.fdi-version }}][Py=${{ matrix.py-version }}][Node=${{ matrix.node-version }}]'
9191
path: backend-sdk-testing
92+
app-server-logs: ${{ github.workspace }}/supertokens-python/python.log

.github/workflows/lint-code.yml

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,11 @@ jobs:
1919
outputs:
2020
pyVersions: '["3.8", "3.9", "3.10", "3.11", "3.12", "3.13"]'
2121

22+
steps:
23+
# Required to avoid errors in runs due to no steps
24+
- name: Placeholder
25+
run: echo "Placeholder"
26+
2227
lint-format:
2328
name: Check linting and formatting
2429
runs-on: ubuntu-latest

CHANGELOG.md

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,45 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
88

99
## [unreleased]
1010

11+
## [0.30.0] - 2025-05-27
12+
### Adds Webauthn (Passkeys) support
13+
- Adds Webauthn recipe with support for:
14+
- Registration, sign-in, and credential verification flows
15+
- Account recovery
16+
- Adds new API endpoints for WebAuthn operations:
17+
- GET `/api/webauthn/email/exists` - Check if email exists in system
18+
- POST `/api/webauthn/options/register` - Handle registration options
19+
- POST `/api/webauthn/options/signin` - Handle sign-in options
20+
- POST `/api/webauthn/signin` - Handle WebAuthn sign-in
21+
- POST `/api/webauthn/signup` - Handle WebAuthn sign-up
22+
- POST `/api/user/webauthn/reset` - Handle account recovery
23+
- POST `/api/user/webauthn/reset/token` - Generate recovery tokens
24+
- Adds WebAuthn support to account linking functionality:
25+
- Support for linking users based on WebAuthn `credential_id`
26+
- Updates `AccountInfo` type to `AccountInfoInput` with WebAuthn fields
27+
- Adds `has_same_webauthn_info_as` method for credential comparison
28+
- Adds FDI support for version `4.1`
29+
- Recipe functions are directly importable from the Webauthn recipe module
30+
- ```python
31+
from supertokens_python.recipe.webauthn import sign_in
32+
33+
await sign_in(...) # Async
34+
sign_in.sync(...) # Sync
35+
```
36+
37+
### Breaking Changes
38+
- Updates supported CDI version from `5.2` to `5.3`
39+
- Changes `AccountInfo` to `AccountInfoInput` in various methods
40+
- This is required to allow querying by a single Webauthn `credential_id`, while the Webauthn login method contains an array of `credential_ids`
41+
- Affected functions:
42+
- `supertokens_python.asyncio.list_users_by_account_info`
43+
- `supertokens_python.syncio.list_users_by_account_info`
44+
- `supertokens_python.recipe.accountlinking.interface.RecipeInterface.list_users_by_account_info`
45+
- `supertokens_python.recipe.accountlinking.recipe_implementation.RecipeImplementation.list_users_by_account_info`
46+
- `supertokens_python.recipe.passwordless.api.implementation.get_passwordless_user_by_account_info`
47+
- `supertokens_python.recipe.passwordless.api.implementation.get_passwordless_user_by_account_info`
48+
49+
1150
## [0.29.2] - 2025-05-19
1251
- Fixes cookies being set without expiry in Django
1352
- Reverts timezone change from 0.28.0 and uses GMT

coreDriverInterfaceSupported.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"_comment": "contains a list of core-driver interfaces branch names that this core supports",
33
"versions": [
4-
"5.2"
4+
"5.3"
55
]
66
}

dev-requirements.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,4 +21,5 @@ pyyaml==6.0.2
2121
requests-mock==1.12.1
2222
respx>=0.13.0, <1.0.0
2323
uvicorn==0.32.0
24+
wasmtime==25.0.0
2425
-e .

frontendDriverInterfaceSupported.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
"2.0",
88
"3.0",
99
"3.1",
10-
"4.0"
10+
"4.0",
11+
"4.1"
1112
]
1213
}

setup.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -82,7 +82,7 @@
8282

8383
setup(
8484
name="supertokens_python",
85-
version="0.29.2",
85+
version="0.30.0",
8686
author="SuperTokens",
8787
license="Apache 2.0",
8888
author_email="team@supertokens.com",
@@ -127,6 +127,7 @@
127127
"pkce<1.1.0",
128128
"pyotp<3",
129129
"python-dateutil<3",
130+
"pydantic>=2.10.6,<3.0.0",
130131
],
131132
python_requires=">=3.8",
132133
include_package_data=True,

supertokens_python/async_to_sync_wrapper.py

Lines changed: 35 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,8 +13,20 @@
1313
# under the License.
1414

1515
import asyncio
16+
from functools import update_wrapper
1617
from os import getenv
17-
from typing import Any, Coroutine, TypeVar
18+
from typing import (
19+
Any,
20+
Callable,
21+
Coroutine,
22+
Generic,
23+
TypeVar,
24+
)
25+
26+
from typing_extensions import ParamSpec
27+
28+
Param = ParamSpec("Param")
29+
RetType = TypeVar("RetType", covariant=True)
1830

1931
_T = TypeVar("_T")
2032

@@ -43,3 +55,25 @@ def create_or_get_event_loop() -> asyncio.AbstractEventLoop:
4355
def sync(co: Coroutine[Any, Any, _T]) -> _T:
4456
loop = create_or_get_event_loop()
4557
return loop.run_until_complete(co)
58+
59+
60+
class syncify(Generic[Param, RetType]):
61+
"""
62+
Decorator to allow async functions to be executed synchronously
63+
using a `sync` attribute.
64+
"""
65+
66+
def __init__(self, func: Callable[Param, Coroutine[Any, Any, RetType]]):
67+
update_wrapper(self, func)
68+
self.func = func
69+
70+
def __call__(
71+
self, *args: Param.args, **kwargs: Param.kwargs
72+
) -> Coroutine[Any, Any, RetType]:
73+
return self.func(*args, **kwargs)
74+
75+
def sync(self, *args: Param.args, **kwargs: Param.kwargs) -> RetType:
76+
"""
77+
Synchronous version of the decorated function.
78+
"""
79+
return sync(self.func(*args, **kwargs))

supertokens_python/asyncio/__init__.py

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,8 @@
2626
)
2727
from supertokens_python.recipe.accountlinking.interfaces import GetUsersResult
2828
from supertokens_python.recipe.accountlinking.recipe import AccountLinkingRecipe
29-
from supertokens_python.types import AccountInfo, User
29+
from supertokens_python.types import User
30+
from supertokens_python.types.base import AccountInfoInput
3031

3132

3233
async def get_users_oldest_first(
@@ -159,7 +160,7 @@ async def update_or_delete_user_id_mapping_info(
159160

160161
async def list_users_by_account_info(
161162
tenant_id: str,
162-
account_info: AccountInfo,
163+
account_info: AccountInfoInput,
163164
do_union_of_account_info: bool = False,
164165
user_context: Optional[Dict[str, Any]] = None,
165166
) -> List[User]:

supertokens_python/auth_utils.py

Lines changed: 12 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
from typing import Any, Awaitable, Callable, Dict, List, Optional, Union
1+
from typing import TYPE_CHECKING, Any, Awaitable, Callable, Dict, List, Optional, Union
22

33
from typing_extensions import Literal
44

@@ -31,37 +31,18 @@
3131
from supertokens_python.recipe.session.interfaces import SessionContainer
3232
from supertokens_python.recipe.thirdparty.types import ThirdPartyInfo
3333
from supertokens_python.types import (
34-
AccountInfo,
3534
LoginMethod,
3635
RecipeUserId,
3736
User,
3837
)
38+
from supertokens_python.types.auth_utils import LinkingToSessionUserFailedError
39+
from supertokens_python.types.base import AccountInfoInput
3940
from supertokens_python.utils import log_debug_message
4041

4142
from .asyncio import get_user
4243

43-
44-
class LinkingToSessionUserFailedError:
45-
status: Literal["LINKING_TO_SESSION_USER_FAILED"] = "LINKING_TO_SESSION_USER_FAILED"
46-
reason: Literal[
47-
"EMAIL_VERIFICATION_REQUIRED",
48-
"RECIPE_USER_ID_ALREADY_LINKED_WITH_ANOTHER_PRIMARY_USER_ID_ERROR",
49-
"ACCOUNT_INFO_ALREADY_ASSOCIATED_WITH_ANOTHER_PRIMARY_USER_ID_ERROR",
50-
"SESSION_USER_ACCOUNT_INFO_ALREADY_ASSOCIATED_WITH_ANOTHER_PRIMARY_USER_ID_ERROR",
51-
"INPUT_USER_IS_NOT_A_PRIMARY_USER",
52-
]
53-
54-
def __init__(
55-
self,
56-
reason: Literal[
57-
"EMAIL_VERIFICATION_REQUIRED",
58-
"RECIPE_USER_ID_ALREADY_LINKED_WITH_ANOTHER_PRIMARY_USER_ID_ERROR",
59-
"ACCOUNT_INFO_ALREADY_ASSOCIATED_WITH_ANOTHER_PRIMARY_USER_ID_ERROR",
60-
"SESSION_USER_ACCOUNT_INFO_ALREADY_ASSOCIATED_WITH_ANOTHER_PRIMARY_USER_ID_ERROR",
61-
"INPUT_USER_IS_NOT_A_PRIMARY_USER",
62-
],
63-
):
64-
self.reason = reason
44+
if TYPE_CHECKING:
45+
from supertokens_python.recipe.webauthn.types.base import WebauthnInfoInput
6546

6647

6748
class OkResponse:
@@ -290,6 +271,7 @@ async def get_authenticating_user_and_add_to_current_tenant_if_required(
290271
session: Optional[SessionContainer],
291272
check_credentials_on_tenant: Callable[[str], Awaitable[bool]],
292273
user_context: Dict[str, Any],
274+
webauthn: Optional["WebauthnInfoInput"] = None,
293275
) -> Optional[AuthenticatingUserInfo]:
294276
i = 0
295277
while i < 300:
@@ -303,8 +285,11 @@ async def get_authenticating_user_and_add_to_current_tenant_if_required(
303285
)
304286
existing_users = await AccountLinkingRecipe.get_instance().recipe_implementation.list_users_by_account_info(
305287
tenant_id=tenant_id,
306-
account_info=AccountInfo(
307-
email=email, phone_number=phone_number, third_party=third_party
288+
account_info=AccountInfoInput(
289+
email=email,
290+
phone_number=phone_number,
291+
third_party=third_party,
292+
webauthn=webauthn,
308293
),
309294
do_union_of_account_info=True,
310295
user_context=user_context,
@@ -324,6 +309,7 @@ async def get_authenticating_user_and_add_to_current_tenant_if_required(
324309
(email is not None and lm.has_same_email_as(email))
325310
or lm.has_same_phone_number_as(phone_number)
326311
or lm.has_same_third_party_info_as(third_party)
312+
or lm.has_same_webauthn_info_as(webauthn)
327313
)
328314
),
329315
None,

0 commit comments

Comments
 (0)
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy