import sys from pydantic import model_validator from pydantic_settings import BaseSettings, SettingsConfigDict class Settings(BaseSettings): DATABASE_URL: str = "postgresql+asyncpg://postgres:postgres@localhost:5432/umbra" SECRET_KEY: str = "your-secret-key-change-in-production" ENVIRONMENT: str = "development" COOKIE_SECURE: bool | None = None # Auto-derives from ENVIRONMENT if not explicitly set OPENWEATHERMAP_API_KEY: str = "" # Session config — sliding window SESSION_MAX_AGE_DAYS: int = 7 # Sliding window: inactive sessions expire after 7 days SESSION_TOKEN_HARD_CEILING_DAYS: int = 30 # Absolute token lifetime for itsdangerous max_age # MFA token config (short-lived token bridging password OK → TOTP verification) MFA_TOKEN_MAX_AGE_SECONDS: int = 300 # 5 minutes # TOTP issuer name shown in authenticator apps TOTP_ISSUER: str = "UMBRA" # CORS allowed origins (comma-separated) CORS_ORIGINS: str = "http://localhost:5173" # Public-facing URL used in ntfy notification click links UMBRA_URL: str = "http://localhost" # Concurrent session limit per user (oldest evicted when exceeded) MAX_SESSIONS_PER_USER: int = 10 model_config = SettingsConfigDict( env_file=".env", env_file_encoding="utf-8", case_sensitive=True ) @model_validator(mode="after") def derive_cookie_secure(self) -> "Settings": if self.COOKIE_SECURE is None: self.COOKIE_SECURE = self.ENVIRONMENT == "production" assert self.COOKIE_SECURE is not None # type narrowing return self settings = Settings() print( f"INFO: COOKIE_SECURE={settings.COOKIE_SECURE} " f"(ENVIRONMENT={settings.ENVIRONMENT})", file=sys.stderr, ) if settings.SECRET_KEY == "your-secret-key-change-in-production": if settings.ENVIRONMENT != "development": print( "FATAL: Default SECRET_KEY detected in non-development environment. " "Set a unique SECRET_KEY in .env immediately.", file=sys.stderr, ) sys.exit(1) else: print( "WARNING: Using default SECRET_KEY. Set SECRET_KEY in .env for production.", file=sys.stderr, )