Files
memory-infrastructure-palace/code/websites/pokedex.online/server/utils/env-validator.js

235 lines
6.4 KiB
JavaScript
Raw Blame History

This file contains invisible Unicode characters
This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/**
* 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 = {
// Deployment Configuration
DEPLOYMENT_TARGET: {
required: true,
description: 'Deployment environment (dev, docker-local, production)',
validate: val => ['dev', 'docker-local', 'production'].includes(val)
},
// 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
},
// Frontend URL for CORS
FRONTEND_URL: {
required: true,
description: 'Frontend URL for CORS',
validate: (val, env) => {
if (!val) return false;
// Validate that FRONTEND_URL matches DEPLOYMENT_TARGET (if set)
const target = env?.DEPLOYMENT_TARGET;
if (!target) return true; // Skip validation if target not set yet
if (target === 'dev' && !val.includes('localhost:5173')) {
console.error(
'⚠️ FRONTEND_URL should be http://localhost:5173 for dev target'
);
return false;
}
if (target === 'docker-local' && !val.includes('localhost:8099')) {
console.error(
'⚠️ FRONTEND_URL should be http://localhost:8099 for docker-local target'
);
return false;
}
if (target === 'production' && !val.includes('app.pokedex.online')) {
console.error(
'⚠️ FRONTEND_URL should be https://app.pokedex.online for production target'
);
return false;
}
return true;
}
},
// 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
},
// 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(
` DEPLOYMENT_TARGET: ${process.env.DEPLOYMENT_TARGET || 'not set'}`
);
console.log(` NODE_ENV: ${process.env.NODE_ENV || 'not set'}`);
console.log(` PORT: ${process.env.PORT || 'not set'}`);
console.log(` FRONTEND_URL: ${process.env.FRONTEND_URL || '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() {
const deploymentTarget = process.env.DEPLOYMENT_TARGET || 'dev';
const frontendUrl = process.env.FRONTEND_URL;
return {
deploymentTarget,
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 - Single origin based on deployment target
cors: {
origin: frontendUrl,
credentials: true
},
// Security
session: {
secret: process.env.SESSION_SECRET || 'dev-secret-change-in-production'
},
// Discord User Permissions
discord: {
adminUsers: process.env.DISCORD_ADMIN_USERS
? process.env.DISCORD_ADMIN_USERS.split(',').map(u =>
u.trim().toLowerCase()
)
: []
}
};
}
// Run validation when executed directly
if (import.meta.url === `file://${process.argv[1]}`) {
validateOrExit();
}