Skip to content

Commit 734ba4b

Browse files
committed
Use transaction.atomic for passkeys loggin.
1 parent b1e42ae commit 734ba4b

1 file changed

Lines changed: 45 additions & 36 deletions

File tree

hypha/apply/users/passkey_views.py

Lines changed: 45 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
from django.contrib.auth import login
77
from django.contrib.auth.decorators import login_required
88
from django.core.exceptions import PermissionDenied
9+
from django.db import transaction
910
from django.http import JsonResponse
1011
from django.shortcuts import get_object_or_404, render, resolve_url
1112
from django.utils import timezone
@@ -206,51 +207,59 @@ def passkey_auth_complete(request):
206207
return JsonResponse({"error": _("Invalid credential")}, status=400)
207208

208209
try:
209-
passkey = Passkey.objects.select_related("user").get(
210-
credential_id=credential_id_b64
211-
)
210+
with transaction.atomic():
211+
passkey = (
212+
Passkey.objects.select_related("user")
213+
.select_for_update()
214+
.get(credential_id=credential_id_b64)
215+
)
216+
217+
user_handle = data["response"].get("userHandle")
218+
if user_handle:
219+
if base64url_to_bytes(user_handle) != str(passkey.user.pk).encode():
220+
return JsonResponse(
221+
{"error": _("User handle mismatch")}, status=400
222+
)
223+
credential = AuthenticationCredential(
224+
id=data["id"],
225+
raw_id=base64url_to_bytes(data["rawId"]),
226+
response=AuthenticatorAssertionResponse(
227+
client_data_json=base64url_to_bytes(
228+
data["response"]["clientDataJSON"]
229+
),
230+
authenticator_data=base64url_to_bytes(
231+
data["response"]["authenticatorData"]
232+
),
233+
signature=base64url_to_bytes(data["response"]["signature"]),
234+
user_handle=base64url_to_bytes(user_handle)
235+
if user_handle
236+
else None,
237+
),
238+
)
239+
verification = verify_authentication_response(
240+
credential=credential,
241+
expected_challenge=challenge,
242+
expected_rp_id=_get_rp_id(request),
243+
expected_origin=_get_origin(request),
244+
credential_public_key=base64url_to_bytes(passkey.public_key),
245+
credential_current_sign_count=passkey.sign_count,
246+
require_user_verification=True,
247+
)
248+
249+
passkey.sign_count = verification.new_sign_count
250+
passkey.last_used_at = timezone.now()
251+
passkey.save(update_fields=["sign_count", "last_used_at"])
252+
253+
user = passkey.user
212254
except Passkey.DoesNotExist:
213255
return JsonResponse({"error": _("Unknown credential")}, status=400)
214-
215-
try:
216-
user_handle = data["response"].get("userHandle")
217-
if user_handle:
218-
if base64url_to_bytes(user_handle) != str(passkey.user.pk).encode():
219-
return JsonResponse({"error": _("User handle mismatch")}, status=400)
220-
credential = AuthenticationCredential(
221-
id=data["id"],
222-
raw_id=base64url_to_bytes(data["rawId"]),
223-
response=AuthenticatorAssertionResponse(
224-
client_data_json=base64url_to_bytes(data["response"]["clientDataJSON"]),
225-
authenticator_data=base64url_to_bytes(
226-
data["response"]["authenticatorData"]
227-
),
228-
signature=base64url_to_bytes(data["response"]["signature"]),
229-
user_handle=base64url_to_bytes(user_handle) if user_handle else None,
230-
),
231-
)
232-
verification = verify_authentication_response(
233-
credential=credential,
234-
expected_challenge=challenge,
235-
expected_rp_id=_get_rp_id(request),
236-
expected_origin=_get_origin(request),
237-
credential_public_key=base64url_to_bytes(passkey.public_key),
238-
credential_current_sign_count=passkey.sign_count,
239-
require_user_verification=True,
240-
)
241256
except Exception:
242257
logger.warning(
243258
"Passkey authentication verification failed for credential %s",
244259
credential_id_b64,
245260
exc_info=True,
246261
)
247262
return JsonResponse({"error": _("Verification failed")}, status=400)
248-
249-
passkey.sign_count = verification.new_sign_count
250-
passkey.last_used_at = timezone.now()
251-
passkey.save(update_fields=["sign_count", "last_used_at"])
252-
253-
user = passkey.user
254263
user.backend = settings.CUSTOM_AUTH_BACKEND
255264
login(request, user)
256265
request.session["passkey_authenticated"] = True

0 commit comments

Comments
 (0)