2026-04-23 15:33:11 +03:30
|
|
|
// backend/src/config/configuration.ts
|
2026-05-16 22:40:42 +03:30
|
|
|
|
|
|
|
|
/** Matches values accepted by jsonwebtoken `expiresIn` (via ms), e.g. 7d, 15m, or plain seconds. */
|
|
|
|
|
const JWT_TIMESPAN_PATTERN =
|
|
|
|
|
/^\d+(\.\d+)?(ms|s|m|h|d|w|y)?$/i;
|
|
|
|
|
|
|
|
|
|
function assertJwtSecret(value: string, envKey: string): string {
|
|
|
|
|
const trimmed = value.trim();
|
|
|
|
|
if (!trimmed) {
|
|
|
|
|
throw new Error(`❌ Environment variable ${envKey} is required but not set`);
|
|
|
|
|
}
|
|
|
|
|
if (trimmed.length < 16) {
|
|
|
|
|
throw new Error(`❌ ${envKey} must be at least 16 characters`);
|
|
|
|
|
}
|
|
|
|
|
if (/CHANGE_ME/i.test(trimmed)) {
|
|
|
|
|
throw new Error(`❌ ${envKey} must be changed from the placeholder value`);
|
|
|
|
|
}
|
|
|
|
|
return trimmed;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function assertJwtTimespan(value: string, envKey: string): string {
|
|
|
|
|
const trimmed = value.trim();
|
|
|
|
|
if (!trimmed) {
|
|
|
|
|
throw new Error(`❌ Environment variable ${envKey} is required but not set`);
|
|
|
|
|
}
|
|
|
|
|
if (!/^\d+$/.test(trimmed) && !JWT_TIMESPAN_PATTERN.test(trimmed)) {
|
|
|
|
|
throw new Error(
|
|
|
|
|
`❌ ${envKey}="${value}" is invalid. Use a duration like 7d, 15m, 30d, or a number of seconds.`,
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
return trimmed;
|
|
|
|
|
}
|
|
|
|
|
|
2026-04-23 15:33:11 +03:30
|
|
|
export interface Config {
|
|
|
|
|
port: number;
|
|
|
|
|
database: {
|
|
|
|
|
url: string;
|
|
|
|
|
};
|
|
|
|
|
jwt: {
|
|
|
|
|
secret: string;
|
|
|
|
|
expiresIn: string;
|
2026-05-16 22:40:42 +03:30
|
|
|
refreshSecret: string;
|
|
|
|
|
refreshExpiresIn: string;
|
2026-04-23 15:33:11 +03:30
|
|
|
};
|
|
|
|
|
throttle: {
|
|
|
|
|
ttl: number;
|
|
|
|
|
limit: number;
|
|
|
|
|
};
|
2026-07-04 00:01:58 +03:30
|
|
|
sms: {
|
|
|
|
|
apiKey: string | null;
|
|
|
|
|
templateId: number;
|
|
|
|
|
};
|
2026-04-23 15:33:11 +03:30
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export default (): Config => {
|
|
|
|
|
// Helper function to get required env var with type safety
|
|
|
|
|
const getEnvVar = (key: string): string => {
|
|
|
|
|
const value = process.env[key];
|
|
|
|
|
if (!value) {
|
|
|
|
|
throw new Error(`❌ Environment variable ${key} is required but not set`);
|
|
|
|
|
}
|
|
|
|
|
return value;
|
|
|
|
|
};
|
|
|
|
|
|
2026-05-16 22:40:42 +03:30
|
|
|
// Helper for optional env vars with defaults (whitespace-only counts as unset)
|
2026-04-23 15:33:11 +03:30
|
|
|
const getEnvVarWithDefault = (key: string, defaultValue: string): string => {
|
2026-05-16 22:40:42 +03:30
|
|
|
const value = process.env[key]?.trim();
|
|
|
|
|
return value ? value : defaultValue;
|
2026-04-23 15:33:11 +03:30
|
|
|
};
|
|
|
|
|
|
|
|
|
|
const getEnvVarAsNumber = (key: string, defaultValue: number): number => {
|
|
|
|
|
const value = process.env[key];
|
|
|
|
|
if (!value) return defaultValue;
|
|
|
|
|
const parsed = parseInt(value, 10);
|
|
|
|
|
return isNaN(parsed) ? defaultValue : parsed;
|
|
|
|
|
};
|
|
|
|
|
|
2026-05-16 22:40:42 +03:30
|
|
|
const jwtSecret = assertJwtSecret(getEnvVar('JWT_SECRET'), 'JWT_SECRET');
|
|
|
|
|
const jwtExpiresIn = assertJwtTimespan(
|
|
|
|
|
getEnvVarWithDefault('JWT_EXPIRES_IN', '7d'),
|
|
|
|
|
'JWT_EXPIRES_IN',
|
|
|
|
|
);
|
|
|
|
|
const jwtRefreshSecret = assertJwtSecret(
|
|
|
|
|
getEnvVarWithDefault('JWT_REFRESH_SECRET', jwtSecret),
|
|
|
|
|
'JWT_REFRESH_SECRET',
|
|
|
|
|
);
|
|
|
|
|
const jwtRefreshExpiresIn = assertJwtTimespan(
|
|
|
|
|
getEnvVarWithDefault('JWT_REFRESH_EXPIRES_IN', '30d'),
|
|
|
|
|
'JWT_REFRESH_EXPIRES_IN',
|
|
|
|
|
);
|
|
|
|
|
|
2026-04-23 15:33:11 +03:30
|
|
|
return {
|
|
|
|
|
port: getEnvVarAsNumber('PORT', 3000),
|
|
|
|
|
database: {
|
|
|
|
|
url: getEnvVar('DATABASE_URL'),
|
|
|
|
|
},
|
|
|
|
|
jwt: {
|
2026-05-16 22:40:42 +03:30
|
|
|
secret: jwtSecret,
|
|
|
|
|
expiresIn: jwtExpiresIn,
|
|
|
|
|
refreshSecret: jwtRefreshSecret,
|
|
|
|
|
refreshExpiresIn: jwtRefreshExpiresIn,
|
2026-04-23 15:33:11 +03:30
|
|
|
},
|
|
|
|
|
throttle: {
|
|
|
|
|
ttl: getEnvVarAsNumber('THROTTLE_TTL', 60),
|
|
|
|
|
limit: getEnvVarAsNumber('THROTTLE_LIMIT', 100),
|
|
|
|
|
},
|
2026-07-04 00:01:58 +03:30
|
|
|
sms: {
|
|
|
|
|
apiKey: process.env.SMS_IR_API_KEY?.trim() || null,
|
|
|
|
|
templateId: getEnvVarAsNumber('SMS_IR_TEMPLATE_ID', 123456),
|
|
|
|
|
},
|
2026-04-23 15:33:11 +03:30
|
|
|
};
|
|
|
|
|
};
|