teecup/app/config.py
Erol Haagenrud c314d4866e Ekte SMTP-utsending er bygget og verifisert — med faktisk levering, ikke bare kodegjennomgang.
Bygget: ny app/email.py (send_magic_link_email, kjører smtplib via asyncio.to_thread siden det er synkront, håndterer både implisitt TLS (port 465) og STARTTLS dynamisk siden jeg bevisst ikke leste TEECUP_SMTP_PORT-verdien). app/config.py fikk nye, valgfrie innstillinger — SMTP_CONFIGURED er IKKE _required, så scratch-/dev-testing fortsatt fungerer uendret uten SMTP satt opp.

Sikkerhetsdesign: en driftsfeil i selve utsendingen (feil passord, SMTP nede, eller ingenting konfigurert) logges kun server-side og endrer aldri klientresponsen — bevarer request-link sitt anti-enumereringsvern.

Verifisert i to trinn:

Dev-log-flyten uendret uten SMTP satt (regresjonstest).
Én ekte test-e-post sendt til hei@erol.no, med credentials videreført fra .env til scratch-containeren uten at jeg noensinne leste verdiene — du bekreftet mottak.
Dette er første gang noe i prosjektet er bevist ved ekte, ekstern levering fremfor bare curl/scratch-container. CLAUDE.md/FEATURE_BACKLOG.md oppdatert.
2026-07-16 20:59:59 +02:00

64 lines
2.6 KiB
Python

"""
Konfigurasjon for TeeCup-API-et.
ADR (åpent spørsmål 1): TeeCup bruker EGNE, uavhengige secrets — den skal ikke
falle tilbake på teeoff sine. Manglende variabel skal feile høyt ved oppstart,
ikke stille bruke en usikker standard.
"""
import os
def _required(name: str) -> str:
value = os.environ.get(name, "").strip()
if not value:
raise RuntimeError(
f"Miljøvariabelen {name} mangler. TeeCup starter ikke uten den "
f"(ingen fallback til teeoff sine verdier — se ADR)."
)
return value
class Settings:
# Peker på teecup_db, og bruker RUNTIME-rollen teecup_app (ikke teeoff_admin).
# Format: postgresql://teecup_app:<passord>@<host>:<port>/teecup_db
DATABASE_URL: str = _required("TEECUP_DATABASE_URL")
# Egen sesjons-secret, uavhengig av teeoff.
SESSION_SECRET: str = _required("TEECUP_SESSION_SECRET")
DB_POOL_MIN: int = int(os.getenv("TEECUP_DB_POOL_MIN", "2"))
DB_POOL_MAX: int = int(os.getenv("TEECUP_DB_POOL_MAX", "10"))
# Ekte autentisering (magic-link + JWT-sesjon). Ikke hemmeligheter --
# sane defaults, ikke _required.
SESSION_MAX_AGE_SECONDS: int = int(
os.getenv("TEECUP_SESSION_MAX_AGE_SECONDS", str(30 * 24 * 3600))
)
MAGIC_LINK_MAX_AGE_MINUTES: int = int(os.getenv("TEECUP_MAGIC_LINK_MAX_AGE_MINUTES", "20"))
MAGIC_LINK_COOLDOWN_SECONDS: int = int(os.getenv("TEECUP_MAGIC_LINK_COOLDOWN_SECONDS", "60"))
# Logger rå magic-link-token server-side i stedet for å sende e-post.
# ALDRI på som standard -- må settes eksplisitt, kun for dev/scratch-
# testing (samme klasse feil som denne runden finnes for å lukke for
# X-Debug-User-Id-headeren).
DEV_LOG_MAGIC_LINKS: bool = os.getenv("TEECUP_DEV_LOG_MAGIC_LINKS", "").strip().lower() in (
"1",
"true",
"yes",
)
# Ekte e-postutsending for magic-link (ADR-009: egne credentials, ikke
# delt med teeoff). Valgfritt (ikke _required) -- scratch-/dev-testing
# skal fortsatt fungere uten SMTP satt, via DEV_LOG_MAGIC_LINKS over.
SMTP_SERVER: str | None = os.environ.get("TEECUP_SMTP_SERVER", "").strip() or None
SMTP_PORT: int | None = (
int(os.environ["TEECUP_SMTP_PORT"]) if os.environ.get("TEECUP_SMTP_PORT", "").strip() else None
)
SMTP_USER: str | None = os.environ.get("TEECUP_SMTP_USER", "").strip() or None
SMTP_PASS: str | None = os.environ.get("TEECUP_SMTP_PASS", "").strip() or None
FROM_EMAIL: str | None = os.environ.get("TEECUP_FROM_EMAIL", "").strip() or None
SMTP_CONFIGURED: bool = bool(SMTP_SERVER and SMTP_PORT and SMTP_USER and SMTP_PASS and FROM_EMAIL)
settings = Settings()