13
13
14
14
from django_security_keys .ext .two_factor import forms
15
15
from django_security_keys .ext .two_factor .forms import SecurityKeyDeviceValidation
16
- from django_security_keys .models import SecurityKey , SecurityKeyDevice
17
-
18
-
16
+ from django_security_keys .models import SecurityKey , SecurityKeyDevice , UserHandle
17
+ import json
18
+ from webauthn . helpers import base64url_to_bytes
19
19
class DisableView (two_factor .views .DisableView ):
20
20
def dispatch (self , * args : Any , ** kwargs : Any ) -> HttpResponse :
21
21
self .success_url = "/"
@@ -38,7 +38,7 @@ def has_security_key_step(self) -> bool:
38
38
return False
39
39
40
40
return (
41
- len (SecurityKey .credentials (self .get_user ().username , self . request . session ))
41
+ len (SecurityKey .credentials (self .get_user ().username ))
42
42
> 0
43
43
)
44
44
@@ -52,49 +52,52 @@ def post(
52
52
self , * args : Any , ** kwargs : Any
53
53
) -> HttpResponseRedirect | TemplateResponse :
54
54
request = self .request
55
- passwordless = self .attempt_passwordless_auth (request , ** kwargs )
56
- if passwordless :
57
- return passwordless
55
+ if not request .POST .get ("auth-username" ):
56
+ attempt_passkey_auth = self .attempt_passkey_auth (request , ** kwargs )
57
+ if attempt_passkey_auth :
58
+ return attempt_passkey_auth
58
59
return super ().post (* args , ** kwargs )
59
60
60
- def attempt_passwordless_auth (
61
+ def attempt_passkey_auth (
61
62
self , request : WSGIRequest , ** kwargs : Any
62
63
) -> HttpResponseRedirect | None :
63
64
"""
64
- Prepares and attempts a passwordless authentication
65
+ Prepares and attempts a passkey authentication
65
66
using a security key credential.
66
67
67
68
This requires that the auth-username and credential
68
69
fields are set in the POST data.
69
70
70
- This requires that the PasswordlessAuthenticationBackend is
71
- loaded.
72
71
"""
73
72
74
73
if self .steps .current == "auth" :
75
- credential = request .POST .get ("credential" )
76
- username = request .POST .get ("auth-username" )
77
-
78
- # support password-less login using webauthn
79
- if username and credential :
74
+ try :
75
+ credential = request .POST .get ("credential" )
80
76
try :
77
+ user_handle = base64url_to_bytes (json .loads (credential )['response' ]['userHandle' ]).decode ('utf-8' )
78
+ username = UserHandle .objects .get (handle = user_handle ).user .username
79
+ except :
80
+ raise Exception ("Failed login using passkey" )
81
+ # support passkey login using webauthn
82
+ if username and credential :
81
83
user = authenticate (
82
84
request , username = username , u2f_credential = credential
83
85
)
86
+ if not user :
87
+ raise Exception ("Failed login using passkey" )
84
88
self .storage .reset ()
85
89
self .storage .authenticated_user = user
86
90
self .storage .data ["authentication_time" ] = int (time .time ())
87
91
form = self .get_form (
88
92
data = self .request .POST , files = self .request .FILES
89
93
)
90
-
91
94
if self .steps .current == self .steps .last :
92
95
return self .render_done (form , ** kwargs )
93
96
return self .render_next_step (form )
94
97
95
- except Exception as exc :
96
- self .passwordless_error = f"{ exc } "
97
- return self .render_goto_step ("auth" )
98
+ except Exception as exc :
99
+ self .passkey_error = f"{ exc } "
100
+ return self .render_goto_step ("auth" )
98
101
99
102
def get_context_data (
100
103
self , form : AuthenticationForm | SecurityKeyDeviceValidation , ** kwargs : Any
@@ -110,7 +113,7 @@ def get_context_data(
110
113
if self .has_security_key_step ():
111
114
context ["other_devices" ] += [self .get_security_key_device ()]
112
115
113
- context ["passwordless_error " ] = getattr (self , "passwordless_error " , None )
116
+ context ["passkey_error " ] = getattr (self , "passkey_error " , None )
114
117
115
118
if self .steps .current == "security-key" :
116
119
context ["device" ] = self .get_security_key_device ()
0 commit comments