tangled
alpha
login
or
join now
futur.blue
/
pegasus
57
fork
atom
objective categorical abstract machine language personal data server
57
fork
atom
overview
issues
2
pulls
pipelines
Add manual passkey login trigger button
futur.blue
2 months ago
7d0bf431
8313fe8a
verified
This commit was signed with the committer's
known signature
.
futur.blue
SSH Key Fingerprint:
SHA256:QHGqHWNpqYyw9bt8KmPuJIyeZX9SZewBZ0PR1COtKQ0=
+68
-62
1 changed file
expand all
collapse all
unified
split
frontend
src
templates
LoginPage.mlx
+68
-62
frontend/src/templates/LoginPage.mlx
···
31
let passkeyError, setPasskeyError = useState (fun () -> None) in
32
let passkeyLoading, setPasskeyLoading = useState (fun () -> false) in
33
let currentOptions = useRef (None : Js.Json.t option) in
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
34
let _ =
35
React.useEffect0 (fun () ->
36
(* only start passkey autofill if not in 2FA step *)
37
if not two_fa_required then
38
-
let _ =
39
-
WebAuthn.browserSupportsWebAuthnAutofill ()
40
-
|> Js.Promise.then_ (fun supported ->
41
-
if supported then begin
42
-
Fetch.fetch "/account/passkeys/login/options"
43
-
|> Js.Promise.then_ (fun response ->
44
-
if Fetch.Response.ok response then
45
-
Fetch.Response.json response
46
-
else Js.Exn.raiseError "Failed to get options" )
47
-
|> Js.Promise.then_ (fun options ->
48
-
currentOptions.current <- Some options ;
49
-
WebAuthn.startAuthentication
50
-
{optionsJSON= options; useBrowserAutofill= true} )
51
-
|> Js.Promise.then_ (fun credential ->
52
-
setPasskeyLoading (fun _ -> true) ;
53
-
let challenge =
54
-
match currentOptions.current with
55
-
| Some opts ->
56
-
Js.Dict.unsafeGet (Obj.magic opts) "challenge"
57
-
| None ->
58
-
Js.Json.string ""
59
-
in
60
-
let body =
61
-
Js.Json.object_
62
-
(Js.Dict.fromArray
63
-
[| ( "response"
64
-
, Js.Json.string (Js.Json.stringify credential)
65
-
)
66
-
; ("challenge", challenge) |] )
67
-
in
68
-
Fetch.fetchWithInit
69
-
( "/account/passkeys/login/verify?redirect_url="
70
-
^ Js.Global.encodeURIComponent redirect_url )
71
-
(Fetch.RequestInit.make ~method_:Post
72
-
~body:(Fetch.BodyInit.make (Js.Json.stringify body))
73
-
~headers:
74
-
(Fetch.HeadersInit.makeWithArray
75
-
[|("Content-Type", "application/json")|] )
76
-
() ) )
77
-
|> Js.Promise.then_ (fun response ->
78
-
setPasskeyLoading (fun _ -> false) ;
79
-
if Fetch.Response.ok response then
80
-
Fetch.Response.json response
81
-
|> Js.Promise.then_ (fun json ->
82
-
let redirect =
83
-
Js.Dict.unsafeGet (Obj.magic json) "redirect"
84
-
|> Js.Json.decodeString
85
-
|> Option.value ~default:"/account"
86
-
in
87
-
Webapi.Dom.(Window.setLocation window redirect) ;
88
-
Js.Promise.resolve () )
89
-
else begin
90
-
setPasskeyError (fun _ ->
91
-
Some "Passkey authentication failed" ) ;
92
-
Js.Promise.resolve ()
93
-
end )
94
-
|> Js.Promise.catch (fun _ ->
95
-
(* user cancelled or error *)
96
-
Js.Promise.resolve () )
97
-
end
98
-
else Js.Promise.resolve () )
99
-
in
100
None
101
else None )
102
in
···
203
null )
204
<Button type_="submit" formMethod="post" className="mt-2">
205
(string (if passkeyLoading then "signing in..." else "sign in"))
0
0
0
0
0
0
0
0
206
</Button>
207
</form>
208
<span className="text-sm text-mist-100">
···
31
let passkeyError, setPasskeyError = useState (fun () -> None) in
32
let passkeyLoading, setPasskeyLoading = useState (fun () -> false) in
33
let currentOptions = useRef (None : Js.Json.t option) in
34
+
let%browser_only triggerPasskeyAutofill () =
35
+
WebAuthn.browserSupportsWebAuthnAutofill ()
36
+
|> Js.Promise.then_ (fun supported ->
37
+
if supported then begin
38
+
Fetch.fetch "/account/passkeys/login/options"
39
+
|> Js.Promise.then_ (fun response ->
40
+
if Fetch.Response.ok response then Fetch.Response.json response
41
+
else Js.Exn.raiseError "Failed to get options" )
42
+
|> Js.Promise.then_ (fun options ->
43
+
currentOptions.current <- Some options ;
44
+
WebAuthn.startAuthentication
45
+
{optionsJSON= options; useBrowserAutofill= true} )
46
+
|> Js.Promise.then_ (fun credential ->
47
+
setPasskeyLoading (fun _ -> true) ;
48
+
let challenge =
49
+
match currentOptions.current with
50
+
| Some opts ->
51
+
Js.Dict.unsafeGet (Obj.magic opts) "challenge"
52
+
| None ->
53
+
Js.Json.string ""
54
+
in
55
+
let body =
56
+
Js.Json.object_
57
+
(Js.Dict.fromArray
58
+
[| ( "response"
59
+
, Js.Json.string (Js.Json.stringify credential) )
60
+
; ("challenge", challenge) |] )
61
+
in
62
+
Fetch.fetchWithInit
63
+
( "/account/passkeys/login/verify?redirect_url="
64
+
^ Js.Global.encodeURIComponent redirect_url )
65
+
(Fetch.RequestInit.make ~method_:Post
66
+
~body:(Fetch.BodyInit.make (Js.Json.stringify body))
67
+
~headers:
68
+
(Fetch.HeadersInit.makeWithArray
69
+
[|("Content-Type", "application/json")|] )
70
+
() ) )
71
+
|> Js.Promise.then_ (fun response ->
72
+
setPasskeyLoading (fun _ -> false) ;
73
+
if Fetch.Response.ok response then
74
+
Fetch.Response.json response
75
+
|> Js.Promise.then_ (fun json ->
76
+
let redirect =
77
+
Js.Dict.unsafeGet (Obj.magic json) "redirect"
78
+
|> Js.Json.decodeString
79
+
|> Option.value ~default:"/account"
80
+
in
81
+
Webapi.Dom.(Window.setLocation window redirect) ;
82
+
Js.Promise.resolve () )
83
+
else begin
84
+
setPasskeyError (fun _ -> Some "Passkey authentication failed") ;
85
+
Js.Promise.resolve ()
86
+
end )
87
+
|> Js.Promise.catch (fun _ ->
88
+
(* user cancelled or error *)
89
+
Js.Promise.resolve () )
90
+
end
91
+
else Js.Promise.resolve () )
92
+
in
93
let _ =
94
React.useEffect0 (fun () ->
95
(* only start passkey autofill if not in 2FA step *)
96
if not two_fa_required then
97
+
let _ = triggerPasskeyAutofill () in
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
98
None
99
else None )
100
in
···
201
null )
202
<Button type_="submit" formMethod="post" className="mt-2">
203
(string (if passkeyLoading then "signing in..." else "sign in"))
204
+
</Button>
205
+
<Button
206
+
kind=`Tertiary
207
+
className="text-sm"
208
+
onClick=(fun _ ->
209
+
let _ = triggerPasskeyAutofill () in
210
+
() )>
211
+
(string "sign in with passkey")
212
</Button>
213
</form>
214
<span className="text-sm text-mist-100">