🔧 Add environment variable validation utility
This commit is contained in:
167
code/websites/pokedex.online/server/utils/env-validator.js
Normal file
167
code/websites/pokedex.online/server/utils/env-validator.js
Normal file
@@ -0,0 +1,167 @@
|
||||
/**
|
||||
* Environment Variable Validation
|
||||
*
|
||||
* Validates required environment variables at startup and provides
|
||||
* helpful error messages for production deployments.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Required environment variables for production
|
||||
*/
|
||||
const REQUIRED_ENV_VARS = {
|
||||
// Server Configuration
|
||||
NODE_ENV: {
|
||||
required: true,
|
||||
description: 'Environment mode (development, production)',
|
||||
validate: (val) => ['development', 'production', 'test'].includes(val)
|
||||
},
|
||||
PORT: {
|
||||
required: true,
|
||||
description: 'Server port number',
|
||||
validate: (val) => !isNaN(parseInt(val)) && parseInt(val) > 0 && parseInt(val) < 65536
|
||||
},
|
||||
|
||||
// Optional but recommended for production
|
||||
SESSION_SECRET: {
|
||||
required: false,
|
||||
description: 'Secret key for session encryption',
|
||||
warn: (val) => !val || val.length < 32 ? 'SESSION_SECRET should be at least 32 characters for security' : null
|
||||
},
|
||||
FRONTEND_URL: {
|
||||
required: false,
|
||||
description: 'Frontend URL for CORS (required in production)',
|
||||
warn: (val, env) => env.NODE_ENV === 'production' && !val ? 'FRONTEND_URL should be set in production for proper CORS' : null
|
||||
},
|
||||
|
||||
// Challonge OAuth (optional)
|
||||
CHALLONGE_CLIENT_ID: {
|
||||
required: false,
|
||||
description: 'Challonge OAuth client ID'
|
||||
},
|
||||
CHALLONGE_CLIENT_SECRET: {
|
||||
required: false,
|
||||
description: 'Challonge OAuth client secret'
|
||||
},
|
||||
CHALLONGE_REDIRECT_URI: {
|
||||
required: false,
|
||||
description: 'OAuth redirect URI'
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Validate environment variables
|
||||
* @returns {Object} Validation result with errors and warnings
|
||||
*/
|
||||
export function validateEnvironment() {
|
||||
const errors = [];
|
||||
const warnings = [];
|
||||
const missing = [];
|
||||
|
||||
// Check required variables
|
||||
for (const [key, config] of Object.entries(REQUIRED_ENV_VARS)) {
|
||||
const value = process.env[key];
|
||||
|
||||
// Check if required variable is missing
|
||||
if (config.required && !value) {
|
||||
errors.push(`Missing required environment variable: ${key} - ${config.description}`);
|
||||
missing.push(key);
|
||||
continue;
|
||||
}
|
||||
|
||||
// Validate value if present
|
||||
if (value && config.validate && !config.validate(value)) {
|
||||
errors.push(`Invalid value for ${key}: "${value}" - ${config.description}`);
|
||||
}
|
||||
|
||||
// Check for warnings
|
||||
if (config.warn) {
|
||||
const warning = config.warn(value, process.env);
|
||||
if (warning) {
|
||||
warnings.push(`${key}: ${warning}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
valid: errors.length === 0,
|
||||
errors,
|
||||
warnings,
|
||||
missing
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Validate environment and exit if critical errors found
|
||||
* @param {boolean} exitOnError - Whether to exit process on validation errors (default: true)
|
||||
*/
|
||||
export function validateOrExit(exitOnError = true) {
|
||||
const result = validateEnvironment();
|
||||
|
||||
// Print validation results
|
||||
console.log('\n🔍 Environment Validation:');
|
||||
console.log(` NODE_ENV: ${process.env.NODE_ENV || 'not set'}`);
|
||||
console.log(` PORT: ${process.env.PORT || 'not set'}`);
|
||||
|
||||
// Show errors
|
||||
if (result.errors.length > 0) {
|
||||
console.error('\n❌ Environment Validation Errors:');
|
||||
result.errors.forEach(error => console.error(` - ${error}`));
|
||||
|
||||
if (result.missing.length > 0) {
|
||||
console.error('\n💡 Tip: Create a .env file with these variables:');
|
||||
result.missing.forEach(key => {
|
||||
console.error(` ${key}=your_value_here`);
|
||||
});
|
||||
console.error('\n See .env.example for reference');
|
||||
}
|
||||
|
||||
if (exitOnError) {
|
||||
console.error('\n❌ Server cannot start due to environment errors\n');
|
||||
process.exit(1);
|
||||
}
|
||||
} else {
|
||||
console.log(' ✅ All required variables present');
|
||||
}
|
||||
|
||||
// Show warnings
|
||||
if (result.warnings.length > 0) {
|
||||
console.warn('\n⚠️ Environment Warnings:');
|
||||
result.warnings.forEach(warning => console.warn(` - ${warning}`));
|
||||
}
|
||||
|
||||
console.log('');
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get configuration object with validated environment variables
|
||||
* @returns {Object} Configuration object
|
||||
*/
|
||||
export function getConfig() {
|
||||
return {
|
||||
nodeEnv: process.env.NODE_ENV || 'development',
|
||||
port: parseInt(process.env.PORT || '3001'),
|
||||
isProduction: process.env.NODE_ENV === 'production',
|
||||
isDevelopment: process.env.NODE_ENV === 'development',
|
||||
|
||||
// Challonge OAuth
|
||||
challonge: {
|
||||
clientId: process.env.CHALLONGE_CLIENT_ID,
|
||||
clientSecret: process.env.CHALLONGE_CLIENT_SECRET,
|
||||
redirectUri: process.env.CHALLONGE_REDIRECT_URI,
|
||||
configured: !!(process.env.CHALLONGE_CLIENT_ID && process.env.CHALLONGE_CLIENT_SECRET)
|
||||
},
|
||||
|
||||
// CORS
|
||||
cors: {
|
||||
origin: process.env.NODE_ENV === 'production'
|
||||
? process.env.FRONTEND_URL
|
||||
: ['http://localhost:5173', 'http://localhost:5174', 'http://localhost:5175']
|
||||
},
|
||||
|
||||
// Security
|
||||
session: {
|
||||
secret: process.env.SESSION_SECRET || 'dev-secret-change-in-production'
|
||||
}
|
||||
};
|
||||
}
|
||||
Reference in New Issue
Block a user