"use client"; import { useEffect, useRef, useState } from "react"; import { useRouter } from "next/navigation"; import { coerceToArrayBuffer, coerceToBase64Url } from "#/utils/base64"; import { Button, ButtonVariants } from "./Button"; import Alert from "./Alert"; import { Spinner } from "./Spinner"; type Props = { loginName: string; authRequestId?: string; altPassword: boolean; }; export default function LoginPasskey({ loginName, authRequestId, altPassword, }: Props) { const [error, setError] = useState(""); const [loading, setLoading] = useState(false); const router = useRouter(); const initialized = useRef(false); useEffect(() => { if (!initialized.current) { initialized.current = true; setLoading(true); updateSessionForChallenge() .then((response) => { console.log(response); const pK = response.challenges.webAuthN.publicKeyCredentialRequestOptions .publicKey; if (pK) { submitLoginAndContinue(pK) .then(() => { setLoading(false); }) .catch((error) => { setError(error); setLoading(false); }); } else { setError("Could not request passkey challenge"); setLoading(false); } }) .catch((error) => { setError(error); setLoading(false); }); } }, []); async function updateSessionForChallenge() { setLoading(true); const res = await fetch("/api/session", { method: "PUT", headers: { "Content-Type": "application/json", }, body: JSON.stringify({ loginName, challenges: { webAuthN: { domain: "", userVerificationRequirement: 1, }, }, authRequestId, }), }); setLoading(false); if (!res.ok) { const error = await res.json(); throw error.details.details; } return res.json(); } async function submitLogin(data: any) { setLoading(true); console.log(data); const res = await fetch("/api/session", { method: "PUT", headers: { "Content-Type": "application/json", }, body: JSON.stringify({ loginName, webAuthN: { credentialAssertionData: data }, authRequestId, }), }); const response = await res.json(); setLoading(false); if (!res.ok) { setError(response.details); return Promise.reject(response.details); } return response; } async function submitLoginAndContinue( publicKey: any ): Promise { publicKey.challenge = coerceToArrayBuffer( publicKey.challenge, "publicKey.challenge" ); publicKey.allowCredentials.map((listItem: any) => { listItem.id = coerceToArrayBuffer( listItem.id, "publicKey.allowCredentials.id" ); }); navigator.credentials .get({ publicKey, }) .then((assertedCredential: any) => { if (assertedCredential) { const authData = new Uint8Array( assertedCredential.response.authenticatorData ); const clientDataJSON = new Uint8Array( assertedCredential.response.clientDataJSON ); const rawId = new Uint8Array(assertedCredential.rawId); const sig = new Uint8Array(assertedCredential.response.signature); const userHandle = new Uint8Array( assertedCredential.response.userHandle ); const data = { id: assertedCredential.id, rawId: coerceToBase64Url(rawId, "rawId"), type: assertedCredential.type, response: { authenticatorData: coerceToBase64Url(authData, "authData"), clientDataJSON: coerceToBase64Url( clientDataJSON, "clientDataJSON" ), signature: coerceToBase64Url(sig, "sig"), userHandle: coerceToBase64Url(userHandle, "userHandle"), }, }; return submitLogin(data).then((resp) => { return router.push( `/signedin?` + new URLSearchParams( authRequestId ? { loginName: resp.factors.user.loginName, authRequestId, } : { loginName: resp.factors.user.loginName, } ) ); }); } else { setLoading(false); setError("An error on retrieving passkey"); return null; } }) .catch((error) => { console.error(error); setLoading(false); // setError(error); return null; }); } return (
{error && (
{error}
)}
{altPassword ? ( ) : ( )}
); }