diff --git a/docs/todo.md b/docs/todo.md index ca953562..bbfbf5a4 100644 --- a/docs/todo.md +++ b/docs/todo.md @@ -97,6 +97,46 @@ isolierte Instanz (keine Multi-Tenancy im Code), Provisioning + Abrechnung ## ✅ Erledigt +- [x] **🔐 Passwort-Komplexität + Portal-Credentials-UX** + - **Problem**: Bisher reichten 6 Zeichen für gesetzte Passwörter + (Portal-Login, User-Reset, Registrierung, User-Anlage). Das hat + der Pentest bemängelt, und es entsprach auch nicht dem, was wir + selbst von Endkunden erwarten würden. + - **Lösung**: + * `validatePasswordComplexity()` in `passwordGenerator.ts`: + mind. 12 Zeichen + Großbuchstaben + Kleinbuchstaben + Ziffer + + Sonderzeichen, mit detaillierter Fehlerliste auf deutsch. + * Erzwungen in **5 Endpoints**: `setPortalPassword`, + `confirmPasswordReset`, `register`, `createUser`, `updateUser`. + - **Neue UX im Kunden-Portal-Block (CustomerDetail)**: + * **Generate-Button**: erzeugt 16-Zeichen-Zufallspasswort, das + garantiert allen Komplexitätsregeln entspricht, und füllt + das Eingabefeld direkt aus. + * **Send-Credentials-Button**: schickt Login-URL + Username + + Klartext-Passwort an die Kunden-E-Mail. Funktioniert nur, + wenn "Portal aktiviert" tatsächlich aktiviert ist. + * **Live-Komplexitäts-Hint** beim Tippen: ✓/○-Liste zeigt + sofort, welche Regeln noch fehlen. + * `alert()`-Boxen durch Toast-Notifications ersetzt. + - **Live-verifiziert**: schwaches Passwort `hallo123` → HTTP 400 + mit Fehlerliste, komplexes Passwort `Hallo123!Test` → HTTP 200, + Generator-Endpoint liefert 16-Zeichen-Passwort, Send-Credentials + versendet Mail nur bei portalEnabled=true. + +- [x] **🌐 Real-IP hinter Nginx-Proxy-Manager** + - **Problem**: Rate-Limiter und Security-Monitor haben statt der + echten Client-IP nur die NPM-IP (`172.0.2.12`) geloggt. Damit + wären alle Threshold-basierten Blockings nutzlos – ein Brute- + Force von 100 verschiedenen Clients wäre für uns 1 Quelle. + - **Root Cause**: `app.set('trust proxy', 'loopback')` – das passt + nur, wenn der Proxy auf 127.0.0.1 läuft. NPM läuft aber auf + einem anderen Host, also wurde X-Forwarded-For ignoriert. + - **Fix**: trust-proxy abhängig von `HTTPS_ENABLED`: + `HTTPS_ENABLED=true` → `1` (genau 1 Hop, der NPM), sonst + `loopback` (Direkt-Verbindungen lokal). + - **Live-verifiziert**: req.ip zeigt jetzt die echte Browser-IP + statt der NPM-IP, Threshold-Events triggern korrekt. + - [x] **🚨 KRITISCH: IDOR auf Stressfrei-Email-Sub-Routes (Pentest-Fund)** - **Realer Angriff erfolgreich durchgespielt**: Portal-User konnte über `/api/stressfrei-emails/{id}/credentials` die kompletten Klartext-