// Boot-time environment validation. Fails fast on clearly-invalid values so a // misconfigured deployment stops loudly instead of misbehaving later, and warns // on security-relevant gaps in production. Keep this the single place that // asserts config invariants at startup. // Leading import so Node's type-stripping activates for `node --check`. import type { Db } from '../types/db'; const isProd = () => process.env.NODE_ENV === 'production'; function isIntString(v: string | undefined): boolean { return v === undefined || /^\d+$/.test(v.trim()); } /** * Validate environment configuration. Throws (exit 1 in the caller) on fatal * misconfiguration; logs warnings for security-relevant gaps. */ function validateEnv(): void { const fatal: string[] = []; if (!isIntString(process.env.PORT)) { fatal.push(`PORT must be an integer (got "${process.env.PORT}")`); } if (!isIntString(process.env.SESSION_CLEANUP_INTERVAL_MS)) { fatal.push( `SESSION_CLEANUP_INTERVAL_MS must be an integer (got "${process.env.SESSION_CLEANUP_INTERVAL_MS}")`, ); } const key = process.env.TOKEN_ENCRYPTION_KEY?.trim(); if (key && key.length < 16) { fatal.push( 'TOKEN_ENCRYPTION_KEY is set but too short (use a long random string, e.g. 48 random bytes hex)', ); } if (fatal.length) { console.error('[config] FATAL — invalid configuration:'); for (const f of fatal) console.error(` ✗ ${f}`); throw new Error('Invalid environment configuration'); } // Security-relevant warnings (do NOT fail — existing deployments intentionally // run on the DB-stored auto-key). if (isProd() && !key) { console.warn( '[config] WARNING: TOKEN_ENCRYPTION_KEY is not set in production. Encrypted secrets ' + '(SimpleFIN token, SMTP/OIDC secrets) fall back to a key stored inside the database — ' + 'anyone with DB/backup read access can decrypt them. Set TOKEN_ENCRYPTION_KEY (outside ' + 'the DB) to a long random string.', ); } // WebAuthn credentials are cryptographically bound to the Relying Party ID. // With the localhost default, passkeys registered in production would bind to // "localhost" and silently fail to verify on the real domain. if (isProd() && !process.env.WEBAUTHN_RP_ID) { console.warn( '[config] WARNING: WEBAUTHN_RP_ID is not set in production (defaults to "localhost"). ' + 'Passkeys/security keys will not work behind your real domain. Set it to the bare ' + 'hostname users sign in on, e.g. WEBAUTHN_RP_ID=bills.example.com.', ); } } module.exports = { validateEnv };