Auto-derive COOKIE_SECURE from ENVIRONMENT setting
COOKIE_SECURE now defaults to None and auto-derives from ENVIRONMENT (production → true, else false) via a Pydantic model_validator. Explicit env var values are still respected as an override escape hatch. Adds a startup log line showing the resolved value. Restructures .env.example with clear sections and inline docs, removes redundant production checklist block. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
21aa670a39
commit
0c7d057654
34
.env.example
34
.env.example
@ -1,32 +1,34 @@
|
||||
# ──────────────────────────────────────
|
||||
# Database
|
||||
# ──────────────────────────────────────
|
||||
POSTGRES_USER=umbra
|
||||
POSTGRES_PASSWORD=changeme_in_production
|
||||
POSTGRES_DB=umbra
|
||||
|
||||
# Backend
|
||||
DATABASE_URL=postgresql+asyncpg://umbra:changeme_in_production@db:5432/umbra
|
||||
|
||||
# ──────────────────────────────────────
|
||||
# Application
|
||||
# ──────────────────────────────────────
|
||||
# Generate with: python3 -c "import secrets; print(secrets.token_hex(32))"
|
||||
SECRET_KEY=change-this-to-a-random-secret-key-in-production
|
||||
|
||||
# Environment (development|production — controls Swagger/ReDoc visibility)
|
||||
# ENVIRONMENT=development
|
||||
# development | production — controls Swagger/ReDoc visibility and cookie defaults
|
||||
ENVIRONMENT=development
|
||||
|
||||
# CORS allowed origins (comma-separated, default: http://localhost:5173)
|
||||
# CORS_ORIGINS=http://localhost:5173
|
||||
# CORS_ORIGINS=https://umbra.example.com
|
||||
|
||||
# Timezone (applied to backend + db containers via env_file)
|
||||
TZ=Australia/Perth
|
||||
|
||||
# Session cookie security
|
||||
# Set to true when serving over HTTPS. Required before any TLS deployment.
|
||||
# COOKIE_SECURE=true
|
||||
|
||||
# ──────────────────────────────────────
|
||||
# Integrations
|
||||
# ──────────────────────────────────────
|
||||
OPENWEATHERMAP_API_KEY=your-openweathermap-api-key
|
||||
|
||||
# Production security checklist (enable all before any non-internal deployment):
|
||||
# 1. Set SECRET_KEY to output of: openssl rand -hex 32
|
||||
# 2. Set POSTGRES_PASSWORD to a strong unique value
|
||||
# 3. Set ENVIRONMENT=production (disables Swagger/ReDoc on backend:8000)
|
||||
# 4. Set COOKIE_SECURE=true (requires TLS termination at nginx or upstream)
|
||||
# 5. Add HSTS to nginx.conf: add_header Strict-Transport-Security "max-age=63072000; includeSubDomains" always;
|
||||
# 6. Complete user_id migration (migration 026) before enabling multi-user accounts
|
||||
# ──────────────────────────────────────
|
||||
# Overrides (rarely needed)
|
||||
# ──────────────────────────────────────
|
||||
# COOKIE_SECURE auto-derives from ENVIRONMENT (production → true).
|
||||
# Only set explicitly to override, e.g. false for a non-TLS prod behind a proxy.
|
||||
# COOKIE_SECURE=false
|
||||
|
||||
@ -143,17 +143,13 @@ python3 -c "import secrets; print(secrets.token_hex(32))"
|
||||
python3 -c "import secrets; print(secrets.token_urlsafe(24))"
|
||||
# or: openssl rand -base64 24
|
||||
|
||||
# Set ENVIRONMENT to disable Swagger/ReDoc
|
||||
# Set ENVIRONMENT to disable Swagger/ReDoc and auto-enable secure cookies
|
||||
ENVIRONMENT=production
|
||||
|
||||
# Enable secure cookies (requires HTTPS)
|
||||
COOKIE_SECURE=true
|
||||
```
|
||||
|
||||
Additionally for production:
|
||||
- Place behind a reverse proxy with TLS termination (e.g., Caddy, Traefik, or nginx with Let's Encrypt)
|
||||
- Set `COOKIE_SECURE=true` to enforce HTTPS-only session cookies
|
||||
- Set `ENVIRONMENT=production` to disable API documentation endpoints
|
||||
- Set `ENVIRONMENT=production` — this disables API docs and auto-enables HTTPS-only session cookies (`COOKIE_SECURE` derives from `ENVIRONMENT`; override with `COOKIE_SECURE=false` if running non-TLS prod behind a proxy)
|
||||
- Set `CORS_ORIGINS` to your actual domain (e.g., `https://umbra.example.com`)
|
||||
- Consider adding HSTS headers at the TLS-terminating proxy layer
|
||||
|
||||
|
||||
@ -1,4 +1,5 @@
|
||||
import sys
|
||||
from pydantic import model_validator
|
||||
from pydantic_settings import BaseSettings, SettingsConfigDict
|
||||
|
||||
|
||||
@ -6,7 +7,7 @@ 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 = False
|
||||
COOKIE_SECURE: bool | None = None # Auto-derives from ENVIRONMENT if not explicitly set
|
||||
OPENWEATHERMAP_API_KEY: str = ""
|
||||
|
||||
# Session config — sliding window
|
||||
@ -28,8 +29,20 @@ class Settings(BaseSettings):
|
||||
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":
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user