Spaces:
Sleeping
Sleeping
| """ | |
| Environment Configuration Manager for SCA Backend | |
| Handles environment-specific settings and validates configuration | |
| """ | |
| import os | |
| import secrets | |
| import warnings | |
| from pathlib import Path | |
| from dotenv import load_dotenv | |
| from typing import Literal | |
| # Load environment variables from root .env file | |
| try: | |
| env_path = Path(__file__).resolve().parent.parent / '.env' | |
| load_dotenv(env_path) | |
| except Exception: | |
| load_dotenv() # Fallback to default behavior | |
| class Config: | |
| """Base configuration class""" | |
| # Project Root | |
| # Handle Docker flattened structure vs Local nested structure | |
| if (Path(__file__).parent / 'app.py').exists(): | |
| BASE_DIR = Path(__file__).parent | |
| else: | |
| BASE_DIR = Path(__file__).resolve().parent.parent | |
| # Deployment Environment: 'local' (Development) or 'production' (Cloud/Web) | |
| # This dictates SECURITY strictness, not functionality. | |
| DEPLOYMENT_ENV: Literal['local', 'production'] = os.environ.get('DEPLOYMENT_ENV', 'local').lower() | |
| # Flask Settings | |
| # Debug enabled in Local, disabled in Production | |
| FLASK_DEBUG = os.environ.get('FLASK_DEBUG', '1' if DEPLOYMENT_ENV == 'local' else '0') == '1' | |
| # JWT Configuration | |
| # Key MUST be at least 32 bytes (256 bits) for HS256 to avoid security warnings. | |
| # In production, if missing, we generate a random 32-byte key to stay running safely. | |
| _default_key = 'sca-local-secure-ultra-long-secret-key-32-char' | |
| JWT_SECRET_KEY = os.environ.get('JWT_SECRET_KEY') | |
| if not JWT_SECRET_KEY: | |
| if DEPLOYMENT_ENV == 'local': | |
| JWT_SECRET_KEY = _default_key | |
| else: | |
| # Generate random 32-byte hex for production if not provided | |
| JWT_SECRET_KEY = secrets.token_hex(32) | |
| print(f"⚠️ WARNING: JWT_SECRET_KEY not set. Using ephemeral key: {JWT_SECRET_KEY[:8]}...") | |
| # Suppress the warning if the key is still somehow short (e.g. user-set 27 chars) | |
| # but we've already done our best by defaulting to 32+ characters. | |
| warnings.filterwarnings("ignore", category=UserWarning, module="jwt") | |
| JWT_ALGORITHM = 'HS256' | |
| JWT_ACCESS_TOKEN_EXPIRES_HOURS = int(os.environ.get('JWT_ACCESS_TOKEN_EXPIRES_HOURS', '24')) | |
| JWT_REFRESH_TOKEN_EXPIRES_DAYS = int(os.environ.get('JWT_REFRESH_TOKEN_EXPIRES_DAYS', '7')) | |
| # CORS | |
| # In Local, allow all. In Production, strict. | |
| CORS_ORIGINS = os.environ.get('CORS_ORIGINS', '*' if DEPLOYMENT_ENV == 'local' else '').split(',') | |
| # Database | |
| DATABASE_URL = os.environ.get('DATABASE_URL', f"sqlite:///{BASE_DIR / 'outputs' / 'sca_events.db'}") | |
| # Blockchain | |
| BLOCKCHAIN_RPC_URL = os.environ.get('BLOCKCHAIN_RPC_URL') | |
| BLOCKCHAIN_PRIVATE_KEY = os.environ.get('BLOCKCHAIN_PRIVATE_KEY') | |
| CONTRACT_ADDRESS = os.environ.get('CONTRACT_ADDRESS') or os.environ.get('VITE_CONTRACT_ADDRESS') | |
| # Department | |
| TARGET_DEPT = os.environ.get('TARGET_DEPT', 'CS_DEPARTMENT') | |
| # Security | |
| FORCE_HTTPS = os.environ.get('FORCE_HTTPS', 'true' if DEPLOYMENT_ENV == 'production' else 'false').lower() == 'true' | |
| # Rate Limiting | |
| RATE_LIMIT_ENABLED = os.environ.get('RATE_LIMIT_ENABLED', 'true' if DEPLOYMENT_ENV == 'production' else 'false').lower() == 'true' | |
| RATE_LIMIT_PER_MINUTE = int(os.environ.get('RATE_LIMIT_PER_MINUTE', '60')) | |
| # Logging | |
| LOG_LEVEL = os.environ.get('LOG_LEVEL', 'INFO') | |
| def is_local(cls) -> bool: | |
| """Check if running locally (Relaxed Security)""" | |
| return cls.DEPLOYMENT_ENV == 'local' | |
| def is_production(cls) -> bool: | |
| """Check if running in production (Strict Security)""" | |
| return cls.DEPLOYMENT_ENV == 'production' | |
| def validate(cls): | |
| """Validate configuration and warn about insecure settings""" | |
| warnings = [] | |
| errors = [] | |
| # Check Production Security | |
| if cls.is_production(): | |
| if not cls.JWT_SECRET_KEY or cls.JWT_SECRET_KEY == 'sca-local-secret-key': | |
| errors.append("CRITICAL: JWT_SECRET_KEY must be set to a secure value in Production!") | |
| if cls.FLASK_DEBUG: | |
| errors.append("CRITICAL: FLASK_DEBUG must be disabled in Production!") | |
| if not cls.FORCE_HTTPS: | |
| warnings.append("WARNING: FORCE_HTTPS should be enabled in Production") | |
| if not cls.DATABASE_URL or 'sqlite' in cls.DATABASE_URL: | |
| warnings.append("WARNING: Using SQLite in Production. Recommended: PostgreSQL.") | |
| # Print warnings and errors | |
| if warnings: | |
| print("\n⚠️ CONFIGURATION WARNINGS:") | |
| for warning in warnings: | |
| print(f" {warning}") | |
| if errors: | |
| print("\n❌ CONFIGURATION ERRORS:") | |
| for error in errors: | |
| print(f" {error}") | |
| print("\n Please fix these issues before deploying to Production!\n") | |
| if not warnings and not errors: | |
| print(f"\n✓ Configuration validated for {cls.DEPLOYMENT_ENV.upper()} environment") | |
| return len(errors) == 0 | |
| def get_info(cls) -> dict: | |
| """Get configuration info (safe for logging)""" | |
| return { | |
| 'deployment': 'Production (Cloud)' if cls.is_production() else 'Local (Dev)', | |
| 'capability': 'Mainnet (Full Node)', # ALWAYS Mainnet capability | |
| 'debug': cls.FLASK_DEBUG, | |
| 'database': cls.DATABASE_URL.split('/')[-1] if '/' in cls.DATABASE_URL else cls.DATABASE_URL, | |
| 'cors_origins': 'Allow All (*)' if cls.is_local() else len(cls.CORS_ORIGINS), | |
| 'blockchain_configured': bool(cls.BLOCKCHAIN_PRIVATE_KEY and cls.CONTRACT_ADDRESS), | |
| 'https_enforced': cls.FORCE_HTTPS | |
| } | |
| # Singleton config instance | |
| config = Config() | |
| if __name__ == '__main__': | |
| """Test configuration""" | |
| print("="*60) | |
| print("SCA ENVIRONMENT CONFIGURATION") | |
| print("="*60) | |
| config.validate() | |
| print("\nConfiguration Info:") | |
| import json | |
| print(json.dumps(config.get_info(), indent=2)) | |