|
6 | 6 | from django.contrib.auth import login |
7 | 7 | from django.contrib.auth.decorators import login_required |
8 | 8 | from django.core.exceptions import PermissionDenied |
| 9 | +from django.db import transaction |
9 | 10 | from django.http import JsonResponse |
10 | 11 | from django.shortcuts import get_object_or_404, render, resolve_url |
11 | 12 | from django.utils import timezone |
@@ -206,51 +207,59 @@ def passkey_auth_complete(request): |
206 | 207 | return JsonResponse({"error": _("Invalid credential")}, status=400) |
207 | 208 |
|
208 | 209 | 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 |
212 | 254 | except Passkey.DoesNotExist: |
213 | 255 | 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 | | - ) |
241 | 256 | except Exception: |
242 | 257 | logger.warning( |
243 | 258 | "Passkey authentication verification failed for credential %s", |
244 | 259 | credential_id_b64, |
245 | 260 | exc_info=True, |
246 | 261 | ) |
247 | 262 | 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 |
254 | 263 | user.backend = settings.CUSTOM_AUTH_BACKEND |
255 | 264 | login(request, user) |
256 | 265 | request.session["passkey_authenticated"] = True |
|
0 commit comments