/** * SECURE ENVIRONMENT VALIDATOR * * This validates environment variables without exposing their values * and provides helpful error messages for missing configuration. */ import { logger } from './secureLogger'; interface EnvironmentConfig { supabaseUrl: string; supabaseAnonKey: string; apiBaseUrl: string; isDevelopment: boolean; isProduction: boolean; } class SecureEnvironment { private config: EnvironmentConfig | null = null; private validationError: string | null = null; /** * Validate and load environment configuration * Returns sanitized config without exposing actual values */ public validateAndLoad(): EnvironmentConfig { if (this.config && !this.validationError) { return this.config; } try { const env = (typeof import.meta !== 'undefined' && import.meta.env) ? import.meta.env : (process.env as any); // Check for required environment variables const supabaseUrl = env.VITE_SUPABASE_URL || env.SUPABASE_URL; const supabaseAnonKey = env.VITE_SUPABASE_ANON_KEY || env.SUPABASE_ANON_KEY; if (!supabaseUrl) { throw new Error('VITE_SUPABASE_URL is required'); } if (!supabaseAnonKey) { throw new Error('VITE_SUPABASE_ANON_KEY is required'); } // Validate URL format if (!supabaseUrl.startsWith('https://') || !supabaseUrl.includes('.supabase.co')) { throw new Error('VITE_SUPABASE_URL must be a valid Supabase URL'); } // Check for dangerous environment variables that shouldn't be in client const dangerousVars = [ 'VITE_GEMMA_API_KEY', 'VITE_OPENROUTER_API_KEY', // Legacy names kept for leak detection during migration cleanups. 'VITE_GEMINI_API_KEY_1', 'VITE_GEMINI_API_KEY_2', 'VITE_GEMINI_API_KEY_3', 'VITE_GEMINI_API_KEY_4', 'VITE_GEMINI_API_KEY_5' ]; const foundDangerousVars = dangerousVars.filter(varName => env[varName]); if (foundDangerousVars.length > 0 && env.NODE_ENV === 'production') { logger.error('[Security] WARNING: Sensitive API keys found in production environment'); logger.error('These variables should be removed from client-side code:', foundDangerousVars); } this.config = { supabaseUrl, supabaseAnonKey, apiBaseUrl: env.VITE_API_BASE_URL || 'http://localhost:3001', isDevelopment: env.NODE_ENV === 'development', isProduction: env.NODE_ENV === 'production' }; logger.log('[Security] Environment configuration validated successfully'); return this.config; } catch (error) { this.validationError = error instanceof Error ? error.message : 'Unknown validation error'; logger.error('[Security] Environment validation failed:', this.validationError); throw error; } } /** * Get sanitized environment info for debugging (no sensitive values) */ public getEnvironmentInfo(): Record { const config = this.validateAndLoad(); return { supabaseConfigured: !!config.supabaseUrl, supabaseUrl: config.supabaseUrl ? config.supabaseUrl.substring(0, 20) + '...' : 'Not configured', apiBaseUrl: config.apiBaseUrl, environment: config.isProduction ? 'production' : 'development', securityLevel: config.isProduction ? 'high' : 'development' }; } /** * Check if all required configuration is present */ public isConfigurationValid(): boolean { try { this.validateAndLoad(); return true; } catch { return false; } } /** * Get configuration error message if validation failed */ public getConfigurationError(): string | null { return this.validationError; } } // Export singleton instance export const secureEnv = new SecureEnvironment(); // Export configuration getter export function getSecureConfig(): EnvironmentConfig { return secureEnv.validateAndLoad(); } // Export environment info for debugging (safe to display) export function getEnvironmentInfo(): Record { return secureEnv.getEnvironmentInfo(); } export default secureEnv;