passkey-register.js
⎇
Raw
1import { startRegistration } from "/assets/simplewebauthn-browser.js";
2
3const usernameInput = document.getElementById("username");
4const applicationInput = document.getElementById("application");
5const section = document.getElementById("passkey-section");
6const btn = document.getElementById("passkey-register-btn");
7const errEl = document.getElementById("passkey-error");
8
9if (section) section.style.display = "block";
10
11function showError(msg) {
12 if (!errEl) return;
13 errEl.textContent = msg;
14 errEl.className = msg ? "form-error" : "";
15 errEl.style.display = msg ? "" : "none";
16}
17
18function showPending() {
19 const container = document.querySelector(".auth-container");
20 if (container) {
21 container.innerHTML =
22 '<h1 class="page-title">Create account</h1>' +
23 '<p class="form-success">Your account has been submitted for review. You will be able to log in once an admin approves it.</p>' +
24 '<p class="auth-footer">Already have an account? <a href="/login">Sign in</a></p>';
25 }
26}
27
28if (btn) {
29 const originalText = btn.textContent;
30 btn.addEventListener("click", async () => {
31 const username = usernameInput?.value.trim() ?? "";
32 if (!username) {
33 usernameInput?.focus();
34 return;
35 }
36 if (!/^[a-zA-Z0-9_-]+$/.test(username)) {
37 showError(
38 "Username may only contain letters, numbers, hyphens, and underscores",
39 );
40 return;
41 }
42 if (applicationInput && !applicationInput.value.trim()) {
43 applicationInput.focus();
44 return;
45 }
46
47 btn.disabled = true;
48 btn.textContent = "Creating account…";
49 showError("");
50 try {
51 const createResp = await fetch("/auth/passkey/create-user", {
52 method: "POST",
53 headers: { "Content-Type": "application/json" },
54 body: JSON.stringify({
55 username,
56 application: applicationInput
57 ? applicationInput.value.trim()
58 : undefined,
59 }),
60 });
61 if (!createResp.ok) {
62 const err = await createResp.json().catch(() => ({}));
63 showError(err.error ?? "Failed to create account");
64 return;
65 }
66 const data = await createResp.json();
67 // In queue mode the account is pending, but the passkey still has
68 // to be registered now, while the create-user session is valid.
69 const pending = Boolean(data.pending);
70
71 btn.textContent = "Waiting for authenticator…";
72 const optsResp = await fetch("/auth/passkey/register/options", {
73 method: "POST",
74 });
75 if (!optsResp.ok)
76 throw new Error("Failed to get registration options");
77 const opts = await optsResp.json();
78 const result = await startRegistration({ optionsJSON: opts });
79 const verResp = await fetch("/auth/passkey/register/verify", {
80 method: "POST",
81 headers: { "Content-Type": "application/json" },
82 body: JSON.stringify(result),
83 });
84 if (verResp.ok) {
85 if (pending) {
86 showPending();
87 } else {
88 window.location.href = "/";
89 }
90 } else {
91 const err = await verResp.json().catch(() => ({}));
92 showError(err.error ?? "Passkey registration failed");
93 }
94 } catch (e) {
95 showError(
96 "Passkey registration failed: " +
97 (e instanceof Error ? e.message : String(e)),
98 );
99 } finally {
100 btn.disabled = false;
101 btn.textContent = originalText;
102 }
103 });
104}
105