sca-neural-node / config.py
Pratham Amritkar
deploy: update from local backend
227930f
Raw
History Blame Contribute Delete
6.24 kB
"""
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')
@classmethod
def is_local(cls) -> bool:
"""Check if running locally (Relaxed Security)"""
return cls.DEPLOYMENT_ENV == 'local'
@classmethod
def is_production(cls) -> bool:
"""Check if running in production (Strict Security)"""
return cls.DEPLOYMENT_ENV == 'production'
@classmethod
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
@classmethod
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))