BillTracker/utils/env.cts

68 lines
2.6 KiB
TypeScript

// 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 };