UNPKG

codecrucible-synth

Version:

Production-Ready AI Development Platform with Multi-Voice Synthesis, Smithery MCP Integration, Enterprise Security, and Zero-Timeout Reliability

503 lines 16.8 kB
/** * Encrypted Configuration Management * Provides secure configuration loading with environment-specific encryption */ import path from 'path'; import { SecretsManager } from './secrets-manager.js'; import { logger } from '../logger.js'; export class EncryptedConfig { secretsManager; config = {}; schema; environment; options; watchers = []; constructor(options) { this.options = options; this.environment = options.environment; this.schema = options.schema; this.secretsManager = new SecretsManager({ storePath: options.secretsPath, masterKeyPath: path.join(options.secretsPath, 'master.key'), }); } /** * Initialize encrypted configuration */ async initialize(masterPassword) { try { await this.secretsManager.initialize(masterPassword); await this.loadConfiguration(); if (this.options.validateOnLoad && this.schema) { this.validateConfiguration(); } logger.info('Encrypted configuration initialized', { environment: this.environment, configKeys: Object.keys(this.config).length, }); } catch (error) { logger.error('Failed to initialize encrypted configuration', error); throw error; } } /** * Get configuration value */ async get(key, defaultValue) { try { // Check if value exists in memory cache if (Object.prototype.hasOwnProperty.call(this.config, key)) { return this.config[key]; } // Check schema for default value if (this.schema && this.schema[key] && this.schema[key].default !== undefined) { return this.schema[key].default; } // Try to load from secrets (for sensitive values) if (this.schema && this.schema[key] && this.schema[key].sensitive) { const secretValue = await this.secretsManager.getSecret(this.getSecretKey(key)); if (secretValue !== null) { const parsedValue = this.parseValue(secretValue, this.schema[key].type); this.config[key] = parsedValue; return parsedValue; } } // Return default value if provided if (defaultValue !== undefined) { return defaultValue; } // Check if required in schema if (this.schema && this.schema[key] && this.schema[key].required) { throw new Error(`Required configuration key '${key}' not found`); } return undefined; } catch (error) { logger.error('Failed to get configuration value', error, { key }); throw error; } } /** * Set configuration value */ async set(key, value) { try { // Validate against schema if available if (this.schema && this.schema[key]) { this.validateValue(key, value, this.schema[key]); } const oldValue = this.config[key]; // Store sensitive values in secrets manager if (this.schema && this.schema[key] && this.schema[key].sensitive) { const secretKey = this.getSecretKey(key); const stringValue = this.stringifyValue(value); await this.secretsManager.storeSecret(secretKey, stringValue, { description: this.schema[key].description, tags: ['config', this.environment], }); } else { // Store non-sensitive values in memory this.config[key] = value; } // Notify watchers this.notifyWatchers(key, value, oldValue); logger.debug('Configuration value updated', { key, sensitive: this.schema?.[key]?.sensitive || false, }); } catch (error) { logger.error('Failed to set configuration value', error, { key }); throw error; } } /** * Get multiple configuration values */ async getAll(keys) { try { const result = {}; const keysToGet = keys || (this.schema ? Object.keys(this.schema) : Object.keys(this.config)); for (const key of keysToGet) { try { result[key] = await this.get(key); } catch (error) { // Continue with other keys if one fails logger.warn('Failed to get configuration key', { key, error: error.message }); } } return result; } catch (error) { logger.error('Failed to get all configuration values', error); throw error; } } /** * Update multiple configuration values */ async setMany(values) { try { const updates = Object.entries(values); for (const [key, value] of updates) { await this.set(key, value); } logger.info('Multiple configuration values updated', { keysUpdated: updates.length, }); } catch (error) { logger.error('Failed to set multiple configuration values', error); throw error; } } /** * Remove configuration value */ async remove(key) { try { let removed = false; // Remove from secrets if sensitive if (this.schema && this.schema[key] && this.schema[key].sensitive) { const secretKey = this.getSecretKey(key); removed = await this.secretsManager.deleteSecret(secretKey); } // Remove from memory if (Object.prototype.hasOwnProperty.call(this.config, key)) { delete this.config[key]; removed = true; } if (removed) { this.notifyWatchers(key, undefined, this.config[key]); logger.debug('Configuration value removed', { key }); } return removed; } catch (error) { logger.error('Failed to remove configuration value', error, { key }); throw error; } } /** * Watch for configuration changes */ watch(callback) { this.watchers.push(callback); } /** * Unwatch configuration changes */ unwatch(callback) { const index = this.watchers.indexOf(callback); if (index > -1) { this.watchers.splice(index, 1); } } /** * Validate entire configuration against schema */ validateConfiguration() { if (!this.schema) return; const errors = []; for (const [key, definition] of Object.entries(this.schema)) { try { if (definition.required && !Object.prototype.hasOwnProperty.call(this.config, key)) { // Check if it's available as a secret if (!definition.sensitive) { errors.push(`Required configuration key '${key}' is missing`); } } if (Object.prototype.hasOwnProperty.call(this.config, key)) { this.validateValue(key, this.config[key], definition); } } catch (error) { errors.push(`Validation failed for '${key}': ${error.message}`); } } if (errors.length > 0) { throw new Error(`Configuration validation failed: ${errors.join(', ')}`); } } /** * Export configuration (excluding sensitive values) */ async exportConfig(includeSensitive = false) { try { const exported = {}; if (this.schema) { for (const [key, definition] of Object.entries(this.schema)) { if (definition.sensitive && !includeSensitive) { exported[key] = '[REDACTED]'; } else { exported[key] = await this.get(key); } } } else { // Export all non-sensitive values from memory for (const [key, value] of Object.entries(this.config)) { exported[key] = value; } } return exported; } catch (error) { logger.error('Failed to export configuration', error); throw error; } } /** * Reload configuration from storage */ async reload() { try { this.config = {}; await this.loadConfiguration(); logger.info('Configuration reloaded', { environment: this.environment, }); } catch (error) { logger.error('Failed to reload configuration', error); throw error; } } /** * Get configuration schema */ getSchema() { return this.schema; } /** * Update configuration schema */ updateSchema(schema) { this.schema = schema; if (this.options.validateOnLoad) { this.validateConfiguration(); } logger.info('Configuration schema updated', { keys: Object.keys(schema).length, }); } /** * Check if configuration has a specific key */ async has(key) { try { const value = await this.get(key); return value !== undefined; } catch { return false; } } /** * Get configuration statistics */ async getStats() { const sensitiveKeys = this.schema ? Object.values(this.schema).filter(def => def.sensitive).length : 0; const secrets = await this.secretsManager.listSecrets(['config', this.environment]); const secretKeys = secrets.filter(secret => secret.name.startsWith(`config_${this.environment}_`)).length; return { totalKeys: this.schema ? Object.keys(this.schema).length : Object.keys(this.config).length, sensitiveKeys, memoryKeys: Object.keys(this.config).length, secretKeys, environment: this.environment, }; } /** * Load configuration from various sources */ async loadConfiguration() { // Load from environment variables await this.loadFromEnvironment(); // Load from file if specified if (this.options.configPath) { await this.loadFromFile(); } // Load sensitive values from secrets manager await this.loadSensitiveValues(); } /** * Load configuration from environment variables */ async loadFromEnvironment() { if (!this.schema) return; for (const [key, definition] of Object.entries(this.schema)) { const envKey = this.getEnvironmentKey(key); const envValue = process.env[envKey]; if (envValue !== undefined) { try { const parsedValue = this.parseValue(envValue, definition.type); if (definition.sensitive) { // Store sensitive env values in secrets await this.secretsManager.storeSecret(this.getSecretKey(key), envValue, { description: `Environment variable: ${envKey}`, tags: ['config', 'env', this.environment], }); } else { this.config[key] = parsedValue; } } catch (error) { logger.warn('Failed to parse environment variable', { key: envKey, error: error.message, }); } } } } /** * Load configuration from file */ async loadFromFile() { try { // Implementation would load from YAML/JSON file // For now, we'll skip file loading in this example logger.debug('File configuration loading not implemented'); } catch (error) { logger.error('Failed to load configuration from file', error); } } /** * Load sensitive values from secrets manager */ async loadSensitiveValues() { if (!this.schema) return; for (const [key, definition] of Object.entries(this.schema)) { if (definition.sensitive) { try { const secretValue = await this.secretsManager.getSecret(this.getSecretKey(key)); if (secretValue !== null) { const parsedValue = this.parseValue(secretValue, definition.type); this.config[key] = parsedValue; } } catch (error) { logger.debug('Failed to load sensitive configuration value', { key, error: error.message, }); } } } } /** * Generate secret key for configuration */ getSecretKey(configKey) { return `config_${this.environment}_${configKey}`; } /** * Generate environment variable key */ getEnvironmentKey(configKey) { return `CODECRUCIBLE_${configKey.toUpperCase()}`; } /** * Validate a configuration value against schema definition */ validateValue(key, value, definition) { // Type validation if (!this.isCorrectType(value, definition.type)) { throw new Error(`Expected ${definition.type}, got ${typeof value}`); } // Custom validation if (definition.validation && !definition.validation(value)) { throw new Error('Custom validation failed'); } } /** * Check if value matches expected type */ isCorrectType(value, expectedType) { switch (expectedType) { case 'string': return typeof value === 'string'; case 'number': return typeof value === 'number' && !isNaN(value); case 'boolean': return typeof value === 'boolean'; case 'object': return typeof value === 'object' && value !== null && !Array.isArray(value); case 'array': return Array.isArray(value); default: return true; } } /** * Parse string value to correct type */ parseValue(value, type) { switch (type) { case 'string': return value; case 'number': { const num = Number(value); if (isNaN(num)) throw new Error(`Cannot parse '${value}' as number`); return num; } case 'boolean': return ['true', '1', 'yes', 'on'].includes(value.toLowerCase()); case 'object': case 'array': try { return JSON.parse(value); } catch { throw new Error(`Cannot parse '${value}' as JSON`); } default: return value; } } /** * Convert value to string for storage */ stringifyValue(value) { if (typeof value === 'string') { return value; } return JSON.stringify(value); } /** * Notify watchers of configuration changes */ notifyWatchers(key, value, oldValue) { for (const watcher of this.watchers) { try { watcher(key, value, oldValue); } catch (error) { logger.error('Configuration watcher error', error, { key }); } } } /** * Clean up resources */ async stop() { await this.secretsManager.stop(); this.watchers = []; this.config = {}; logger.info('Encrypted configuration stopped'); } } //# sourceMappingURL=encrypted-config.js.map