0fc29a34dd
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
99 lines
3.6 KiB
Python
99 lines
3.6 KiB
Python
"""Configuration surface — the single place deployment/environment config is read.
|
|
|
|
INV-8: no deployment shape is baked into framework code. Every URL, credential, or
|
|
relay coordinate arrives through here from the environment (resolved from Secret
|
|
Manager in deployed environments). The localhost defaults match compose.yaml so a
|
|
clean `scripts/dev.sh` checkout needs no env setup.
|
|
"""
|
|
from __future__ import annotations
|
|
|
|
import os
|
|
|
|
# Dev default points at the single Postgres container compose.yaml brings up. In
|
|
# PPE/Prod the deployment supplies ECOMM_DATABASE_URL from Secret Manager (INV-8).
|
|
_DEFAULT_DATABASE_URL = "postgresql://ecomm:ecomm@localhost:5432/ecomm"
|
|
|
|
|
|
def database_url() -> str:
|
|
"""The psycopg DSN for the application database."""
|
|
return os.environ.get("ECOMM_DATABASE_URL") or _DEFAULT_DATABASE_URL
|
|
|
|
|
|
# Session signing secret. Dev default is intentionally well-known and insecure — a clean
|
|
# checkout works with no setup; deployed environments MUST supply ECOMM_SESSION_SECRET from
|
|
# Secret Manager (INV-8, §6.6). Rotating it invalidates all sessions at once (§6.2).
|
|
_DEFAULT_SESSION_SECRET = "dev-insecure-session-secret-change-me"
|
|
|
|
|
|
def session_secret() -> str:
|
|
"""The HMAC key for signed session cookies (and the one-time-code pepper)."""
|
|
return os.environ.get("ECOMM_SESSION_SECRET") or _DEFAULT_SESSION_SECRET
|
|
|
|
|
|
def cookie_secure() -> bool:
|
|
"""Whether to mark the session cookie Secure (HTTPS-only). True in deployed envs."""
|
|
return os.environ.get("ECOMM_COOKIE_SECURE", "").strip().lower() in {"1", "true", "yes", "on"}
|
|
|
|
|
|
def mailer_kind() -> str:
|
|
"""Which mailer adapter to build: 'log' (dev/tests) or 'smtp' (deployed)."""
|
|
return os.environ.get("ECOMM_MAILER") or "log"
|
|
|
|
|
|
# SMTP relay coordinates (deployed envs only; INV-8 — host/port/user/from are non-secret
|
|
# overlay values, the password is a Secret Manager reference resolved by the deploy).
|
|
|
|
|
|
def smtp_host() -> str:
|
|
return os.environ.get("ECOMM_SMTP_HOST", "")
|
|
|
|
|
|
def smtp_port() -> int:
|
|
return int(os.environ.get("ECOMM_SMTP_PORT") or "587")
|
|
|
|
|
|
def smtp_user() -> str:
|
|
return os.environ.get("ECOMM_SMTP_USER", "")
|
|
|
|
|
|
def smtp_password() -> str:
|
|
return os.environ.get("ECOMM_SMTP_PASSWORD", "")
|
|
|
|
|
|
def smtp_from() -> str:
|
|
"""The From header; defaults to the relay user."""
|
|
return os.environ.get("ECOMM_SMTP_FROM") or smtp_user()
|
|
|
|
|
|
def smtp_starttls() -> bool:
|
|
"""STARTTLS on the relay connection (default on; disable only for odd relays)."""
|
|
return os.environ.get("ECOMM_SMTP_STARTTLS", "1").strip().lower() not in {"0", "false", "no", "off"}
|
|
|
|
|
|
def objectstore_kind() -> str:
|
|
"""Which objectstore adapter to build: 'local' (dev/tests) or 'gcs' (deployed)."""
|
|
return os.environ.get("ECOMM_OBJECTSTORE_KIND") or "local"
|
|
|
|
|
|
def objectstore_bucket() -> str:
|
|
"""The GCS bucket name for the media objects (gcs adapter; deployment overlay)."""
|
|
return os.environ.get("ECOMM_OBJECTSTORE_BUCKET", "")
|
|
|
|
|
|
def objectstore_local_dir() -> str:
|
|
"""Base directory for the local-disk objectstore adapter (dev/tests)."""
|
|
return os.environ.get("ECOMM_OBJECTSTORE_DIR") or "/tmp/ecomm-objectstore"
|
|
|
|
|
|
def public_base_url() -> str:
|
|
"""Absolute origin for hosted image URLs in export (e.g. APP_URL); '' → relative."""
|
|
return (os.environ.get("APP_URL") or "").rstrip("/")
|
|
|
|
|
|
def image_fetch_allow_private() -> bool:
|
|
"""Allow image fetch from private/loopback hosts (dev/E2E fixture host only).
|
|
Default off — the SSRF guard rejects private ranges in deployed envs."""
|
|
return os.environ.get("ECOMM_IMAGE_FETCH_ALLOW_PRIVATE", "").strip().lower() in {
|
|
"1", "true", "yes", "on",
|
|
}
|