cauthy-auth 0.24.3 → 0.26.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/client.d.mts +36 -7
- package/dist/client.d.ts +36 -7
- package/dist/client.js +14 -1
- package/dist/client.js.map +1 -1
- package/dist/client.mjs +14 -1
- package/dist/client.mjs.map +1 -1
- package/dist/index.d.mts +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.js +25 -5
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +25 -5
- package/dist/index.mjs.map +1 -1
- package/dist/react.d.mts +40 -6
- package/dist/react.d.ts +40 -6
- package/dist/react.js +39 -7
- package/dist/react.js.map +1 -1
- package/dist/react.mjs +39 -7
- package/dist/react.mjs.map +1 -1
- package/dist/svelte.d.mts +1 -1
- package/dist/svelte.d.ts +1 -1
- package/dist/svelte.js +14 -1
- package/dist/svelte.js.map +1 -1
- package/dist/svelte.mjs +14 -1
- package/dist/svelte.mjs.map +1 -1
- package/dist/{validate-B_iC-4y8.d.mts → validate-BTRit1O1.d.mts} +1 -1
- package/dist/{validate-B_iC-4y8.d.ts → validate-BTRit1O1.d.ts} +1 -1
- package/dist/vue.d.mts +1 -1
- package/dist/vue.d.ts +1 -1
- package/dist/vue.js +14 -1
- package/dist/vue.js.map +1 -1
- package/dist/vue.mjs +14 -1
- package/dist/vue.mjs.map +1 -1
- package/package.json +1 -1
package/dist/svelte.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/svelte.ts","../src/passkey.ts","../src/url.ts","../src/theme.ts","../src/validate.ts","../src/client.ts","../src/store.ts","../src/permissions.ts"],"sourcesContent":["// Svelte: who is signed in, as stores.\n//\n// // src/lib/auth.ts\n// export const { session, status, auth, signIn, signOut } = createCauthySession()\n//\n// <script>\n// import { session, auth } from '$lib/auth'\n// </script>\n// {#if $session}Hello {$session.user.name}{/if}\n//\n// `session` and `status` keep Svelte's store contract, so `$session` works in\n// Svelte 4 and 5 alike, and nothing here imports Svelte.\n//\n// On the server (SvelteKit) the routes are one file, with `createCauthy()`:\n//\n// // src/routes/api/auth/[...cauthy]/+server.ts\n// export const GET = ({ request }) => auth.handler(request)\n// export const POST = GET\n\nimport { createSessionStore, type SessionState, type SessionStore, type SessionStoreOptions } from './store'\nimport type { PublicSession } from './types'\n\n/** A value Svelte can read with `$`: its store contract. */\nexport interface Readable<T> {\n subscribe(run: (value: T) => void): () => void\n}\n\nexport interface CauthySession extends Pick<SessionStore, 'refresh' | 'signIn' | 'signOut' | 'accountUrl' | 'auth' | 'basePath'> {\n /** The signed-in person and their organizations, or null: `$session`. */\n session: Readable<PublicSession | null>\n /** `$status`: loading, authenticated or unauthenticated. */\n status: Readable<SessionState['status']>\n}\n\n/**\n * Give it `initialSession` from a load function (`event.locals`, or\n * `auth.toPublic(await auth.getSession(request))`) and the first paint knows\n * who is there.\n */\nexport function createCauthySession(options: SessionStoreOptions = {}): CauthySession {\n const store = createSessionStore(options)\n // Each store tells its subscribers only when its own value changed.\n const part = <T,>(pick: (s: SessionState) => T): Readable<T> => ({\n subscribe(run) {\n let last: T | undefined\n let told = false\n return store.subscribe((s) => {\n const value = pick(s)\n if (told && value === last) return\n told = true\n last = value\n run(value)\n })\n },\n })\n return {\n session: part((s) => s.session),\n status: part((s) => s.status),\n refresh: store.refresh,\n signIn: store.signIn,\n signOut: store.signOut,\n accountUrl: store.accountUrl,\n auth: store.auth,\n basePath: store.basePath,\n }\n}\n\nexport type { SessionStoreOptions as CauthySessionOptions } from './store'\nexport type { AuthClient, AuthError, AuthMethods, AuthResult, Account, AccountSession, Passkey, PasskeyKind, SSOConnection } from './client'\nexport type { PublicSession, SessionOrganization, SessionUser } from './types'\nexport { can, definePolicy, type PermissionOf, type Policy, type RoleOf } from './permissions'\n","// A passkey ceremony in the browser: what Cauthy says to ask for, in the\n// shape the browser's WebAuthn API takes, and what the browser answers, in\n// the shape Cauthy reads. Both travel as JSON, with bytes in base64url.\n\nimport type { AuthError } from './client'\n\n/** Whether this browser can use a passkey at all. */\nexport function canUsePasskeys(): boolean {\n return typeof window !== 'undefined' && typeof window.PublicKeyCredential === 'function' &&\n typeof navigator !== 'undefined' && typeof navigator.credentials?.get === 'function'\n}\n\n/**\n * Whether this browser offers passkeys in the autofill of a field marked\n * `autocomplete=\"username webauthn\"`, so the person picks one without\n * looking for a button.\n */\nexport async function canAutofillPasskeys(): Promise<boolean> {\n if (!canUsePasskeys()) return false\n const pkc = window.PublicKeyCredential as unknown as { isConditionalMediationAvailable?: () => Promise<boolean> }\n try {\n return typeof pkc.isConditionalMediationAvailable === 'function' && (await pkc.isConditionalMediationAvailable()) === true\n } catch {\n return false\n }\n}\n\nfunction bytes(text: string): ArrayBuffer {\n const b64 = text.replace(/-/g, '+').replace(/_/g, '/').padEnd(Math.ceil(text.length / 4) * 4, '=')\n const bin = atob(b64)\n const out = new Uint8Array(bin.length)\n for (let i = 0; i < bin.length; i++) out[i] = bin.charCodeAt(i)\n return out.buffer\n}\n\nfunction text(buf: ArrayBuffer | null | undefined): string | null {\n if (!buf) return null\n const view = new Uint8Array(buf)\n let bin = ''\n for (let i = 0; i < view.byteLength; i++) bin += String.fromCharCode(view[i])\n return btoa(bin).replace(/\\+/g, '-').replace(/\\//g, '_').replace(/=+$/, '')\n}\n\nconst withId = (list: any[] | undefined) => (list ?? []).map((c) => ({ ...c, id: bytes(c.id) }))\n\n/**\n * Asks the browser for a passkey, with the options Cauthy sent. `conditional`:\n * in the autofill of the page's username field, waiting until the person\n * picks one or `signal` aborts.\n */\nexport async function getPasskey(options: any, how: { mediation?: 'conditional'; signal?: AbortSignal } = {}): Promise<Record<string, unknown>> {\n const publicKey = { ...options, challenge: bytes(options.challenge), allowCredentials: withId(options.allowCredentials) }\n const cred = (await navigator.credentials.get({ publicKey, ...how } as CredentialRequestOptions)) as PublicKeyCredential | null\n if (cred === null) throw new DOMException('No passkey was chosen.', 'NotAllowedError')\n const r = cred.response as AuthenticatorAssertionResponse\n return {\n id: cred.id, rawId: text(cred.rawId), type: cred.type,\n response: {\n authenticatorData: text(r.authenticatorData), clientDataJSON: text(r.clientDataJSON),\n signature: text(r.signature), userHandle: text(r.userHandle),\n },\n clientExtensionResults: cred.getClientExtensionResults?.() ?? {},\n }\n}\n\n/** Asks the browser to make a passkey, with the options Cauthy sent. */\nexport async function makePasskey(options: any): Promise<Record<string, unknown>> {\n const publicKey = {\n ...options,\n challenge: bytes(options.challenge),\n user: { ...options.user, id: bytes(options.user.id) },\n excludeCredentials: withId(options.excludeCredentials),\n }\n const cred = (await navigator.credentials.create({ publicKey })) as PublicKeyCredential | null\n if (cred === null) throw new DOMException('No passkey was made.', 'NotAllowedError')\n const r = cred.response as AuthenticatorAttestationResponse\n return {\n id: cred.id, rawId: text(cred.rawId), type: cred.type,\n response: {\n clientDataJSON: text(r.clientDataJSON), attestationObject: text(r.attestationObject),\n transports: typeof r.getTransports === 'function' ? r.getTransports() : [],\n },\n clientExtensionResults: cred.getClientExtensionResults?.() ?? {},\n }\n}\n\n/** What the browser's refusal means, for the screen. */\nexport function ceremonyError(e: unknown): AuthError {\n const name = e instanceof Error || e instanceof DOMException ? e.name : ''\n if (name === 'NotAllowedError' || name === 'AbortError') return { code: 'cancelled', message: 'The passkey was not used. Try again when you are ready.' }\n if (name === 'InvalidStateError') return { code: 'passkey_exists', message: 'This device already has a passkey for this account.' }\n if (name === 'SecurityError') return { code: 'passkey_unavailable', message: 'Passkeys cannot be used on this page.' }\n return { code: 'passkey_failed', message: 'The passkey could not be used. Try again, or sign in another way.' }\n}\n","/**\n * The address without its trailing slashes: `https://auth.example.com/` and\n * `https://auth.example.com` name the same issuer. A loop, not `/\\/+$/`,\n * which backtracks over a long run of slashes.\n */\nexport function withoutTrailingSlashes(s: string): string {\n let end = s.length\n while (end > 0 && s.charCodeAt(end - 1) === 47) end--\n return s.slice(0, end)\n}\n","// An application's look, for Cauthy's hosted pages.\n//\n// Cauthy's pages — sign-in, sign-up, the account page — can take the colours,\n// corners, typeface and logo of the application that sends its people there,\n// so signing in reads as a page of the application. A theme is a short list of\n// values, which Cauthy checks one by one; it is never CSS.\n//\n// An application built with Stackmyth already has those values, as its\n// `--sm-*` tokens. `themeFromStackmyth` reads them out of its stylesheets.\n\nimport { withoutTrailingSlashes } from './url'\n\n/** How an application looks on Cauthy's hosted pages. Every value is optional. */\n/** One font file the application serves: a family in one weight (or a range) and one style. */\nexport interface ThemeFont {\n family: string\n /** An https address of a `.woff2` file. It must answer with `Access-Control-Allow-Origin` for Cauthy's origin. */\n url: string\n /** `400`, or `\"100 900\"` for a variable font. Default: 400. */\n weight?: number | string\n style?: 'normal' | 'italic'\n}\n\n/** What an application says on its sign-in page, in one language. */\nexport interface ThemeTexts {\n /** Replaces \"Sign in\". */\n heading?: string\n /** Replaces \"to continue to …\". */\n subheading?: string\n /** The messages of the split layout's panel, shown one at a time (up to 5). */\n panel?: string[]\n /** Links at the foot of the split layout: privacy, terms (up to 4). */\n links?: { label: string; url: string }[]\n}\n\nexport interface CauthyTheme {\n /** The page. Colours are `#rrggbb` or `rgba(r, g, b, a)`. */\n background?: string\n /** The card, and inputs. */\n surface?: string\n /** Headings and what is typed. */\n text?: string\n /** Paragraphs, labels, small print. */\n textMuted?: string\n border?: string\n /** The fields and the secondary buttons, when they are not the card's colours. */\n inputBackground?: string\n inputBorder?: string\n /** The main button, and what is written on it. */\n primary?: string\n primaryText?: string\n /** Links, and the focus ring. */\n accent?: string\n error?: string\n errorBackground?: string\n success?: string\n successBackground?: string\n /** Corners of cards and inputs, in pixels (0–40). */\n radius?: number\n /** Corners of buttons, in pixels (0–40). */\n buttonRadius?: number\n /** A CSS font-family list: the application's typeface first, a system font after it. */\n fontFamily?: string\n /** The files of the typefaces `fontFamily` names, for people who do not have them installed. */\n fonts?: ThemeFont[]\n /** An image shown where \"Cauthy\" is, over https. */\n logoUrl?: string\n /**\n * How the sign-in pages are laid out: `card` (the default, one card in the\n * middle) or `split` (the form on one side, a panel that presents the\n * application on the other).\n */\n layout?: 'card' | 'split'\n /** The colours the split layout's backdrop blends, one to four. */\n panelColors?: string[]\n /** The colour of what is written on the panel. */\n panelText?: string\n /** An https image shown over the panel's messages. */\n panelIconUrl?: string\n /** The application's own words on the sign-in page, by language (`en`, `es`). */\n texts?: Record<string, ThemeTexts>\n /**\n * The application's Stackmyth tokens (`--sm-*`), from `tokensFromStackmyth`.\n * Cauthy's pages are drawn with Stackmyth's components, so these make its\n * buttons and fields the application's own. They are laid over the named values.\n */\n tokens?: Record<string, string>\n}\n\nexport interface StackmythThemeOptions {\n /**\n * The named theme to read, when the stylesheet keeps several under\n * `[data-theme=…]` (`\"vennet\"`). Without it only what applies to every\n * page (`:root`) is read.\n */\n theme?: string\n /** Which of the application's two modes to take. Default: `light`. */\n mode?: 'light' | 'dark'\n logoUrl?: string\n}\n\nexport interface ImportedTheme {\n theme: CauthyTheme\n /** Tokens that were there and could not be carried over, and why. */\n warnings: string[]\n}\n\nconst MAX_RADIUS = 40\n\n// ── Reading custom properties out of CSS ───────────────────────────────────\n\ninterface Declaration {\n name: string\n value: string\n /** The selectors and at-rules it sits inside, outermost first. */\n within: string[]\n}\n\n// The stylesheet without its /* comments */, read in one pass. An unclosed\n// comment runs to the end, as a browser reads it.\nfunction withoutComments(css: string): string {\n let out = ''\n let i = 0\n for (;;) {\n const open = css.indexOf('/*', i)\n if (open < 0) return out + css.slice(i)\n out += css.slice(i, open)\n const close = css.indexOf('*/', open + 2)\n if (close < 0) return out\n i = close + 2\n }\n}\n\nfunction declarations(css: string): Declaration[] {\n const text = withoutComments(css)\n const out: Declaration[] = []\n const within: string[] = []\n let chunk = ''\n let quote = ''\n let depth = 0 // parentheses, so a `;` inside url(…) does not end anything\n const flush = () => {\n const m = /^\\s*(--[\\w-]+)\\s*:\\s*([\\s\\S]+?)\\s*$/.exec(chunk)\n if (m) out.push({ name: m[1], value: m[2].replace(/\\s+/g, ' ').replace(/\\s*!important$/, ''), within: [...within] })\n chunk = ''\n }\n for (const ch of text) {\n if (quote) {\n chunk += ch\n if (ch === quote) quote = ''\n continue\n }\n if (ch === '\"' || ch === \"'\") {\n quote = ch\n chunk += ch\n } else if (ch === '(') {\n depth++\n chunk += ch\n } else if (ch === ')') {\n depth = Math.max(0, depth - 1)\n chunk += ch\n } else if (ch === '{' && depth === 0) {\n within.push(chunk.trim().replace(/\\s+/g, ' '))\n chunk = ''\n } else if (ch === '}' && depth === 0) {\n flush()\n within.pop()\n } else if (ch === ';' && depth === 0) {\n flush()\n } else {\n chunk += ch\n }\n }\n return out\n}\n\nconst DARK = /prefers-color-scheme:\\s*dark|data-(mode|theme|color-scheme)=[\"']?dark|\\.dark\\b/i\nconst LIGHT = /prefers-color-scheme:\\s*light|data-(mode|theme|color-scheme)=[\"']?light|\\.light\\b/i\nconst NAMED = /data-theme=[\"']?([\\w-]+)/i\n\n/** Whether a declaration applies to the page being themed. */\nfunction applies(d: Declaration, o: Required<Pick<StackmythThemeOptions, 'mode'>> & { theme?: string }): boolean {\n for (const scope of d.within) {\n if (o.mode === 'light' && DARK.test(scope)) return false\n if (o.mode === 'dark' && LIGHT.test(scope)) return false\n const named = NAMED.exec(scope)\n if (named && !/^(dark|light)$/i.test(named[1]) && named[1] !== o.theme) return false\n // Component-level rules (`.btn { --sm-… }`) are not the page's theme;\n // the class a mode is switched with (`.dark`, as shadcn/ui does) is.\n if (!scope.startsWith('@') && !/:root|^html\\b|^body\\b|\\[data-|^\\*$|:host|^\\.(dark|light)\\b/.test(scope)) return false\n }\n return true\n}\n\n// ── Tokens ─────────────────────────────────────────────────────────────────\n\n/** What a token's value may be written with, as Cauthy checks it. */\nconst TOKEN_CHARS = /^[A-Za-z0-9 #%.,()/'\"_+*-]+$/\nconst TOKEN_FUNCS = new Set(['var', 'calc', 'min', 'max', 'clamp', 'rgb', 'rgba', 'hsl', 'hsla', 'hwb', 'lab', 'lch', 'oklab', 'oklch', 'color',\n 'color-mix', 'light-dark', 'cubic-bezier', 'steps', 'linear', 'linear-gradient', 'radial-gradient', 'conic-gradient',\n 'repeating-linear-gradient', 'repeating-radial-gradient', 'drop-shadow', 'blur', 'translate', 'translatex', 'translatey', 'scale', 'rotate'])\n\nfunction tokenValue(value: string): string | null {\n const v = value.replace(/\\s+/g, ' ').trim()\n if (v === '' || v.length > 400 || !TOKEN_CHARS.test(v)) return null\n for (const m of v.matchAll(/([A-Za-z-]+)\\(/g)) if (!TOKEN_FUNCS.has(m[1].toLowerCase())) return null\n if (/^\\(|[ ,(]\\(/.test(v)) return null\n return v\n}\n\n/** A scope that is the page's theme itself: the root, or the theme and mode attributes — never a component inside it. */\nconst THEME_SELECTOR = /^(?::root|html|body|\\[data-(?:theme|mode|color-scheme)(?:=[\"']?[\\w-]+[\"']?)?\\])$/i\n// Checked a selector at a time (split on commas and spaces) rather than with\n// one pattern over the list, which backtracks on a long one.\nconst isThemeScope = (selectors: string) => {\n const parts = selectors.split(/[\\s,]+/).filter(Boolean)\n return parts.length > 0 && parts.every((one) => THEME_SELECTOR.test(one))\n}\n\nexport interface StackmythTokens {\n /** Every `--sm-*` token the stylesheets set for the page, ready for `theme.tokens`. */\n tokens: Record<string, string>\n /** Tokens that were there and Cauthy would not take, and why. */\n warnings: string[]\n}\n\n/**\n * Reads an application's Stackmyth tokens out of its stylesheets: every\n * `--sm-*` custom property its theme sets, in the order the sheets are given\n * (a later one wins). Its own names — a brand palette the tokens point at —\n * are written in, so what comes out stands on Stackmyth's names alone.\n *\n * Cauthy's sign-in pages are drawn with Stackmyth's components: these tokens\n * are what makes them look like the application.\n */\nexport function tokensFromStackmyth(css: string | string[], options: StackmythThemeOptions = {}): StackmythTokens {\n const mode = options.mode ?? 'light'\n const all = (Array.isArray(css) ? css : [css]).flatMap(declarations)\n const named = options.theme ?? all.map((d) => d.within.map((w) => NAMED.exec(w)?.[1]).find((n) => n && !/^(dark|light)$/i.test(n))).find(Boolean)\n const vars = new Map<string, string>()\n for (const d of all) {\n if (!applies(d, { mode, theme: named })) continue\n if (!d.within.every((w) => w.startsWith('@') || isThemeScope(w))) continue\n vars.set(d.name, d.value)\n }\n // The application's own names are written in; Stackmyth's are left as references.\n const inline = (value: string, depth = 0): string | null => {\n if (depth > 16) return null\n let failed = false\n let out = value\n for (let guard = 0; guard < 64; guard++) {\n const next = out.replace(/var\\(\\s*(--(?!sm-)[\\w-]+)\\s*(?:,\\s*([^()]*(?:\\([^()]*\\)[^()]*)*))?\\)/, (_m, name: string, fallback?: string) => {\n const known = vars.get(name)\n const got = known !== undefined ? inline(known, depth + 1) : fallback !== undefined ? inline(fallback.trim(), depth + 1) : null\n if (got === null) failed = true\n return got ?? ''\n })\n if (failed) return null\n if (next === out) break\n out = next\n }\n return out\n }\n const tokens: Record<string, string> = {}\n const warnings: string[] = []\n for (const [name, raw] of vars) {\n if (!/^--sm-[a-z0-9]+(?:-[a-z0-9]+)*$/.test(name)) continue\n const written = inline(raw)\n const value = written === null ? null : tokenValue(written)\n if (value === null) warnings.push(`${name}: \"${raw.length > 60 ? raw.slice(0, 57) + '…' : raw}\" is not a value Cauthy takes`)\n else tokens[name] = value\n }\n if (Object.keys(tokens).length === 0) warnings.push('no --sm-* token was found in these stylesheets')\n return { tokens, warnings }\n}\n\n// ── Values ─────────────────────────────────────────────────────────────────\n\nfunction resolve(value: string, vars: Map<string, string>, seen = 0): string | null {\n if (seen > 16) return null\n let unresolved = false\n // Innermost var() first: fallbacks may hold another var().\n let out = value\n for (let guard = 0; guard < 32 && /var\\(/.test(out); guard++) {\n out = out.replace(/var\\(\\s*(--[\\w-]+)\\s*(?:,\\s*([^()]*(?:\\([^()]*\\)[^()]*)*))?\\)/, (_m, name: string, fallback?: string) => {\n const known = vars.get(name)\n const next = known !== undefined ? resolve(known, vars, seen + 1) : fallback !== undefined ? resolve(fallback.trim(), vars, seen + 1) : null\n if (next === null) unresolved = true\n return next ?? ''\n })\n if (unresolved) return null\n }\n return /var\\(/.test(out) ? null : out.trim()\n}\n\nconst clamp01 = (n: number) => Math.min(1, Math.max(0, n))\nconst hex2 = (n: number) => Math.round(clamp01(n) * 255).toString(16).padStart(2, '0')\nconst gamma = (c: number) => (c <= 0.0031308 ? 12.92 * c : 1.055 * Math.pow(c, 1 / 2.4) - 0.055)\n\nfunction out(r: number, g: number, b: number, a = 1): string {\n if (a >= 1) return `#${hex2(r)}${hex2(g)}${hex2(b)}`\n return `rgba(${Math.round(clamp01(r) * 255)}, ${Math.round(clamp01(g) * 255)}, ${Math.round(clamp01(b) * 255)}, ${Math.round(clamp01(a) * 1000) / 1000})`\n}\n\nconst alpha = (s: string | undefined) => (s === undefined ? 1 : s.endsWith('%') ? parseFloat(s) / 100 : parseFloat(s))\n\nconst KEYWORDS: Record<string, string> = { white: '#ffffff', black: '#000000' }\n\n/** A colour as Cauthy takes it — `#rrggbb` or `rgba(…)` — or null when it is none it can read. */\nexport function toColor(value: string): string | null {\n const v = value.trim().toLowerCase()\n if (KEYWORDS[v]) return KEYWORDS[v]\n if (/^#([0-9a-f]{3,4}|[0-9a-f]{6}|[0-9a-f]{8})$/.test(v)) return v\n let m = /^rgba?\\(\\s*([\\d.]+%?)[\\s,]+([\\d.]+%?)[\\s,]+([\\d.]+%?)(?:\\s*[,/]\\s*([\\d.]+%?))?\\s*\\)$/.exec(v)\n if (m) {\n const ch = (s: string) => (s.endsWith('%') ? parseFloat(s) / 100 : parseFloat(s) / 255)\n return out(ch(m[1]), ch(m[2]), ch(m[3]), alpha(m[4]))\n }\n m = /^hsla?\\(\\s*([\\d.]+)(?:deg)?[\\s,]+([\\d.]+)%[\\s,]+([\\d.]+)%(?:\\s*[,/]\\s*([\\d.]+%?))?\\s*\\)$/.exec(v)\n if (m) {\n const h = ((parseFloat(m[1]) % 360) + 360) % 360\n const s = parseFloat(m[2]) / 100\n const l = parseFloat(m[3]) / 100\n const f = (n: number) => {\n const k = (n + h / 30) % 12\n return l - s * Math.min(l, 1 - l) * Math.max(-1, Math.min(k - 3, 9 - k, 1))\n }\n return out(f(0), f(8), f(4), alpha(m[4]))\n }\n m = /^oklch\\(\\s*([\\d.]+%?)\\s+([\\d.]+%?)\\s+([\\d.]+)(?:deg)?(?:\\s*\\/\\s*([\\d.]+%?))?\\s*\\)$/.exec(v)\n if (m) {\n const L = m[1].endsWith('%') ? parseFloat(m[1]) / 100 : parseFloat(m[1])\n const C = m[2].endsWith('%') ? (parseFloat(m[2]) / 100) * 0.4 : parseFloat(m[2])\n const H = (parseFloat(m[3]) * Math.PI) / 180\n const a = C * Math.cos(H)\n const b = C * Math.sin(H)\n const l = Math.pow(L + 0.3963377774 * a + 0.2158037573 * b, 3)\n const mm = Math.pow(L - 0.1055613458 * a - 0.0638541728 * b, 3)\n const s = Math.pow(L - 0.0894841775 * a - 1.291485548 * b, 3)\n return out(\n gamma(4.0767416621 * l - 3.3077115913 * mm + 0.2309699292 * s),\n gamma(-1.2684380046 * l + 2.6097574011 * mm - 0.3413193965 * s),\n gamma(-0.0041960863 * l - 0.7034186147 * mm + 1.707614701 * s),\n alpha(m[4]),\n )\n }\n return null\n}\n\n/** A length in pixels, within what Cauthy takes, or null. */\nfunction toRadius(value: string): number | null {\n const m = /^([\\d.]+)(px|rem|em)?$/.exec(value.trim())\n if (!m) return null\n const px = parseFloat(m[1]) * (m[2] === 'rem' || m[2] === 'em' ? 16 : 1)\n return Number.isFinite(px) ? Math.min(MAX_RADIUS, Math.max(0, Math.round(px))) : null\n}\n\n/** A font list as Cauthy takes it, or null. */\nfunction toFont(value: string): string | null {\n const v = value.trim()\n return /^[A-Za-z0-9 ,'\"_-]{1,200}$/.test(v) && (v.match(/\"/g)?.length ?? 0) % 2 === 0 && (v.match(/'/g)?.length ?? 0) % 2 === 0 ? v : null\n}\n\n// ── Stackmyth ──────────────────────────────────────────────────────────────\n\n/** Which Stackmyth token each value of a theme is read from, first found wins. */\nconst COLORS: [keyof CauthyTheme, string[]][] = [\n ['background', ['--sm-bg-surface', '--sm-surface']],\n ['surface', ['--sm-bg-surface-raised', '--sm-surface-raised']],\n ['text', ['--sm-text-primary', '--sm-surface-text']],\n ['textMuted', ['--sm-text-secondary']],\n ['border', ['--sm-border-default', '--sm-surface-border']],\n ['primary', ['--sm-primary', '--sm-bg-interactive']],\n ['primaryText', ['--sm-primary-text', '--sm-text-inverse']],\n ['accent', ['--sm-text-brand', '--sm-border-focus', '--sm-accent']],\n ['error', ['--sm-error-soft-text', '--sm-error']],\n ['errorBackground', ['--sm-error-soft']],\n ['success', ['--sm-success-soft-text', '--sm-success']],\n ['successBackground', ['--sm-success-soft']],\n]\n\n/**\n * Reads an application's Stackmyth theme out of its stylesheets.\n *\n * Give it the CSS that declares the tokens, in the order the application\n * loads it — Stackmyth's own (`@stackmyth/core/core.vars.css`, or a theme\n * file) and then the application's brand file that re-points them. Tokens\n * that point at the application's own variables are followed. The\n * application's dark mode is left out unless asked for.\n */\nexport function themeFromStackmyth(css: string | string[], options: StackmythThemeOptions = {}): ImportedTheme {\n const o = { mode: options.mode ?? 'light', theme: options.theme }\n const vars = new Map<string, string>()\n for (const sheet of Array.isArray(css) ? css : [css]) {\n for (const d of declarations(sheet)) if (applies(d, o)) vars.set(d.name, d.value)\n }\n const theme: CauthyTheme = {}\n const warnings: string[] = []\n\n const read = <T>(tokens: string[], to: (v: string) => T | null, what: string): T | undefined => {\n for (const token of tokens) {\n const raw = vars.get(token)\n if (raw === undefined) continue\n const value = resolve(raw, vars)\n const got = value === null ? null : to(value)\n if (got !== null) return got\n warnings.push(`${token}: \"${raw}\" is not ${what} Cauthy can take`)\n }\n return undefined\n }\n\n for (const [field, tokens] of COLORS) {\n const c = read(tokens, toColor, 'a colour')\n if (c !== undefined) (theme as Record<string, unknown>)[field] = c\n }\n const radius = read(['--sm-radius-container', '--sm-radius-md'], toRadius, 'a length')\n if (radius !== undefined) theme.radius = radius\n const button = read(['--sm-radius-interactive', '--sm-radius-sm'], toRadius, 'a length')\n if (button !== undefined) theme.buttonRadius = button\n const font = read(['--sm-font-family'], toFont, 'a font list')\n if (font !== undefined) theme.fontFamily = font\n if (options.logoUrl) theme.logoUrl = options.logoUrl\n\n if (vars.size === 0) warnings.push('no custom properties were found: is this the stylesheet that declares the --sm-* tokens?')\n else if (![...vars.keys()].some((k) => k.startsWith('--sm-'))) warnings.push('no --sm-* token was found in these stylesheets')\n return { theme, warnings }\n}\n\n// ── Any stylesheet ─────────────────────────────────────────────────────────\n\n/** The values of a theme a stylesheet can say. */\nexport type ThemeField =\n | 'background' | 'surface' | 'text' | 'textMuted' | 'border' | 'inputBackground' | 'inputBorder'\n | 'primary' | 'primaryText' | 'accent' | 'error' | 'errorBackground' | 'success' | 'successBackground'\n | 'radius' | 'buttonRadius' | 'fontFamily'\n\n/** How a stylesheet names its look: Stackmyth's tokens, shadcn/ui's (and Tailwind's), or the application's own. */\nexport type CssFormat = 'stackmyth' | 'shadcn' | 'custom'\n\nexport interface CssThemeOptions {\n /** Which names to read. Default: `auto`, told from the stylesheet itself. */\n format?: CssFormat | 'auto'\n /** Which of the application's two modes to take. Default: `light`. */\n mode?: 'light' | 'dark'\n /** The named theme to read, when the stylesheet keeps several under `[data-theme=…]`. */\n theme?: string\n /** For `custom`: the variable each value is read from, `{ primary: '--brand' }`. */\n map?: Partial<Record<ThemeField, string>>\n}\n\nexport interface CssImportedTheme extends ImportedTheme {\n /** The names that were read. */\n format: CssFormat\n}\n\nconst RADII: ThemeField[] = ['radius', 'buttonRadius']\n\n/** shadcn/ui's names, as Tailwind 3 (`--primary`) and Tailwind 4 (`--color-primary`) write them. */\nconst SHADCN: [ThemeField, string[]][] = [\n ['background', ['--background', '--color-background']],\n ['surface', ['--card', '--color-card', '--popover', '--color-popover']],\n ['text', ['--foreground', '--color-foreground']],\n ['textMuted', ['--muted-foreground', '--color-muted-foreground']],\n ['border', ['--border', '--color-border']],\n ['inputBorder', ['--input', '--color-input']],\n ['primary', ['--primary', '--color-primary']],\n ['primaryText', ['--primary-foreground', '--color-primary-foreground']],\n ['accent', ['--ring', '--color-ring', '--primary', '--color-primary']],\n ['error', ['--destructive', '--color-destructive']],\n ['radius', ['--radius']],\n ['buttonRadius', ['--radius']],\n ['fontFamily', ['--font-sans', '--font-family']],\n]\n\n/** A colour as shadcn/ui writes it on Tailwind 3 — `222.2 84% 4.9%`, the inside of hsl() — or any other. */\nfunction shadcnColor(value: string): string | null {\n return /^[\\d.]+(?:deg)?\\s+[\\d.]+%\\s+[\\d.]+%(?:\\s*\\/\\s*[\\d.]+%?)?$/.test(value.trim()) ? toColor(`hsl(${value.trim()})`) : toColor(value)\n}\n\n/** Every custom property a stylesheet sets for the page, the last one winning. */\nfunction pageVariables(css: string | string[], mode: 'light' | 'dark', theme?: string): Map<string, string> {\n const vars = new Map<string, string>()\n for (const sheet of Array.isArray(css) ? css : [css]) {\n for (const d of declarations(sheet)) if (applies(d, { mode, theme })) vars.set(d.name, d.value)\n }\n return vars\n}\n\n/** The custom properties a stylesheet sets for the page, by name: what a person picks from to map their own. */\nexport function cssVariables(css: string | string[], options: Pick<CssThemeOptions, 'mode' | 'theme'> = {}): string[] {\n return [...pageVariables(css, options.mode ?? 'light', options.theme).keys()].sort()\n}\n\n/**\n * Reads an application's look out of its stylesheets, whatever names it uses:\n * Stackmyth's tokens, shadcn/ui's (`--primary`, `--radius`, on Tailwind 3 or\n * 4), or the application's own variables given in `map`. What comes out is a\n * theme of named values, never CSS.\n */\nexport function themeFromCss(css: string | string[], options: CssThemeOptions = {}): CssImportedTheme {\n const mode = options.mode ?? 'light'\n const vars = pageVariables(css, mode, options.theme)\n const names = [...vars.keys()]\n let format: CssFormat\n if (options.format && options.format !== 'auto') format = options.format\n else if (options.map && Object.keys(options.map).length > 0) format = 'custom'\n else if (names.some((n) => n.startsWith('--sm-'))) format = 'stackmyth'\n else if (names.some((n) => /^--(color-)?(primary|background|foreground)$/.test(n))) format = 'shadcn'\n else format = 'custom'\n\n if (format === 'stackmyth') return { ...themeFromStackmyth(css, { mode, theme: options.theme }), format }\n\n const spec: [ThemeField, string[]][] = format === 'shadcn'\n ? SHADCN\n : Object.entries(options.map ?? {}).filter(([, v]) => v).map(([k, v]) => [k as ThemeField, [String(v).startsWith('--') ? String(v) : `--${v}`]])\n const theme: CauthyTheme = {}\n const warnings: string[] = []\n for (const [field, tokens] of spec) {\n for (const token of tokens) {\n const raw = vars.get(token)\n if (raw === undefined) continue\n const value = resolve(raw, vars)\n const got = value === null ? null\n : RADII.includes(field) ? toRadius(value)\n : field === 'fontFamily' ? toFont(value)\n : format === 'shadcn' ? shadcnColor(value) : toColor(value)\n if (got !== null) {\n (theme as Record<string, unknown>)[field] = got\n break\n }\n warnings.push(`${token}: \"${raw.length > 60 ? raw.slice(0, 57) + '…' : raw}\" is not ${RADII.includes(field) ? 'a length' : field === 'fontFamily' ? 'a font list' : 'a colour'} Cauthy can take`)\n }\n }\n if (vars.size === 0) warnings.push('no custom properties were found in these stylesheets')\n else if (format === 'custom' && spec.length === 0) warnings.push('these stylesheets use names of their own: say which variable each value is read from')\n return { theme, warnings, format }\n}\n\n// ── On the application's own screens ───────────────────────────────────────\n\n/**\n * A theme as the CSS variables of `cauthy-auth/styles.css` (`--cauthy-*`), for\n * the forms an application shows on its own pages. The page's own background,\n * the layout and the words are Cauthy's pages' matter, and left out.\n */\nexport function themeVariables(theme: CauthyTheme): Record<string, string> {\n const vars: Record<string, string> = {}\n const put = (name: string, v: string | number | undefined) => {\n if (v === undefined || v === '') return\n vars[`--cauthy-${name}`] = typeof v === 'number' ? `${v}px` : v\n }\n put('surface', theme.surface)\n put('text', theme.text)\n put('text-muted', theme.textMuted)\n put('border', theme.border)\n put('input-background', theme.inputBackground)\n put('input-border', theme.inputBorder)\n put('primary', theme.primary)\n put('primary-text', theme.primaryText)\n put('accent', theme.accent)\n put('error', theme.error)\n put('error-background', theme.errorBackground)\n put('success', theme.success)\n put('success-background', theme.successBackground)\n put('radius', theme.radius)\n put('button-radius', theme.buttonRadius)\n put('font', theme.fontFamily)\n return vars\n}\n\n// ── Sending it to Cauthy ───────────────────────────────────────────────────\n\n/** A theme as Cauthy's API names its values. */\nexport function toWire(theme: CauthyTheme): Record<string, unknown> {\n const names: Record<keyof CauthyTheme, string> = {\n background: 'background', surface: 'surface', text: 'text', textMuted: 'text_muted', border: 'border',\n primary: 'primary', primaryText: 'primary_text', accent: 'accent', error: 'error', errorBackground: 'error_background',\n success: 'success', successBackground: 'success_background', radius: 'radius', buttonRadius: 'button_radius',\n fontFamily: 'font_family', logoUrl: 'logo_url', fonts: 'fonts', layout: 'layout', panelColors: 'panel_colors',\n panelText: 'panel_text', panelIconUrl: 'panel_icon_url', texts: 'texts', inputBackground: 'input_background', inputBorder: 'input_border', tokens: 'tokens',\n }\n const wire: Record<string, unknown> = {}\n for (const [k, v] of Object.entries(theme)) {\n if (v === undefined || v === '' || (Array.isArray(v) && v.length === 0)) continue\n wire[names[k as keyof CauthyTheme]] = v\n }\n return wire\n}\n\n/** A theme as Cauthy answers it, in this package's names. */\nexport function fromWire(wire: Record<string, unknown>): CauthyTheme {\n const names: Record<string, keyof CauthyTheme> = {\n background: 'background', surface: 'surface', text: 'text', text_muted: 'textMuted', border: 'border',\n primary: 'primary', primary_text: 'primaryText', accent: 'accent', error: 'error', error_background: 'errorBackground',\n success: 'success', success_background: 'successBackground', radius: 'radius', button_radius: 'buttonRadius',\n font_family: 'fontFamily', logo_url: 'logoUrl', fonts: 'fonts', layout: 'layout', panel_colors: 'panelColors',\n panel_text: 'panelText', panel_icon_url: 'panelIconUrl', texts: 'texts', input_background: 'inputBackground', input_border: 'inputBorder', tokens: 'tokens',\n }\n const theme: Record<string, unknown> = {}\n for (const [k, v] of Object.entries(wire ?? {})) if (names[k] && v !== undefined && v !== null && v !== '') theme[names[k]] = v\n return theme as CauthyTheme\n}\n\nexport interface PushThemeOptions {\n /** Cauthy's address. Default: `CAUTHY_ISSUER`. */\n issuer?: string\n /** The application's client id. Default: `CAUTHY_CLIENT_ID`. */\n clientId?: string\n /** The workspace's slug. Default: `CAUTHY_TENANT`. */\n tenant?: string\n /**\n * A personal access token with `oauth_clients:manage`. Default:\n * `CAUTHY_TOKEN`. It belongs in the deploy pipeline, never in the application.\n */\n token?: string\n theme: CauthyTheme\n fetch?: typeof fetch\n}\n\n/**\n * Gives the application its look on Cauthy's pages. It replaces the theme: a\n * value left out goes back to Cauthy's own. Run it when the brand changes —\n * from a script or the deploy — not on every request.\n */\nexport async function pushTheme(o: PushThemeOptions): Promise<void> {\n const env = (n: string) => (typeof process !== 'undefined' ? process.env?.[n] : undefined)\n const issuer = withoutTrailingSlashes(o.issuer ?? env('CAUTHY_ISSUER') ?? '')\n const clientId = o.clientId ?? env('CAUTHY_CLIENT_ID') ?? ''\n const tenant = o.tenant ?? env('CAUTHY_TENANT') ?? ''\n const token = o.token ?? env('CAUTHY_TOKEN') ?? ''\n for (const [name, v] of Object.entries({ CAUTHY_ISSUER: issuer, CAUTHY_CLIENT_ID: clientId, CAUTHY_TENANT: tenant, CAUTHY_TOKEN: token })) {\n if (v === '') throw new Error(`cauthy-auth: ${name} is required to push a theme`)\n }\n const res = await (o.fetch ?? fetch)(`${issuer}/v1/oauth/clients/${encodeURIComponent(clientId)}/theme`, {\n method: 'PUT',\n headers: { authorization: `Bearer ${token}`, 'x-tenant-key': tenant, 'content-type': 'application/json' },\n body: JSON.stringify(toWire(o.theme)),\n })\n if (!res.ok) {\n const body = (await res.json().catch(() => null)) as { errors?: { message?: string }[] } | null\n throw new Error(`cauthy-auth: Cauthy refused the theme (${res.status}): ${body?.errors?.[0]?.message ?? res.statusText}`)\n }\n}\n","// What a sign-in or sign-up form is checked for before anything is sent, in\n// the browser and again on the server: the same rules in both places, so the\n// form says what the server would.\n\nexport type AuthField = 'email' | 'password' | 'newPassword' | 'name' | 'code'\n\n/** A value of a form that cannot be sent as it is. */\nexport interface FieldProblem {\n field: AuthField\n /** Stable, for translating: `required`, `invalid_email`, `too_short`, `too_long`. */\n code: 'required' | 'invalid_email' | 'too_short' | 'too_long'\n message: string\n}\n\n/** The shortest password Cauthy takes for a new account. */\nexport const MIN_PASSWORD = 8\nconst MAX_PASSWORD = 256\nconst MAX_NAME = 120\n\n// One @, something on each side, a dot in the domain, no spaces: what catches\n// a typo. Whether the address exists is Cauthy's to find out.\nconst EMAIL = /^[^\\s@]+@[^\\s@]+\\.[^\\s@]+$/\n\nexport function checkEmail(value: unknown): FieldProblem | null {\n const v = typeof value === 'string' ? value.trim() : ''\n if (v === '') return { field: 'email', code: 'required', message: 'Enter your email address.' }\n if (v.length > 254 || !EMAIL.test(v)) return { field: 'email', code: 'invalid_email', message: 'That does not look like an email address.' }\n return null\n}\n\n/** A password being typed to sign in: only that there is one. */\nexport function checkPassword(value: unknown): FieldProblem | null {\n if (typeof value !== 'string' || value === '') return { field: 'password', code: 'required', message: 'Enter your password.' }\n if (value.length > MAX_PASSWORD) return { field: 'password', code: 'too_long', message: 'That password is too long.' }\n return null\n}\n\n/** A password being chosen for a new account. */\nexport function checkNewPassword(value: unknown): FieldProblem | null {\n const basic = checkPassword(value)\n if (basic) return basic\n if ((value as string).length < MIN_PASSWORD) {\n return { field: 'password', code: 'too_short', message: `Use at least ${MIN_PASSWORD} characters.` }\n }\n return null\n}\n\nexport function checkName(value: unknown): FieldProblem | null {\n if (value === undefined || value === null || value === '') return null\n if (typeof value !== 'string' || value.trim().length > MAX_NAME) return { field: 'name', code: 'too_long', message: `Use at most ${MAX_NAME} characters.` }\n return null\n}\n\nexport function checkCode(value: unknown): FieldProblem | null {\n const v = typeof value === 'string' ? value.trim() : ''\n if (v === '') return { field: 'code', code: 'required', message: 'Enter the code.' }\n if (v.length > 40) return { field: 'code', code: 'too_long', message: 'That is not a code.' }\n return null\n}\n\n/** The first problem of a form, or null when it can be sent. */\nexport function firstProblem(...checks: (FieldProblem | null)[]): FieldProblem | null {\n return checks.find((c) => c !== null) ?? null\n}\n","// The application's own sign-in screens, in the browser.\n//\n// const auth = createAuthClient()\n// const r = await auth.signIn.email({ email, password })\n// if (!r.ok) show(r.error.message) // r.error.field says which input\n// else if (r.status === 'mfa_required') askForTheCode()\n//\n// Every call goes to the application's own server (`/api/auth/*`), never to\n// Cauthy: the browser holds no token, before or after. A form is checked here\n// before it is sent, with the rules the server checks again.\n\nimport { canAutofillPasskeys, canUsePasskeys, ceremonyError, getPasskey, makePasskey } from './passkey'\nimport { fromWire, type CauthyTheme } from './theme'\nimport type { PublicSession } from './types'\nimport { checkCode, checkEmail, checkName, checkNewPassword, checkPassword, firstProblem, type AuthField, type FieldProblem } from './validate'\nimport { withoutTrailingSlashes } from './url'\n\nexport { checkCode, checkEmail, checkName, checkNewPassword, checkPassword, MIN_PASSWORD, type AuthField, type FieldProblem } from './validate'\n\n/** What went wrong, in a form the screen can show. */\nexport interface AuthError {\n /** Stable, for translating: `invalid_credentials`, `invalid_email`, `too_short`, `email_taken`, `unavailable`… */\n code: string\n message: string\n /** The input it is about, when it is about one. */\n field?: AuthField\n}\n\nexport type AuthResult<S extends string, More = unknown> = ({ ok: true; status: S } & More) | { ok: false; error: AuthError }\n\n/** What the screen may offer. */\nexport interface AuthMethods {\n /** The providers the workspace signs people in with: `{ id: 'google', name: 'Google' }`. */\n providers: { id: string; name: string }[]\n /** Whether people may create an account themselves. */\n signUp: boolean\n magicLink: boolean\n /** Whether to offer a passkey. */\n passkey: boolean\n /**\n * Whether the passkey is used on this page itself. False: `signIn.passkey`\n * sends the person to Cauthy's page for that step alone, and they come back\n * signed in. That happens only when this page is outside the domain the\n * workspace's passkeys belong to.\n */\n passkeyHere: boolean\n /**\n * Whether Cauthy's page can use the passkey, for that step alone: where a\n * page that cannot, or a browser that refuses to here, sends the person.\n */\n passkeyOnCauthy: boolean\n /**\n * The workspace's Cloudflare Turnstile site key, when it asks for the\n * challenge: signing in, creating an account, and asking for a sign-in link\n * or a new password then take its answer (`captchaToken`). The ready-made\n * forms show it themselves.\n */\n captchaSiteKey?: string\n /**\n * Whether the workspace signs some people in through their company's SSO,\n * found by their work address (`signIn.ssoDiscover`, `signIn.sso`).\n */\n sso: boolean\n}\n\n/** A company's sign-in (an SSO connection of the workspace). */\nexport interface SSOConnection {\n /** What `signIn.sso({ connection })` takes. */\n id: string\n /** The company's name, for \"Continue with …\". */\n name: string\n /** The address can sign in no other way: a password, link or provider answers `sso_required`. */\n enforced: boolean\n}\n\n/**\n * What holds a passkey, as its authenticator says: a security key (a YubiKey),\n * one synced across the person's devices (iCloud Keychain, Google Password\n * Manager), one on a single device (Windows Hello), or unknown (made before\n * Cauthy kept this).\n */\nexport type PasskeyKind = 'security_key' | 'synced' | 'device' | 'unknown'\n\n/** A passkey of the signed-in account. Moments are UTC. */\nexport interface Passkey {\n id: string\n /** What the person called it (\"My laptop\"), or \"\". */\n name: string\n kind: PasskeyKind\n createdAt: string\n /** When it last signed the person in, or null. */\n lastUsedAt: string | null\n}\n\n/** A person's own account, as their account screen shows it. */\nexport interface Account {\n id: string\n email: string\n name: string\n emailVerified: boolean\n /** False for an account that only signs in through a provider: it has no current password to ask for. */\n hasPassword: boolean\n mfaEnabled: boolean\n}\n\n/** Somewhere the person is signed in. Moments are UTC. */\n/** An AI agent the person allowed to act for them on one of the workspace's servers. */\nexport interface ConnectedAgent {\n id: string\n /** The name the agent gave itself (Claude, Cursor…): not checked by anyone. */\n name: string\n /** Where the agent receives its access: the one thing about it that is sure. */\n redirectHost: string\n resource: { name: string; uri: string }\n /** What the person allowed it, in their words. */\n scopes: { name: string; description: string }[]\n /** UTC, ISO 8601. */\n connectedAt: string\n lastUsedAt: string\n}\n\n/** An organization the person belongs to, with what their role lets them do with its people. */\nexport interface OrganizationSeat {\n id: string\n /** The client system it is of (`pondari`). */\n system: string\n name: string\n kind: string\n /** The person's role in it. */\n role: string\n /** Cauthy's member powers, from the system's catalog: cauthy:members read and manage. */\n can: { readMembers: boolean; manageMembers: boolean }\n}\n\n/** A person of an organization, as a member who may see them reads it. */\nexport interface OrganizationPerson {\n userId: string\n email: string\n name: string\n role: string\n /** The account's: `active`, `suspended`… A disabled member is still listed. */\n status: string\n /** Invited and not yet accepted. */\n invited: boolean\n /** Manages the organization's members. */\n manager: boolean\n /** The signed-in person. */\n you: boolean\n /** UTC, ISO 8601. */\n since: string\n}\n\n/** A role of the organization's system, as the catalog names it. */\nexport interface OrganizationRoleChoice {\n role: string\n name: string\n description: string\n /** Holding it manages the organization's members. */\n manager: boolean\n}\n\n/**\n * The signed-in person's organizations, and their people. Seeing them needs\n * cauthy:members read in the person's role; changing them needs manage. Cauthy\n * decides, with the person's own session: the application authorizes nothing.\n */\nexport interface OrganizationsClient {\n /** The organizations the person belongs to; one system's only with `system`. */\n list(system?: string): Promise<OrganizationSeat[]>\n /** The people of one, or null when the person may not see them. */\n members(organizationId: string): Promise<OrganizationPerson[] | null>\n /** The roles a member may be given, or null when the person may not see them. */\n roles(organizationId: string): Promise<OrganizationRoleChoice[] | null>\n /** Adds a person by their address; an address with no account gets one and its invitation. */\n add(organizationId: string, form: { email: string; role: string; name?: string; locale?: string }): Promise<AuthResult<'added', { member: OrganizationPerson }>>\n setRole(organizationId: string, userId: string, role: string): Promise<AuthResult<'changed', { userId: string; role: string }>>\n /** Removes a member; the person themself may always leave. */\n remove(organizationId: string, userId: string): Promise<AuthResult<'removed', { userId: string }>>\n /** Sends a member's invitation again. */\n inviteAgain(organizationId: string, userId: string, form?: { locale?: string }): Promise<AuthResult<'sent', { userId: string }>>\n}\n\nexport interface AccountSession {\n /** \"Mac · Chrome\", or \"\" when the browser did not say. */\n device: string\n ip: string\n createdAt: string\n expiresAt: string\n}\n\n/**\n * The signed-in person's own account. What weakens it — the password, the\n * second step, its recovery codes — asks for the current password again.\n */\n/** A provider the account can also sign in with. */\nexport interface LinkedIdentity {\n provider: string\n /** The address the provider has for the person, when it said. */\n email: string\n name: string\n /** When it was linked, in UTC (ISO 8601). */\n linkedAt: string\n}\n\n/** How an account takes its second step. */\nexport interface SecondStep {\n totp: boolean\n /** The phone codes are sent to, masked (`+57 ••• ••• 4567`). */\n phone: { number: string; channel: string } | null\n /** The ways the workspace sends codes to phones. */\n phoneChannels: string[]\n}\n\nexport interface AccountClient {\n /** The account, or null when nobody is signed in. */\n get(): Promise<Account | null>\n update(form: { name: string }): Promise<AuthResult<'saved', { account: Account }>>\n /** Ends every other session of the account; this one stays signed in. */\n changePassword(form: { password: string; newPassword: string }): Promise<AuthResult<'changed', { signedIn: boolean }>>\n mfa: {\n /** Starts turning the second step on: show `secret` (or `otpauthUri` as a QR code) to add to an authenticator app, then call `verify`. */\n setup(form: { password: string }): Promise<AuthResult<'started', { secret: string; otpauthUri: string }>>\n /** Turns it on with a code from the app. `recoveryCodes` are shown this once. */\n verify(form: { code: string }): Promise<AuthResult<'enabled', { recoveryCodes: string[] }>>\n disable(form: { password: string }): Promise<AuthResult<'disabled'>>\n /** New recovery codes; the earlier ones stop working. */\n recoveryCodes(form: { password: string }): Promise<AuthResult<'renewed', { recoveryCodes: string[] }>>\n /**\n * How the account takes its second step: an authenticator app, a phone\n * (masked), and the ways the workspace sends codes to phones (`sms`,\n * `whatsapp`; none when it has no Twilio account for it).\n */\n factors(): Promise<SecondStep | null>\n /** Adds a phone as the second step: a code is sent to it, for `verifyPhone`. */\n addPhone(form: { phone: string; channel: 'sms' | 'whatsapp'; password: string }): Promise<AuthResult<'sent', { sentTo: string; channel: string }>>\n /** Proves the phone with the code it received. `recoveryCodes` come with a first second step, shown this once. */\n verifyPhone(form: { code: string }): Promise<AuthResult<'enabled', { recoveryCodes: string[] }>>\n removePhone(form: { password: string }): Promise<AuthResult<'removed'>>\n }\n /**\n * Asks to sign in with another address. Nothing changes yet: the new address\n * is mailed a link to confirm it, and once it is confirmed the old one is\n * told, with a link that undoes it. Both open Cauthy's own page.\n */\n changeEmail(form: { email: string; password: string }): Promise<AuthResult<'sent', { email: string }>>\n /** The providers (Google…) the account can also sign in with. */\n identities(): Promise<LinkedIdentity[]>\n /**\n * Sends the browser to the provider, to link it to the account; it comes back\n * at `returnTo` with `?linked=<provider>`, or `?auth_error=<code>`.\n */\n link(provider: string, options?: { returnTo?: string }): void\n /** Where `link` sends the browser, for a plain link. */\n linkUrl(provider: string, options?: { returnTo?: string }): string\n /** The account stops signing in with that provider. Refused when it would be left with no way to sign in. */\n unlink(provider: string): Promise<AuthResult<'unlinked', { provider: string }>>\n /**\n * A guest becomes an account of their own with an address and a password —\n * the same id, so what they did stays theirs. `email_taken`: the address has\n * an account already; offer to sign in with it.\n */\n upgrade(form: { email: string; password: string; name?: string }): Promise<AuthResult<'signed_in', { session: PublicSession }>>\n /** Where a guest goes to become the account of a provider's address (Google…). */\n upgradeUrl(provider: string, options?: { returnTo?: string }): string\n /** Sends a guest to `upgradeUrl`; they come back signed in as the account. */\n upgradeWith(provider: string, options?: { returnTo?: string }): void\n /** Where the account is signed in. */\n sessions(): Promise<AccountSession[]>\n /** Signs the account out everywhere, here too. */\n signOutEverywhere(): Promise<AuthResult<'signed_out'>>\n /** The AI agents the person connected. */\n agents(): Promise<ConnectedAgent[]>\n /** Disconnects an agent: it loses its access within minutes. */\n disconnectAgent(id: string): Promise<AuthResult<'disconnected', { id: string }>>\n /** The account's passkeys. */\n passkeys(): Promise<Passkey[]>\n /**\n * Makes a passkey on this device, on this page: the browser asks the person\n * for their fingerprint, face or screen lock. An account with a password\n * proves it again first. `cancelled`: the person closed the browser's prompt.\n * `kind` sends the browser straight to one: `'device'`, this device's\n * fingerprint, face or screen lock; `'security_key'`, a security key.\n * Without it, the browser offers all.\n */\n addPasskey(form?: { password?: string; name?: string; kind?: 'device' | 'security_key' }): Promise<AuthResult<'added', { passkey: Passkey }>>\n removePasskey(id: string): Promise<AuthResult<'removed', { id: string }>>\n}\n\nexport interface AuthClient {\n signIn: {\n /**\n * A guest session, before the visitor signs up: the application keeps what\n * they do under `session.user.id`, and `account.upgrade` (or\n * `account.upgradeWith`) makes it their account, same id. Only where the\n * workspace allows guests (`anonymous_disabled` otherwise).\n */\n anonymously(form?: { captchaToken?: string }): Promise<AuthResult<'signed_in', { session: PublicSession }>>\n /** With address and password. `mfa_required`: ask for the code and call `signIn.mfa`. */\n email(form: { email: string; password: string; captchaToken?: string }): Promise<AuthResult<'signed_in', { session: PublicSession }> | AuthResult<'mfa_required'>>\n /** The second step: the code of the authenticator app, or a recovery code. */\n mfa(form: { code: string }): Promise<AuthResult<'signed_in', { session: PublicSession }>>\n /** While the second step is asked for: whether the account has an authenticator app, the phone (masked) a code can be sent to, and phones that approve sign-ins. */\n mfaMethods(): Promise<AuthResult<'ok', { totp: boolean; phone: { number: string; channel: string } | null; push: { devices: number } | null }>>\n /**\n * Sends a push to the account's phones (the application's app, with\n * cauthy-expo): show `number`, which the person picks on the phone, then\n * call `mfaPushWait`.\n */\n mfaPush(): Promise<AuthResult<'sent', { number: number }>>\n /**\n * Waits for the phone's answer, listening (server-sent events) rather than\n * asking again and again, and signs in when it approves. `denied`: turned\n * down on the phone; `expired`: nobody answered in time.\n */\n mfaPushWait(o?: { signal?: AbortSignal }): Promise<AuthResult<'signed_in', { session: PublicSession }>>\n /** Sends a code to the account's phone, by SMS or WhatsApp; `mfa` takes it. A few per sign-in. */\n mfaSendCode(): Promise<AuthResult<'sent', { sentTo: string; channel: string }>>\n /**\n * Mails a link (and a code) that signs the person in. The answer is the\n * same whether or not the address has an account. `create`: an address\n * with no account gets one when the link or code is used, if the\n * workspace accepts new accounts — one screen signs people in and up.\n * `returnTo` (a path of this site) travels in the link: the page it opens\n * gets it as `?return_to=`, and `emailCode` answers it.\n */\n magicLink(form: { email: string; captchaToken?: string; create?: boolean; returnTo?: string }): Promise<AuthResult<'sent'>>\n /** Signs in with the token the mailed link opened the page with (`ml_token`). */\n magicLinkVerify(form: { token: string }): Promise<AuthResult<'signed_in', { session: PublicSession }>>\n /** Signs in with the code that came in the same mail as the link, for somebody reading it on another device. */\n emailCode(form: { code: string }): Promise<AuthResult<'signed_in', { session: PublicSession; returnTo?: string }>>\n /**\n * Signs in with a passkey, on this page: the browser asks the person for\n * their fingerprint, face or screen lock. When this page cannot use the\n * workspace's passkeys (`methods().passkeyHere` is false), the browser goes\n * to Cauthy's page for that step alone and comes back signed in, at\n * `returnTo`: then the answer is `redirected`. `cancelled`: the person\n * closed the browser's prompt. `email`, when the person typed it: the\n * browser is told which passkeys are theirs, so a security key that did\n * not keep its passkey (one made before cauthy-auth 0.18.0) is found too.\n */\n passkey(options?: { returnTo?: string; locale?: string; email?: string }): Promise<AuthResult<'signed_in', { session: PublicSession }> | AuthResult<'redirected'>>\n /**\n * Offers the person's passkeys in the browser's autofill of the email\n * field, which must carry `autocomplete=\"username webauthn\"`, and signs in\n * with the one they pick. It waits until they pick one or `signal`\n * aborts: abort it before calling `passkey` or sending the form.\n * `cancelled`: aborted, or this browser or page cannot (then there is\n * nothing to offer, and the passkey button still works).\n */\n passkeyAutofill(options?: { signal?: AbortSignal }): Promise<AuthResult<'signed_in', { session: PublicSession }>>\n /**\n * Google One Tap: Google's prompt over the page offers the Google account\n * the browser already has, and signs in with it in one tap, as the Google\n * sign-in does (the same account). Needs the workspace's Google sign-in,\n * and the page's address among the Google client's authorized JavaScript\n * origins. Waits until the person picks an account or closes the prompt\n * (`cancelled`), or `signal` aborts. `one_tap_unavailable`: the workspace\n * has no Google sign-in.\n */\n googleOneTap(options?: { signal?: AbortSignal; context?: 'signin' | 'signup' | 'use' }): Promise<AuthResult<'signed_in', { session: PublicSession }>>\n /** Where `passkey` sends the browser when this page cannot use the passkey itself. */\n passkeyUrl(options?: { returnTo?: string; locale?: string }): string\n /** Sends the browser to the provider; it comes back signed in, at `returnTo`. */\n social(provider: string, options?: { returnTo?: string }): void\n /**\n * Which company's sign-in a work address uses, if any (`found`), or\n * `none`. It says so of the address's domain, never whether the address\n * has an account.\n */\n ssoDiscover(form: { email: string }): Promise<AuthResult<'found', { connection: SSOConnection }> | AuthResult<'none'>>\n /**\n * Sends the browser to the person's company (found by `email`, or named\n * by `connection`); it comes back signed in, at `returnTo`. A sign-in the\n * company refused comes back with `?auth_error=`. Any other way of\n * signing in answers `sso_required` for an address its company holds to\n * SSO: send it here then.\n */\n sso(target: { email: string } | { connection: string }, options?: { returnTo?: string }): void\n /** Where `sso` sends the browser, for a plain link. */\n ssoUrl(target: { email: string } | { connection: string }, options?: { returnTo?: string }): string\n /** Where `social` sends the browser, for a plain link. */\n socialUrl(provider: string, options?: { returnTo?: string }): string\n }\n signUp: {\n /** `verification_required`: the account exists and must open the link Cauthy mailed before it can sign in. */\n email(form: { email: string; password: string; name?: string; captchaToken?: string }): Promise<AuthResult<'signed_in', { session: PublicSession }> | AuthResult<'verification_required', { email: string }>>\n }\n /** Mails a link to choose a new password. The answer is the same whether or not the address has an account. */\n forgotPassword(form: { email: string; captchaToken?: string }): Promise<AuthResult<'sent'>>\n /** Sets the new password, with the token the mailed link opened the page with (`token`). */\n resetPassword(form: { token: string; password: string }): Promise<AuthResult<'changed'>>\n /** Confirms the address, with the token the mailed link opened the page with (`token`). */\n verifyEmail(form: { token: string }): Promise<AuthResult<'verified'>>\n signOut(): Promise<AuthResult<'signed_out'>>\n /** The signed-in person's own account. */\n account: AccountClient\n /** The signed-in person's organizations, and their people. */\n organizations: OrganizationsClient\n /** Who is signed in, or null. */\n getSession(): Promise<PublicSession | null>\n methods(): Promise<AuthMethods>\n /**\n * How the workspace's forms look — colours, corners, typeface — as a person\n * set it in Cauthy's dashboard. Read once per page; null when Cauthy cannot say.\n */\n look(): Promise<CauthyTheme | null>\n}\n\nexport interface AuthClientOptions {\n /** Where the server mounted the routes. Default: `/api/auth`. */\n basePath?: string\n fetch?: typeof fetch\n /**\n * The language the person reads, for what Cauthy mails them (a sign-in\n * link, a new password, a confirmation): a tag like `es` or `es-CO`, or a\n * function that answers it when the person can change it. Default: the\n * page's own (`<html lang>`), else the browser's.\n */\n locale?: string | (() => string | undefined)\n}\n\n/** Google Identity Services, as much of it as One Tap uses. */\ninterface GoogleMoment {\n isNotDisplayed?: () => boolean\n isSkippedMoment?: () => boolean\n isDismissedMoment?: () => boolean\n getDismissedReason?: () => string\n}\ninterface GoogleIdentity {\n initialize(config: Record<string, unknown>): void\n prompt(listener?: (moment: GoogleMoment) => void): void\n cancel(): void\n}\n\nlet googleScript: Promise<GoogleIdentity> | null = null\n\n/** Loads Google's script once per page. */\nfunction loadGoogleIdentity(): Promise<GoogleIdentity> {\n const ready = () => (window as unknown as { google?: { accounts?: { id?: GoogleIdentity } } }).google?.accounts?.id\n if (ready()) return Promise.resolve(ready()!)\n googleScript ??= new Promise<GoogleIdentity>((resolve, reject) => {\n const script = document.createElement('script')\n script.src = 'https://accounts.google.com/gsi/client'\n script.async = true\n script.onload = () => (ready() ? resolve(ready()!) : reject(new Error('google identity services did not load')))\n script.onerror = () => {\n googleScript = null\n reject(new Error('google identity services did not load'))\n }\n document.head.appendChild(script)\n })\n return googleScript\n}\n\n/** How long Cauthy waits for a passkey ceremony to be answered. */\nconst PASSKEY_SECONDS = 300\n\nconst UNAVAILABLE: AuthError = { code: 'unavailable', message: 'Sign-in is unavailable right now. Try again in a moment.' }\n\nexport function createAuthClient(options: AuthClientOptions = {}): AuthClient {\n const base = withoutTrailingSlashes(options.basePath ?? '/api/auth')\n const doFetch: typeof fetch = (...args) => (options.fetch ?? fetch)(...args)\n const locale = (): string => {\n const said = typeof options.locale === 'function' ? options.locale() : options.locale\n return (said ?? (typeof document === 'undefined' ? '' : document.documentElement?.lang) ?? '').trim()\n }\n\n async function post(path: string, form: Record<string, unknown>, bad: FieldProblem | null, captchaToken?: string): Promise<any> {\n if (bad) return { ok: false, error: { code: bad.code, message: bad.message, field: bad.field } }\n try {\n const res = await doFetch(`${base}${path}`, {\n method: 'POST',\n credentials: 'same-origin',\n headers: { 'content-type': 'application/json', accept: 'application/json', ...(locale() ? { 'x-cauthy-locale': locale() } : {}), ...(captchaToken ? { 'x-cauthy-captcha': captchaToken } : {}) },\n body: JSON.stringify(form),\n })\n const body = (await res.json().catch(() => null)) as any\n if (res.ok && body && typeof body.status === 'string') return { ok: true, ...body }\n const e = body?.error\n return { ok: false, error: e && typeof e.code === 'string' ? { code: e.code, message: String(e.message ?? ''), ...(e.field ? { field: e.field } : {}) } : UNAVAILABLE }\n } catch {\n return { ok: false, error: UNAVAILABLE }\n }\n }\n\n let looked: Promise<CauthyTheme | null> | undefined\n\n async function read(path: string): Promise<any> {\n try {\n const res = await doFetch(`${base}${path}`, { credentials: 'same-origin', headers: { accept: 'application/json' } })\n return res.ok ? await res.json() : null\n } catch {\n return null\n }\n }\n\n const upgradeUrl = (provider: string, o: { returnTo?: string } = {}) =>\n `${base}/me/upgrade/social/${encodeURIComponent(provider)}${o.returnTo ? `?return_to=${encodeURIComponent(o.returnTo)}` : ''}`\n const linkUrl = (provider: string, o: { returnTo?: string } = {}) =>\n `${base}/me/identities/link/${encodeURIComponent(provider)}${o.returnTo ? `?return_to=${encodeURIComponent(o.returnTo)}` : ''}`\n const socialUrl = (provider: string, o: { returnTo?: string } = {}) =>\n `${base}/sign-in/social/${encodeURIComponent(provider)}${o.returnTo ? `?return_to=${encodeURIComponent(o.returnTo)}` : ''}`\n\n const ssoUrl = (target: { email: string } | { connection: string }, o: { returnTo?: string } = {}) => {\n const q = new URLSearchParams('connection' in target ? { connection: target.connection } : { email: target.email.trim() })\n if (o.returnTo) q.set('return_to', o.returnTo)\n return `${base}/sign-in/sso?${q}`\n }\n\n const passkeyUrl = (o: { returnTo?: string; locale?: string } = {}) => {\n const q = new URLSearchParams({ method: 'passkey' })\n if (o.returnTo) q.set('return_to', o.returnTo)\n if (o.locale) q.set('locale', o.locale)\n return `${base}/login?${q}`\n }\n\n async function methods(): Promise<AuthMethods> {\n try {\n const res = await doFetch(`${base}/methods`, { credentials: 'same-origin', headers: { accept: 'application/json' } })\n if (res.ok) {\n // A server from before the passkey on the page does not say; then it is not.\n const m = (await res.json()) as Partial<AuthMethods>\n return { providers: [], signUp: true, magicLink: false, passkey: false, ...m, passkeyHere: m.passkeyHere === true, passkeyOnCauthy: m.passkeyOnCauthy ?? m.passkey === true, sso: m.sso === true }\n }\n } catch {\n // falls through\n }\n return { providers: [], signUp: true, magicLink: false, passkey: false, passkeyHere: false, passkeyOnCauthy: false, sso: false }\n }\n\n /** The two steps of a ceremony: what to ask the browser, then its answer. */\n async function ceremony(begin: string, finish: string, form: Record<string, unknown>, run: (options: any) => Promise<Record<string, unknown>>, more: Record<string, unknown> = {}): Promise<any> {\n const begun = await post(begin, form, null)\n if (!begun.ok) return begun\n let credential: Record<string, unknown>\n try {\n credential = await run(begun.options)\n } catch (e) {\n return { ok: false, error: ceremonyError(e) }\n }\n return post(finish, { credential, ...more }, null)\n }\n\n return {\n signIn: {\n anonymously: (f = {}) => post('/sign-in/anonymous', {}, null, f.captchaToken),\n email: (f) => post('/sign-in/email', { email: f.email, password: f.password }, firstProblem(checkEmail(f.email), checkPassword(f.password)), f.captchaToken),\n mfa: (f) => post('/sign-in/mfa', { code: f.code }, checkCode(f.code)),\n mfaMethods: () => post('/sign-in/mfa/methods', {}, null),\n mfaSendCode: () => post('/sign-in/mfa/send', {}, null),\n mfaPush: () => post('/sign-in/mfa/push', {}, null),\n async mfaPushWait(o = {}) {\n const status = await new Promise<string>((resolve) => {\n if (typeof EventSource !== 'function') return resolve('approved') // finish says whether it was\n const source = new EventSource(`${base}/sign-in/mfa/push/events`, { withCredentials: true })\n const stop = (st: string) => {\n source.close()\n resolve(st)\n }\n o.signal?.addEventListener('abort', () => stop('aborted'))\n source.addEventListener('status', (e) => {\n const st = (() => {\n try {\n return String(JSON.parse((e as MessageEvent).data).status)\n } catch {\n return ''\n }\n })()\n if (st && st !== 'pending') stop(st)\n })\n source.onerror = () => {\n if (source.readyState === EventSource.CLOSED) stop('expired')\n }\n })\n if (status === 'aborted') return { ok: false, error: { code: 'aborted', message: 'Stopped waiting.' } }\n if (status === 'denied') return { ok: false, error: { code: 'push_denied', message: 'The sign-in was turned down on the phone.' } }\n if (status === 'expired') return { ok: false, error: { code: 'push_expired', message: 'Nobody answered on the phone in time.' } }\n return post('/sign-in/mfa/push/finish', {}, null)\n },\n magicLink: (f) => post('/sign-in/magic-link', { email: f.email, ...(f.create ? { create: true } : {}), ...(f.returnTo ? { returnTo: f.returnTo } : {}) }, checkEmail(f.email), f.captchaToken),\n magicLinkVerify: (f) => post('/sign-in/magic-link/verify', { token: f.token }, null),\n emailCode: (f) => post('/sign-in/email-code', { code: f.code }, checkCode(f.code)),\n passkeyUrl,\n async passkey(o) {\n const m = await methods()\n if (m.passkeyHere && canUsePasskeys()) {\n const email = o?.email?.trim() ?? ''\n const r = await ceremony('/sign-in/passkey/begin', '/sign-in/passkey/finish', email !== '' && checkEmail(email) === null ? { email } : {}, getPasskey)\n // A browser that does not read the domain's related origins refuses\n // this page: Cauthy's, then, when it can.\n if (r.ok || r.error.code !== 'passkey_unavailable' || !m.passkeyOnCauthy) return r\n } else if (!m.passkeyOnCauthy) {\n return { ok: false, error: { code: 'passkey_unavailable', message: 'Passkeys cannot be used here.' } }\n }\n window.location.assign(passkeyUrl(o))\n return { ok: true, status: 'redirected' }\n },\n async passkeyAutofill(o = {}) {\n const cancelled = { ok: false as const, error: { code: 'cancelled', message: 'No passkey was chosen.' } }\n const m = await methods()\n if (!m.passkeyHere || !(await canAutofillPasskeys())) return cancelled\n for (;;) {\n if (o.signal?.aborted) return cancelled\n const begun = await post('/sign-in/passkey/begin', {}, null)\n if (!begun.ok) return begun\n // Cauthy's challenge lives five minutes: a page left open asks for\n // a fresh one before it runs out.\n const renew = new AbortController()\n const stop = () => renew.abort()\n o.signal?.addEventListener('abort', stop, { once: true })\n const timer = setTimeout(stop, (PASSKEY_SECONDS - 30) * 1000)\n let credential: Record<string, unknown>\n try {\n credential = await getPasskey(begun.options, { mediation: 'conditional', signal: renew.signal })\n } catch (e) {\n if (o.signal?.aborted) return cancelled\n if (renew.signal.aborted) continue\n return { ok: false, error: ceremonyError(e) }\n } finally {\n clearTimeout(timer)\n o.signal?.removeEventListener('abort', stop)\n }\n return post('/sign-in/passkey/finish', { credential }, null)\n }\n },\n async googleOneTap(o = {}) {\n const cancelled = { ok: false as const, error: { code: 'cancelled', message: 'No Google account was chosen.' } }\n if (typeof window === 'undefined' || o.signal?.aborted) return cancelled\n const begun = await post('/sign-in/google-one-tap/begin', {}, null)\n if (!begun.ok) return begun\n let gis: GoogleIdentity\n try {\n gis = await loadGoogleIdentity()\n } catch {\n return { ok: false, error: { code: 'unavailable', message: 'Google could not be reached. Try again, or sign in another way.' } }\n }\n const credential = await new Promise<string | null>((done) => {\n o.signal?.addEventListener('abort', () => {\n gis.cancel()\n done(null)\n }, { once: true })\n gis.initialize({\n client_id: begun.clientId, nonce: begun.nonce, context: o.context ?? 'signin',\n callback: (r: { credential?: string }) => done(r.credential ?? null),\n auto_select: false, cancel_on_tap_outside: true, itp_support: true, use_fedcm_for_prompt: true,\n })\n gis.prompt((moment: GoogleMoment) => {\n // The prompt was not shown, or the person closed it.\n if (moment.isNotDisplayed?.() || moment.isSkippedMoment?.()) done(null)\n else if (moment.isDismissedMoment?.() && moment.getDismissedReason?.() !== 'credential_returned') done(null)\n })\n })\n if (credential === null) return cancelled\n return post('/sign-in/google-one-tap', { credential }, null)\n },\n socialUrl,\n social(provider, o) {\n window.location.assign(socialUrl(provider, o))\n },\n ssoDiscover: (f) => post('/sign-in/sso/discover', { email: f.email.trim() }, checkEmail(f.email)),\n ssoUrl,\n sso(target, o) {\n window.location.assign(ssoUrl(target, o))\n },\n },\n signUp: {\n email: (f) => post('/sign-up/email', { email: f.email, password: f.password, name: f.name }, firstProblem(checkName(f.name), checkEmail(f.email), checkNewPassword(f.password)), f.captchaToken),\n },\n forgotPassword: (f) => post('/forgot-password', { email: f.email }, checkEmail(f.email), f.captchaToken),\n resetPassword: (f) => post('/reset-password', { token: f.token, password: f.password }, checkNewPassword(f.password)),\n verifyEmail: (f) => post('/verify-email', { token: f.token }, null),\n signOut: () => post('/sign-out', {}, null),\n organizations: {\n async list(system) {\n const body = await read(`/me/organizations${system ? `?system=${encodeURIComponent(system)}` : ''}`)\n return body && Array.isArray(body.organizations) ? (body.organizations as OrganizationSeat[]) : []\n },\n async members(id) {\n const body = await read(`/me/organizations/members?id=${encodeURIComponent(id)}`)\n return body && Array.isArray(body.members) ? (body.members as OrganizationPerson[]) : null\n },\n async roles(id) {\n const body = await read(`/me/organizations/roles?id=${encodeURIComponent(id)}`)\n return body && Array.isArray(body.roles) ? (body.roles as OrganizationRoleChoice[]) : null\n },\n add: (id, f) => post('/me/organizations/members/add', { organizationId: id, email: f.email, role: f.role, name: f.name ?? '', locale: f.locale ?? '' }, checkEmail(f.email)),\n setRole: (id, userId, role) => post('/me/organizations/members/role', { organizationId: id, userId, role }, null),\n remove: (id, userId) => post('/me/organizations/members/remove', { organizationId: id, userId }, null),\n inviteAgain: (id, userId, f = {}) => post('/me/organizations/members/invite', { organizationId: id, userId, locale: f.locale ?? '' }, null),\n },\n account: {\n async get() {\n const body = await read('/me')\n return body && typeof body.account === 'object' ? (body.account as Account) : null\n },\n update: (f) => post('/me', { name: f.name }, checkName(f.name)),\n changePassword: (f) => {\n const bad = checkNewPassword(f.newPassword)\n return post('/me/password', { password: f.password, newPassword: f.newPassword }, bad ? { ...bad, field: 'newPassword' } : null)\n },\n mfa: {\n setup: (f) => post('/me/mfa/setup', { password: f.password }, null),\n verify: (f) => post('/me/mfa/verify', { code: f.code }, checkCode(f.code)),\n disable: (f) => post('/me/mfa/disable', { password: f.password }, null),\n recoveryCodes: (f) => post('/me/mfa/recovery-codes', { password: f.password }, null),\n async factors() {\n const body = await read('/me/mfa/factors')\n return body && typeof body.totp === 'boolean' ? (body as SecondStep) : null\n },\n addPhone: (f) => post('/me/mfa/phone', { phone: f.phone, channel: f.channel, password: f.password }, null),\n verifyPhone: (f) => post('/me/mfa/phone/verify', { code: f.code }, checkCode(f.code)),\n removePhone: (f) => post('/me/mfa/phone/remove', { password: f.password }, null),\n },\n changeEmail: (f) => post('/me/email', { email: f.email, password: f.password }, checkEmail(f.email)),\n async identities() {\n const body = await read('/me/identities')\n return body && Array.isArray(body.identities) ? (body.identities as LinkedIdentity[]) : []\n },\n linkUrl,\n link(provider, o) {\n window.location.assign(linkUrl(provider, o))\n },\n unlink: (provider) => post('/me/identities/unlink', { provider }, null),\n async sessions() {\n const body = await read('/me/sessions')\n return body && Array.isArray(body.sessions) ? (body.sessions as AccountSession[]) : []\n },\n signOutEverywhere: () => post('/me/sessions/end', {}, null),\n upgrade: (f) => post('/me/upgrade', { email: f.email, password: f.password, name: f.name ?? '' }, firstProblem(checkName(f.name), checkEmail(f.email), checkNewPassword(f.password))),\n upgradeUrl,\n upgradeWith(provider, o) {\n window.location.assign(upgradeUrl(provider, o))\n },\n async agents() {\n const body = await read('/me/agents')\n return body && Array.isArray(body.agents) ? (body.agents as ConnectedAgent[]) : []\n },\n disconnectAgent: (id) => post('/me/agents/disconnect', { id }, null),\n async passkeys() {\n const body = await read('/me/passkeys')\n return body && Array.isArray(body.passkeys) ? (body.passkeys as Passkey[]) : []\n },\n addPasskey(f = {}) {\n if (!canUsePasskeys()) return Promise.resolve({ ok: false, error: { code: 'passkey_unavailable', message: 'This browser cannot make passkeys.' } })\n return ceremony('/me/passkeys/add/begin', '/me/passkeys/add/finish', { password: f.password ?? '', ...(f.kind ? { kind: f.kind } : {}) }, makePasskey, { name: f.name ?? '' })\n },\n removePasskey: (id) => post('/me/passkeys/remove', { id }, null),\n },\n async getSession() {\n try {\n const res = await doFetch(`${base}/session`, { credentials: 'same-origin', headers: { accept: 'application/json' } })\n if (!res.ok) return null\n return ((await res.json()) as { session: PublicSession | null }).session\n } catch {\n return null\n }\n },\n look() {\n looked ??= read('/look').then((wire) => (wire && typeof wire === 'object' ? fromWire(wire) : null))\n return looked\n },\n methods,\n }\n}\n\nexport { canAutofillPasskeys, canUsePasskeys } from './passkey'\nexport { createSessionStore, type SessionState, type SessionStore, type SessionStoreOptions } from './store'\n","// Who is signed in, kept for the browser: the same thing <CauthyProvider>\n// keeps for React, with no framework in it. `cauthy-auth/vue` and\n// `cauthy-auth/svelte` are this store in each one's own terms, and anything\n// else (Solid, Angular, a page with no framework) can use it as it is.\n//\n// const store = createSessionStore()\n// const stop = store.subscribe(({ session, status }) => render(session))\n// await store.auth.signIn.email({ email, password }) // subscribers hear the new session\n//\n// While somebody listens it asks the server who is signed in, asks again\n// shortly before the session's token runs out, and when the tab is looked at\n// again. With nobody listening it does nothing.\n\nimport { createAuthClient, type AuthClient, type AuthClientOptions } from './client'\nimport type { PublicSession } from './types'\nimport { withoutTrailingSlashes } from './url'\n\nexport interface SessionState {\n /** The signed-in person and their organizations, or null. */\n session: PublicSession | null\n status: 'loading' | 'authenticated' | 'unauthenticated'\n}\n\nexport interface SessionStoreOptions extends AuthClientOptions {\n /**\n * The session the server already read while rendering (SSR): the first\n * paint is then right, with no \"loading\". `null` means \"nobody\"; leave it\n * out when the server did not look.\n */\n initialSession?: PublicSession | null\n}\n\nexport interface SessionStore {\n /** What is known now. */\n get(): SessionState\n /** Calls `listener` now and on every change. Answers how to stop. */\n subscribe(listener: (state: SessionState) => void): () => void\n /** Asks the server again who is signed in. */\n refresh(): Promise<PublicSession | null>\n /** Sends the browser to Cauthy's page to sign in, and back to `returnTo` (default: where it is now). */\n signIn(options?: { returnTo?: string; locale?: string; prompt?: 'login' }): void\n /** Signs out of this application and of Cauthy in this browser. */\n signOut(): void\n /** Where the person looks after their account on Cauthy's page. */\n accountUrl(returnTo?: string): string\n /**\n * The functions of an application's own screens. They are\n * `createAuthClient()`'s, and what changes who is signed in is heard by the\n * subscribers with no reload.\n */\n auth: AuthClient\n /** Where the server mounted the routes. */\n basePath: string\n}\n\n/** Ask again this long before the session's token runs out. */\nconst AHEAD_MS = 60_000\n\nexport function createSessionStore(options: SessionStoreOptions = {}): SessionStore {\n const base = withoutTrailingSlashes(options.basePath ?? '/api/auth')\n const doFetch: typeof fetch = (...args) => (options.fetch ?? fetch)(...args)\n const first = options.initialSession\n let state: SessionState = { session: first ?? null, status: first === undefined ? 'loading' : first === null ? 'unauthenticated' : 'authenticated' }\n const listeners = new Set<(state: SessionState) => void>()\n let asking: Promise<PublicSession | null> | null = null\n let timer: ReturnType<typeof setTimeout> | undefined\n let asked = first !== undefined\n\n const set = (next: SessionState) => {\n if (next.session === state.session && next.status === state.status) return\n state = next\n schedule()\n for (const l of [...listeners]) l(state)\n }\n\n function schedule(): void {\n clearTimeout(timer)\n timer = undefined\n if (listeners.size === 0 || state.session === null) return\n const wait = Math.max(new Date(state.session.expiresAt).getTime() - Date.now() - AHEAD_MS, 5_000)\n timer = setTimeout(() => void refresh(), wait)\n }\n\n function refresh(): Promise<PublicSession | null> {\n asked = true\n asking ??= (async () => {\n try {\n const res = await doFetch(`${base}/session`, { credentials: 'same-origin', headers: { accept: 'application/json' } })\n if (!res.ok) throw new Error(String(res.status))\n const body = (await res.json()) as { session: PublicSession | null; status: string }\n // \"expired\" is the server unable to reach Cauthy: what is on screen\n // stays, and the next ask settles it.\n if (body.status !== 'expired') set({ session: body.session, status: body.session ? 'authenticated' : 'unauthenticated' })\n return body.session\n } catch {\n if (state.status === 'loading') set({ session: null, status: 'unauthenticated' })\n return null\n } finally {\n asking = null\n }\n })()\n return asking\n }\n\n const onVisible = () => {\n if (document.visibilityState === 'visible') void refresh()\n }\n const inBrowser = () => typeof document !== 'undefined'\n\n const client = createAuthClient({ basePath: base, fetch: options.fetch })\n const then = async <T,>(running: Promise<T>): Promise<T> => {\n const r = await running\n await refresh()\n return r\n }\n\n return {\n get: () => state,\n subscribe(listener) {\n listeners.add(listener)\n if (listeners.size === 1 && inBrowser()) {\n document.addEventListener('visibilitychange', onVisible)\n schedule()\n if (!asked) void refresh()\n }\n listener(state)\n return () => {\n listeners.delete(listener)\n if (listeners.size === 0) {\n clearTimeout(timer)\n timer = undefined\n if (inBrowser()) document.removeEventListener('visibilitychange', onVisible)\n }\n }\n },\n refresh,\n signIn(o = {}) {\n const q = new URLSearchParams({ return_to: o.returnTo ?? window.location.pathname + window.location.search })\n if (o.locale) q.set('locale', o.locale)\n if (o.prompt) q.set('prompt', o.prompt)\n window.location.assign(`${base}/login?${q}`)\n },\n signOut() {\n window.location.assign(`${base}/logout`)\n },\n accountUrl(returnTo) {\n const back = returnTo ?? (typeof window === 'undefined' ? '/' : window.location.pathname + window.location.search)\n return `${base}/account?return_to=${encodeURIComponent(back)}`\n },\n auth: {\n ...client,\n signIn: {\n ...client.signIn,\n email: (f) => then(client.signIn.email(f)),\n mfa: (f) => then(client.signIn.mfa(f)),\n magicLinkVerify: (f) => then(client.signIn.magicLinkVerify(f)),\n emailCode: (f) => then(client.signIn.emailCode(f)),\n passkey: (o) => then(client.signIn.passkey(o)),\n },\n signUp: { email: (f) => then(client.signUp.email(f)) },\n signOut: () => then(client.signOut()),\n account: {\n ...client.account,\n update: (f) => then(client.account.update(f)),\n changePassword: (f) => then(client.account.changePassword(f)),\n signOutEverywhere: () => then(client.account.signOutEverywhere()),\n },\n },\n basePath: base,\n }\n}\n","// What a person may do in an organization.\n//\n// When a system keeps its catalog of roles in Cauthy, each of a person's\n// organizations arrives with what their role — and anything given or denied to\n// them apart from it — lets them do. Checking is a lookup in that list: no\n// call to Cauthy, on the server or in the browser.\n\nimport { withoutTrailingSlashes } from './url'\n\n/** The part of an organization that says what its member may do. */\nexport interface Permitted {\n /** \"resource:action\" entries the person is allowed; \"*\" is any. Absent when the system keeps no catalog in Cauthy. */\n permissions?: string[]\n /** Entries the person is denied whatever else allows them. */\n denied?: string[]\n}\n\nfunction matches(entry: string, resource: string, action: string): boolean {\n // The resource may itself hold a colon: the action is what follows the last one.\n const i = entry.lastIndexOf(':')\n if (i < 0) return false\n const r = entry.slice(0, i)\n const a = entry.slice(i + 1)\n return (r === '*' || r === resource) && (a === '*' || a === action)\n}\n\n/**\n * Whether the person may do an action on a resource in an organization:\n * allowed by some entry, and denied by none. A denial wins.\n *\n * An organization of a system that keeps no catalog in Cauthy carries no\n * permissions, and the answer is `false`: decide by its `role` instead.\n *\n * if (!can(organization, 'invoicing', 'write')) return forbidden()\n */\nexport function can(organization: Permitted | null | undefined, resource: string, action: string): boolean {\n if (!organization?.permissions) return false\n if ((organization.denied ?? []).some((d) => matches(d, resource, action))) return false\n return organization.permissions.some((p) => matches(p, resource, action))\n}\n\n// ── A system's catalog of roles ────────────────────────────────────────────\n\n/** Something a role may do: an action on a resource, in organizations of a kind (\"*\", the default, is every kind). */\nexport interface PolicyPermission {\n kind?: string\n resource: string\n action: string\n}\n\nexport interface PolicyRole {\n role: string\n name?: string\n description?: string\n /** Roles whose permissions this one has as well. */\n inherits?: string[]\n permissions?: PolicyPermission[]\n}\n\n// ── The catalog, with its types ────────────────────────────────────────────\n//\n// A catalog written down once gives the roles and the permissions their\n// names: `can(org, 'ledger:write')` then takes only a permission the catalog\n// has, the editor offers them, and a typo does not compile.\n//\n// export const policy = definePolicy([\n// { role: 'reader', permissions: [{ resource: 'ledger', action: 'read' }] },\n// { role: 'accountant', inherits: ['reader'], permissions: [{ resource: 'ledger', action: 'write' }] },\n// ])\n// export const { can, hasRole } = policy\n//\n// can(organization, 'ledger:write') // 'ledger:wirte' does not compile\n// hasRole(organization, 'accountant')\n//\n// Write it by hand, or let `cauthy-auth policy --pull` write it from the\n// catalog Cauthy has.\n\n/** A role as `definePolicy` reads it: any `PolicyRole`, kept as written. */\ntype RoleShape = {\n readonly role: string\n readonly name?: string\n readonly description?: string\n readonly inherits?: readonly string[]\n readonly permissions?: readonly { readonly kind?: string; readonly resource: string; readonly action: string }[]\n}\n\n// \"*\" in a catalog is \"any\": the name it stands for is any text.\ntype Named<S extends string> = S extends '*' ? string : S\n\ntype PermissionsIn<R> = R extends { readonly permissions: readonly (infer P)[] }\n ? P extends { readonly resource: infer Res extends string; readonly action: infer Act extends string } ? `${Named<Res>}:${Named<Act>}` : never\n : never\n\n/** The roles a catalog names: `RoleOf<typeof policy>`. */\nexport type RoleOf<P> = P extends Policy<infer R, string> ? R : never\n/** The \"resource:action\" permissions a catalog names: `PermissionOf<typeof policy>`. */\nexport type PermissionOf<P> = P extends Policy<string, infer X> ? X : never\n\nexport interface Policy<Role extends string = string, Permission extends string = string> {\n /** The catalog as Cauthy takes it: `pushPolicy({ roles: policy.roles })`. */\n roles: PolicyRole[]\n /** Every role's name. */\n roleNames: Role[]\n /** Every permission some role has, as \"resource:action\". */\n permissions: Permission[]\n /**\n * Whether the person may do this in the organization: `can(org, 'ledger:write')`.\n * It reads what Cauthy sent with the session, so a catalog edited in Cauthy\n * since this file was written is still what decides.\n */\n can(organization: Permitted | null | undefined, permission: Permission): boolean\n /** Whether the person's role in the organization is one of these. */\n hasRole(organization: { role: string } | null | undefined, ...roles: Role[]): boolean\n /**\n * What a role may do by this catalog, with what it inherits: for showing a\n * role, never for deciding about a person (their exceptions are not here).\n */\n permissionsOf(role: Role): Permission[]\n}\n\n/** Gives a catalog of roles its types. See the top of this section. */\nexport function definePolicy<const T extends readonly RoleShape[]>(roles: T): Policy<T[number]['role'], PermissionsIn<T[number]>> {\n type Role = T[number]['role']\n type Permission = PermissionsIn<T[number]>\n const entry = (p: { resource: string; action: string }) => `${p.resource}:${p.action}` as Permission\n const byName = new Map(roles.map((r) => [r.role, r]))\n const split = (permission: string): [string, string] => {\n const i = permission.lastIndexOf(':')\n return i < 0 ? [permission, ''] : [permission.slice(0, i), permission.slice(i + 1)]\n }\n return {\n roles: roles.map((r) => ({\n role: r.role,\n ...(r.name !== undefined ? { name: r.name } : {}),\n ...(r.description !== undefined ? { description: r.description } : {}),\n ...(r.inherits ? { inherits: [...r.inherits] } : {}),\n ...(r.permissions ? { permissions: r.permissions.map((p) => ({ ...p })) } : {}),\n })),\n roleNames: roles.map((r) => r.role),\n permissions: [...new Set(roles.flatMap((r) => (r.permissions ?? []).map(entry)))],\n can(organization, permission) {\n const [resource, action] = split(permission)\n return can(organization, resource, action)\n },\n hasRole(organization, ...wanted) {\n return organization != null && (wanted as string[]).includes(organization.role)\n },\n permissionsOf(role: Role) {\n const seen = new Set<string>()\n const out = new Set<Permission>()\n const walk = (name: string) => {\n if (seen.has(name)) return // a catalog that inherits in a circle still ends\n seen.add(name)\n const r = byName.get(name)\n for (const p of r?.permissions ?? []) out.add(entry(p))\n for (const parent of r?.inherits ?? []) walk(parent)\n }\n walk(role)\n return [...out]\n },\n }\n}\n\nconst IDENT = /^[A-Za-z_$][A-Za-z0-9_$]*$/\n\n/**\n * A TypeScript module that defines the catalog given, for `cauthy-auth policy\n * --pull` to write: the application's types then come from the catalog\n * Cauthy has.\n */\nexport function policySource(system: string, roles: PolicyRole[]): string {\n const text = (v: string) => JSON.stringify(v)\n const key = (k: string) => (IDENT.test(k) ? k : text(k))\n const one = (r: PolicyRole) => {\n const parts = [`role: ${text(r.role)}`]\n if (r.name) parts.push(`name: ${text(r.name)}`)\n if (r.description) parts.push(`description: ${text(r.description)}`)\n if (r.inherits?.length) parts.push(`inherits: [${r.inherits.map(text).join(', ')}]`)\n if (r.permissions?.length) {\n const list = r.permissions.map((p) => ` { ${[...(p.kind && p.kind !== '*' ? [`${key('kind')}: ${text(p.kind)}`] : []), `resource: ${text(p.resource)}`, `action: ${text(p.action)}`].join(', ')} },`)\n parts.push(`permissions: [\\n${list.join('\\n')}\\n ]`)\n }\n return ` {\\n ${parts.join(',\\n ')},\\n },`\n }\n return `// The catalog of roles of ${text(system)}, as Cauthy has it.\n// Written by \\`cauthy-auth policy --pull\\`: run it again after the catalog\n// changes, and do not edit this file by hand.\n\nimport { definePolicy, type PermissionOf, type RoleOf } from 'cauthy-auth'\n\nexport const policy = definePolicy([\n${roles.map(one).join('\\n')}\n])\n\nexport const { can, hasRole } = policy\nexport type Role = RoleOf<typeof policy>\nexport type Permission = PermissionOf<typeof policy>\n`\n}\n\n/** Reads a system's catalog of roles from Cauthy, with a personal access token. */\nexport async function pullPolicy(o: Omit<PushPolicyOptions, 'roles'> = {}): Promise<PolicyRole[]> {\n const env = (n: string) => (typeof process !== 'undefined' ? process.env?.[n] : undefined)\n const issuer = withoutTrailingSlashes(o.issuer ?? env('CAUTHY_ISSUER') ?? '')\n const system = o.system ?? env('CAUTHY_SYSTEM') ?? ''\n const tenant = o.tenant ?? env('CAUTHY_TENANT') ?? ''\n const token = o.token ?? env('CAUTHY_TOKEN') ?? ''\n for (const [name, v] of Object.entries({ CAUTHY_ISSUER: issuer, CAUTHY_SYSTEM: system, CAUTHY_TENANT: tenant, CAUTHY_TOKEN: token })) {\n if (v === '') throw new Error(`cauthy-auth: ${name} is required to read a policy`)\n }\n const res = await (o.fetch ?? fetch)(`${issuer}/v1/systems/${encodeURIComponent(system)}/policy`, {\n headers: { authorization: `Bearer ${token}`, 'x-tenant-key': tenant, accept: 'application/json' },\n })\n const body = (await res.json().catch(() => null)) as { data?: { roles?: PolicyRole[] }; errors?: { message?: string }[] } | null\n if (!res.ok) throw new Error(`cauthy-auth: Cauthy refused to give the policy (${res.status}): ${body?.errors?.[0]?.message ?? res.statusText}`)\n return body?.data?.roles ?? []\n}\n\nexport interface PushPolicyOptions {\n /** The system whose catalog this is. Default: `CAUTHY_SYSTEM`. */\n system?: string\n roles: PolicyRole[]\n /** Cauthy's address. Default: `CAUTHY_ISSUER`. */\n issuer?: string\n /** The workspace's slug. Default: `CAUTHY_TENANT`. */\n tenant?: string\n /** A personal access token with `organizations:manage`. Default: `CAUTHY_TOKEN`. */\n token?: string\n fetch?: typeof fetch\n}\n\n/**\n * Gives Cauthy a system's catalog of roles, replacing the one it has. It is\n * the way to load a first catalog from what a system already had; from then on\n * the catalog is Cauthy's, edited in its dashboard, and pushing again would\n * overwrite what was edited there.\n */\nexport async function pushPolicy(o: PushPolicyOptions): Promise<void> {\n const env = (n: string) => (typeof process !== 'undefined' ? process.env?.[n] : undefined)\n const issuer = withoutTrailingSlashes(o.issuer ?? env('CAUTHY_ISSUER') ?? '')\n const system = o.system ?? env('CAUTHY_SYSTEM') ?? ''\n const tenant = o.tenant ?? env('CAUTHY_TENANT') ?? ''\n const token = o.token ?? env('CAUTHY_TOKEN') ?? ''\n for (const [name, v] of Object.entries({ CAUTHY_ISSUER: issuer, CAUTHY_SYSTEM: system, CAUTHY_TENANT: tenant, CAUTHY_TOKEN: token })) {\n if (v === '') throw new Error(`cauthy-auth: ${name} is required to push a policy`)\n }\n const res = await (o.fetch ?? fetch)(`${issuer}/v1/systems/${encodeURIComponent(system)}/policy`, {\n method: 'PUT',\n headers: { authorization: `Bearer ${token}`, 'x-tenant-key': tenant, 'content-type': 'application/json' },\n body: JSON.stringify({ roles: o.roles }),\n })\n if (!res.ok) {\n const body = (await res.json().catch(() => null)) as { errors?: { message?: string }[] } | null\n throw new Error(`cauthy-auth: Cauthy refused the policy (${res.status}): ${body?.errors?.[0]?.message ?? res.statusText}`)\n }\n}\n"],"mappings":"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","names":["text"]}
|
|
1
|
+
{"version":3,"sources":["../src/svelte.ts","../src/passkey.ts","../src/url.ts","../src/theme.ts","../src/validate.ts","../src/client.ts","../src/store.ts","../src/permissions.ts"],"sourcesContent":["// Svelte: who is signed in, as stores.\n//\n// // src/lib/auth.ts\n// export const { session, status, auth, signIn, signOut } = createCauthySession()\n//\n// <script>\n// import { session, auth } from '$lib/auth'\n// </script>\n// {#if $session}Hello {$session.user.name}{/if}\n//\n// `session` and `status` keep Svelte's store contract, so `$session` works in\n// Svelte 4 and 5 alike, and nothing here imports Svelte.\n//\n// On the server (SvelteKit) the routes are one file, with `createCauthy()`:\n//\n// // src/routes/api/auth/[...cauthy]/+server.ts\n// export const GET = ({ request }) => auth.handler(request)\n// export const POST = GET\n\nimport { createSessionStore, type SessionState, type SessionStore, type SessionStoreOptions } from './store'\nimport type { PublicSession } from './types'\n\n/** A value Svelte can read with `$`: its store contract. */\nexport interface Readable<T> {\n subscribe(run: (value: T) => void): () => void\n}\n\nexport interface CauthySession extends Pick<SessionStore, 'refresh' | 'signIn' | 'signOut' | 'accountUrl' | 'auth' | 'basePath'> {\n /** The signed-in person and their organizations, or null: `$session`. */\n session: Readable<PublicSession | null>\n /** `$status`: loading, authenticated or unauthenticated. */\n status: Readable<SessionState['status']>\n}\n\n/**\n * Give it `initialSession` from a load function (`event.locals`, or\n * `auth.toPublic(await auth.getSession(request))`) and the first paint knows\n * who is there.\n */\nexport function createCauthySession(options: SessionStoreOptions = {}): CauthySession {\n const store = createSessionStore(options)\n // Each store tells its subscribers only when its own value changed.\n const part = <T,>(pick: (s: SessionState) => T): Readable<T> => ({\n subscribe(run) {\n let last: T | undefined\n let told = false\n return store.subscribe((s) => {\n const value = pick(s)\n if (told && value === last) return\n told = true\n last = value\n run(value)\n })\n },\n })\n return {\n session: part((s) => s.session),\n status: part((s) => s.status),\n refresh: store.refresh,\n signIn: store.signIn,\n signOut: store.signOut,\n accountUrl: store.accountUrl,\n auth: store.auth,\n basePath: store.basePath,\n }\n}\n\nexport type { SessionStoreOptions as CauthySessionOptions } from './store'\nexport type { AuthClient, AuthError, AuthMethods, AuthResult, Account, AccountSession, Passkey, PasskeyKind, SSOConnection } from './client'\nexport type { PublicSession, SessionOrganization, SessionUser } from './types'\nexport { can, definePolicy, type PermissionOf, type Policy, type RoleOf } from './permissions'\n","// A passkey ceremony in the browser: what Cauthy says to ask for, in the\n// shape the browser's WebAuthn API takes, and what the browser answers, in\n// the shape Cauthy reads. Both travel as JSON, with bytes in base64url.\n\nimport type { AuthError } from './client'\n\n/** Whether this browser can use a passkey at all. */\nexport function canUsePasskeys(): boolean {\n return typeof window !== 'undefined' && typeof window.PublicKeyCredential === 'function' &&\n typeof navigator !== 'undefined' && typeof navigator.credentials?.get === 'function'\n}\n\n/**\n * Whether this browser offers passkeys in the autofill of a field marked\n * `autocomplete=\"username webauthn\"`, so the person picks one without\n * looking for a button.\n */\nexport async function canAutofillPasskeys(): Promise<boolean> {\n if (!canUsePasskeys()) return false\n const pkc = window.PublicKeyCredential as unknown as { isConditionalMediationAvailable?: () => Promise<boolean> }\n try {\n return typeof pkc.isConditionalMediationAvailable === 'function' && (await pkc.isConditionalMediationAvailable()) === true\n } catch {\n return false\n }\n}\n\nfunction bytes(text: string): ArrayBuffer {\n const b64 = text.replace(/-/g, '+').replace(/_/g, '/').padEnd(Math.ceil(text.length / 4) * 4, '=')\n const bin = atob(b64)\n const out = new Uint8Array(bin.length)\n for (let i = 0; i < bin.length; i++) out[i] = bin.charCodeAt(i)\n return out.buffer\n}\n\nfunction text(buf: ArrayBuffer | null | undefined): string | null {\n if (!buf) return null\n const view = new Uint8Array(buf)\n let bin = ''\n for (let i = 0; i < view.byteLength; i++) bin += String.fromCharCode(view[i])\n return btoa(bin).replace(/\\+/g, '-').replace(/\\//g, '_').replace(/=+$/, '')\n}\n\nconst withId = (list: any[] | undefined) => (list ?? []).map((c) => ({ ...c, id: bytes(c.id) }))\n\n/**\n * Asks the browser for a passkey, with the options Cauthy sent. `conditional`:\n * in the autofill of the page's username field, waiting until the person\n * picks one or `signal` aborts.\n */\nexport async function getPasskey(options: any, how: { mediation?: 'conditional'; signal?: AbortSignal } = {}): Promise<Record<string, unknown>> {\n const publicKey = { ...options, challenge: bytes(options.challenge), allowCredentials: withId(options.allowCredentials) }\n const cred = (await navigator.credentials.get({ publicKey, ...how } as CredentialRequestOptions)) as PublicKeyCredential | null\n if (cred === null) throw new DOMException('No passkey was chosen.', 'NotAllowedError')\n const r = cred.response as AuthenticatorAssertionResponse\n return {\n id: cred.id, rawId: text(cred.rawId), type: cred.type,\n response: {\n authenticatorData: text(r.authenticatorData), clientDataJSON: text(r.clientDataJSON),\n signature: text(r.signature), userHandle: text(r.userHandle),\n },\n clientExtensionResults: cred.getClientExtensionResults?.() ?? {},\n }\n}\n\n/** Asks the browser to make a passkey, with the options Cauthy sent. */\nexport async function makePasskey(options: any): Promise<Record<string, unknown>> {\n const publicKey = {\n ...options,\n challenge: bytes(options.challenge),\n user: { ...options.user, id: bytes(options.user.id) },\n excludeCredentials: withId(options.excludeCredentials),\n }\n const cred = (await navigator.credentials.create({ publicKey })) as PublicKeyCredential | null\n if (cred === null) throw new DOMException('No passkey was made.', 'NotAllowedError')\n const r = cred.response as AuthenticatorAttestationResponse\n return {\n id: cred.id, rawId: text(cred.rawId), type: cred.type,\n response: {\n clientDataJSON: text(r.clientDataJSON), attestationObject: text(r.attestationObject),\n transports: typeof r.getTransports === 'function' ? r.getTransports() : [],\n },\n clientExtensionResults: cred.getClientExtensionResults?.() ?? {},\n }\n}\n\n/** What the browser's refusal means, for the screen. */\nexport function ceremonyError(e: unknown): AuthError {\n const name = e instanceof Error || e instanceof DOMException ? e.name : ''\n if (name === 'NotAllowedError' || name === 'AbortError') return { code: 'cancelled', message: 'The passkey was not used. Try again when you are ready.' }\n if (name === 'InvalidStateError') return { code: 'passkey_exists', message: 'This device already has a passkey for this account.' }\n if (name === 'SecurityError') return { code: 'passkey_unavailable', message: 'Passkeys cannot be used on this page.' }\n return { code: 'passkey_failed', message: 'The passkey could not be used. Try again, or sign in another way.' }\n}\n","/**\n * The address without its trailing slashes: `https://auth.example.com/` and\n * `https://auth.example.com` name the same issuer. A loop, not `/\\/+$/`,\n * which backtracks over a long run of slashes.\n */\nexport function withoutTrailingSlashes(s: string): string {\n let end = s.length\n while (end > 0 && s.charCodeAt(end - 1) === 47) end--\n return s.slice(0, end)\n}\n","// An application's look, for Cauthy's hosted pages.\n//\n// Cauthy's pages — sign-in, sign-up, the account page — can take the colours,\n// corners, typeface and logo of the application that sends its people there,\n// so signing in reads as a page of the application. A theme is a short list of\n// values, which Cauthy checks one by one; it is never CSS.\n//\n// An application built with Stackmyth already has those values, as its\n// `--sm-*` tokens. `themeFromStackmyth` reads them out of its stylesheets.\n\nimport { withoutTrailingSlashes } from './url'\n\n/** How an application looks on Cauthy's hosted pages. Every value is optional. */\n/** One font file the application serves: a family in one weight (or a range) and one style. */\nexport interface ThemeFont {\n family: string\n /** An https address of a `.woff2` file. It must answer with `Access-Control-Allow-Origin` for Cauthy's origin. */\n url: string\n /** `400`, or `\"100 900\"` for a variable font. Default: 400. */\n weight?: number | string\n style?: 'normal' | 'italic'\n}\n\n/** What an application says on its sign-in page, in one language. */\nexport interface ThemeTexts {\n /** Replaces \"Sign in\". */\n heading?: string\n /** Replaces \"to continue to …\". */\n subheading?: string\n /** The messages of the split layout's panel, shown one at a time (up to 5). */\n panel?: string[]\n /** Links at the foot of the split layout: privacy, terms (up to 4). */\n links?: { label: string; url: string }[]\n}\n\nexport interface CauthyTheme {\n /** The page. Colours are `#rrggbb` or `rgba(r, g, b, a)`. */\n background?: string\n /** The card, and inputs. */\n surface?: string\n /** Headings and what is typed. */\n text?: string\n /** Paragraphs, labels, small print. */\n textMuted?: string\n border?: string\n /** The fields and the secondary buttons, when they are not the card's colours. */\n inputBackground?: string\n inputBorder?: string\n /** The main button, and what is written on it. */\n primary?: string\n primaryText?: string\n /** Links, and the focus ring. */\n accent?: string\n error?: string\n errorBackground?: string\n success?: string\n successBackground?: string\n /** Corners of cards and inputs, in pixels (0–40). */\n radius?: number\n /** Corners of buttons, in pixels (0–40). */\n buttonRadius?: number\n /** A CSS font-family list: the application's typeface first, a system font after it. */\n fontFamily?: string\n /** The files of the typefaces `fontFamily` names, for people who do not have them installed. */\n fonts?: ThemeFont[]\n /** An image shown where \"Cauthy\" is, over https. */\n logoUrl?: string\n /**\n * How the sign-in pages are laid out: `card` (the default, one card in the\n * middle) or `split` (the form on one side, a panel that presents the\n * application on the other).\n */\n layout?: 'card' | 'split'\n /** The colours the split layout's backdrop blends, one to four. */\n panelColors?: string[]\n /** The colour of what is written on the panel. */\n panelText?: string\n /** An https image shown over the panel's messages. */\n panelIconUrl?: string\n /** The application's own words on the sign-in page, by language (`en`, `es`). */\n texts?: Record<string, ThemeTexts>\n /**\n * The application's Stackmyth tokens (`--sm-*`), from `tokensFromStackmyth`.\n * Cauthy's pages are drawn with Stackmyth's components, so these make its\n * buttons and fields the application's own. They are laid over the named values.\n */\n tokens?: Record<string, string>\n}\n\nexport interface StackmythThemeOptions {\n /**\n * The named theme to read, when the stylesheet keeps several under\n * `[data-theme=…]` (`\"vennet\"`). Without it only what applies to every\n * page (`:root`) is read.\n */\n theme?: string\n /** Which of the application's two modes to take. Default: `light`. */\n mode?: 'light' | 'dark'\n logoUrl?: string\n}\n\nexport interface ImportedTheme {\n theme: CauthyTheme\n /** Tokens that were there and could not be carried over, and why. */\n warnings: string[]\n}\n\nconst MAX_RADIUS = 40\n\n// ── Reading custom properties out of CSS ───────────────────────────────────\n\ninterface Declaration {\n name: string\n value: string\n /** The selectors and at-rules it sits inside, outermost first. */\n within: string[]\n}\n\n// The stylesheet without its /* comments */, read in one pass. An unclosed\n// comment runs to the end, as a browser reads it.\nfunction withoutComments(css: string): string {\n let out = ''\n let i = 0\n for (;;) {\n const open = css.indexOf('/*', i)\n if (open < 0) return out + css.slice(i)\n out += css.slice(i, open)\n const close = css.indexOf('*/', open + 2)\n if (close < 0) return out\n i = close + 2\n }\n}\n\nfunction declarations(css: string): Declaration[] {\n const text = withoutComments(css)\n const out: Declaration[] = []\n const within: string[] = []\n let chunk = ''\n let quote = ''\n let depth = 0 // parentheses, so a `;` inside url(…) does not end anything\n const flush = () => {\n const m = /^\\s*(--[\\w-]+)\\s*:\\s*([\\s\\S]+?)\\s*$/.exec(chunk)\n if (m) out.push({ name: m[1], value: m[2].replace(/\\s+/g, ' ').replace(/\\s*!important$/, ''), within: [...within] })\n chunk = ''\n }\n for (const ch of text) {\n if (quote) {\n chunk += ch\n if (ch === quote) quote = ''\n continue\n }\n if (ch === '\"' || ch === \"'\") {\n quote = ch\n chunk += ch\n } else if (ch === '(') {\n depth++\n chunk += ch\n } else if (ch === ')') {\n depth = Math.max(0, depth - 1)\n chunk += ch\n } else if (ch === '{' && depth === 0) {\n within.push(chunk.trim().replace(/\\s+/g, ' '))\n chunk = ''\n } else if (ch === '}' && depth === 0) {\n flush()\n within.pop()\n } else if (ch === ';' && depth === 0) {\n flush()\n } else {\n chunk += ch\n }\n }\n return out\n}\n\nconst DARK = /prefers-color-scheme:\\s*dark|data-(mode|theme|color-scheme)=[\"']?dark|\\.dark\\b/i\nconst LIGHT = /prefers-color-scheme:\\s*light|data-(mode|theme|color-scheme)=[\"']?light|\\.light\\b/i\nconst NAMED = /data-theme=[\"']?([\\w-]+)/i\n\n/** Whether a declaration applies to the page being themed. */\nfunction applies(d: Declaration, o: Required<Pick<StackmythThemeOptions, 'mode'>> & { theme?: string }): boolean {\n for (const scope of d.within) {\n if (o.mode === 'light' && DARK.test(scope)) return false\n if (o.mode === 'dark' && LIGHT.test(scope)) return false\n const named = NAMED.exec(scope)\n if (named && !/^(dark|light)$/i.test(named[1]) && named[1] !== o.theme) return false\n // Component-level rules (`.btn { --sm-… }`) are not the page's theme;\n // the class a mode is switched with (`.dark`, as shadcn/ui does) is.\n if (!scope.startsWith('@') && !/:root|^html\\b|^body\\b|\\[data-|^\\*$|:host|^\\.(dark|light)\\b/.test(scope)) return false\n }\n return true\n}\n\n// ── Tokens ─────────────────────────────────────────────────────────────────\n\n/** What a token's value may be written with, as Cauthy checks it. */\nconst TOKEN_CHARS = /^[A-Za-z0-9 #%.,()/'\"_+*-]+$/\nconst TOKEN_FUNCS = new Set(['var', 'calc', 'min', 'max', 'clamp', 'rgb', 'rgba', 'hsl', 'hsla', 'hwb', 'lab', 'lch', 'oklab', 'oklch', 'color',\n 'color-mix', 'light-dark', 'cubic-bezier', 'steps', 'linear', 'linear-gradient', 'radial-gradient', 'conic-gradient',\n 'repeating-linear-gradient', 'repeating-radial-gradient', 'drop-shadow', 'blur', 'translate', 'translatex', 'translatey', 'scale', 'rotate'])\n\nfunction tokenValue(value: string): string | null {\n const v = value.replace(/\\s+/g, ' ').trim()\n if (v === '' || v.length > 400 || !TOKEN_CHARS.test(v)) return null\n for (const m of v.matchAll(/([A-Za-z-]+)\\(/g)) if (!TOKEN_FUNCS.has(m[1].toLowerCase())) return null\n if (/^\\(|[ ,(]\\(/.test(v)) return null\n return v\n}\n\n/** A scope that is the page's theme itself: the root, or the theme and mode attributes — never a component inside it. */\nconst THEME_SELECTOR = /^(?::root|html|body|\\[data-(?:theme|mode|color-scheme)(?:=[\"']?[\\w-]+[\"']?)?\\])$/i\n// Checked a selector at a time (split on commas and spaces) rather than with\n// one pattern over the list, which backtracks on a long one.\nconst isThemeScope = (selectors: string) => {\n const parts = selectors.split(/[\\s,]+/).filter(Boolean)\n return parts.length > 0 && parts.every((one) => THEME_SELECTOR.test(one))\n}\n\nexport interface StackmythTokens {\n /** Every `--sm-*` token the stylesheets set for the page, ready for `theme.tokens`. */\n tokens: Record<string, string>\n /** Tokens that were there and Cauthy would not take, and why. */\n warnings: string[]\n}\n\n/**\n * Reads an application's Stackmyth tokens out of its stylesheets: every\n * `--sm-*` custom property its theme sets, in the order the sheets are given\n * (a later one wins). Its own names — a brand palette the tokens point at —\n * are written in, so what comes out stands on Stackmyth's names alone.\n *\n * Cauthy's sign-in pages are drawn with Stackmyth's components: these tokens\n * are what makes them look like the application.\n */\nexport function tokensFromStackmyth(css: string | string[], options: StackmythThemeOptions = {}): StackmythTokens {\n const mode = options.mode ?? 'light'\n const all = (Array.isArray(css) ? css : [css]).flatMap(declarations)\n const named = options.theme ?? all.map((d) => d.within.map((w) => NAMED.exec(w)?.[1]).find((n) => n && !/^(dark|light)$/i.test(n))).find(Boolean)\n const vars = new Map<string, string>()\n for (const d of all) {\n if (!applies(d, { mode, theme: named })) continue\n if (!d.within.every((w) => w.startsWith('@') || isThemeScope(w))) continue\n vars.set(d.name, d.value)\n }\n // The application's own names are written in; Stackmyth's are left as references.\n const inline = (value: string, depth = 0): string | null => {\n if (depth > 16) return null\n let failed = false\n let out = value\n for (let guard = 0; guard < 64; guard++) {\n const next = out.replace(/var\\(\\s*(--(?!sm-)[\\w-]+)\\s*(?:,\\s*([^()]*(?:\\([^()]*\\)[^()]*)*))?\\)/, (_m, name: string, fallback?: string) => {\n const known = vars.get(name)\n const got = known !== undefined ? inline(known, depth + 1) : fallback !== undefined ? inline(fallback.trim(), depth + 1) : null\n if (got === null) failed = true\n return got ?? ''\n })\n if (failed) return null\n if (next === out) break\n out = next\n }\n return out\n }\n const tokens: Record<string, string> = {}\n const warnings: string[] = []\n for (const [name, raw] of vars) {\n if (!/^--sm-[a-z0-9]+(?:-[a-z0-9]+)*$/.test(name)) continue\n const written = inline(raw)\n const value = written === null ? null : tokenValue(written)\n if (value === null) warnings.push(`${name}: \"${raw.length > 60 ? raw.slice(0, 57) + '…' : raw}\" is not a value Cauthy takes`)\n else tokens[name] = value\n }\n if (Object.keys(tokens).length === 0) warnings.push('no --sm-* token was found in these stylesheets')\n return { tokens, warnings }\n}\n\n// ── Values ─────────────────────────────────────────────────────────────────\n\nfunction resolve(value: string, vars: Map<string, string>, seen = 0): string | null {\n if (seen > 16) return null\n let unresolved = false\n // Innermost var() first: fallbacks may hold another var().\n let out = value\n for (let guard = 0; guard < 32 && /var\\(/.test(out); guard++) {\n out = out.replace(/var\\(\\s*(--[\\w-]+)\\s*(?:,\\s*([^()]*(?:\\([^()]*\\)[^()]*)*))?\\)/, (_m, name: string, fallback?: string) => {\n const known = vars.get(name)\n const next = known !== undefined ? resolve(known, vars, seen + 1) : fallback !== undefined ? resolve(fallback.trim(), vars, seen + 1) : null\n if (next === null) unresolved = true\n return next ?? ''\n })\n if (unresolved) return null\n }\n return /var\\(/.test(out) ? null : out.trim()\n}\n\nconst clamp01 = (n: number) => Math.min(1, Math.max(0, n))\nconst hex2 = (n: number) => Math.round(clamp01(n) * 255).toString(16).padStart(2, '0')\nconst gamma = (c: number) => (c <= 0.0031308 ? 12.92 * c : 1.055 * Math.pow(c, 1 / 2.4) - 0.055)\n\nfunction out(r: number, g: number, b: number, a = 1): string {\n if (a >= 1) return `#${hex2(r)}${hex2(g)}${hex2(b)}`\n return `rgba(${Math.round(clamp01(r) * 255)}, ${Math.round(clamp01(g) * 255)}, ${Math.round(clamp01(b) * 255)}, ${Math.round(clamp01(a) * 1000) / 1000})`\n}\n\nconst alpha = (s: string | undefined) => (s === undefined ? 1 : s.endsWith('%') ? parseFloat(s) / 100 : parseFloat(s))\n\nconst KEYWORDS: Record<string, string> = { white: '#ffffff', black: '#000000' }\n\n/** A colour as Cauthy takes it — `#rrggbb` or `rgba(…)` — or null when it is none it can read. */\nexport function toColor(value: string): string | null {\n const v = value.trim().toLowerCase()\n if (KEYWORDS[v]) return KEYWORDS[v]\n if (/^#([0-9a-f]{3,4}|[0-9a-f]{6}|[0-9a-f]{8})$/.test(v)) return v\n let m = /^rgba?\\(\\s*([\\d.]+%?)[\\s,]+([\\d.]+%?)[\\s,]+([\\d.]+%?)(?:\\s*[,/]\\s*([\\d.]+%?))?\\s*\\)$/.exec(v)\n if (m) {\n const ch = (s: string) => (s.endsWith('%') ? parseFloat(s) / 100 : parseFloat(s) / 255)\n return out(ch(m[1]), ch(m[2]), ch(m[3]), alpha(m[4]))\n }\n m = /^hsla?\\(\\s*([\\d.]+)(?:deg)?[\\s,]+([\\d.]+)%[\\s,]+([\\d.]+)%(?:\\s*[,/]\\s*([\\d.]+%?))?\\s*\\)$/.exec(v)\n if (m) {\n const h = ((parseFloat(m[1]) % 360) + 360) % 360\n const s = parseFloat(m[2]) / 100\n const l = parseFloat(m[3]) / 100\n const f = (n: number) => {\n const k = (n + h / 30) % 12\n return l - s * Math.min(l, 1 - l) * Math.max(-1, Math.min(k - 3, 9 - k, 1))\n }\n return out(f(0), f(8), f(4), alpha(m[4]))\n }\n m = /^oklch\\(\\s*([\\d.]+%?)\\s+([\\d.]+%?)\\s+([\\d.]+)(?:deg)?(?:\\s*\\/\\s*([\\d.]+%?))?\\s*\\)$/.exec(v)\n if (m) {\n const L = m[1].endsWith('%') ? parseFloat(m[1]) / 100 : parseFloat(m[1])\n const C = m[2].endsWith('%') ? (parseFloat(m[2]) / 100) * 0.4 : parseFloat(m[2])\n const H = (parseFloat(m[3]) * Math.PI) / 180\n const a = C * Math.cos(H)\n const b = C * Math.sin(H)\n const l = Math.pow(L + 0.3963377774 * a + 0.2158037573 * b, 3)\n const mm = Math.pow(L - 0.1055613458 * a - 0.0638541728 * b, 3)\n const s = Math.pow(L - 0.0894841775 * a - 1.291485548 * b, 3)\n return out(\n gamma(4.0767416621 * l - 3.3077115913 * mm + 0.2309699292 * s),\n gamma(-1.2684380046 * l + 2.6097574011 * mm - 0.3413193965 * s),\n gamma(-0.0041960863 * l - 0.7034186147 * mm + 1.707614701 * s),\n alpha(m[4]),\n )\n }\n return null\n}\n\n/** A length in pixels, within what Cauthy takes, or null. */\nfunction toRadius(value: string): number | null {\n const m = /^([\\d.]+)(px|rem|em)?$/.exec(value.trim())\n if (!m) return null\n const px = parseFloat(m[1]) * (m[2] === 'rem' || m[2] === 'em' ? 16 : 1)\n return Number.isFinite(px) ? Math.min(MAX_RADIUS, Math.max(0, Math.round(px))) : null\n}\n\n/** A font list as Cauthy takes it, or null. */\nfunction toFont(value: string): string | null {\n const v = value.trim()\n return /^[A-Za-z0-9 ,'\"_-]{1,200}$/.test(v) && (v.match(/\"/g)?.length ?? 0) % 2 === 0 && (v.match(/'/g)?.length ?? 0) % 2 === 0 ? v : null\n}\n\n// ── Stackmyth ──────────────────────────────────────────────────────────────\n\n/** Which Stackmyth token each value of a theme is read from, first found wins. */\nconst COLORS: [keyof CauthyTheme, string[]][] = [\n ['background', ['--sm-bg-surface', '--sm-surface']],\n ['surface', ['--sm-bg-surface-raised', '--sm-surface-raised']],\n ['text', ['--sm-text-primary', '--sm-surface-text']],\n ['textMuted', ['--sm-text-secondary']],\n ['border', ['--sm-border-default', '--sm-surface-border']],\n ['primary', ['--sm-primary', '--sm-bg-interactive']],\n ['primaryText', ['--sm-primary-text', '--sm-text-inverse']],\n ['accent', ['--sm-text-brand', '--sm-border-focus', '--sm-accent']],\n ['error', ['--sm-error-soft-text', '--sm-error']],\n ['errorBackground', ['--sm-error-soft']],\n ['success', ['--sm-success-soft-text', '--sm-success']],\n ['successBackground', ['--sm-success-soft']],\n]\n\n/**\n * Reads an application's Stackmyth theme out of its stylesheets.\n *\n * Give it the CSS that declares the tokens, in the order the application\n * loads it — Stackmyth's own (`@stackmyth/core/core.vars.css`, or a theme\n * file) and then the application's brand file that re-points them. Tokens\n * that point at the application's own variables are followed. The\n * application's dark mode is left out unless asked for.\n */\nexport function themeFromStackmyth(css: string | string[], options: StackmythThemeOptions = {}): ImportedTheme {\n const o = { mode: options.mode ?? 'light', theme: options.theme }\n const vars = new Map<string, string>()\n for (const sheet of Array.isArray(css) ? css : [css]) {\n for (const d of declarations(sheet)) if (applies(d, o)) vars.set(d.name, d.value)\n }\n const theme: CauthyTheme = {}\n const warnings: string[] = []\n\n const read = <T>(tokens: string[], to: (v: string) => T | null, what: string): T | undefined => {\n for (const token of tokens) {\n const raw = vars.get(token)\n if (raw === undefined) continue\n const value = resolve(raw, vars)\n const got = value === null ? null : to(value)\n if (got !== null) return got\n warnings.push(`${token}: \"${raw}\" is not ${what} Cauthy can take`)\n }\n return undefined\n }\n\n for (const [field, tokens] of COLORS) {\n const c = read(tokens, toColor, 'a colour')\n if (c !== undefined) (theme as Record<string, unknown>)[field] = c\n }\n const radius = read(['--sm-radius-container', '--sm-radius-md'], toRadius, 'a length')\n if (radius !== undefined) theme.radius = radius\n const button = read(['--sm-radius-interactive', '--sm-radius-sm'], toRadius, 'a length')\n if (button !== undefined) theme.buttonRadius = button\n const font = read(['--sm-font-family'], toFont, 'a font list')\n if (font !== undefined) theme.fontFamily = font\n if (options.logoUrl) theme.logoUrl = options.logoUrl\n\n if (vars.size === 0) warnings.push('no custom properties were found: is this the stylesheet that declares the --sm-* tokens?')\n else if (![...vars.keys()].some((k) => k.startsWith('--sm-'))) warnings.push('no --sm-* token was found in these stylesheets')\n return { theme, warnings }\n}\n\n// ── Any stylesheet ─────────────────────────────────────────────────────────\n\n/** The values of a theme a stylesheet can say. */\nexport type ThemeField =\n | 'background' | 'surface' | 'text' | 'textMuted' | 'border' | 'inputBackground' | 'inputBorder'\n | 'primary' | 'primaryText' | 'accent' | 'error' | 'errorBackground' | 'success' | 'successBackground'\n | 'radius' | 'buttonRadius' | 'fontFamily'\n\n/** How a stylesheet names its look: Stackmyth's tokens, shadcn/ui's (and Tailwind's), or the application's own. */\nexport type CssFormat = 'stackmyth' | 'shadcn' | 'custom'\n\nexport interface CssThemeOptions {\n /** Which names to read. Default: `auto`, told from the stylesheet itself. */\n format?: CssFormat | 'auto'\n /** Which of the application's two modes to take. Default: `light`. */\n mode?: 'light' | 'dark'\n /** The named theme to read, when the stylesheet keeps several under `[data-theme=…]`. */\n theme?: string\n /** For `custom`: the variable each value is read from, `{ primary: '--brand' }`. */\n map?: Partial<Record<ThemeField, string>>\n}\n\nexport interface CssImportedTheme extends ImportedTheme {\n /** The names that were read. */\n format: CssFormat\n}\n\nconst RADII: ThemeField[] = ['radius', 'buttonRadius']\n\n/** shadcn/ui's names, as Tailwind 3 (`--primary`) and Tailwind 4 (`--color-primary`) write them. */\nconst SHADCN: [ThemeField, string[]][] = [\n ['background', ['--background', '--color-background']],\n ['surface', ['--card', '--color-card', '--popover', '--color-popover']],\n ['text', ['--foreground', '--color-foreground']],\n ['textMuted', ['--muted-foreground', '--color-muted-foreground']],\n ['border', ['--border', '--color-border']],\n ['inputBorder', ['--input', '--color-input']],\n ['primary', ['--primary', '--color-primary']],\n ['primaryText', ['--primary-foreground', '--color-primary-foreground']],\n ['accent', ['--ring', '--color-ring', '--primary', '--color-primary']],\n ['error', ['--destructive', '--color-destructive']],\n ['radius', ['--radius']],\n ['buttonRadius', ['--radius']],\n ['fontFamily', ['--font-sans', '--font-family']],\n]\n\n/** A colour as shadcn/ui writes it on Tailwind 3 — `222.2 84% 4.9%`, the inside of hsl() — or any other. */\nfunction shadcnColor(value: string): string | null {\n return /^[\\d.]+(?:deg)?\\s+[\\d.]+%\\s+[\\d.]+%(?:\\s*\\/\\s*[\\d.]+%?)?$/.test(value.trim()) ? toColor(`hsl(${value.trim()})`) : toColor(value)\n}\n\n/** Every custom property a stylesheet sets for the page, the last one winning. */\nfunction pageVariables(css: string | string[], mode: 'light' | 'dark', theme?: string): Map<string, string> {\n const vars = new Map<string, string>()\n for (const sheet of Array.isArray(css) ? css : [css]) {\n for (const d of declarations(sheet)) if (applies(d, { mode, theme })) vars.set(d.name, d.value)\n }\n return vars\n}\n\n/** The custom properties a stylesheet sets for the page, by name: what a person picks from to map their own. */\nexport function cssVariables(css: string | string[], options: Pick<CssThemeOptions, 'mode' | 'theme'> = {}): string[] {\n return [...pageVariables(css, options.mode ?? 'light', options.theme).keys()].sort()\n}\n\n/**\n * Reads an application's look out of its stylesheets, whatever names it uses:\n * Stackmyth's tokens, shadcn/ui's (`--primary`, `--radius`, on Tailwind 3 or\n * 4), or the application's own variables given in `map`. What comes out is a\n * theme of named values, never CSS.\n */\nexport function themeFromCss(css: string | string[], options: CssThemeOptions = {}): CssImportedTheme {\n const mode = options.mode ?? 'light'\n const vars = pageVariables(css, mode, options.theme)\n const names = [...vars.keys()]\n let format: CssFormat\n if (options.format && options.format !== 'auto') format = options.format\n else if (options.map && Object.keys(options.map).length > 0) format = 'custom'\n else if (names.some((n) => n.startsWith('--sm-'))) format = 'stackmyth'\n else if (names.some((n) => /^--(color-)?(primary|background|foreground)$/.test(n))) format = 'shadcn'\n else format = 'custom'\n\n if (format === 'stackmyth') return { ...themeFromStackmyth(css, { mode, theme: options.theme }), format }\n\n const spec: [ThemeField, string[]][] = format === 'shadcn'\n ? SHADCN\n : Object.entries(options.map ?? {}).filter(([, v]) => v).map(([k, v]) => [k as ThemeField, [String(v).startsWith('--') ? String(v) : `--${v}`]])\n const theme: CauthyTheme = {}\n const warnings: string[] = []\n for (const [field, tokens] of spec) {\n for (const token of tokens) {\n const raw = vars.get(token)\n if (raw === undefined) continue\n const value = resolve(raw, vars)\n const got = value === null ? null\n : RADII.includes(field) ? toRadius(value)\n : field === 'fontFamily' ? toFont(value)\n : format === 'shadcn' ? shadcnColor(value) : toColor(value)\n if (got !== null) {\n (theme as Record<string, unknown>)[field] = got\n break\n }\n warnings.push(`${token}: \"${raw.length > 60 ? raw.slice(0, 57) + '…' : raw}\" is not ${RADII.includes(field) ? 'a length' : field === 'fontFamily' ? 'a font list' : 'a colour'} Cauthy can take`)\n }\n }\n if (vars.size === 0) warnings.push('no custom properties were found in these stylesheets')\n else if (format === 'custom' && spec.length === 0) warnings.push('these stylesheets use names of their own: say which variable each value is read from')\n return { theme, warnings, format }\n}\n\n// ── On the application's own screens ───────────────────────────────────────\n\n/**\n * A theme as the CSS variables of `cauthy-auth/styles.css` (`--cauthy-*`), for\n * the forms an application shows on its own pages. The page's own background,\n * the layout and the words are Cauthy's pages' matter, and left out.\n */\nexport function themeVariables(theme: CauthyTheme): Record<string, string> {\n const vars: Record<string, string> = {}\n const put = (name: string, v: string | number | undefined) => {\n if (v === undefined || v === '') return\n vars[`--cauthy-${name}`] = typeof v === 'number' ? `${v}px` : v\n }\n put('surface', theme.surface)\n put('text', theme.text)\n put('text-muted', theme.textMuted)\n put('border', theme.border)\n put('input-background', theme.inputBackground)\n put('input-border', theme.inputBorder)\n put('primary', theme.primary)\n put('primary-text', theme.primaryText)\n put('accent', theme.accent)\n put('error', theme.error)\n put('error-background', theme.errorBackground)\n put('success', theme.success)\n put('success-background', theme.successBackground)\n put('radius', theme.radius)\n put('button-radius', theme.buttonRadius)\n put('font', theme.fontFamily)\n return vars\n}\n\n// ── Sending it to Cauthy ───────────────────────────────────────────────────\n\n/** A theme as Cauthy's API names its values. */\nexport function toWire(theme: CauthyTheme): Record<string, unknown> {\n const names: Record<keyof CauthyTheme, string> = {\n background: 'background', surface: 'surface', text: 'text', textMuted: 'text_muted', border: 'border',\n primary: 'primary', primaryText: 'primary_text', accent: 'accent', error: 'error', errorBackground: 'error_background',\n success: 'success', successBackground: 'success_background', radius: 'radius', buttonRadius: 'button_radius',\n fontFamily: 'font_family', logoUrl: 'logo_url', fonts: 'fonts', layout: 'layout', panelColors: 'panel_colors',\n panelText: 'panel_text', panelIconUrl: 'panel_icon_url', texts: 'texts', inputBackground: 'input_background', inputBorder: 'input_border', tokens: 'tokens',\n }\n const wire: Record<string, unknown> = {}\n for (const [k, v] of Object.entries(theme)) {\n if (v === undefined || v === '' || (Array.isArray(v) && v.length === 0)) continue\n wire[names[k as keyof CauthyTheme]] = v\n }\n return wire\n}\n\n/** A theme as Cauthy answers it, in this package's names. */\nexport function fromWire(wire: Record<string, unknown>): CauthyTheme {\n const names: Record<string, keyof CauthyTheme> = {\n background: 'background', surface: 'surface', text: 'text', text_muted: 'textMuted', border: 'border',\n primary: 'primary', primary_text: 'primaryText', accent: 'accent', error: 'error', error_background: 'errorBackground',\n success: 'success', success_background: 'successBackground', radius: 'radius', button_radius: 'buttonRadius',\n font_family: 'fontFamily', logo_url: 'logoUrl', fonts: 'fonts', layout: 'layout', panel_colors: 'panelColors',\n panel_text: 'panelText', panel_icon_url: 'panelIconUrl', texts: 'texts', input_background: 'inputBackground', input_border: 'inputBorder', tokens: 'tokens',\n }\n const theme: Record<string, unknown> = {}\n for (const [k, v] of Object.entries(wire ?? {})) if (names[k] && v !== undefined && v !== null && v !== '') theme[names[k]] = v\n return theme as CauthyTheme\n}\n\nexport interface PushThemeOptions {\n /** Cauthy's address. Default: `CAUTHY_ISSUER`. */\n issuer?: string\n /** The application's client id. Default: `CAUTHY_CLIENT_ID`. */\n clientId?: string\n /** The workspace's slug. Default: `CAUTHY_TENANT`. */\n tenant?: string\n /**\n * A personal access token with `oauth_clients:manage`. Default:\n * `CAUTHY_TOKEN`. It belongs in the deploy pipeline, never in the application.\n */\n token?: string\n theme: CauthyTheme\n fetch?: typeof fetch\n}\n\n/**\n * Gives the application its look on Cauthy's pages. It replaces the theme: a\n * value left out goes back to Cauthy's own. Run it when the brand changes —\n * from a script or the deploy — not on every request.\n */\nexport async function pushTheme(o: PushThemeOptions): Promise<void> {\n const env = (n: string) => (typeof process !== 'undefined' ? process.env?.[n] : undefined)\n const issuer = withoutTrailingSlashes(o.issuer ?? env('CAUTHY_ISSUER') ?? '')\n const clientId = o.clientId ?? env('CAUTHY_CLIENT_ID') ?? ''\n const tenant = o.tenant ?? env('CAUTHY_TENANT') ?? ''\n const token = o.token ?? env('CAUTHY_TOKEN') ?? ''\n for (const [name, v] of Object.entries({ CAUTHY_ISSUER: issuer, CAUTHY_CLIENT_ID: clientId, CAUTHY_TENANT: tenant, CAUTHY_TOKEN: token })) {\n if (v === '') throw new Error(`cauthy-auth: ${name} is required to push a theme`)\n }\n const res = await (o.fetch ?? fetch)(`${issuer}/v1/oauth/clients/${encodeURIComponent(clientId)}/theme`, {\n method: 'PUT',\n headers: { authorization: `Bearer ${token}`, 'x-tenant-key': tenant, 'content-type': 'application/json' },\n body: JSON.stringify(toWire(o.theme)),\n })\n if (!res.ok) {\n const body = (await res.json().catch(() => null)) as { errors?: { message?: string }[] } | null\n throw new Error(`cauthy-auth: Cauthy refused the theme (${res.status}): ${body?.errors?.[0]?.message ?? res.statusText}`)\n }\n}\n","// What a sign-in or sign-up form is checked for before anything is sent, in\n// the browser and again on the server: the same rules in both places, so the\n// form says what the server would.\n\nexport type AuthField = 'email' | 'password' | 'newPassword' | 'name' | 'code' | 'organization'\n\n/** A value of a form that cannot be sent as it is. */\nexport interface FieldProblem {\n field: AuthField\n /** Stable, for translating: `required`, `invalid_email`, `too_short`, `too_long`. */\n code: 'required' | 'invalid_email' | 'too_short' | 'too_long'\n message: string\n}\n\n/** The shortest password Cauthy takes for a new account. */\nexport const MIN_PASSWORD = 8\nconst MAX_PASSWORD = 256\nconst MAX_NAME = 120\n\n// One @, something on each side, a dot in the domain, no spaces: what catches\n// a typo. Whether the address exists is Cauthy's to find out.\nconst EMAIL = /^[^\\s@]+@[^\\s@]+\\.[^\\s@]+$/\n\nexport function checkEmail(value: unknown): FieldProblem | null {\n const v = typeof value === 'string' ? value.trim() : ''\n if (v === '') return { field: 'email', code: 'required', message: 'Enter your email address.' }\n if (v.length > 254 || !EMAIL.test(v)) return { field: 'email', code: 'invalid_email', message: 'That does not look like an email address.' }\n return null\n}\n\n/** A password being typed to sign in: only that there is one. */\nexport function checkPassword(value: unknown): FieldProblem | null {\n if (typeof value !== 'string' || value === '') return { field: 'password', code: 'required', message: 'Enter your password.' }\n if (value.length > MAX_PASSWORD) return { field: 'password', code: 'too_long', message: 'That password is too long.' }\n return null\n}\n\n/** A password being chosen for a new account. */\nexport function checkNewPassword(value: unknown): FieldProblem | null {\n const basic = checkPassword(value)\n if (basic) return basic\n if ((value as string).length < MIN_PASSWORD) {\n return { field: 'password', code: 'too_short', message: `Use at least ${MIN_PASSWORD} characters.` }\n }\n return null\n}\n\nexport function checkName(value: unknown): FieldProblem | null {\n if (value === undefined || value === null || value === '') return null\n if (typeof value !== 'string' || value.trim().length > MAX_NAME) return { field: 'name', code: 'too_long', message: `Use at most ${MAX_NAME} characters.` }\n return null\n}\n\n/** The company's name a sign-up carries when the application's system makes an organization per sign-up. */\nexport const MAX_ORGANIZATION = 120\n\nexport function checkOrganization(value: unknown, required = false): FieldProblem | null {\n const v = typeof value === 'string' ? value.trim() : ''\n if (v === '') return required ? { field: 'organization', code: 'required', message: \"Enter your company's name.\" } : null\n if (v.length > MAX_ORGANIZATION) return { field: 'organization', code: 'too_long', message: `Use at most ${MAX_ORGANIZATION} characters.` }\n return null\n}\n\nexport function checkCode(value: unknown): FieldProblem | null {\n const v = typeof value === 'string' ? value.trim() : ''\n if (v === '') return { field: 'code', code: 'required', message: 'Enter the code.' }\n if (v.length > 40) return { field: 'code', code: 'too_long', message: 'That is not a code.' }\n return null\n}\n\n/** The first problem of a form, or null when it can be sent. */\nexport function firstProblem(...checks: (FieldProblem | null)[]): FieldProblem | null {\n return checks.find((c) => c !== null) ?? null\n}\n","// The application's own sign-in screens, in the browser.\n//\n// const auth = createAuthClient()\n// const r = await auth.signIn.email({ email, password })\n// if (!r.ok) show(r.error.message) // r.error.field says which input\n// else if (r.status === 'mfa_required') askForTheCode()\n//\n// Every call goes to the application's own server (`/api/auth/*`), never to\n// Cauthy: the browser holds no token, before or after. A form is checked here\n// before it is sent, with the rules the server checks again.\n\nimport { canAutofillPasskeys, canUsePasskeys, ceremonyError, getPasskey, makePasskey } from './passkey'\nimport { fromWire, type CauthyTheme } from './theme'\nimport type { PublicSession } from './types'\nimport { checkCode, checkEmail, checkName, checkNewPassword, checkPassword, firstProblem, type AuthField, type FieldProblem, checkOrganization } from './validate'\nimport { withoutTrailingSlashes } from './url'\n\nexport { checkCode, checkEmail, checkName, checkNewPassword, checkPassword, MIN_PASSWORD, type AuthField, type FieldProblem } from './validate'\n\n/** What went wrong, in a form the screen can show. */\nexport interface AuthError {\n /** Stable, for translating: `invalid_credentials`, `invalid_email`, `too_short`, `email_taken`, `unavailable`… */\n code: string\n message: string\n /** The input it is about, when it is about one. */\n field?: AuthField\n}\n\nexport type AuthResult<S extends string, More = unknown> = ({ ok: true; status: S } & More) | { ok: false; error: AuthError }\n\n/** What the screen may offer. */\nexport interface AuthMethods {\n /** The providers the workspace signs people in with: `{ id: 'google', name: 'Google' }`. */\n providers: { id: string; name: string }[]\n /** Whether people may create an account themselves. */\n signUp: boolean\n /** Set when the application's system makes an organization per sign-up: the form asks the company's name. */\n organization?: { ask: boolean; required: boolean }\n magicLink: boolean\n /** Whether to offer a passkey. */\n passkey: boolean\n /**\n * Whether the passkey is used on this page itself. False: `signIn.passkey`\n * sends the person to Cauthy's page for that step alone, and they come back\n * signed in. That happens only when this page is outside the domain the\n * workspace's passkeys belong to.\n */\n passkeyHere: boolean\n /**\n * Whether Cauthy's page can use the passkey, for that step alone: where a\n * page that cannot, or a browser that refuses to here, sends the person.\n */\n passkeyOnCauthy: boolean\n /**\n * The workspace's Cloudflare Turnstile site key, when it asks for the\n * challenge: signing in, creating an account, and asking for a sign-in link\n * or a new password then take its answer (`captchaToken`). The ready-made\n * forms show it themselves.\n */\n captchaSiteKey?: string\n /**\n * Whether the workspace signs some people in through their company's SSO,\n * found by their work address (`signIn.ssoDiscover`, `signIn.sso`).\n */\n sso: boolean\n}\n\n/** A company's sign-in (an SSO connection of the workspace). */\nexport interface SSOConnection {\n /** What `signIn.sso({ connection })` takes. */\n id: string\n /** The company's name, for \"Continue with …\". */\n name: string\n /** The address can sign in no other way: a password, link or provider answers `sso_required`. */\n enforced: boolean\n}\n\n/**\n * What holds a passkey, as its authenticator says: a security key (a YubiKey),\n * one synced across the person's devices (iCloud Keychain, Google Password\n * Manager), one on a single device (Windows Hello), or unknown (made before\n * Cauthy kept this).\n */\nexport type PasskeyKind = 'security_key' | 'synced' | 'device' | 'unknown'\n\n/** A passkey of the signed-in account. Moments are UTC. */\nexport interface Passkey {\n id: string\n /** What the person called it (\"My laptop\"), or \"\". */\n name: string\n kind: PasskeyKind\n createdAt: string\n /** When it last signed the person in, or null. */\n lastUsedAt: string | null\n}\n\n/** A person's own account, as their account screen shows it. */\nexport interface Account {\n id: string\n email: string\n name: string\n emailVerified: boolean\n /** False for an account that only signs in through a provider: it has no current password to ask for. */\n hasPassword: boolean\n mfaEnabled: boolean\n}\n\n/** Somewhere the person is signed in. Moments are UTC. */\n/** An AI agent the person allowed to act for them on one of the workspace's servers. */\nexport interface ConnectedAgent {\n id: string\n /** The name the agent gave itself (Claude, Cursor…): not checked by anyone. */\n name: string\n /** Where the agent receives its access: the one thing about it that is sure. */\n redirectHost: string\n resource: { name: string; uri: string }\n /** What the person allowed it, in their words. */\n scopes: { name: string; description: string }[]\n /** UTC, ISO 8601. */\n connectedAt: string\n lastUsedAt: string\n}\n\n/** An organization the person belongs to, with what their role lets them do with its people. */\nexport interface OrganizationSeat {\n id: string\n /** The client system it is of (`pondari`). */\n system: string\n name: string\n kind: string\n /** The person's role in it. */\n role: string\n /** Cauthy's member powers, from the system's catalog: cauthy:members read and manage. */\n can: { readMembers: boolean; manageMembers: boolean }\n}\n\n/** A person of an organization, as a member who may see them reads it. */\nexport interface OrganizationPerson {\n userId: string\n email: string\n name: string\n role: string\n /** The account's: `active`, `suspended`… A disabled member is still listed. */\n status: string\n /** Invited and not yet accepted. */\n invited: boolean\n /** Manages the organization's members. */\n manager: boolean\n /** The signed-in person. */\n you: boolean\n /** UTC, ISO 8601. */\n since: string\n}\n\n/** A role of the organization's system, as the catalog names it. */\nexport interface OrganizationRoleChoice {\n role: string\n name: string\n description: string\n /** Holding it manages the organization's members. */\n manager: boolean\n}\n\n/**\n * The signed-in person's organizations, and their people. Seeing them needs\n * cauthy:members read in the person's role; changing them needs manage. Cauthy\n * decides, with the person's own session: the application authorizes nothing.\n */\nexport interface OrganizationsClient {\n /** The organizations the person belongs to; one system's only with `system`. */\n list(system?: string): Promise<OrganizationSeat[]>\n /** The people of one, or null when the person may not see them. */\n members(organizationId: string): Promise<OrganizationPerson[] | null>\n /** The roles a member may be given, or null when the person may not see them. */\n roles(organizationId: string): Promise<OrganizationRoleChoice[] | null>\n /** Adds a person by their address; an address with no account gets one and its invitation. */\n add(organizationId: string, form: { email: string; role: string; name?: string; locale?: string }): Promise<AuthResult<'added', { member: OrganizationPerson }>>\n setRole(organizationId: string, userId: string, role: string): Promise<AuthResult<'changed', { userId: string; role: string }>>\n /** Removes a member; the person themself may always leave. */\n remove(organizationId: string, userId: string): Promise<AuthResult<'removed', { userId: string }>>\n /** Sends a member's invitation again. */\n inviteAgain(organizationId: string, userId: string, form?: { locale?: string }): Promise<AuthResult<'sent', { userId: string }>>\n}\n\nexport interface AccountSession {\n /** \"Mac · Chrome\", or \"\" when the browser did not say. */\n device: string\n ip: string\n createdAt: string\n expiresAt: string\n}\n\n/**\n * The signed-in person's own account. What weakens it — the password, the\n * second step, its recovery codes — asks for the current password again.\n */\n/** A provider the account can also sign in with. */\nexport interface LinkedIdentity {\n provider: string\n /** The address the provider has for the person, when it said. */\n email: string\n name: string\n /** When it was linked, in UTC (ISO 8601). */\n linkedAt: string\n}\n\n/** How an account takes its second step. */\nexport interface SecondStep {\n totp: boolean\n /** The phone codes are sent to, masked (`+57 ••• ••• 4567`). */\n phone: { number: string; channel: string } | null\n /** The ways the workspace sends codes to phones. */\n phoneChannels: string[]\n}\n\nexport interface AccountClient {\n /** The account, or null when nobody is signed in. */\n get(): Promise<Account | null>\n update(form: { name: string }): Promise<AuthResult<'saved', { account: Account }>>\n /** Ends every other session of the account; this one stays signed in. */\n changePassword(form: { password: string; newPassword: string }): Promise<AuthResult<'changed', { signedIn: boolean }>>\n mfa: {\n /** Starts turning the second step on: show `secret` (or `otpauthUri` as a QR code) to add to an authenticator app, then call `verify`. */\n setup(form: { password: string }): Promise<AuthResult<'started', { secret: string; otpauthUri: string }>>\n /** Turns it on with a code from the app. `recoveryCodes` are shown this once. */\n verify(form: { code: string }): Promise<AuthResult<'enabled', { recoveryCodes: string[] }>>\n disable(form: { password: string }): Promise<AuthResult<'disabled'>>\n /** New recovery codes; the earlier ones stop working. */\n recoveryCodes(form: { password: string }): Promise<AuthResult<'renewed', { recoveryCodes: string[] }>>\n /**\n * How the account takes its second step: an authenticator app, a phone\n * (masked), and the ways the workspace sends codes to phones (`sms`,\n * `whatsapp`; none when it has no Twilio account for it).\n */\n factors(): Promise<SecondStep | null>\n /** Adds a phone as the second step: a code is sent to it, for `verifyPhone`. */\n addPhone(form: { phone: string; channel: 'sms' | 'whatsapp'; password: string }): Promise<AuthResult<'sent', { sentTo: string; channel: string }>>\n /** Proves the phone with the code it received. `recoveryCodes` come with a first second step, shown this once. */\n verifyPhone(form: { code: string }): Promise<AuthResult<'enabled', { recoveryCodes: string[] }>>\n removePhone(form: { password: string }): Promise<AuthResult<'removed'>>\n }\n /**\n * Asks to sign in with another address. Nothing changes yet: the new address\n * is mailed a link to confirm it, and once it is confirmed the old one is\n * told, with a link that undoes it. Both open Cauthy's own page.\n */\n changeEmail(form: { email: string; password: string }): Promise<AuthResult<'sent', { email: string }>>\n /** The providers (Google…) the account can also sign in with. */\n identities(): Promise<LinkedIdentity[]>\n /**\n * Sends the browser to the provider, to link it to the account; it comes back\n * at `returnTo` with `?linked=<provider>`, or `?auth_error=<code>`.\n */\n link(provider: string, options?: { returnTo?: string }): void\n /** Where `link` sends the browser, for a plain link. */\n linkUrl(provider: string, options?: { returnTo?: string }): string\n /** The account stops signing in with that provider. Refused when it would be left with no way to sign in. */\n unlink(provider: string): Promise<AuthResult<'unlinked', { provider: string }>>\n /**\n * A guest becomes an account of their own with an address and a password —\n * the same id, so what they did stays theirs. `email_taken`: the address has\n * an account already; offer to sign in with it.\n */\n upgrade(form: { email: string; password: string; name?: string }): Promise<AuthResult<'signed_in', { session: PublicSession }>>\n /** Where a guest goes to become the account of a provider's address (Google…). */\n upgradeUrl(provider: string, options?: { returnTo?: string }): string\n /** Sends a guest to `upgradeUrl`; they come back signed in as the account. */\n upgradeWith(provider: string, options?: { returnTo?: string }): void\n /** Where the account is signed in. */\n sessions(): Promise<AccountSession[]>\n /** Signs the account out everywhere, here too. */\n signOutEverywhere(): Promise<AuthResult<'signed_out'>>\n /** The AI agents the person connected. */\n agents(): Promise<ConnectedAgent[]>\n /** Disconnects an agent: it loses its access within minutes. */\n disconnectAgent(id: string): Promise<AuthResult<'disconnected', { id: string }>>\n /** The account's passkeys. */\n passkeys(): Promise<Passkey[]>\n /**\n * Makes a passkey on this device, on this page: the browser asks the person\n * for their fingerprint, face or screen lock. An account with a password\n * proves it again first. `cancelled`: the person closed the browser's prompt.\n * `kind` sends the browser straight to one: `'device'`, this device's\n * fingerprint, face or screen lock; `'security_key'`, a security key.\n * Without it, the browser offers all.\n */\n addPasskey(form?: { password?: string; name?: string; kind?: 'device' | 'security_key' }): Promise<AuthResult<'added', { passkey: Passkey }>>\n removePasskey(id: string): Promise<AuthResult<'removed', { id: string }>>\n}\n\nexport interface AuthClient {\n signIn: {\n /**\n * A guest session, before the visitor signs up: the application keeps what\n * they do under `session.user.id`, and `account.upgrade` (or\n * `account.upgradeWith`) makes it their account, same id. Only where the\n * workspace allows guests (`anonymous_disabled` otherwise).\n */\n anonymously(form?: { captchaToken?: string }): Promise<AuthResult<'signed_in', { session: PublicSession }>>\n /**\n * With address and password. `mfa_required`: ask for the code and call\n * `signIn.mfa`. `method: 'email'` says the code was just mailed to the\n * account's address (it has no factor of its own and the workspace, or\n * the sign-in's risk, asked a second step): say so, and offer\n * `mfaSendEmail` for another.\n */\n email(form: { email: string; password: string; captchaToken?: string }): Promise<AuthResult<'signed_in', { session: PublicSession }> | AuthResult<'mfa_required', { method?: 'email' }>>\n /** The second step: the code of the authenticator app, the phone, the email, or a recovery code. */\n mfa(form: { code: string }): Promise<AuthResult<'signed_in', { session: PublicSession }>>\n /**\n * While the second step is asked for: whether the account has an\n * authenticator app, the phone (masked) a code can be sent to, phones\n * that approve sign-ins, and the address (masked) a code went to when\n * the step is by email.\n */\n mfaMethods(): Promise<AuthResult<'ok', { totp: boolean; phone: { number: string; channel: string } | null; push: { devices: number } | null; email: { to: string } | null }>>\n /** Mails the sign-in's code again, when its second step is by email; `mfa` takes it. A few per sign-in. */\n mfaSendEmail(): Promise<AuthResult<'sent', { sentTo: string }>>\n /**\n * Sends a push to the account's phones (the application's app, with\n * cauthy-expo): show `number`, which the person picks on the phone, then\n * call `mfaPushWait`.\n */\n mfaPush(): Promise<AuthResult<'sent', { number: number }>>\n /**\n * Waits for the phone's answer, listening (server-sent events) rather than\n * asking again and again, and signs in when it approves. `denied`: turned\n * down on the phone; `expired`: nobody answered in time.\n */\n mfaPushWait(o?: { signal?: AbortSignal }): Promise<AuthResult<'signed_in', { session: PublicSession }>>\n /** Sends a code to the account's phone, by SMS or WhatsApp; `mfa` takes it. A few per sign-in. */\n mfaSendCode(): Promise<AuthResult<'sent', { sentTo: string; channel: string }>>\n /**\n * Mails a link (and a code) that signs the person in. The answer is the\n * same whether or not the address has an account. `create`: an address\n * with no account gets one when the link or code is used, if the\n * workspace accepts new accounts — one screen signs people in and up.\n * `returnTo` (a path of this site) travels in the link: the page it opens\n * gets it as `?return_to=`, and `emailCode` answers it.\n */\n magicLink(form: { email: string; captchaToken?: string; create?: boolean; returnTo?: string }): Promise<AuthResult<'sent'>>\n /** Signs in with the token the mailed link opened the page with (`ml_token`). */\n magicLinkVerify(form: { token: string }): Promise<AuthResult<'signed_in', { session: PublicSession }>>\n /** Signs in with the code that came in the same mail as the link, for somebody reading it on another device. */\n emailCode(form: { code: string }): Promise<AuthResult<'signed_in', { session: PublicSession; returnTo?: string }>>\n /**\n * Signs in with a passkey, on this page: the browser asks the person for\n * their fingerprint, face or screen lock. When this page cannot use the\n * workspace's passkeys (`methods().passkeyHere` is false), the browser goes\n * to Cauthy's page for that step alone and comes back signed in, at\n * `returnTo`: then the answer is `redirected`. `cancelled`: the person\n * closed the browser's prompt. `email`, when the person typed it: the\n * browser is told which passkeys are theirs, so a security key that did\n * not keep its passkey (one made before cauthy-auth 0.18.0) is found too.\n */\n passkey(options?: { returnTo?: string; locale?: string; email?: string }): Promise<AuthResult<'signed_in', { session: PublicSession }> | AuthResult<'redirected'>>\n /**\n * Offers the person's passkeys in the browser's autofill of the email\n * field, which must carry `autocomplete=\"username webauthn\"`, and signs in\n * with the one they pick. It waits until they pick one or `signal`\n * aborts: abort it before calling `passkey` or sending the form.\n * `cancelled`: aborted, or this browser or page cannot (then there is\n * nothing to offer, and the passkey button still works).\n */\n passkeyAutofill(options?: { signal?: AbortSignal }): Promise<AuthResult<'signed_in', { session: PublicSession }>>\n /**\n * Google One Tap: Google's prompt over the page offers the Google account\n * the browser already has, and signs in with it in one tap, as the Google\n * sign-in does (the same account). Needs the workspace's Google sign-in,\n * and the page's address among the Google client's authorized JavaScript\n * origins. Waits until the person picks an account or closes the prompt\n * (`cancelled`), or `signal` aborts. `one_tap_unavailable`: the workspace\n * has no Google sign-in.\n */\n googleOneTap(options?: { signal?: AbortSignal; context?: 'signin' | 'signup' | 'use' }): Promise<AuthResult<'signed_in', { session: PublicSession }>>\n /** Where `passkey` sends the browser when this page cannot use the passkey itself. */\n passkeyUrl(options?: { returnTo?: string; locale?: string }): string\n /** Sends the browser to the provider; it comes back signed in, at `returnTo`. */\n social(provider: string, options?: { returnTo?: string }): void\n /**\n * Which company's sign-in a work address uses, if any (`found`), or\n * `none`. It says so of the address's domain, never whether the address\n * has an account.\n */\n ssoDiscover(form: { email: string }): Promise<AuthResult<'found', { connection: SSOConnection }> | AuthResult<'none'>>\n /**\n * Sends the browser to the person's company (found by `email`, or named\n * by `connection`); it comes back signed in, at `returnTo`. A sign-in the\n * company refused comes back with `?auth_error=`. Any other way of\n * signing in answers `sso_required` for an address its company holds to\n * SSO: send it here then.\n */\n sso(target: { email: string } | { connection: string }, options?: { returnTo?: string }): void\n /** Where `sso` sends the browser, for a plain link. */\n ssoUrl(target: { email: string } | { connection: string }, options?: { returnTo?: string }): string\n /** Where `social` sends the browser, for a plain link. */\n socialUrl(provider: string, options?: { returnTo?: string }): string\n }\n signUp: {\n /** `verification_required`: the account exists and must open the link Cauthy mailed before it can sign in. */\n email(form: { email: string; password: string; name?: string; organization?: string; captchaToken?: string }): Promise<AuthResult<'signed_in', { session: PublicSession }> | AuthResult<'verification_required', { email: string }>>\n }\n /** Mails a link to choose a new password. The answer is the same whether or not the address has an account. */\n forgotPassword(form: { email: string; captchaToken?: string }): Promise<AuthResult<'sent'>>\n /** Sets the new password, with the token the mailed link opened the page with (`token`). */\n resetPassword(form: { token: string; password: string }): Promise<AuthResult<'changed'>>\n /** Confirms the address, with the token the mailed link opened the page with (`token`). */\n /** Confirms the address a mailed link is for. The person is signed in from the link when their account asks no second step. */\n verifyEmail(form: { token: string }): Promise<AuthResult<'verified'> | AuthResult<'signed_in', { session: PublicSession }>>\n signOut(): Promise<AuthResult<'signed_out'>>\n /** The signed-in person's own account. */\n account: AccountClient\n /** The signed-in person's organizations, and their people. */\n organizations: OrganizationsClient\n /** Who is signed in, or null. */\n getSession(): Promise<PublicSession | null>\n methods(): Promise<AuthMethods>\n /**\n * How the workspace's forms look — colours, corners, typeface — as a person\n * set it in Cauthy's dashboard. Read once per page; null when Cauthy cannot say.\n */\n look(): Promise<CauthyTheme | null>\n}\n\nexport interface AuthClientOptions {\n /** Where the server mounted the routes. Default: `/api/auth`. */\n basePath?: string\n fetch?: typeof fetch\n /**\n * The language the person reads, for what Cauthy mails them (a sign-in\n * link, a new password, a confirmation): a tag like `es` or `es-CO`, or a\n * function that answers it when the person can change it. Default: the\n * page's own (`<html lang>`), else the browser's.\n */\n locale?: string | (() => string | undefined)\n}\n\n/** Google Identity Services, as much of it as One Tap uses. */\ninterface GoogleMoment {\n isNotDisplayed?: () => boolean\n isSkippedMoment?: () => boolean\n isDismissedMoment?: () => boolean\n getDismissedReason?: () => string\n}\ninterface GoogleIdentity {\n initialize(config: Record<string, unknown>): void\n prompt(listener?: (moment: GoogleMoment) => void): void\n cancel(): void\n}\n\nlet googleScript: Promise<GoogleIdentity> | null = null\n\n/** Loads Google's script once per page. */\nfunction loadGoogleIdentity(): Promise<GoogleIdentity> {\n const ready = () => (window as unknown as { google?: { accounts?: { id?: GoogleIdentity } } }).google?.accounts?.id\n if (ready()) return Promise.resolve(ready()!)\n googleScript ??= new Promise<GoogleIdentity>((resolve, reject) => {\n const script = document.createElement('script')\n script.src = 'https://accounts.google.com/gsi/client'\n script.async = true\n script.onload = () => (ready() ? resolve(ready()!) : reject(new Error('google identity services did not load')))\n script.onerror = () => {\n googleScript = null\n reject(new Error('google identity services did not load'))\n }\n document.head.appendChild(script)\n })\n return googleScript\n}\n\n/** How long Cauthy waits for a passkey ceremony to be answered. */\nconst PASSKEY_SECONDS = 300\n\nconst UNAVAILABLE: AuthError = { code: 'unavailable', message: 'Sign-in is unavailable right now. Try again in a moment.' }\n\nexport function createAuthClient(options: AuthClientOptions = {}): AuthClient {\n const base = withoutTrailingSlashes(options.basePath ?? '/api/auth')\n const doFetch: typeof fetch = (...args) => (options.fetch ?? fetch)(...args)\n const locale = (): string => {\n const said = typeof options.locale === 'function' ? options.locale() : options.locale\n return (said ?? (typeof document === 'undefined' ? '' : document.documentElement?.lang) ?? '').trim()\n }\n\n async function post(path: string, form: Record<string, unknown>, bad: FieldProblem | null, captchaToken?: string): Promise<any> {\n if (bad) return { ok: false, error: { code: bad.code, message: bad.message, field: bad.field } }\n try {\n const res = await doFetch(`${base}${path}`, {\n method: 'POST',\n credentials: 'same-origin',\n headers: { 'content-type': 'application/json', accept: 'application/json', ...(locale() ? { 'x-cauthy-locale': locale() } : {}), ...(captchaToken ? { 'x-cauthy-captcha': captchaToken } : {}) },\n body: JSON.stringify(form),\n })\n const body = (await res.json().catch(() => null)) as any\n if (res.ok && body && typeof body.status === 'string') return { ok: true, ...body }\n const e = body?.error\n return { ok: false, error: e && typeof e.code === 'string' ? { code: e.code, message: String(e.message ?? ''), ...(e.field ? { field: e.field } : {}) } : UNAVAILABLE }\n } catch {\n return { ok: false, error: UNAVAILABLE }\n }\n }\n\n let looked: Promise<CauthyTheme | null> | undefined\n\n async function read(path: string): Promise<any> {\n try {\n const res = await doFetch(`${base}${path}`, { credentials: 'same-origin', headers: { accept: 'application/json' } })\n return res.ok ? await res.json() : null\n } catch {\n return null\n }\n }\n\n const upgradeUrl = (provider: string, o: { returnTo?: string } = {}) =>\n `${base}/me/upgrade/social/${encodeURIComponent(provider)}${o.returnTo ? `?return_to=${encodeURIComponent(o.returnTo)}` : ''}`\n const linkUrl = (provider: string, o: { returnTo?: string } = {}) =>\n `${base}/me/identities/link/${encodeURIComponent(provider)}${o.returnTo ? `?return_to=${encodeURIComponent(o.returnTo)}` : ''}`\n const socialUrl = (provider: string, o: { returnTo?: string } = {}) =>\n `${base}/sign-in/social/${encodeURIComponent(provider)}${o.returnTo ? `?return_to=${encodeURIComponent(o.returnTo)}` : ''}`\n\n const ssoUrl = (target: { email: string } | { connection: string }, o: { returnTo?: string } = {}) => {\n const q = new URLSearchParams('connection' in target ? { connection: target.connection } : { email: target.email.trim() })\n if (o.returnTo) q.set('return_to', o.returnTo)\n return `${base}/sign-in/sso?${q}`\n }\n\n const passkeyUrl = (o: { returnTo?: string; locale?: string } = {}) => {\n const q = new URLSearchParams({ method: 'passkey' })\n if (o.returnTo) q.set('return_to', o.returnTo)\n if (o.locale) q.set('locale', o.locale)\n return `${base}/login?${q}`\n }\n\n async function methods(): Promise<AuthMethods> {\n try {\n const res = await doFetch(`${base}/methods`, { credentials: 'same-origin', headers: { accept: 'application/json' } })\n if (res.ok) {\n // A server from before the passkey on the page does not say; then it is not.\n const m = (await res.json()) as Partial<AuthMethods>\n return { providers: [], signUp: true, magicLink: false, passkey: false, ...m, passkeyHere: m.passkeyHere === true, passkeyOnCauthy: m.passkeyOnCauthy ?? m.passkey === true, sso: m.sso === true }\n }\n } catch {\n // falls through\n }\n return { providers: [], signUp: true, magicLink: false, passkey: false, passkeyHere: false, passkeyOnCauthy: false, sso: false }\n }\n\n /** The two steps of a ceremony: what to ask the browser, then its answer. */\n async function ceremony(begin: string, finish: string, form: Record<string, unknown>, run: (options: any) => Promise<Record<string, unknown>>, more: Record<string, unknown> = {}): Promise<any> {\n const begun = await post(begin, form, null)\n if (!begun.ok) return begun\n let credential: Record<string, unknown>\n try {\n credential = await run(begun.options)\n } catch (e) {\n return { ok: false, error: ceremonyError(e) }\n }\n return post(finish, { credential, ...more }, null)\n }\n\n return {\n signIn: {\n anonymously: (f = {}) => post('/sign-in/anonymous', {}, null, f.captchaToken),\n email: (f) => post('/sign-in/email', { email: f.email, password: f.password }, firstProblem(checkEmail(f.email), checkPassword(f.password)), f.captchaToken),\n mfa: (f) => post('/sign-in/mfa', { code: f.code }, checkCode(f.code)),\n mfaMethods: () => post('/sign-in/mfa/methods', {}, null),\n mfaSendCode: () => post('/sign-in/mfa/send', {}, null),\n mfaSendEmail: () => post('/sign-in/mfa/email', {}, null),\n mfaPush: () => post('/sign-in/mfa/push', {}, null),\n async mfaPushWait(o = {}) {\n const status = await new Promise<string>((resolve) => {\n if (typeof EventSource !== 'function') return resolve('approved') // finish says whether it was\n const source = new EventSource(`${base}/sign-in/mfa/push/events`, { withCredentials: true })\n const stop = (st: string) => {\n source.close()\n resolve(st)\n }\n o.signal?.addEventListener('abort', () => stop('aborted'))\n source.addEventListener('status', (e) => {\n const st = (() => {\n try {\n return String(JSON.parse((e as MessageEvent).data).status)\n } catch {\n return ''\n }\n })()\n if (st && st !== 'pending') stop(st)\n })\n source.onerror = () => {\n if (source.readyState === EventSource.CLOSED) stop('expired')\n }\n })\n if (status === 'aborted') return { ok: false, error: { code: 'aborted', message: 'Stopped waiting.' } }\n if (status === 'denied') return { ok: false, error: { code: 'push_denied', message: 'The sign-in was turned down on the phone.' } }\n if (status === 'expired') return { ok: false, error: { code: 'push_expired', message: 'Nobody answered on the phone in time.' } }\n return post('/sign-in/mfa/push/finish', {}, null)\n },\n magicLink: (f) => post('/sign-in/magic-link', { email: f.email, ...(f.create ? { create: true } : {}), ...(f.returnTo ? { returnTo: f.returnTo } : {}) }, checkEmail(f.email), f.captchaToken),\n magicLinkVerify: (f) => post('/sign-in/magic-link/verify', { token: f.token }, null),\n emailCode: (f) => post('/sign-in/email-code', { code: f.code }, checkCode(f.code)),\n passkeyUrl,\n async passkey(o) {\n const m = await methods()\n if (m.passkeyHere && canUsePasskeys()) {\n const email = o?.email?.trim() ?? ''\n const r = await ceremony('/sign-in/passkey/begin', '/sign-in/passkey/finish', email !== '' && checkEmail(email) === null ? { email } : {}, getPasskey)\n // A browser that does not read the domain's related origins refuses\n // this page: Cauthy's, then, when it can.\n if (r.ok || r.error.code !== 'passkey_unavailable' || !m.passkeyOnCauthy) return r\n } else if (!m.passkeyOnCauthy) {\n return { ok: false, error: { code: 'passkey_unavailable', message: 'Passkeys cannot be used here.' } }\n }\n window.location.assign(passkeyUrl(o))\n return { ok: true, status: 'redirected' }\n },\n async passkeyAutofill(o = {}) {\n const cancelled = { ok: false as const, error: { code: 'cancelled', message: 'No passkey was chosen.' } }\n const m = await methods()\n if (!m.passkeyHere || !(await canAutofillPasskeys())) return cancelled\n for (;;) {\n if (o.signal?.aborted) return cancelled\n const begun = await post('/sign-in/passkey/begin', {}, null)\n if (!begun.ok) return begun\n // Cauthy's challenge lives five minutes: a page left open asks for\n // a fresh one before it runs out.\n const renew = new AbortController()\n const stop = () => renew.abort()\n o.signal?.addEventListener('abort', stop, { once: true })\n const timer = setTimeout(stop, (PASSKEY_SECONDS - 30) * 1000)\n let credential: Record<string, unknown>\n try {\n credential = await getPasskey(begun.options, { mediation: 'conditional', signal: renew.signal })\n } catch (e) {\n if (o.signal?.aborted) return cancelled\n if (renew.signal.aborted) continue\n return { ok: false, error: ceremonyError(e) }\n } finally {\n clearTimeout(timer)\n o.signal?.removeEventListener('abort', stop)\n }\n return post('/sign-in/passkey/finish', { credential }, null)\n }\n },\n async googleOneTap(o = {}) {\n const cancelled = { ok: false as const, error: { code: 'cancelled', message: 'No Google account was chosen.' } }\n if (typeof window === 'undefined' || o.signal?.aborted) return cancelled\n const begun = await post('/sign-in/google-one-tap/begin', {}, null)\n if (!begun.ok) return begun\n let gis: GoogleIdentity\n try {\n gis = await loadGoogleIdentity()\n } catch {\n return { ok: false, error: { code: 'unavailable', message: 'Google could not be reached. Try again, or sign in another way.' } }\n }\n const credential = await new Promise<string | null>((done) => {\n o.signal?.addEventListener('abort', () => {\n gis.cancel()\n done(null)\n }, { once: true })\n gis.initialize({\n client_id: begun.clientId, nonce: begun.nonce, context: o.context ?? 'signin',\n callback: (r: { credential?: string }) => done(r.credential ?? null),\n auto_select: false, cancel_on_tap_outside: true, itp_support: true, use_fedcm_for_prompt: true,\n })\n gis.prompt((moment: GoogleMoment) => {\n // The prompt was not shown, or the person closed it.\n if (moment.isNotDisplayed?.() || moment.isSkippedMoment?.()) done(null)\n else if (moment.isDismissedMoment?.() && moment.getDismissedReason?.() !== 'credential_returned') done(null)\n })\n })\n if (credential === null) return cancelled\n return post('/sign-in/google-one-tap', { credential }, null)\n },\n socialUrl,\n social(provider, o) {\n window.location.assign(socialUrl(provider, o))\n },\n ssoDiscover: (f) => post('/sign-in/sso/discover', { email: f.email.trim() }, checkEmail(f.email)),\n ssoUrl,\n sso(target, o) {\n window.location.assign(ssoUrl(target, o))\n },\n },\n signUp: {\n email: (f) => post('/sign-up/email', { email: f.email, password: f.password, name: f.name, organization: f.organization },\n firstProblem(checkName(f.name), checkEmail(f.email), checkNewPassword(f.password), checkOrganization(f.organization)), f.captchaToken),\n },\n forgotPassword: (f) => post('/forgot-password', { email: f.email }, checkEmail(f.email), f.captchaToken),\n resetPassword: (f) => post('/reset-password', { token: f.token, password: f.password }, checkNewPassword(f.password)),\n verifyEmail: (f) => post('/verify-email', { token: f.token }, null),\n signOut: () => post('/sign-out', {}, null),\n organizations: {\n async list(system) {\n const body = await read(`/me/organizations${system ? `?system=${encodeURIComponent(system)}` : ''}`)\n return body && Array.isArray(body.organizations) ? (body.organizations as OrganizationSeat[]) : []\n },\n async members(id) {\n const body = await read(`/me/organizations/members?id=${encodeURIComponent(id)}`)\n return body && Array.isArray(body.members) ? (body.members as OrganizationPerson[]) : null\n },\n async roles(id) {\n const body = await read(`/me/organizations/roles?id=${encodeURIComponent(id)}`)\n return body && Array.isArray(body.roles) ? (body.roles as OrganizationRoleChoice[]) : null\n },\n add: (id, f) => post('/me/organizations/members/add', { organizationId: id, email: f.email, role: f.role, name: f.name ?? '', locale: f.locale ?? '' }, checkEmail(f.email)),\n setRole: (id, userId, role) => post('/me/organizations/members/role', { organizationId: id, userId, role }, null),\n remove: (id, userId) => post('/me/organizations/members/remove', { organizationId: id, userId }, null),\n inviteAgain: (id, userId, f = {}) => post('/me/organizations/members/invite', { organizationId: id, userId, locale: f.locale ?? '' }, null),\n },\n account: {\n async get() {\n const body = await read('/me')\n return body && typeof body.account === 'object' ? (body.account as Account) : null\n },\n update: (f) => post('/me', { name: f.name }, checkName(f.name)),\n changePassword: (f) => {\n const bad = checkNewPassword(f.newPassword)\n return post('/me/password', { password: f.password, newPassword: f.newPassword }, bad ? { ...bad, field: 'newPassword' } : null)\n },\n mfa: {\n setup: (f) => post('/me/mfa/setup', { password: f.password }, null),\n verify: (f) => post('/me/mfa/verify', { code: f.code }, checkCode(f.code)),\n disable: (f) => post('/me/mfa/disable', { password: f.password }, null),\n recoveryCodes: (f) => post('/me/mfa/recovery-codes', { password: f.password }, null),\n async factors() {\n const body = await read('/me/mfa/factors')\n return body && typeof body.totp === 'boolean' ? (body as SecondStep) : null\n },\n addPhone: (f) => post('/me/mfa/phone', { phone: f.phone, channel: f.channel, password: f.password }, null),\n verifyPhone: (f) => post('/me/mfa/phone/verify', { code: f.code }, checkCode(f.code)),\n removePhone: (f) => post('/me/mfa/phone/remove', { password: f.password }, null),\n },\n changeEmail: (f) => post('/me/email', { email: f.email, password: f.password }, checkEmail(f.email)),\n async identities() {\n const body = await read('/me/identities')\n return body && Array.isArray(body.identities) ? (body.identities as LinkedIdentity[]) : []\n },\n linkUrl,\n link(provider, o) {\n window.location.assign(linkUrl(provider, o))\n },\n unlink: (provider) => post('/me/identities/unlink', { provider }, null),\n async sessions() {\n const body = await read('/me/sessions')\n return body && Array.isArray(body.sessions) ? (body.sessions as AccountSession[]) : []\n },\n signOutEverywhere: () => post('/me/sessions/end', {}, null),\n upgrade: (f) => post('/me/upgrade', { email: f.email, password: f.password, name: f.name ?? '' }, firstProblem(checkName(f.name), checkEmail(f.email), checkNewPassword(f.password))),\n upgradeUrl,\n upgradeWith(provider, o) {\n window.location.assign(upgradeUrl(provider, o))\n },\n async agents() {\n const body = await read('/me/agents')\n return body && Array.isArray(body.agents) ? (body.agents as ConnectedAgent[]) : []\n },\n disconnectAgent: (id) => post('/me/agents/disconnect', { id }, null),\n async passkeys() {\n const body = await read('/me/passkeys')\n return body && Array.isArray(body.passkeys) ? (body.passkeys as Passkey[]) : []\n },\n addPasskey(f = {}) {\n if (!canUsePasskeys()) return Promise.resolve({ ok: false, error: { code: 'passkey_unavailable', message: 'This browser cannot make passkeys.' } })\n return ceremony('/me/passkeys/add/begin', '/me/passkeys/add/finish', { password: f.password ?? '', ...(f.kind ? { kind: f.kind } : {}) }, makePasskey, { name: f.name ?? '' })\n },\n removePasskey: (id) => post('/me/passkeys/remove', { id }, null),\n },\n async getSession() {\n try {\n const res = await doFetch(`${base}/session`, { credentials: 'same-origin', headers: { accept: 'application/json' } })\n if (!res.ok) return null\n return ((await res.json()) as { session: PublicSession | null }).session\n } catch {\n return null\n }\n },\n look() {\n looked ??= read('/look').then((wire) => (wire && typeof wire === 'object' ? fromWire(wire) : null))\n return looked\n },\n methods,\n }\n}\n\nexport { canAutofillPasskeys, canUsePasskeys } from './passkey'\nexport { createSessionStore, type SessionState, type SessionStore, type SessionStoreOptions } from './store'\n","// Who is signed in, kept for the browser: the same thing <CauthyProvider>\n// keeps for React, with no framework in it. `cauthy-auth/vue` and\n// `cauthy-auth/svelte` are this store in each one's own terms, and anything\n// else (Solid, Angular, a page with no framework) can use it as it is.\n//\n// const store = createSessionStore()\n// const stop = store.subscribe(({ session, status }) => render(session))\n// await store.auth.signIn.email({ email, password }) // subscribers hear the new session\n//\n// While somebody listens it asks the server who is signed in, asks again\n// shortly before the session's token runs out, and when the tab is looked at\n// again. With nobody listening it does nothing.\n\nimport { createAuthClient, type AuthClient, type AuthClientOptions } from './client'\nimport type { PublicSession } from './types'\nimport { withoutTrailingSlashes } from './url'\n\nexport interface SessionState {\n /** The signed-in person and their organizations, or null. */\n session: PublicSession | null\n status: 'loading' | 'authenticated' | 'unauthenticated'\n}\n\nexport interface SessionStoreOptions extends AuthClientOptions {\n /**\n * The session the server already read while rendering (SSR): the first\n * paint is then right, with no \"loading\". `null` means \"nobody\"; leave it\n * out when the server did not look.\n */\n initialSession?: PublicSession | null\n}\n\nexport interface SessionStore {\n /** What is known now. */\n get(): SessionState\n /** Calls `listener` now and on every change. Answers how to stop. */\n subscribe(listener: (state: SessionState) => void): () => void\n /** Asks the server again who is signed in. */\n refresh(): Promise<PublicSession | null>\n /** Sends the browser to Cauthy's page to sign in, and back to `returnTo` (default: where it is now). */\n signIn(options?: { returnTo?: string; locale?: string; prompt?: 'login' }): void\n /** Signs out of this application and of Cauthy in this browser. */\n signOut(): void\n /** Where the person looks after their account on Cauthy's page. */\n accountUrl(returnTo?: string): string\n /**\n * The functions of an application's own screens. They are\n * `createAuthClient()`'s, and what changes who is signed in is heard by the\n * subscribers with no reload.\n */\n auth: AuthClient\n /** Where the server mounted the routes. */\n basePath: string\n}\n\n/** Ask again this long before the session's token runs out. */\nconst AHEAD_MS = 60_000\n\nexport function createSessionStore(options: SessionStoreOptions = {}): SessionStore {\n const base = withoutTrailingSlashes(options.basePath ?? '/api/auth')\n const doFetch: typeof fetch = (...args) => (options.fetch ?? fetch)(...args)\n const first = options.initialSession\n let state: SessionState = { session: first ?? null, status: first === undefined ? 'loading' : first === null ? 'unauthenticated' : 'authenticated' }\n const listeners = new Set<(state: SessionState) => void>()\n let asking: Promise<PublicSession | null> | null = null\n let timer: ReturnType<typeof setTimeout> | undefined\n let asked = first !== undefined\n\n const set = (next: SessionState) => {\n if (next.session === state.session && next.status === state.status) return\n state = next\n schedule()\n for (const l of [...listeners]) l(state)\n }\n\n function schedule(): void {\n clearTimeout(timer)\n timer = undefined\n if (listeners.size === 0 || state.session === null) return\n const wait = Math.max(new Date(state.session.expiresAt).getTime() - Date.now() - AHEAD_MS, 5_000)\n timer = setTimeout(() => void refresh(), wait)\n }\n\n function refresh(): Promise<PublicSession | null> {\n asked = true\n asking ??= (async () => {\n try {\n const res = await doFetch(`${base}/session`, { credentials: 'same-origin', headers: { accept: 'application/json' } })\n if (!res.ok) throw new Error(String(res.status))\n const body = (await res.json()) as { session: PublicSession | null; status: string }\n // \"expired\" is the server unable to reach Cauthy: what is on screen\n // stays, and the next ask settles it.\n if (body.status !== 'expired') set({ session: body.session, status: body.session ? 'authenticated' : 'unauthenticated' })\n return body.session\n } catch {\n if (state.status === 'loading') set({ session: null, status: 'unauthenticated' })\n return null\n } finally {\n asking = null\n }\n })()\n return asking\n }\n\n const onVisible = () => {\n if (document.visibilityState === 'visible') void refresh()\n }\n const inBrowser = () => typeof document !== 'undefined'\n\n const client = createAuthClient({ basePath: base, fetch: options.fetch })\n const then = async <T,>(running: Promise<T>): Promise<T> => {\n const r = await running\n await refresh()\n return r\n }\n\n return {\n get: () => state,\n subscribe(listener) {\n listeners.add(listener)\n if (listeners.size === 1 && inBrowser()) {\n document.addEventListener('visibilitychange', onVisible)\n schedule()\n if (!asked) void refresh()\n }\n listener(state)\n return () => {\n listeners.delete(listener)\n if (listeners.size === 0) {\n clearTimeout(timer)\n timer = undefined\n if (inBrowser()) document.removeEventListener('visibilitychange', onVisible)\n }\n }\n },\n refresh,\n signIn(o = {}) {\n const q = new URLSearchParams({ return_to: o.returnTo ?? window.location.pathname + window.location.search })\n if (o.locale) q.set('locale', o.locale)\n if (o.prompt) q.set('prompt', o.prompt)\n window.location.assign(`${base}/login?${q}`)\n },\n signOut() {\n window.location.assign(`${base}/logout`)\n },\n accountUrl(returnTo) {\n const back = returnTo ?? (typeof window === 'undefined' ? '/' : window.location.pathname + window.location.search)\n return `${base}/account?return_to=${encodeURIComponent(back)}`\n },\n auth: {\n ...client,\n signIn: {\n ...client.signIn,\n email: (f) => then(client.signIn.email(f)),\n mfa: (f) => then(client.signIn.mfa(f)),\n magicLinkVerify: (f) => then(client.signIn.magicLinkVerify(f)),\n emailCode: (f) => then(client.signIn.emailCode(f)),\n passkey: (o) => then(client.signIn.passkey(o)),\n },\n signUp: { email: (f) => then(client.signUp.email(f)) },\n signOut: () => then(client.signOut()),\n account: {\n ...client.account,\n update: (f) => then(client.account.update(f)),\n changePassword: (f) => then(client.account.changePassword(f)),\n signOutEverywhere: () => then(client.account.signOutEverywhere()),\n },\n },\n basePath: base,\n }\n}\n","// What a person may do in an organization.\n//\n// When a system keeps its catalog of roles in Cauthy, each of a person's\n// organizations arrives with what their role — and anything given or denied to\n// them apart from it — lets them do. Checking is a lookup in that list: no\n// call to Cauthy, on the server or in the browser.\n\nimport { withoutTrailingSlashes } from './url'\n\n/** The part of an organization that says what its member may do. */\nexport interface Permitted {\n /** \"resource:action\" entries the person is allowed; \"*\" is any. Absent when the system keeps no catalog in Cauthy. */\n permissions?: string[]\n /** Entries the person is denied whatever else allows them. */\n denied?: string[]\n}\n\nfunction matches(entry: string, resource: string, action: string): boolean {\n // The resource may itself hold a colon: the action is what follows the last one.\n const i = entry.lastIndexOf(':')\n if (i < 0) return false\n const r = entry.slice(0, i)\n const a = entry.slice(i + 1)\n return (r === '*' || r === resource) && (a === '*' || a === action)\n}\n\n/**\n * Whether the person may do an action on a resource in an organization:\n * allowed by some entry, and denied by none. A denial wins.\n *\n * An organization of a system that keeps no catalog in Cauthy carries no\n * permissions, and the answer is `false`: decide by its `role` instead.\n *\n * if (!can(organization, 'invoicing', 'write')) return forbidden()\n */\nexport function can(organization: Permitted | null | undefined, resource: string, action: string): boolean {\n if (!organization?.permissions) return false\n if ((organization.denied ?? []).some((d) => matches(d, resource, action))) return false\n return organization.permissions.some((p) => matches(p, resource, action))\n}\n\n// ── A system's catalog of roles ────────────────────────────────────────────\n\n/** Something a role may do: an action on a resource, in organizations of a kind (\"*\", the default, is every kind). */\nexport interface PolicyPermission {\n kind?: string\n resource: string\n action: string\n}\n\nexport interface PolicyRole {\n role: string\n name?: string\n description?: string\n /** Roles whose permissions this one has as well. */\n inherits?: string[]\n permissions?: PolicyPermission[]\n}\n\n// ── The catalog, with its types ────────────────────────────────────────────\n//\n// A catalog written down once gives the roles and the permissions their\n// names: `can(org, 'ledger:write')` then takes only a permission the catalog\n// has, the editor offers them, and a typo does not compile.\n//\n// export const policy = definePolicy([\n// { role: 'reader', permissions: [{ resource: 'ledger', action: 'read' }] },\n// { role: 'accountant', inherits: ['reader'], permissions: [{ resource: 'ledger', action: 'write' }] },\n// ])\n// export const { can, hasRole } = policy\n//\n// can(organization, 'ledger:write') // 'ledger:wirte' does not compile\n// hasRole(organization, 'accountant')\n//\n// Write it by hand, or let `cauthy-auth policy --pull` write it from the\n// catalog Cauthy has.\n\n/** A role as `definePolicy` reads it: any `PolicyRole`, kept as written. */\ntype RoleShape = {\n readonly role: string\n readonly name?: string\n readonly description?: string\n readonly inherits?: readonly string[]\n readonly permissions?: readonly { readonly kind?: string; readonly resource: string; readonly action: string }[]\n}\n\n// \"*\" in a catalog is \"any\": the name it stands for is any text.\ntype Named<S extends string> = S extends '*' ? string : S\n\ntype PermissionsIn<R> = R extends { readonly permissions: readonly (infer P)[] }\n ? P extends { readonly resource: infer Res extends string; readonly action: infer Act extends string } ? `${Named<Res>}:${Named<Act>}` : never\n : never\n\n/** The roles a catalog names: `RoleOf<typeof policy>`. */\nexport type RoleOf<P> = P extends Policy<infer R, string> ? R : never\n/** The \"resource:action\" permissions a catalog names: `PermissionOf<typeof policy>`. */\nexport type PermissionOf<P> = P extends Policy<string, infer X> ? X : never\n\nexport interface Policy<Role extends string = string, Permission extends string = string> {\n /** The catalog as Cauthy takes it: `pushPolicy({ roles: policy.roles })`. */\n roles: PolicyRole[]\n /** Every role's name. */\n roleNames: Role[]\n /** Every permission some role has, as \"resource:action\". */\n permissions: Permission[]\n /**\n * Whether the person may do this in the organization: `can(org, 'ledger:write')`.\n * It reads what Cauthy sent with the session, so a catalog edited in Cauthy\n * since this file was written is still what decides.\n */\n can(organization: Permitted | null | undefined, permission: Permission): boolean\n /** Whether the person's role in the organization is one of these. */\n hasRole(organization: { role: string } | null | undefined, ...roles: Role[]): boolean\n /**\n * What a role may do by this catalog, with what it inherits: for showing a\n * role, never for deciding about a person (their exceptions are not here).\n */\n permissionsOf(role: Role): Permission[]\n}\n\n/** Gives a catalog of roles its types. See the top of this section. */\nexport function definePolicy<const T extends readonly RoleShape[]>(roles: T): Policy<T[number]['role'], PermissionsIn<T[number]>> {\n type Role = T[number]['role']\n type Permission = PermissionsIn<T[number]>\n const entry = (p: { resource: string; action: string }) => `${p.resource}:${p.action}` as Permission\n const byName = new Map(roles.map((r) => [r.role, r]))\n const split = (permission: string): [string, string] => {\n const i = permission.lastIndexOf(':')\n return i < 0 ? [permission, ''] : [permission.slice(0, i), permission.slice(i + 1)]\n }\n return {\n roles: roles.map((r) => ({\n role: r.role,\n ...(r.name !== undefined ? { name: r.name } : {}),\n ...(r.description !== undefined ? { description: r.description } : {}),\n ...(r.inherits ? { inherits: [...r.inherits] } : {}),\n ...(r.permissions ? { permissions: r.permissions.map((p) => ({ ...p })) } : {}),\n })),\n roleNames: roles.map((r) => r.role),\n permissions: [...new Set(roles.flatMap((r) => (r.permissions ?? []).map(entry)))],\n can(organization, permission) {\n const [resource, action] = split(permission)\n return can(organization, resource, action)\n },\n hasRole(organization, ...wanted) {\n return organization != null && (wanted as string[]).includes(organization.role)\n },\n permissionsOf(role: Role) {\n const seen = new Set<string>()\n const out = new Set<Permission>()\n const walk = (name: string) => {\n if (seen.has(name)) return // a catalog that inherits in a circle still ends\n seen.add(name)\n const r = byName.get(name)\n for (const p of r?.permissions ?? []) out.add(entry(p))\n for (const parent of r?.inherits ?? []) walk(parent)\n }\n walk(role)\n return [...out]\n },\n }\n}\n\nconst IDENT = /^[A-Za-z_$][A-Za-z0-9_$]*$/\n\n/**\n * A TypeScript module that defines the catalog given, for `cauthy-auth policy\n * --pull` to write: the application's types then come from the catalog\n * Cauthy has.\n */\nexport function policySource(system: string, roles: PolicyRole[]): string {\n const text = (v: string) => JSON.stringify(v)\n const key = (k: string) => (IDENT.test(k) ? k : text(k))\n const one = (r: PolicyRole) => {\n const parts = [`role: ${text(r.role)}`]\n if (r.name) parts.push(`name: ${text(r.name)}`)\n if (r.description) parts.push(`description: ${text(r.description)}`)\n if (r.inherits?.length) parts.push(`inherits: [${r.inherits.map(text).join(', ')}]`)\n if (r.permissions?.length) {\n const list = r.permissions.map((p) => ` { ${[...(p.kind && p.kind !== '*' ? [`${key('kind')}: ${text(p.kind)}`] : []), `resource: ${text(p.resource)}`, `action: ${text(p.action)}`].join(', ')} },`)\n parts.push(`permissions: [\\n${list.join('\\n')}\\n ]`)\n }\n return ` {\\n ${parts.join(',\\n ')},\\n },`\n }\n return `// The catalog of roles of ${text(system)}, as Cauthy has it.\n// Written by \\`cauthy-auth policy --pull\\`: run it again after the catalog\n// changes, and do not edit this file by hand.\n\nimport { definePolicy, type PermissionOf, type RoleOf } from 'cauthy-auth'\n\nexport const policy = definePolicy([\n${roles.map(one).join('\\n')}\n])\n\nexport const { can, hasRole } = policy\nexport type Role = RoleOf<typeof policy>\nexport type Permission = PermissionOf<typeof policy>\n`\n}\n\n/** Reads a system's catalog of roles from Cauthy, with a personal access token. */\nexport async function pullPolicy(o: Omit<PushPolicyOptions, 'roles'> = {}): Promise<PolicyRole[]> {\n const env = (n: string) => (typeof process !== 'undefined' ? process.env?.[n] : undefined)\n const issuer = withoutTrailingSlashes(o.issuer ?? env('CAUTHY_ISSUER') ?? '')\n const system = o.system ?? env('CAUTHY_SYSTEM') ?? ''\n const tenant = o.tenant ?? env('CAUTHY_TENANT') ?? ''\n const token = o.token ?? env('CAUTHY_TOKEN') ?? ''\n for (const [name, v] of Object.entries({ CAUTHY_ISSUER: issuer, CAUTHY_SYSTEM: system, CAUTHY_TENANT: tenant, CAUTHY_TOKEN: token })) {\n if (v === '') throw new Error(`cauthy-auth: ${name} is required to read a policy`)\n }\n const res = await (o.fetch ?? fetch)(`${issuer}/v1/systems/${encodeURIComponent(system)}/policy`, {\n headers: { authorization: `Bearer ${token}`, 'x-tenant-key': tenant, accept: 'application/json' },\n })\n const body = (await res.json().catch(() => null)) as { data?: { roles?: PolicyRole[] }; errors?: { message?: string }[] } | null\n if (!res.ok) throw new Error(`cauthy-auth: Cauthy refused to give the policy (${res.status}): ${body?.errors?.[0]?.message ?? res.statusText}`)\n return body?.data?.roles ?? []\n}\n\nexport interface PushPolicyOptions {\n /** The system whose catalog this is. Default: `CAUTHY_SYSTEM`. */\n system?: string\n roles: PolicyRole[]\n /** Cauthy's address. Default: `CAUTHY_ISSUER`. */\n issuer?: string\n /** The workspace's slug. Default: `CAUTHY_TENANT`. */\n tenant?: string\n /** A personal access token with `organizations:manage`. Default: `CAUTHY_TOKEN`. */\n token?: string\n fetch?: typeof fetch\n}\n\n/**\n * Gives Cauthy a system's catalog of roles, replacing the one it has. It is\n * the way to load a first catalog from what a system already had; from then on\n * the catalog is Cauthy's, edited in its dashboard, and pushing again would\n * overwrite what was edited there.\n */\nexport async function pushPolicy(o: PushPolicyOptions): Promise<void> {\n const env = (n: string) => (typeof process !== 'undefined' ? process.env?.[n] : undefined)\n const issuer = withoutTrailingSlashes(o.issuer ?? env('CAUTHY_ISSUER') ?? '')\n const system = o.system ?? env('CAUTHY_SYSTEM') ?? ''\n const tenant = o.tenant ?? env('CAUTHY_TENANT') ?? ''\n const token = o.token ?? env('CAUTHY_TOKEN') ?? ''\n for (const [name, v] of Object.entries({ CAUTHY_ISSUER: issuer, CAUTHY_SYSTEM: system, CAUTHY_TENANT: tenant, CAUTHY_TOKEN: token })) {\n if (v === '') throw new Error(`cauthy-auth: ${name} is required to push a policy`)\n }\n const res = await (o.fetch ?? fetch)(`${issuer}/v1/systems/${encodeURIComponent(system)}/policy`, {\n method: 'PUT',\n headers: { authorization: `Bearer ${token}`, 'x-tenant-key': tenant, 'content-type': 'application/json' },\n body: JSON.stringify({ roles: o.roles }),\n })\n if (!res.ok) {\n const body = (await res.json().catch(() => null)) as { errors?: { message?: string }[] } | null\n throw new Error(`cauthy-auth: Cauthy refused the policy (${res.status}): ${body?.errors?.[0]?.message ?? res.statusText}`)\n }\n}\n"],"mappings":"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","names":["text"]}
|