const LOOPBACK_HOSTS = new Set(["127.0.0.1", "localhost", "::1", "[::1]"]); /** Default web/API origins allowed on loopback binds for the Heute iframe shell. */ const DEFAULT_LOOPBACK_WEB_ORIGINS = [ "http://127.0.0.1:5173", "http://localhost:5173", "http://127.0.0.1:3100", "http://localhost:3100", ]; export function isLoopbackHost(host) { return LOOPBACK_HOSTS.has(host.trim()); } export function timingSafeStringEqual(supplied, expected) { const actual = Buffer.from(expected); const candidate = Buffer.from(supplied ?? ""); if (actual.length !== candidate.length) return false; let difference = 0; for (let index = 0; index < actual.length; index += 1) { difference |= actual[index] ^ candidate[index]; } return difference === 0; } export function hasValidBearerToken(authorization, expectedToken) { const supplied = authorization?.startsWith("Bearer ") ? authorization.slice(7) : ""; return timingSafeStringEqual(supplied, expectedToken); } export function isPublicCockpitRoute(pathname) { return pathname === "/health" || pathname === "/api/health"; } /** Privileged API routes must never receive Access-Control-Allow-Origin: *. */ export function isPrivilegedCockpitRoute(pathname) { if (isPublicCockpitRoute(pathname)) return false; return pathname.startsWith("/api/"); } export function parseCorsOriginAllowlist(value) { if (!value?.trim()) return []; return [...new Set(value.split(",").map((entry) => entry.trim()).filter(Boolean))]; } /** * Cockpit bind/auth settings. Defaults to loopback-only with no token so local * Mission Control keeps working out of the box. */ export function resolveCockpitConfig(env) { const port = Number.parseInt(env.PORT?.trim() || "3088", 10); if (!Number.isInteger(port) || port <= 0 || port > 65_535) { throw new Error("PORT is not a valid port number."); } const host = env.EVOO_COCKPIT_HOST?.trim() || "127.0.0.1"; const loopback = isLoopbackHost(host); const token = env.EVOO_COCKPIT_TOKEN?.trim() || ""; if (!loopback && token === "") { throw new Error( "Set EVOO_COCKPIT_TOKEN when EVOO_COCKPIT_HOST is not loopback (127.0.0.1, localhost, or ::1).", ); } return { port, host, token: loopback ? null : token, requiresAuth: !loopback, }; } /** * CORS allowlist. Loopback binds default to local web/API origins; non-loopback * binds require an explicit EVOO_COCKPIT_CORS_ORIGIN comma-separated list. */ export function resolveCockpitCorsConfig(env, cockpitConfig) { const explicit = parseCorsOriginAllowlist(env.EVOO_COCKPIT_CORS_ORIGIN); const loopbackBind = isLoopbackHost(cockpitConfig.host); if (!loopbackBind && explicit.length === 0) { return { loopbackBind: false, allowedOrigins: [], usesDefaultLoopbackOrigins: false, }; } const usesDefaultLoopbackOrigins = loopbackBind && explicit.length === 0; const allowedOrigins = usesDefaultLoopbackOrigins ? DEFAULT_LOOPBACK_WEB_ORIGINS : explicit; return { loopbackBind, allowedOrigins, usesDefaultLoopbackOrigins, }; } /** * Resolve the Access-Control-Allow-Origin value for a request. * Returns a reflected origin string, null when CORS headers should be omitted, * or false when the Origin is present but not allowed. */ export function resolveAllowedCorsOrigin(requestOrigin, corsConfig, pathname) { const origin = requestOrigin?.trim() ?? ""; if (!origin) return null; if (isPrivilegedCockpitRoute(pathname)) { return corsConfig.allowedOrigins.includes(origin) ? origin : false; } if (corsConfig.loopbackBind && corsConfig.usesDefaultLoopbackOrigins) { return corsConfig.allowedOrigins.includes(origin) ? origin : false; } return corsConfig.allowedOrigins.includes(origin) ? origin : false; } export function applyCockpitCorsHeaders(req, res, pathname, corsConfig) { res.setHeader("Access-Control-Allow-Methods", "GET, POST, OPTIONS"); res.setHeader("Access-Control-Allow-Headers", "Content-Type, Authorization"); const allowed = resolveAllowedCorsOrigin(req.headers.origin, corsConfig, pathname); if (typeof allowed === "string") { res.setHeader("Access-Control-Allow-Origin", allowed); res.setHeader("Vary", "Origin"); } return allowed; } export function formatCockpitListenUrl(host, port) { const displayHost = host.includes(":") && !host.startsWith("[") ? `[${host}]` : host; return `http://${displayHost}:${port}`; }