objective categorical abstract machine language personal data server
65
fork

Configure Feed

Select the types of activity you want to include in your feed.

Add manual passkey login trigger button

futurGH 7d0bf431 8313fe8a

+68 -62
+68 -62
frontend/src/templates/LoginPage.mlx
··· 31 31 let passkeyError, setPasskeyError = useState (fun () -> None) in 32 32 let passkeyLoading, setPasskeyLoading = useState (fun () -> false) in 33 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 34 93 let _ = 35 94 React.useEffect0 (fun () -> 36 95 (* only start passkey autofill if not in 2FA step *) 37 96 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 97 + let _ = triggerPasskeyAutofill () in 100 98 None 101 99 else None ) 102 100 in ··· 203 201 null ) 204 202 <Button type_="submit" formMethod="post" className="mt-2"> 205 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") 206 212 </Button> 207 213 </form> 208 214 <span className="text-sm text-mist-100">