getcert-backend / src /utils /secureEnvironment.ts
ggop213's picture
fix(cert): auto-issue certificate on payment confirmation & sync course to 100% certified
46ab86e
Raw History Blame Contribute Delete
4.17 kB
/**
* 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<string, any> {
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<string, any> {
return secureEnv.getEnvironmentInfo();
}
export default secureEnv;