fix(auth): enforce JWT secrets in all environments, not just production
validateEnv() previously skipped validation entirely when NODE_ENV !== 'production', allowing the app to start without JWT_SECRET and JWT_REFRESH_SECRET in dev/staging. Split required vars into ALWAYS_REQUIRED (JWT secrets) and REQUIRED_IN_PRODUCTION (infrastructure) so security-critical secrets are validated in every environment. Co-Authored-By: Paperclip <noreply@paperclip.ing>
This commit is contained in:
@@ -1,11 +1,16 @@
|
|||||||
/**
|
/**
|
||||||
* Validates that critical environment variables are set in production.
|
* Validates that critical environment variables are set at application startup.
|
||||||
* Call this at application startup before any module initialization.
|
*
|
||||||
|
* Security-sensitive vars (JWT secrets) are required in ALL environments.
|
||||||
|
* Infrastructure vars (DATABASE_URL, CORS, Redis) are required only in production.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
const REQUIRED_IN_PRODUCTION: readonly string[] = [
|
const ALWAYS_REQUIRED: readonly string[] = [
|
||||||
'JWT_SECRET',
|
'JWT_SECRET',
|
||||||
'JWT_REFRESH_SECRET',
|
'JWT_REFRESH_SECRET',
|
||||||
|
];
|
||||||
|
|
||||||
|
const REQUIRED_IN_PRODUCTION: readonly string[] = [
|
||||||
'DATABASE_URL',
|
'DATABASE_URL',
|
||||||
'CORS_ORIGINS',
|
'CORS_ORIGINS',
|
||||||
'REDIS_HOST',
|
'REDIS_HOST',
|
||||||
@@ -30,14 +35,11 @@ const REQUIRED_WHEN_USED: ReadonlyMap<string, string> = new Map([
|
|||||||
|
|
||||||
export function validateEnv(): void {
|
export function validateEnv(): void {
|
||||||
const isProduction = process.env['NODE_ENV'] === 'production';
|
const isProduction = process.env['NODE_ENV'] === 'production';
|
||||||
|
|
||||||
if (!isProduction) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const missing: string[] = [];
|
const missing: string[] = [];
|
||||||
|
|
||||||
for (const key of REQUIRED_IN_PRODUCTION) {
|
// JWT secrets are required in every environment — a missing secret is a
|
||||||
|
// security risk regardless of NODE_ENV.
|
||||||
|
for (const key of ALWAYS_REQUIRED) {
|
||||||
if (!process.env[key]) {
|
if (!process.env[key]) {
|
||||||
missing.push(key);
|
missing.push(key);
|
||||||
}
|
}
|
||||||
@@ -45,7 +47,26 @@ export function validateEnv(): void {
|
|||||||
|
|
||||||
if (missing.length > 0) {
|
if (missing.length > 0) {
|
||||||
throw new Error(
|
throw new Error(
|
||||||
`Missing required environment variables in production:\n ${missing.join('\n ')}\n` +
|
`Missing required environment variables:\n ${missing.join('\n ')}\n` +
|
||||||
|
'JWT_SECRET and JWT_REFRESH_SECRET must always be set. See .env.example.',
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!isProduction) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Infrastructure vars — required in production only.
|
||||||
|
const missingProd: string[] = [];
|
||||||
|
for (const key of REQUIRED_IN_PRODUCTION) {
|
||||||
|
if (!process.env[key]) {
|
||||||
|
missingProd.push(key);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (missingProd.length > 0) {
|
||||||
|
throw new Error(
|
||||||
|
`Missing required environment variables in production:\n ${missingProd.join('\n ')}\n` +
|
||||||
'See .env.example for the full list of variables.',
|
'See .env.example for the full list of variables.',
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user