# TK Wiki Newsletter Admin Produktionsnahes internes Tool zur Erstellung von Thomas-Krenn.AG Newslettern auf Basis der letzten Wiki-Änderungen. ## Funktionen - Login und Benutzerverwaltung mit RBAC (`admin`, `editor`, `reader`) - Verpflichtender CSRF-Schutz auf allen POST-Formularen - Filter in der Web-UI: - Zeitraum in Tagen oder „Letzter Monat“ - Artikel-Auswahl: alle, nur neue oder nur bearbeitete Artikel - Kategorie - Redaktioneller Feinschliff pro Ausgabe: - **Redaktionsnotiz** – optionaler Hinweistext, der als eigene Box im Newsletter erscheint - **Top-Highlights** – bis zu 3 Artikel per Titel hervorheben (erscheinen oben als Highlight-Box) - Datenquelle: MediaWiki API (`recentchanges` über `wikiDE/api.php`) - E-Mail-Layout im Thomas-Krenn Corporate Design: - Hero-Header mit Logo, Artikel-Karten mit „Zum Beitrag“-Button, Kontakt- und Adress-Footer - Neu-/Bearbeitet-Trennung, Zusammenfassungs-Kacheln, Outlook-taugliche Kopiervorschau - Export: - Plain Text (für Outlook) - Raw HTML - Optionaler SMTP-Versand: - UI-konfigurierbar (nur für Admins) - Verteilerlisten mit E-Mail-Validierung - Versand per **BCC** (Empfänger nicht im sichtbaren `To:`-Header) - flexibler Zeitplan (täglich / wöchentlich / monatlich) über einen Hintergrund-Scheduler - eigene Inhalts-Vorgaben für den geplanten Versand (Zeitraum, Artikel-Auswahl, Kategorie) - **„Generieren“ verschickt nie** – manueller Versand nur per Button „Newsletter jetzt senden“ - automatischer Versand nur mit **zwei** aktiven Häkchen: Zeitplan + Bestätigung „ohne manuelle Freigabe“ - Versandprotokoll (nur für Editor/Admin sichtbar) - Docker/Compose Betrieb mit Container-Hardening, inkl. automatischem Image-Build & Push in die Gitea Container Registry (per Versions-Tag) ## Start mit Docker 1. Konfiguration anlegen: ```bash cp .env.example .env ``` 2. Wichtige Werte in `.env` setzen (siehe auch [Konfiguration](#konfiguration-env)): | Variable | Lokal (Entwicklung) | Produktion | |----------|---------------------|------------| | `ENVIRONMENT` | `development` | `production` | | `HOST_PORT` | z. B. `8080` | nach Bedarf | | `SECRET_KEY` | beliebig lang | mind. 32 zufällige Zeichen | | `ALLOWED_HOSTS` | `localhost,127.0.0.1` | konkreter Hostname | | `COOKIE_SECURE` | `false` | `true` (mit TLS) | | `ADMIN_BOOTSTRAP_EMAIL` | Admin-E-Mail | Admin-E-Mail | | `ADMIN_BOOTSTRAP_PASSWORD` | Bootstrap-Passwort | starkes Passwort | 3. Start: ```bash docker compose up --build -d ``` 4. Zugriff: - [http://localhost:8080/login](http://localhost:8080/login) (oder dein `HOST_PORT`) > **Hinweis:** Mit `ENVIRONMENT=production` prüft die App beim Start strikt die Konfiguration. Fehlen starke Secrets oder sind unsichere Defaults gesetzt, startet der Container nicht. ## Konfiguration (`.env`) Vollständige Vorlage: `.env.example` | Variable | Beschreibung | |----------|--------------| | `ENVIRONMENT` | `development` = lokale Entwicklung ohne harte Checks; `production` = erzwingt sichere Einstellungen | | `SECRET_KEY` | JWT-Signierung und Verschlüsselung von SMTP-Passwörtern in der DB | | `ALGORITHM` | JWT-Algorithmus (Standard: `HS256`) | | `ACCESS_TOKEN_EXPIRE_MINUTES` | Gültigkeit der Login-Session in Minuten (Standard: `120`) | | `JWT_ISSUER` / `JWT_AUDIENCE` | JWT-Claims zur Token-Validierung | | `HOST_PORT` | Host-Port für Docker Compose (nur Compose, nicht App-intern) | | `DATABASE_URL` | SQLite (Standard) oder PostgreSQL | | `WIKI_API_URL` | MediaWiki-API-Endpunkt | | `ALLOWED_HOSTS` | Kommagetrennte erlaubte Host-Header | | `COOKIE_SECURE` | `Secure`-Flag für Session- und CSRF-Cookies (Pflicht `true` in Produktion) | | `HSTS_MAX_AGE` | HSTS-Header-Dauer in Sekunden (nur bei HTTPS) | | `ADMIN_BOOTSTRAP_EMAIL` / `ADMIN_BOOTSTRAP_PASSWORD` | Erster Admin-Account beim Start | | `ADMIN_BOOTSTRAP_RESET` | Admin auf `.env`-Werte zurücksetzen (nur Entwicklung/Notfall, in Produktion verboten) | | `EDITOR_TIP_MAX_LENGTH` | Max. Zeichen für Redaktionsnotiz (Standard: `10000`) | | `HIGHLIGHTS_MAX_LENGTH` | Max. Zeichen für Top-Highlights (Standard: `5000`) | ## Container-Image (Gitea Packages) Fertige Images werden automatisch in die Gitea Container Registry veröffentlicht: ``` git.hexahost.dev/smueller/tk-wiki-newsletter ``` Verfügbare Tags: `latest`, die Version (`X.Y.Z`, `X.Y`, `X`) sowie der kurze Commit-SHA. Image ziehen und mit vorhandener `.env` starten: ```bash docker login git.hexahost.dev docker pull git.hexahost.dev/smueller/tk-wiki-newsletter:latest docker run -d --name tk-newsletter-admin \ --env-file .env \ -p 8080:8000 \ -v newsletter_data:/app/data \ --read-only \ --tmpfs /tmp \ --security-opt no-new-privileges:true \ git.hexahost.dev/smueller/tk-wiki-newsletter:latest ``` Alternativ in `docker-compose.yml` statt `build: .` das Image referenzieren: ```yaml services: newsletter-admin: image: git.hexahost.dev/smueller/tk-wiki-newsletter:latest # build: . # <- nicht mehr nötig, wenn das fertige Image genutzt wird ``` Die mitgelieferte `docker-compose.yml` setzt bereits `read_only`, `no-new-privileges` und ein `tmpfs` für `/tmp`. Persistente Daten liegen im Volume `newsletter_data` unter `/app/data`. ### Automatischer Build (CI/CD) Der Workflow `.gitea/workflows/docker-build.yml` baut und pusht das Image über Gitea Actions. - **Auslöser**: nur beim Setzen eines Versions-Tags `v*` (kein Build bei normalen Pushes auf `dev`/`main`). - **Vor dem Build**: `pip-audit` prüft die Python-Abhängigkeiten aus `requirements.txt`. - **Release-Ablauf** (Build bei Merge `dev` → `main` mit Version): ```bash git checkout main git merge --no-ff dev git push origin main git tag v1.0.0 git push origin v1.0.0 # <- startet Build & Push ``` - **Benötigte Repo-Secrets** (Settings → Actions → Secrets): - `REGISTRY_TOKEN`: Gitea Access-Token mit Scope **Package: Read and write** - `REGISTRY_USER` (optional): Registry-Benutzername, falls abweichend vom Repo-Owner - **Optionale Repo-Variable**: `REGISTRY`, um den Registry-Host zu überschreiben (Default `git.hexahost.dev`). ## Admin-Login & Passwort zurücksetzen - Das Login erfolgt über **E-Mail + Passwort** (kein separater Benutzername). Beim ersten Start wird der Admin aus `ADMIN_BOOTSTRAP_EMAIL` / `ADMIN_BOOTSTRAP_PASSWORD` angelegt. - **Passwortpolitik:** mindestens 10 Zeichen, mindestens ein Großbuchstabe, ein Kleinbuchstabe und eine Ziffer. - **Rate-Limiting:** nach 5 fehlgeschlagenen Anmeldeversuchen pro IP/E-Mail innerhalb von 5 Minuten wird der Login temporär blockiert. - **Wichtig:** Existiert der Admin bereits (persistente DB im Volume `newsletter_data`), wird eine spätere Passwort-Änderung in der `.env` normalerweise **nicht** übernommen. - Um Passwort/Rolle/Status auf die `.env`-Werte zurückzusetzen (**nur Entwicklung**): `ADMIN_BOOTSTRAP_RESET=true` setzen und Container neu starten: ```bash docker compose up -d --force-recreate ``` Nach erfolgreichem Login `ADMIN_BOOTSTRAP_RESET=false` setzen. In `ENVIRONMENT=production` ist `ADMIN_BOOTSTRAP_RESET=true` nicht erlaubt – die App startet dann nicht. - **Passwort ändern** (Profil) oder **Abmelden** beendet alle aktiven Sessions des Benutzers. ## Rollenmodell | Rolle | Rechte | |-------|--------| | `reader` | Newsletter-Vorschau und Artikellisten ansehen | | `editor` | Newsletter generieren, exportieren und manuell versenden; Versandprotokoll und Empfängerliste einsehen | | `admin` | Zusätzlich Benutzerverwaltung (anlegen, Rolle ändern, aktivieren/deaktivieren) und SMTP-Konfiguration | Admins können in der Benutzerverwaltung (`/admin/users`): - neue Benutzer mit Rolle anlegen - die Rolle bestehender Benutzer ändern (beendet deren Sessions) - Benutzer aktivieren oder deaktivieren (beendet deren Sessions) Das eigene Konto kann weder deaktiviert noch die eigene Rolle geändert werden. ## Sicherheit ### Eingebaute Schutzmaßnahmen - **bcrypt** für Passwort-Hashes - **CSRF** Double-Submit-Cookie auf allen POST-Formularen - **HttpOnly** + **SameSite=Strict** Session-Cookies; `Secure` bei HTTPS (`COOKIE_SECURE=true`) - **JWT** mit `iss`, `aud`, Ablaufzeit und Session-Version (`session_version`) - Session-Invalidierung bei Logout, Passwortwechsel, Rollen- oder Statusänderung - **SMTP-Passwort** verschlüsselt in der Datenbank (Fernet), nicht im HTML-Formular - **Security-Header:** CSP, `X-Frame-Options`, `X-Content-Type-Options`, `Referrer-Policy`, HSTS (bei HTTPS), `Cache-Control: no-store` auf geschützten Seiten - **OpenAPI** (`/docs`, `/redoc`) in Produktion deaktiviert - Abhängigkeiten in `requirements.txt` versioniert; CI führt `pip-audit` aus ### Produktions-Checkliste Bei `ENVIRONMENT=production` erzwingt die App beim Start: - `SECRET_KEY` mit mindestens 32 Zeichen (keine bekannten Defaults) - starkes `ADMIN_BOOTSTRAP_PASSWORD` - `ADMIN_BOOTSTRAP_RESET=false` - `ALLOWED_HOSTS` mit konkreten Hostnamen (nicht `*`) - `COOKIE_SECURE=true` Zusätzlich empfohlen: - Reverse Proxy (z. B. Nginx/Traefik) mit TLS vor den Container; Proxy muss `X-Forwarded-Proto: https` setzen - Bootstrap-Admin-Passwort nach erstem Login im Profil ändern - Netzwerkzugriff auf interne IPs/Netze beschränken - Regelmäßige Backups des `newsletter_data` Volumes (SQLite-Datei unverschlüsselt) ### Beispiel `.env` für Produktion ```env ENVIRONMENT=production SECRET_KEY= ALLOWED_HOSTS=newsletter.intern.firma.de COOKIE_SECURE=true ADMIN_BOOTSTRAP_EMAIL=admin@firma.de ADMIN_BOOTSTRAP_PASSWORD= ADMIN_BOOTSTRAP_RESET=false ```