From 4d3818f961ab2d4f813b2c045aab4762c4bdc3b0 Mon Sep 17 00:00:00 2001 From: FragginWagon Date: Thu, 29 Jan 2026 13:18:41 +0000 Subject: [PATCH] =?UTF-8?q?=F0=9F=94=A7=20Add=20environment=20variable=20v?= =?UTF-8?q?alidation=20utility?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../server/utils/env-validator.js | 167 ++++++++++++++++++ 1 file changed, 167 insertions(+) create mode 100644 code/websites/pokedex.online/server/utils/env-validator.js diff --git a/code/websites/pokedex.online/server/utils/env-validator.js b/code/websites/pokedex.online/server/utils/env-validator.js new file mode 100644 index 0000000..93862f7 --- /dev/null +++ b/code/websites/pokedex.online/server/utils/env-validator.js @@ -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' + } + }; +}