Files
ccn/Services/Website/Resources/Static/js/passkey.js
T

61 lines
2.0 KiB
JavaScript

// CCN — passkey sign-in. Without it, or without WebAuthn, the sign-in screen offers the demo sign-in alone.
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;
button.addEventListener("click", async () => {
button.disabled = true;
status.textContent = "";
try {
const { mode, options } = await post("/sign-in/passkey/options", { email: form.elements.email.value });
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.elements.return.value,
});
window.location.assign(location);
} catch (error) {
// Dismissing the browser's prompt is not an error worth a message.
status.textContent = error.name === "NotAllowedError" ? "" : error.message;
button.disabled = false;
}
});
}