"""Flask application configuration.""" import os from datetime import timedelta from sqlalchemy.pool import StaticPool class ConfigError(Exception): """Raised when required configuration is missing or unsafe.""" def _required_env(varname): """Read an env var; raise ConfigError if missing or empty.""" value = os.environ.get(varname) if not value: raise ConfigError( f'{varname} is required in production. Set it in the environment ' f'before starting the app. Insecure defaults are not permitted in ' f'ProductionConfig.' ) return value class Config: """Base configuration.""" SECRET_KEY = os.environ.get('SECRET_KEY', 'dev-secret-key-change-in-production') SQLALCHEMY_DATABASE_URI = os.environ.get( 'DATABASE_URL', 'mysql+pymysql://root:password@localhost:3306/shopdb_flask', ) SQLALCHEMY_TRACK_MODIFICATIONS = False SQLALCHEMY_ENGINE_OPTIONS = { 'pool_pre_ping': True, 'pool_recycle': 300, } JWT_SECRET_KEY = os.environ.get('JWT_SECRET_KEY', 'jwt-secret-key-change-in-production') JWT_ACCESS_TOKEN_EXPIRES = timedelta( seconds=int(os.environ.get('JWT_ACCESS_TOKEN_EXPIRES', 3600)) ) JWT_REFRESH_TOKEN_EXPIRES = timedelta( seconds=int(os.environ.get('JWT_REFRESH_TOKEN_EXPIRES', 2592000)) ) CORS_ORIGINS = [ origin.strip() for origin in os.environ.get('CORS_ORIGINS', 'http://localhost:5173').split(',') if origin.strip() ] LOG_LEVEL = os.environ.get('LOG_LEVEL', 'INFO') ZABBIX_URL = os.environ.get('ZABBIX_URL', '') ZABBIX_TOKEN = os.environ.get('ZABBIX_TOKEN', '') CACHE_TYPE = 'SimpleCache' CACHE_DEFAULT_TIMEOUT = 600 DEFAULT_PAGE_SIZE = 20 MAX_PAGE_SIZE = 100 class DevelopmentConfig(Config): """Development configuration.""" DEBUG = True SQLALCHEMY_ECHO = True SQLALCHEMY_DATABASE_URI = os.environ.get( 'DATABASE_URL', 'mysql+pymysql://root:rootpassword@127.0.0.1:3306/shopdb_flask', ) class TestingConfig(Config): """Testing configuration.""" TESTING = True SQLALCHEMY_DATABASE_URI = 'sqlite:///:memory:' SQLALCHEMY_ENGINE_OPTIONS = { 'connect_args': {'check_same_thread': False}, 'poolclass': StaticPool, } JWT_ACCESS_TOKEN_EXPIRES = timedelta(seconds=5) class ProductionConfig(Config): """Production configuration. Validation is deferred to validate() so that importing this class in a non-production environment (tests, dev, tooling) does not raise. create_app() invokes validate() when config_name == 'production' so a misconfigured production deploy still fails loud at boot. """ DEBUG = False SQLALCHEMY_ECHO = False JWT_COOKIE_SECURE = True JWT_COOKIE_CSRF_PROTECT = True @classmethod def validate(cls): """Verify production config is safe. Called from create_app.""" secret_key = os.environ.get('SECRET_KEY', '') jwt_secret = os.environ.get('JWT_SECRET_KEY', '') database_url = os.environ.get('DATABASE_URL', '') cors_raw = os.environ.get('CORS_ORIGINS', '').strip() insecure_defaults = { 'dev-secret-key-change-in-production', 'jwt-secret-key-change-in-production', } if not secret_key or secret_key in insecure_defaults: raise ConfigError( 'SECRET_KEY is required in production and must not be the ' 'development default. Set a strong random value in the ' 'environment before starting the app.' ) if not jwt_secret or jwt_secret in insecure_defaults: raise ConfigError( 'JWT_SECRET_KEY is required in production and must not be ' 'the development default. Set a strong random value in the ' 'environment before starting the app.' ) if not database_url: raise ConfigError( 'DATABASE_URL is required in production. No fallback to a ' 'development localhost URL is permitted.' ) if not cors_raw or cors_raw == '*': raise ConfigError( 'CORS_ORIGINS must be a comma-separated allowlist of ' 'explicit origins in production. Wildcard "*" is not ' 'permitted. Example: ' 'CORS_ORIGINS=https://shopdb.example.com,https://shopdb-mirror.example.com' ) config = { 'development': DevelopmentConfig, 'testing': TestingConfig, 'production': ProductionConfig, 'default': DevelopmentConfig, }