// deno-lint-ignore-file const { startRegistration, startAuthentication } = SimpleWebAuthnBrowser; function setStatus(msg, isError = false) { const el = document.getElementById("statusMessage"); if (el) { el.textContent = msg; el.className = isError ? "error" : "success"; el.style.display = msg ? "block" : "none"; } } async function startWebAuthnConditionalLogin() { try { const resp = await fetch("/api/login/challenge", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ username: "" }), }); if (!resp.ok) return; const data = await resp.json(); const asseResp = await startAuthentication({ optionsJSON: data.options, useBrowserAutofill: true, verifyBrowserAutofillInput: true, }); if (!asseResp) return; const verificationResp = await fetch("/api/login/verify", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ response: asseResp }), }); const verificationJSON = await verificationResp.json(); if (verificationJSON.success) { setStatus("Autofill login successful! Redirecting..."); let targetRedirect = "/dashboard"; try { const params = new URLSearchParams(window.location.search); const rawRedirect = params.get("redirect"); if ( rawRedirect && (rawRedirect.startsWith("/") || rawRedirect.includes(".atyg.org")) ) { targetRedirect = rawRedirect; } } catch (_e) {} window.location.replace(targetRedirect); } } catch (err) { // Conditional UI errors (e.g. user canceled autofill prompt) should fail silently console.debug("[WebAuthn Conditional UI]", err); } } async function startWebAuthnLogin(username) { setStatus(""); try { // 1. Fetch challenge const resp = await fetch("/api/login/challenge", { method: "POST", headers: { "Content-Type": "application/json", }, body: JSON.stringify({ username: username || "" }), }); let data; try { data = await resp.json(); } catch { const text = await resp.text().catch(() => ""); setStatus(`Login challenge failed (${resp.status}): ${text}`, true); return; } if (!resp.ok) { setStatus(data.error || "Failed to get login challenge", true); return; } // 2. Pass challenge to authenticator let asseResp; try { asseResp = await startAuthentication({ optionsJSON: data.options }); } catch (error) { setStatus(error.message || "Authentication failed on device", true); throw error; } // Extract PRF extension results let extensionResults; if (typeof asseResp.getClientExtensionResults === "function") { extensionResults = asseResp.getClientExtensionResults(); } else { extensionResults = asseResp.clientExtensionResults || {}; } // 3. Send response back to verify const verificationResp = await fetch("/api/login/verify", { method: "POST", headers: { "Content-Type": "application/json", }, body: JSON.stringify({ response: asseResp, }), }); let verificationJSON; try { verificationJSON = await verificationResp.json(); } catch { const text = await verificationResp.text().catch(() => ""); setStatus( `Login verification failed (${verificationResp.status}): ${text}`, true, ); return; } if (verificationJSON.success) { setStatus("Login successful! Redirecting..."); let targetRedirect = "/dashboard"; try { const params = new URLSearchParams(window.location.search); const rawRedirect = params.get("redirect"); if (rawRedirect) { if (rawRedirect.startsWith("/") && !rawRedirect.startsWith("//")) { targetRedirect = rawRedirect; } else { const parsed = new URL(rawRedirect); if ( parsed.hostname.endsWith(".atyg.org") || parsed.hostname === "atyg.org" || parsed.hostname === "localhost" ) { targetRedirect = rawRedirect; } } } } catch (_e) { // Fallback to default } window.location.replace(targetRedirect); await new Promise((resolve) => setTimeout(resolve, 5000)); } else { setStatus(verificationJSON.error || "Login verification failed", true); } } catch (err) { console.error("[WebAuthn Login]", err); setStatus( err.message || "An unexpected error occurred during authentication", true, ); } } async function startWebAuthnRegistration(username, inviteCode) { setStatus(""); if (!username || !inviteCode) { setStatus("Username and Invite Code are required.", true); return null; } try { // 1. Fetch challenge from API const resp = await fetch("/api/register/challenge", { method: "POST", headers: { "Content-Type": "application/json", }, body: JSON.stringify({ username, inviteCode }), }); let data; try { data = await resp.json(); } catch { const text = await resp.text().catch(() => ""); setStatus(`Challenge request failed (${resp.status}): ${text}`, true); return null; } if (!resp.ok) { setStatus(data.error || "Failed to get registration challenge", true); return null; } // 2. Pass challenge to authenticator let attResp; try { attResp = await startRegistration({ optionsJSON: data.options }); } catch (error) { if (error.name === "InvalidStateError") { setStatus("Authenticator was probably already registered.", true); } else { setStatus(error.message || "Registration failed on device", true); } throw error; } // Extract PRF client extension result let extensionResults; if (typeof attResp.getClientExtensionResults === "function") { extensionResults = attResp.getClientExtensionResults(); } else { extensionResults = attResp.clientExtensionResults || {}; } // 3. Send response back to verify const verificationResp = await fetch("/api/register/verify", { method: "POST", headers: { "Content-Type": "application/json", }, body: JSON.stringify({ username, inviteCode, response: { ...attResp, clientExtensionResults: extensionResults, }, }), }); let verificationJSON; try { verificationJSON = await verificationResp.json(); } catch { const text = await verificationResp.text().catch(() => ""); setStatus( `Verification failed (${verificationResp.status}): ${text}`, true, ); return null; } if (verificationJSON.success) { return verificationJSON; } else { setStatus( verificationJSON.error || "Registration verification failed", true, ); return null; } } catch (err) { console.error(err); return null; } }