objective categorical abstract machine language personal data server

Add manual passkey login trigger button

futur.blue 7d0bf431 8313fe8a

verified
+68 -62
+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 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")) 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 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">