"""Einstellungen, ausschliesslich aus Umgebungsvariablen. Nichts davon steht im Code oder im Image: Passwort und Pfade kommen beim Start von aussen. Ohne gesetztes Passwort bleibt der Erfassungsbereich gesperrt (siehe Phase 3) - ein mitgeliefertes Standardpasswort wäre öffentlich bekannt und damit keines. """ from functools import lru_cache from pathlib import Path from pydantic import Field from pydantic_settings import BaseSettings, SettingsConfigDict class Einstellungen(BaseSettings): model_config = SettingsConfigDict(env_file=".env", extra="ignore") # Die Namen der Umgebungsvariablen sind ausdrücklich angegeben und # englisch - sie stehen in docker-compose.yml und in der Anleitung, und # dort wären deutsche Namen neben ADMIN_PASSWORD & Co. verwirrend. # Ablageorte. Im Container zeigen beide auf eingehängte Volumes, damit # Datenbank und Bilder einen Neustart überleben. datenbank_pfad: Path = Field(default=Path("data/kleiderboerse.sqlite"), validation_alias="DATABASE_PATH") upload_pfad: Path = Field(default=Path("uploads"), validation_alias="UPLOAD_PATH") # Passwort für den Erfassungsbereich. Leer = gesperrt. admin_passwort: str = Field(default="", validation_alias="ADMIN_PASSWORD") admin_passwort_hash: str = Field(default="", validation_alias="ADMIN_PASSWORD_HASH") # Schlüssel, mit dem das Sitzungs-Cookie signiert wird. Ohne festen Wert # wird beim Start einer erzeugt - dann ist man nach jedem Neustart # abgemeldet. Im Betrieb also setzen. secret_key: str = Field(default="", validation_alias="SECRET_KEY") # Secure-Kennzeichen am Sitzungs-Cookie. Standard an; nur zum Testen # über http://localhost abschalten, sonst schickt der Browser das # Cookie gar nicht erst mit. cookie_nur_https: bool = Field(default=True, validation_alias="COOKIE_HTTPS_ONLY") # Grösse eines einzelnen Bild-Uploads. Greift zusätzlich zur Prüfung in # images.py; ein Handyfoto liegt heute bei 3-12 MB. max_upload_bytes: int = Field(default=10 * 1024 * 1024, validation_alias="MAX_UPLOAD_BYTES") # Rate-Limits (pro IP). Reservieren ist ohne Anmeldung möglich, also # steht hier nichts anderes zwischen einem Skript und dem Bestand. limit_reservieren: str = Field(default="5/hour", validation_alias="RATE_LIMIT_RESERVE") limit_anmelden: str = Field(default="10/hour", validation_alias="RATE_LIMIT_LOGIN") # Rechenaufwand für das Passwort-Hashing. 12 ist der sinnvolle # Standard; bcrypt ist absichtlich langsam, damit sich Passwörter # nicht durchprobieren lassen. In Tests wird der Wert gesenkt - # sonst kostet jeder Test rund eine halbe Sekunde nur dafür. bcrypt_runden: int = Field(default=12, validation_alias="BCRYPT_ROUNDS") # Nach dieser Zeit ohne Aktivität wird abgemeldet. sitzung_max_ruhe_sekunden: int = Field(default=7200, validation_alias="SESSION_IDLE_SECONDS") @property def datenbank_url(self) -> str: return f"sqlite:///{self.datenbank_pfad}" @property def admin_aktiv(self) -> bool: """Ohne hinterlegtes Passwort bleibt der Erfassungsbereich gesperrt. Bewusst kein mitgeliefertes Standardpasswort: das stünde in der Anleitung und wäre damit allgemein bekannt. """ return bool(self.admin_passwort_hash or self.admin_passwort) @lru_cache def einstellungen() -> Einstellungen: """Einmal lesen und merken - die Werte ändern sich zur Laufzeit nicht.""" return Einstellungen()