// CCN — passkeys: signing in or joining, and adding one from the account screen. Without it, or without WebAuthn, the demo sign-in remains. const form = document.querySelector("[data-passkey-form]"); const supported = window.PublicKeyCredential && PublicKeyCredential.parseCreationOptionsFromJSON && PublicKeyCredential.parseRequestOptionsFromJSON; if (form && supported) { const button = form.querySelector("[data-passkey]"); const status = form.querySelector("[data-passkey-status]"); const post = async (path, body) => { const response = await fetch(path, { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify(body), }); const answer = await response.json(); if (!response.ok) { throw new Error(answer.error ?? "Something went wrong. Try again."); } return answer; }; button.hidden = false; // The word for a browser without passkeys is on the page by default; this one has them. for (const notice of document.querySelectorAll("[data-passkey-unsupported]")) { notice.hidden = true; } button.addEventListener("click", async () => { button.disabled = true; status.textContent = ""; try { // The button adds a passkey on the account screen, replaces them all from a recovery link, and otherwise signs in or joins. const start = button.dataset.passkey === "add" ? { add: true } : button.dataset.passkey === "recover" ? { recover: form.querySelector("[name=recover]").value } : { email: form.querySelector("[name=email]").value }; const { mode, options } = await post("/sign-in/passkey/options", start); let credential; if (mode === "create") { // A discoverable passkey, so the next sign-in needs no email. options.authenticatorSelection = { residentKey: "required", userVerification: "preferred" }; credential = await navigator.credentials.create({ publicKey: PublicKeyCredential.parseCreationOptionsFromJSON(options), }); } else { credential = await navigator.credentials.get({ publicKey: PublicKeyCredential.parseRequestOptionsFromJSON(options), }); } const { location } = await post("/sign-in/passkey/finish", { credential: credential.toJSON(), return: form.querySelector("[name=return]")?.value, }); window.location.assign(location); } catch (error) { status.textContent = error.name === "NotAllowedError" ? "That didn't go through: the passkey prompt was cancelled. Nothing was created or changed — try again." : error.message; button.disabled = false; } }); }