// Shared passkey helpers (sign-in page + Settings). const b64u = { enc: buf => btoa(String.fromCharCode(...new Uint8Array(buf))).replace(/\+/g, '-').replace(/\//g, '_').replace(/=+$/, ''), dec: s => Uint8Array.from(atob(s.replace(/-/g, '+').replace(/_/g, '/') + '='.repeat((4 - s.length % 4) % 4)), c => c.charCodeAt(0)), }; async function authPost(path, body, method = 'POST') { const r = await fetch(path, { method, headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(body || {}) }); const j = await r.json().catch(() => ({})); if (!r.ok) throw new Error(j.error || 'Request failed (' + r.status + ')'); return j; } async function passkeyRegister(setupCode, name, inviteCode) { const { cid, options } = await authPost('/api/auth/register/options', { setupCode, inviteCode }); options.challenge = b64u.dec(options.challenge); options.user.id = b64u.dec(options.user.id); (options.excludeCredentials || []).forEach(c => { c.id = b64u.dec(c.id); }); const cred = await navigator.credentials.create({ publicKey: options }); await authPost('/api/auth/register/verify', { cid, name, response: { id: cred.id, rawId: b64u.enc(cred.rawId), type: cred.type, response: { clientDataJSON: b64u.enc(cred.response.clientDataJSON), attestationObject: b64u.enc(cred.response.attestationObject), transports: cred.response.getTransports ? cred.response.getTransports() : [] }, clientExtensionResults: cred.getClientExtensionResults(), authenticatorAttachment: cred.authenticatorAttachment, } }); } async function passkeyLogin() { const { cid, options } = await authPost('/api/auth/login/options'); options.challenge = b64u.dec(options.challenge); (options.allowCredentials || []).forEach(c => { c.id = b64u.dec(c.id); }); const a = await navigator.credentials.get({ publicKey: options }); await authPost('/api/auth/login/verify', { cid, response: { id: a.id, rawId: b64u.enc(a.rawId), type: a.type, response: { clientDataJSON: b64u.enc(a.response.clientDataJSON), authenticatorData: b64u.enc(a.response.authenticatorData), signature: b64u.enc(a.response.signature), userHandle: a.response.userHandle ? b64u.enc(a.response.userHandle) : undefined }, clientExtensionResults: a.getClientExtensionResults(), } }); }