Something went wrong. Try again.
The logic for serving and storing associations between users and apps
Something went wrong. Try again.
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175import { defaultClientId, isLocalhostOrigin, parseExactOrigin,} from "./origins";
export type OriginMeta = { origin: string; client_id: string; name: string; logo?: string;};
export type MetadataResult = { ok: boolean; name: string; logo?: string; /** Verbatim `redirect_uris`, used to authorize native callback URLs. */ redirectUris?: string[];};
type CacheEntry = MetadataResult & { fetchedAt: number };
const CACHE_MS = 60 * 60 * 1000;const FETCH_TIMEOUT_MS = 5_000;const MAX_BODY_BYTES = 64 * 1024;const cache = new Map<string, CacheEntry>();
type ClientMetadata = { client_id?: unknown; client_name?: unknown; client_uri?: unknown; logo_uri?: unknown; redirect_uris?: unknown;};
export type MetadataFetchGate = (clientId: string) => Promise<boolean>;
/** True when origin may participate (localhost always; else valid client metadata). */export async function isLiveOrigin( origin: string, gate?: MetadataFetchGate,): Promise<boolean> { const parsed = parseExactOrigin(origin); if (!parsed.ok) return false; if (isLocalhostOrigin(parsed.origin)) return true; const meta = await validateMetadata(parsed.origin, gate); return meta.ok;}
export async function resolveOrigins( origins: string[], gate?: MetadataFetchGate,): Promise<OriginMeta[]> { const unique = [...new Set(origins)].slice(0, 40); const out: OriginMeta[] = []; for (const origin of unique) { const parsed = parseExactOrigin(origin); if (!parsed.ok) continue; const client_id = defaultClientId(parsed.origin); const meta = await validateMetadata(parsed.origin, gate); if (!meta.ok && !isLocalhostOrigin(parsed.origin)) continue; out.push({ origin: parsed.origin, client_id, name: meta.name, logo: meta.logo, }); } return out;}
export async function validateMetadata( origin: string, gate?: MetadataFetchGate,): Promise<MetadataResult> { const clientId = defaultClientId(origin); const cached = cache.get(clientId); if (cached && Date.now() - cached.fetchedAt < CACHE_MS) { return cached; } if (gate) { const allowed = await gate(clientId); if (!allowed) { return { ok: false, name: fallbackClientName(origin) }; } } const result = await fetchMetadata(origin, clientId); cache.set(clientId, { ...result, fetchedAt: Date.now() }); return result;}
/** Test helper: clear the in-memory metadata cache. */export function clearMetadataCache(): void { cache.clear();}
async function fetchMetadata(origin: string, clientId: string): Promise<MetadataResult> { const fallbackName = fallbackClientName(origin); try { const ac = new AbortController(); const timer = setTimeout(() => ac.abort(), FETCH_TIMEOUT_MS); const res = await fetch(clientId, { redirect: "manual", headers: { accept: "application/json" }, signal: ac.signal, }); clearTimeout(timer);
if (res.status !== 200) { return { ok: false, name: fallbackName }; } const contentType = res.headers.get("content-type") ?? ""; if (!contentType.toLowerCase().includes("application/json")) { return { ok: false, name: fallbackName }; } const raw = await res.arrayBuffer(); if (raw.byteLength > MAX_BODY_BYTES) { return { ok: false, name: fallbackName }; } const body = JSON.parse(new TextDecoder().decode(raw)) as ClientMetadata; if (body.client_id !== clientId) { return { ok: false, name: fallbackName }; } if (typeof body.client_uri === "string") { try { if (new URL(body.client_uri).origin !== origin) { return { ok: false, name: fallbackName }; } } catch { return { ok: false, name: fallbackName }; } } if (!hasValidRedirect(body.redirect_uris, origin)) { return { ok: false, name: fallbackName }; } const name = typeof body.client_name === "string" && body.client_name.trim() ? body.client_name.trim() : fallbackName; const logo = typeof body.logo_uri === "string" ? body.logo_uri : undefined; return { ok: true, name, logo, redirectUris: stringEntries(body.redirect_uris) }; } catch { return { ok: false, name: fallbackName }; }}
function stringEntries(value: unknown): string[] { if (!Array.isArray(value)) return []; return value.filter((entry): entry is string => typeof entry === "string").slice(0, 20);}
function hasValidRedirect(redirectUris: unknown, origin: string): boolean { if (!Array.isArray(redirectUris)) return false; return redirectUris.some((uri) => { if (typeof uri !== "string") return false; try { const url = new URL(uri); if (url.origin !== origin) return false; if (url.protocol === "https:") return true; return url.protocol === "http:" && isLocalhostOrigin(origin); } catch { return false; } });}
function fallbackClientName(origin: string): string { try { return new URL(origin).host; } catch { return origin; }}