""" Unified configuration that reads secrets from environment variables. Non-secret configuration (like CORS origins) can have defaults per environment. """ import os class ApplicationConfig: """ Application configuration loaded from environment variables. All secrets MUST be provided via environment variables. """ # Current environment mode CURRENT_SETTINGS = os.environ.get('MODE', 'DEVELOPMENT') # =========================================== # DATABASE CONFIGURATION (Required) # =========================================== POSTGRES_USERNAME = os.environ.get('POSTGRES_USERNAME') POSTGRES_PW = os.environ.get('POSTGRES_PASSWORD') POSTGRES_SERVER = os.environ.get('POSTGRES_SERVER') POSTGRES_PORT = os.environ.get('POSTGRES_PORT', '5432') POSTGRES_DBNAME00 = os.environ.get('POSTGRES_DBNAME') # =========================================== # JWT CONFIGURATION (Required) # =========================================== JWT_SECRET_KEY = os.environ.get('JWT_SECRET_KEY') JWT_ALGORITHM = os.environ.get('JWT_ALGORITHM', 'HS256') # Token expiry in minutes (default: 30 min for prod, can override for dev) JWT_ACCESS_TOKEN_EXPIRE_MINUTES = int(os.environ.get('JWT_ACCESS_TOKEN_EXPIRE_MINUTES', '30')) # =========================================== # AUTHORIZE.NET CONFIGURATION (Required for payments) # =========================================== AUTH_NET_API_LOGIN_ID = os.environ.get('AUTH_NET_API_LOGIN_ID') AUTH_NET_TRANSACTION_KEY = os.environ.get('AUTH_NET_TRANSACTION_KEY') # =========================================== # SMTP CONFIGURATION (Required for password reset) # =========================================== SMTP_SERVER = os.environ.get('SMTP_SERVER', 'smtp.gmail.com') SMTP_PORT = int(os.environ.get('SMTP_PORT', '587')) SMTP_USERNAME = os.environ.get('SMTP_USERNAME') SMTP_PASSWORD = os.environ.get('SMTP_PASSWORD') SMTP_FROM_EMAIL = os.environ.get('SMTP_FROM_EMAIL') # =========================================== # FRONTEND URL (Required for password reset links) # =========================================== FRONTEND_URL = os.environ.get('FRONTEND_URL', 'http://localhost:3000') # =========================================== # CORS ORIGINS - Environment specific defaults # =========================================== _origins_env = os.environ.get('CORS_ORIGINS', '') if _origins_env: # Use comma-separated list from environment origins = [origin.strip() for origin in _origins_env.split(',') if origin.strip()] else: # Default origins based on MODE _mode = os.environ.get('MODE', 'DEVELOPMENT') if _mode == 'PRODUCTION': origins = [ "https://oil.edwineames.com", "https://apiauto.edwineames.com", "https://portal.auburnoil.com", ] elif _mode == 'LOCAL': origins = [ "http://192.168.1.204:9000", "http://192.168.1.204:9613", "http://192.168.1.204:9614", "http://192.168.1.204:9612", "http://192.168.1.204:9611", ] else: # DEVELOPMENT origins = [ "http://localhost:9000", "https://localhost:9513", "http://localhost:9514", "http://localhost:9512", "http://localhost:9511", "http://localhost:5173", "http://localhost:8000", ] # Legacy compatibility - build SQLAlchemy URI if all parts provided if all([POSTGRES_USERNAME, POSTGRES_PW, POSTGRES_SERVER, POSTGRES_DBNAME00]): SQLALCHEMY_DATABASE_URI = "postgresql+psycopg2://{}:{}@{}:{}/{}".format( POSTGRES_USERNAME, POSTGRES_PW, POSTGRES_SERVER, POSTGRES_PORT, POSTGRES_DBNAME00 ) SQLALCHEMY_BINDS = {POSTGRES_DBNAME00: SQLALCHEMY_DATABASE_URI} else: SQLALCHEMY_DATABASE_URI = None SQLALCHEMY_BINDS = {} @classmethod def validate_required(cls): """Validate that all required environment variables are set.""" required = { 'POSTGRES_USERNAME': cls.POSTGRES_USERNAME, 'POSTGRES_PASSWORD': cls.POSTGRES_PW, 'POSTGRES_SERVER': cls.POSTGRES_SERVER, 'POSTGRES_DBNAME': cls.POSTGRES_DBNAME00, 'JWT_SECRET_KEY': cls.JWT_SECRET_KEY, } missing = [key for key, value in required.items() if not value] if missing: raise ValueError( f"Missing required environment variables: {', '.join(missing)}\n" "Please set these in your .env file or docker-compose environment." ) # Warn about weak JWT secret in production if cls.CURRENT_SETTINGS == 'PRODUCTION': if cls.JWT_SECRET_KEY and len(cls.JWT_SECRET_KEY) < 32: print("\033[93mWARNING: JWT_SECRET_KEY should be at least 32 characters for production\033[0m") return True @classmethod def validate_payment_config(cls): """Validate payment configuration (call before processing payments).""" if not cls.AUTH_NET_API_LOGIN_ID or not cls.AUTH_NET_TRANSACTION_KEY: raise ValueError( "Payment processing requires AUTH_NET_API_LOGIN_ID and AUTH_NET_TRANSACTION_KEY" ) return True @classmethod def validate_email_config(cls): """Validate email configuration (call before sending emails).""" required = { 'SMTP_USERNAME': cls.SMTP_USERNAME, 'SMTP_PASSWORD': cls.SMTP_PASSWORD, 'SMTP_FROM_EMAIL': cls.SMTP_FROM_EMAIL, } missing = [key for key, value in required.items() if not value] if missing: raise ValueError( f"Email sending requires: {', '.join(missing)}" ) return True