/** * Shared HIBP policy cache — separated to avoid circular imports between * `auth.service` (which clears it) and the consumer's policy fetcher. */ import { apiFetch } from "../utils/api-fetch"; export interface HibpPolicy { enabled: boolean; enforce: boolean; block_threshold: number; } interface ServerConfig { hibp: HibpPolicy; } /** Conservative default — block re-use of widely-leaked passwords. */ const DEFAULT_POLICY: HibpPolicy = { enabled: true, enforce: true, block_threshold: 1000, }; let cachedPolicy: HibpPolicy | null = null; export function getCachedPolicy(): HibpPolicy | null { return cachedPolicy; } export function setCachedPolicy(policy: HibpPolicy): void { cachedPolicy = policy; } export function clearCachedPolicy(): void { cachedPolicy = null; } /** * Fetch the HIBP policy from the server (`GET /config`). Result is * memoized in the in-process cache for the lifetime of the app — call * once at startup (typically from ``). * Server-unreachable falls back to `DEFAULT_POLICY` so the caller is * never blocked. */ export async function fetchHibpPolicy(): Promise { const cached = cachedPolicy; if (cached) { return cached; } try { const response = await apiFetch("/config"); if (response.ok) { const config: ServerConfig = await response.json(); cachedPolicy = config.hibp; return config.hibp; } } catch { // Server unreachable — fall through to default. } cachedPolicy = DEFAULT_POLICY; return DEFAULT_POLICY; } /** * Synchronous view of the cached policy. Returns the default when * `fetchHibpPolicy` hasn't completed yet; suitable for render-time * gating where blocking is unacceptable. */ export function getHibpPolicy(): HibpPolicy { return cachedPolicy ?? DEFAULT_POLICY; }