Skip to content

Commit 402ce3a

Browse files
committed
fix: webauthn credential listing/removal for non-primary and multiple linked users
ref: supertokens/supertokens-node#1024
1 parent a6f61d8 commit 402ce3a

File tree

2 files changed

+46
-8
lines changed

2 files changed

+46
-8
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
1111
- Adds API endpoints to list and remove Webauthn credentials
1212
- Prevents removal of Webauthn credentials unless all session claims are satisfied
1313
- Changes how sessions are fetched when listing, removing, and registering Webauthn credentials
14+
- Fixes Webauthn credential listing and removal to work even when the Webauthn user is not the primary user and when there are multiple linked Webauthn users
1415
- Updates FDI support to `4.2`
1516

1617
## [0.30.1] - 2025-07-21

supertokens_python/recipe/webauthn/api/implementation.py

Lines changed: 45 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@
1212
# License for the specific language governing permissions and limitations
1313
# under the License.
1414

15-
from typing import Optional, Union, cast
15+
from typing import List, Optional, Union, cast
1616

1717
from typing_extensions import Unpack
1818

@@ -23,6 +23,7 @@
2323
post_auth_checks,
2424
pre_auth_checks,
2525
)
26+
from supertokens_python.exceptions import raise_general_exception
2627
from supertokens_python.recipe.accountlinking.recipe import AccountLinkingRecipe
2728
from supertokens_python.recipe.accountlinking.types import (
2829
AccountInfoWithRecipeId,
@@ -1084,14 +1085,34 @@ async def list_credentials_get(
10841085
user_context: UserContext,
10851086
session: SessionContainer,
10861087
) -> ListCredentialsGETResponse:
1087-
list_credentials_response = (
1088-
await options.recipe_implementation.list_credentials(
1089-
recipe_user_id=session.get_recipe_user_id().get_as_string(),
1090-
user_context=user_context,
1091-
)
1088+
existing_user = await get_user(
1089+
user_id=session.get_user_id(),
1090+
user_context=user_context,
10921091
)
1092+
if existing_user is None:
1093+
raise_general_exception("User not found")
1094+
1095+
recipe_user_ids = [
1096+
lm.recipe_user_id
1097+
for lm in existing_user.login_methods
1098+
if lm.recipe_id == "webauthn"
1099+
]
10931100

1094-
return list_credentials_response
1101+
credentials: List[ListCredentialsGETResponse.Credential] = []
1102+
1103+
for recipe_user_id in recipe_user_ids:
1104+
list_credentials_response = (
1105+
await options.recipe_implementation.list_credentials(
1106+
recipe_user_id=recipe_user_id.get_as_string(),
1107+
user_context=user_context,
1108+
)
1109+
)
1110+
1111+
credentials.extend(list_credentials_response.credentials)
1112+
1113+
return ListCredentialsGETResponse(
1114+
credentials=credentials,
1115+
)
10951116

10961117
async def register_credential_post(
10971118
self,
@@ -1176,10 +1197,26 @@ async def remove_credential_post(
11761197
]
11771198
)
11781199

1200+
user = await get_user(session.get_user_id(), user_context=user_context)
1201+
if user is None:
1202+
raise_general_exception("User not found")
1203+
1204+
required_login_methods = [
1205+
lm
1206+
for lm in user.login_methods
1207+
if lm.recipe_id == "webauthn"
1208+
and lm.webauthn is not None
1209+
and webauthn_credential_id in lm.webauthn.credential_ids
1210+
]
1211+
if len(required_login_methods) == 0:
1212+
raise_general_exception("User not found")
1213+
1214+
recipe_user_id = required_login_methods[0].recipe_user_id
1215+
11791216
remove_credential_response = (
11801217
await options.recipe_implementation.remove_credential(
11811218
webauthn_credential_id=webauthn_credential_id,
1182-
recipe_user_id=session.get_recipe_user_id().get_as_string(),
1219+
recipe_user_id=recipe_user_id.get_as_string(),
11831220
user_context=user_context,
11841221
)
11851222
)

0 commit comments

Comments
 (0)