import { validateNamespace } from "./router.js"; export const PINBOARD_SCOPE_PREFIX = "pinboard:"; // Identity lives in @assistant-ui/passport ("who is this"); the namespace // rules here are pinboard policy ("may they"). export namespace PassportRules { export type Rule = { allow: string[] | "authenticated" | "anonymous" }; } const fail = (message: string): never => { throw new Error(`passport: ${message}`); }; export const passportUnauthorizedReason = (error: unknown): string | null => { if (typeof error !== "object" || error === null) return null; const candidate = error as { name?: unknown; reason?: unknown }; return candidate.name === "PassportUnauthorized" && typeof candidate.reason === "string" ? candidate.reason : null; }; export const validatePassportRules = ( raw: unknown, ): Map => { if (typeof raw !== "object" || raw === null || Array.isArray(raw)) { fail("namespaces must be an object"); } const rules = new Map(); for (const [key, rawRule] of Object.entries(raw as Record)) { if (key !== "*") { try { validateNamespace(key); } catch { fail(`invalid namespace key ${JSON.stringify(key)}`); } } if (typeof rawRule !== "object" || rawRule === null) { fail(`namespace ${JSON.stringify(key)}: rule must be an object`); } const allow = (rawRule as Record)["allow"]; if (allow !== "authenticated" && allow !== "anonymous") { if ( !Array.isArray(allow) || allow.length === 0 || !allow.every((scope) => typeof scope === "string" && scope !== "") ) { fail( `namespace ${JSON.stringify(key)}: allow must be "authenticated", "anonymous", or a non-empty array of scopes`, ); } for (const scope of allow as string[]) { if (scope.startsWith(PINBOARD_SCOPE_PREFIX)) { fail( `namespace ${JSON.stringify(key)}: scope ${JSON.stringify(scope)} uses the reserved "${PINBOARD_SCOPE_PREFIX}" prefix`, ); } } } rules.set(key, { allow } as PassportRules.Rule); } return rules; }; export const passportRuleFor = ( rules: Map, ns: string, ): PassportRules.Rule | null => rules.get(ns) ?? rules.get("*") ?? null;