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
18if (btn) {
19 const originalText = btn.textContent;
20 btn.addEventListener("click", async () => {
21 const username = usernameInput?.value.trim() ?? "";
22 if (!username) {
23 usernameInput?.focus();
24 return;
25 }
26 if (!/^[a-zA-Z0-9_-]+$/.test(username)) {
27 showError(
28 "Username may only contain letters, numbers, hyphens, and underscores",
29 );
30 return;
31 }
32 if (applicationInput && !applicationInput.value.trim()) {
33 applicationInput.focus();
34 return;
35 }
36
37 btn.disabled = true;
38 btn.textContent = "Creating account…";
39 showError("");
40 try {
41 const createResp = await fetch("/auth/passkey/create-user", {
42 method: "POST",
43 headers: { "Content-Type": "application/json" },
44 body: JSON.stringify({
45 username,
46 application: applicationInput
47 ? applicationInput.value.trim()
48 : undefined,
49 }),
50 });
51 if (!createResp.ok) {
52 const err = await createResp.json().catch(() => ({}));
53 showError(err.error ?? "Failed to create account");
54 return;
55 }
56 const data = await createResp.json();
57 if (data.pending) {
58 const container = document.querySelector(".auth-container");
59 if (container) {
60 container.innerHTML =
61 '<h1 class="page-title">Create account</h1>' +
62 '<p class="form-success">Your account has been submitted for review. You will be able to log in once an admin approves it.</p>' +
63 '<p class="auth-footer">Already have an account? <a href="/login">Sign in</a></p>';
64 }
65 return;
66 }
67
68 btn.textContent = "Waiting for authenticator…";
69 const optsResp = await fetch("/auth/passkey/register/options", {
70 method: "POST",
71 });
72 if (!optsResp.ok)
73 throw new Error("Failed to get registration options");
74 const opts = await optsResp.json();
75 const result = await startRegistration({ optionsJSON: opts });
76 const verResp = await fetch("/auth/passkey/register/verify", {
77 method: "POST",
78 headers: { "Content-Type": "application/json" },
79 body: JSON.stringify(result),
80 });
81 if (verResp.ok) {
82 window.location.href = "/";
83 } else {
84 const err = await verResp.json().catch(() => ({}));
85 showError(err.error ?? "Passkey registration failed");
86 }
87 } catch (e) {
88 showError(
89 "Passkey registration failed: " +
90 (e instanceof Error ? e.message : String(e)),
91 );
92 } finally {
93 btn.disabled = false;
94 btn.textContent = originalText;
95 }
96 });
97}
98