# Configuration management with secrets integration import os import logging from datetime import timedelta from secrets_manager import get_secret, get_secrets_manager logger = logging.getLogger(__name__) class Config: """Base configuration with secrets management""" def __init__(self): self.secrets_manager = get_secrets_manager() self._load_config() def _load_config(self): """Load configuration from environment and secrets""" # Flask configuration self.SECRET_KEY = self._get_secret('FLASK_SECRET_KEY', os.environ.get('SECRET_KEY', 'dev-key-change-this')) # Security self.SESSION_COOKIE_SECURE = self._get_bool('SESSION_COOKIE_SECURE', True) self.SESSION_COOKIE_HTTPONLY = True self.SESSION_COOKIE_SAMESITE = 'Lax' self.PERMANENT_SESSION_LIFETIME = timedelta(hours=24) # TTS Configuration self.TTS_SERVER_URL = os.environ.get('TTS_SERVER_URL', 'http://localhost:5050/v1/audio/speech') self.TTS_API_KEY = self._get_secret('TTS_API_KEY', os.environ.get('TTS_API_KEY', '')) # Upload configuration self.UPLOAD_FOLDER = os.environ.get('UPLOAD_FOLDER', None) # Request size limits (in bytes) self.MAX_CONTENT_LENGTH = int(os.environ.get('MAX_CONTENT_LENGTH', 50 * 1024 * 1024)) # 50MB self.MAX_AUDIO_SIZE = int(os.environ.get('MAX_AUDIO_SIZE', 25 * 1024 * 1024)) # 25MB self.MAX_JSON_SIZE = int(os.environ.get('MAX_JSON_SIZE', 1 * 1024 * 1024)) # 1MB self.MAX_IMAGE_SIZE = int(os.environ.get('MAX_IMAGE_SIZE', 10 * 1024 * 1024)) # 10MB # CORS configuration self.CORS_ORIGINS = os.environ.get('CORS_ORIGINS', '*').split(',') self.ADMIN_CORS_ORIGINS = os.environ.get('ADMIN_CORS_ORIGINS', 'http://localhost:*').split(',') # Admin configuration self.ADMIN_TOKEN = self._get_secret('ADMIN_TOKEN', os.environ.get('ADMIN_TOKEN', 'default-admin-token')) # Database configuration (for future use) self.DATABASE_URL = self._get_secret('DATABASE_URL', os.environ.get('DATABASE_URL', 'sqlite:///talk2me.db')) # Redis configuration (for future use) self.REDIS_URL = self._get_secret('REDIS_URL', os.environ.get('REDIS_URL', 'redis://localhost:6379/0')) # Whisper configuration self.WHISPER_MODEL_SIZE = os.environ.get('WHISPER_MODEL_SIZE', 'base') self.WHISPER_DEVICE = os.environ.get('WHISPER_DEVICE', 'auto') # Ollama configuration self.OLLAMA_HOST = os.environ.get('OLLAMA_HOST', 'http://localhost:11434') self.OLLAMA_MODEL = os.environ.get('OLLAMA_MODEL', 'gemma3:27b') # Rate limiting configuration self.RATE_LIMIT_ENABLED = self._get_bool('RATE_LIMIT_ENABLED', True) self.RATE_LIMIT_STORAGE_URL = self._get_secret('RATE_LIMIT_STORAGE_URL', os.environ.get('RATE_LIMIT_STORAGE_URL', 'memory://')) # Logging configuration self.LOG_LEVEL = os.environ.get('LOG_LEVEL', 'INFO') self.LOG_FILE = os.environ.get('LOG_FILE', 'talk2me.log') # Feature flags self.ENABLE_PUSH_NOTIFICATIONS = self._get_bool('ENABLE_PUSH_NOTIFICATIONS', True) self.ENABLE_OFFLINE_MODE = self._get_bool('ENABLE_OFFLINE_MODE', True) self.ENABLE_STREAMING = self._get_bool('ENABLE_STREAMING', True) self.ENABLE_MULTI_SPEAKER = self._get_bool('ENABLE_MULTI_SPEAKER', True) # Performance tuning self.WORKER_CONNECTIONS = int(os.environ.get('WORKER_CONNECTIONS', '1000')) self.WORKER_TIMEOUT = int(os.environ.get('WORKER_TIMEOUT', '120')) # Validate configuration self._validate_config() def _get_secret(self, key: str, default: str = None) -> str: """Get secret from secrets manager or environment""" value = self.secrets_manager.get(key) if value is None: value = default if value is None: logger.warning(f"Configuration {key} not set") return value def _get_bool(self, key: str, default: bool = False) -> bool: """Get boolean configuration value""" value = os.environ.get(key, '').lower() if value in ('true', '1', 'yes', 'on'): return True elif value in ('false', '0', 'no', 'off'): return False return default def _validate_config(self): """Validate configuration values""" # Check required secrets if not self.SECRET_KEY or self.SECRET_KEY == 'dev-key-change-this': logger.warning("Using default SECRET_KEY - this is insecure for production!") if not self.TTS_API_KEY: logger.warning("TTS_API_KEY not configured - TTS functionality may not work") if self.ADMIN_TOKEN == 'default-admin-token': logger.warning("Using default ADMIN_TOKEN - this is insecure for production!") # Validate URLs if not self._is_valid_url(self.TTS_SERVER_URL): logger.error(f"Invalid TTS_SERVER_URL: {self.TTS_SERVER_URL}") # Check file permissions if self.UPLOAD_FOLDER and not os.access(self.UPLOAD_FOLDER, os.W_OK): logger.warning(f"Upload folder {self.UPLOAD_FOLDER} is not writable") def _is_valid_url(self, url: str) -> bool: """Check if URL is valid""" return url.startswith(('http://', 'https://')) def to_dict(self) -> dict: """Export configuration as dictionary (excluding secrets)""" config = {} for key in dir(self): if key.isupper() and not key.startswith('_'): value = getattr(self, key) # Mask sensitive values if any(sensitive in key for sensitive in ['KEY', 'TOKEN', 'PASSWORD', 'SECRET']): config[key] = '***MASKED***' else: config[key] = value return config class DevelopmentConfig(Config): """Development configuration""" def _load_config(self): super()._load_config() self.DEBUG = True self.TESTING = False self.SESSION_COOKIE_SECURE = False # Allow HTTP in development class ProductionConfig(Config): """Production configuration""" def _load_config(self): super()._load_config() self.DEBUG = False self.TESTING = False # Enforce security in production if not self.SECRET_KEY or self.SECRET_KEY == 'dev-key-change-this': raise ValueError("SECRET_KEY must be set in production") if self.ADMIN_TOKEN == 'default-admin-token': raise ValueError("ADMIN_TOKEN must be changed in production") class TestingConfig(Config): """Testing configuration""" def _load_config(self): super()._load_config() self.DEBUG = True self.TESTING = True self.WTF_CSRF_ENABLED = False self.RATE_LIMIT_ENABLED = False # Configuration factory def get_config(env: str = None) -> Config: """Get configuration based on environment""" if env is None: env = os.environ.get('FLASK_ENV', 'development') configs = { 'development': DevelopmentConfig, 'production': ProductionConfig, 'testing': TestingConfig } config_class = configs.get(env, DevelopmentConfig) return config_class() # Convenience function for Flask app def init_app(app): """Initialize Flask app with configuration""" config = get_config() # Apply configuration to app for key in dir(config): if key.isupper(): app.config[key] = getattr(config, key) # Store config object app.app_config = config logger.info(f"Configuration loaded for environment: {os.environ.get('FLASK_ENV', 'development')}")