// Sign in with Verae for the mcp.verae.ai pages (DEC-657). // // Uses the shared Verae sign-in (portal device grant, RFC 8628 - the same flow as `verae login` and the apps): // POST zap /portal/api/device/authorize -> the user approves the code at zap.verae.ai/portal/device.html // POST zap /portal/api/device/token (poll) -> an xVerae- API key for this browser tab. // The key is kept in sessionStorage only (this tab, this origin; gone when the tab closes). Never localStorage, // never a cookie. Stamps send it as Authorization: Bearer to /mcp and are charged to the signed-in account. // Verify and lookups never need it. const ZAP = "https://zap.verae.ai"; const SS_KEY = "verae.signin.key"; export function currentKey() { try { return sessionStorage.getItem(SS_KEY) || ""; } catch (_) { return ""; } } function setKey(k) { try { k ? sessionStorage.setItem(SS_KEY, k) : sessionStorage.removeItem(SS_KEY); } catch (_) {} } async function zap(path, init = {}, key = currentKey()) { const headers = Object.assign({ "Accept": "application/json" }, init.headers || {}); if (init.body) headers["Content-Type"] = "application/json"; if (key) headers["Authorization"] = "Bearer " + key; const res = await fetch(ZAP + path, { method: init.method || "GET", headers, body: init.body, credentials: "omit", cache: "no-store" }); let body = {}; try { body = await res.json(); } catch (_) {} return { status: res.status, ok: res.ok, body }; } /** Price, balance and account for the signed-in key; null (and the key dropped) when it is no longer valid. */ export async function account() { const key = currentKey(); if (!key) return null; const r = await zap("/v1/account"); if (r.status === 401 || r.status === 403) { setKey(""); return null; } if (!r.ok) throw new Error("Could not read your Verae account (" + r.status + ")"); return r.body; } const sleep = (ms) => new Promise((r) => setTimeout(r, ms)); /** * Run the device sign-in. onCode({user_code, url}) is called once the code exists so the page can show it with a * link (a popup opened after an await is often blocked). Resolves with the account, or throws on deny/expiry/cancel. */ export async function signIn({ onCode, signal, clientName } = {}) { const start = await zap("/portal/api/device/authorize", { method: "POST", body: JSON.stringify({ client_name: clientName || ("Verae web (" + location.host + ")"), device_name: "Web browser" }), }, ""); if (!start.ok) throw new Error((start.body && (start.body.error_description || start.body.error)) || ("Sign-in could not start (" + start.status + ")")); const { device_code, user_code, verification_uri_complete, interval, expires_in } = start.body; if (onCode) onCode({ user_code, url: verification_uri_complete }); let wait = Math.max(5, Number(interval) || 5) * 1000; const until = Date.now() + (Number(expires_in) || 600) * 1000; while (Date.now() < until) { await sleep(wait); if (signal && signal.aborted) throw new Error("Sign-in cancelled"); const t = await zap("/portal/api/device/token", { method: "POST", body: JSON.stringify({ grant_type: "urn:ietf:params:oauth:grant-type:device_code", device_code }), }, ""); if (t.ok && t.body.api_key) { setKey(t.body.api_key); return account(); } const e = t.body && t.body.error; if (e === "authorization_pending") continue; if (e === "slow_down") { wait = Math.max(wait + 5000, (Number(t.body.interval) || 0) * 1000); continue; } if (e === "access_denied") throw new Error("Sign-in was denied"); if (e === "expired_token") throw new Error("Sign-in code expired - try again"); throw new Error("Sign-in failed (" + (e || t.status) + ")"); } throw new Error("Sign-in code expired - try again"); } /** Revoke this tab's key and forget it. */ export async function signOut() { const key = currentKey(); setKey(""); if (key) { try { await zap("/v1/tokens/self", { method: "DELETE" }, key); } catch (_) {} } } function el(tag, attrs = {}, text) { const e = document.createElement(tag); for (const [k, v] of Object.entries(attrs)) e.setAttribute(k, v); if (text !== undefined) e.textContent = text; return e; } /** * Mount the sign-in box into `host`. Returns { ensureSignedIn(), refresh(), account() }. * ensureSignedIn() resolves with the account once signed in (starting the sign-in if needed). */ export function mountSignIn(host, opts = {}) { if (!document.getElementById("verae-signin-css")) { const css = el("style", { id: "verae-signin-css" }); css.textContent = ".verae-signin [hidden]{display:none!important}" + ".verae-code{margin:10px 0;padding:10px 14px;border-radius:10px;background:rgba(127,127,127,.12);display:flex;flex-wrap:wrap;gap:6px;align-items:center}" + "#veraeUserCode{font-family:ui-monospace,Menlo,monospace;font-size:18px;letter-spacing:.08em}"; document.head.appendChild(css); } host.textContent = ""; host.classList.add("verae-signin"); const line = el("p", { class: "meta", "data-testid": "signin-status" }); const btnIn = el("button", { type: "button", id: "veraeSignInBtn" }, "Sign in with Verae"); const btnOut = el("button", { type: "button", class: "secondary", id: "veraeSignOutBtn" }, "Sign out"); const codeBox = el("div", { class: "verae-code", hidden: "" }); const row = el("div", { class: "btn-row" }); row.append(btnIn, btnOut); host.append(line, codeBox, row); let acct = null; let pending = null; let abort = null; function fmt(n) { return (typeof n === "number" ? n : 0).toLocaleString() + " credits"; } function render() { btnIn.hidden = !!acct; btnOut.hidden = !acct; if (acct) { line.textContent = "Signed in as " + (acct.email || acct.name || acct.customer_id) + " · price per new document " + (acct.stamp_price_credits == null ? "n/a" : fmt(acct.stamp_price_credits)) + " · balance " + fmt(acct.balance_credits) + " · this month " + fmt(acct.period_spend_credits); } else if (!pending) { line.textContent = "Checking and looking up documents is free and needs no account. Registering a new document needs a Verae account; it is charged to that account."; } if (opts.onChange) opts.onChange(acct); } async function refresh() { try { acct = await account(); } catch (e) { line.textContent = String(e.message || e); } render(); return acct; } function start() { if (pending) return pending; abort = new AbortController(); btnIn.disabled = true; line.textContent = "Starting Verae sign-in…"; pending = signIn({ signal: abort.signal, onCode: ({ user_code, url }) => { codeBox.hidden = false; codeBox.textContent = ""; const a = el("a", { href: url, target: "_blank", rel: "noopener noreferrer", id: "veraeSignInLink" }, "Open the Verae sign-in page"); const cancel = el("button", { type: "button", class: "secondary" }, "Cancel"); cancel.addEventListener("click", () => abort && abort.abort()); codeBox.append(el("span", {}, "Confirm code "), el("strong", { id: "veraeUserCode" }, user_code), el("span", {}, " at zap.verae.ai: "), a, el("span", {}, " "), cancel); line.textContent = "Waiting for you to approve the sign-in in the Verae portal…"; try { window.open(url, "_blank", "noopener"); } catch (_) {} }, }).then((a) => { acct = a; return a; }).finally(() => { pending = null; abort = null; btnIn.disabled = false; codeBox.hidden = true; render(); }); return pending; } btnIn.addEventListener("click", () => { start().catch((e) => { line.textContent = String(e.message || e); }); }); btnOut.addEventListener("click", async () => { await signOut(); acct = null; render(); }); refresh(); return { account: () => acct, refresh, async ensureSignedIn() { return acct || (await refresh()) || start(); }, }; } /** Confirm a charge before stamping. Returns true when the user agrees. */ export function confirmCharge(acct, count, scope = "global") { const price = scope !== "global" ? 0 : (acct && typeof acct.stamp_price_credits === "number" ? acct.stamp_price_credits : null); const total = price == null ? null : price * count; const msg = "Register " + count + " new document" + (count === 1 ? "" : "s") + " with Verae?\n\n" + (price == null ? "Price: see your Verae plan" : ("Price: " + price + " credits each, " + total + " credits in total")) + "\nBalance: " + ((acct && acct.balance_credits) || 0) + " credits" + "\nCharged to: " + ((acct && (acct.email || acct.name || acct.customer_id)) || "your Verae account") + "\n\nDocuments already registered are not charged again."; return window.confirm(msg); }