fix(security): add production env validation and sanitize .env.example

- Add startup env validation that fails fast in production if critical vars
  (JWT_SECRET, JWT_REFRESH_SECRET, DATABASE_URL, CORS_ORIGINS, REDIS_HOST)
  are missing
- Fix CORS_ORIGINS to throw in production instead of defaulting to localhost
- Replace hardcoded dev passwords in .env.example with CHANGE_ME placeholders
- Add missing vars to .env.example (CORS_ORIGINS, SMTP_*, FIREBASE, LOG_LEVEL)
- Warn on missing optional payment/storage vars at startup

Co-Authored-By: Paperclip <noreply@paperclip.ing>
This commit is contained in:
Ho Ngoc Hai
2026-04-08 06:12:39 +07:00
parent d77c14e549
commit e89c8f5810
4 changed files with 112 additions and 10 deletions

View File

@@ -0,0 +1,62 @@
/**
* Validates that critical environment variables are set in production.
* Call this at application startup before any module initialization.
*/
const REQUIRED_IN_PRODUCTION: readonly string[] = [
'JWT_SECRET',
'JWT_REFRESH_SECRET',
'DATABASE_URL',
'CORS_ORIGINS',
'REDIS_HOST',
];
const REQUIRED_WHEN_USED: ReadonlyMap<string, string> = new Map([
['VNPAY_TMN_CODE', 'VNPay payments'],
['VNPAY_HASH_SECRET', 'VNPay payments'],
['MOMO_PARTNER_CODE', 'MoMo payments'],
['MOMO_ACCESS_KEY', 'MoMo payments'],
['MOMO_SECRET_KEY', 'MoMo payments'],
['ZALOPAY_APP_ID', 'ZaloPay payments'],
['ZALOPAY_KEY1', 'ZaloPay payments'],
['ZALOPAY_KEY2', 'ZaloPay payments'],
['MINIO_ACCESS_KEY', 'Media storage'],
['MINIO_SECRET_KEY', 'Media storage'],
]);
export function validateEnv(): void {
const isProduction = process.env['NODE_ENV'] === 'production';
if (!isProduction) {
return;
}
const missing: string[] = [];
for (const key of REQUIRED_IN_PRODUCTION) {
if (!process.env[key]) {
missing.push(key);
}
}
if (missing.length > 0) {
throw new Error(
`Missing required environment variables in production:\n ${missing.join('\n ')}\n` +
'See .env.example for the full list of variables.',
);
}
// Warn about payment/storage vars that are empty — services will fail at
// runtime when invoked, but we surface the warning early.
const warnings: string[] = [];
for (const [key, feature] of REQUIRED_WHEN_USED) {
if (!process.env[key]) {
warnings.push(` ${key} (${feature})`);
}
}
if (warnings.length > 0) {
console.warn(
`[env-validation] The following optional vars are unset — related features will fail at runtime:\n${warnings.join('\n')}`,
);
}
}