In production, CORS_ORIGINS now defaults to UMBRA_URL so deployers only need to set the external URL once. In development it defaults to http://localhost:5173 (Vite dev server). Explicit CORS_ORIGINS env var is still respected as an override for multi-origin or custom setups. This means a production .env only needs: ENVIRONMENT, SECRET_KEY, UMBRA_URL, and DB credentials. COOKIE_SECURE and CORS_ORIGINS both auto-derive. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
73 lines
2.6 KiB
Python
73 lines
2.6 KiB
Python
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). Auto-derives from UMBRA_URL in
|
|
# production or http://localhost:5173 in development. Set explicitly to override.
|
|
CORS_ORIGINS: str | None = None
|
|
|
|
# Public-facing URL used in ntfy notification click links and CORS derivation
|
|
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_defaults(self) -> "Settings":
|
|
if self.COOKIE_SECURE is None:
|
|
self.COOKIE_SECURE = self.ENVIRONMENT == "production"
|
|
if self.CORS_ORIGINS is None:
|
|
if self.ENVIRONMENT == "production":
|
|
self.CORS_ORIGINS = self.UMBRA_URL
|
|
else:
|
|
self.CORS_ORIGINS = "http://localhost:5173"
|
|
assert self.COOKIE_SECURE is not None # type narrowing
|
|
assert self.CORS_ORIGINS is not None
|
|
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,
|
|
)
|