Bei zu vielen Login-Fehlversuchen war ohne Container-Restart kein Weg
zurück. Jetzt sehen Admins die aktiven Sperren und können einzeln
freigeben.
Backend:
- GET /api/settings/rate-limits/active (settings:read)
Liest SecurityEvent RATE_LIMIT_HIT der letzten 15 Min, gruppiert nach
IP, liefert lastEmail/limiters/hitCount/lastHit.
- POST /api/settings/rate-limits/reset (settings:update)
Body { ipAddress } → ruft loginRateLimiter.resetKey + passwordReset-
RateLimiter.resetKey auf (express-rate-limit v7), audited als
UPDATE auf resourceType=RateLimit.
Frontend:
- Neue Seite /settings/rate-limits: Tabelle mit IP/Email/Limiter/Hits/
Letzter-Hit/Aktion. Auto-Refresh alle 15s. Freigeben-Button pro IP.
- Kachel in Settings-Übersicht (orange, ShieldOff-Icon, settings:read).
Live-verifiziert: 11 failed Logins → 429 ab dem 11.; Liste zeigt
IP + Email; POST /reset → 200; danach wieder 401 statt 429; Audit-Log
„Rate-Limit für IP 127.0.0.1 manuell freigegeben" angelegt.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Wenn Admin "Zugangsdaten versenden" klickt, ist das Passwort jetzt ein
echtes Einmalpasswort: beim ersten erfolgreichen Portal-Login werden
Hash + Encrypted-Feld sofort genullt und der Kunde wird zwangsweise
auf eine "Neues Passwort vergeben"-Seite geleitet. Erst nach eigenem
Passwort kommt er ins Portal.
Schema:
- Customer.portalPasswordMustChange: Boolean @default(false)
Backend:
- sendPortalCredentials setzt Flag = true + erweitertes Mail-Template
mit Einmalpasswort-Warnung
- customerLogin: bei Flag=true wird OTP konsumiert (Hash+Encrypted=null,
portalLastLogin aktualisiert), Response enthält mustChangePassword=true
in token-payload + user-objekt
- setCustomerPortalPassword (manuelles Setzen) räumt Flag wieder auf
- changeInitialPortalPassword: neue Service-Funktion + Endpoint
POST /api/auth/change-initial-portal-password (authenticated, nur
Portal-User), validiert Komplexität, setzt neuen Hash, löscht
Encrypted, invalidiert Session via portalTokenInvalidatedAt
Frontend:
- User-Type erweitert um mustChangePassword
- AuthContext.customerLogin gibt User zurück (für sofortige Routing-
Entscheidung)
- Login.tsx: redirect zu /change-initial-password wenn mustChangePassword
- ProtectedRoute: zwingt eingeloggte User mit Flag immer zur Change-Seite
- ChangeInitialPasswordGate: blockt User OHNE Flag vom Zugriff
- ChangeInitialPassword: eigene Seite mit Live-Komplexitäts-Hint,
Passwort-Wiederholung, automatischer Logout + Redirect nach Erfolg
Live-verifiziert (10 Schritte):
- Setzen → Send → DB-Flag=true → OTP-Login gibt mustChange=true und
consumed Hash → Re-Login mit OTP fehlschlägt → Change schwach=400,
komplex=200 → neues Passwort funktioniert → Session invalidated.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Die drei letzten wichtigen Features für ein produktionsreifes 1.0.0:
## 1. Passwort vergessen-Flow
Der klassische Selfservice-Reset per Email – sowohl für Mitarbeiter als auch
für Portal-Kunden. User können sich nicht mehr aussperren, Admin muss nicht
mehr manuell eingreifen.
- Neues Link "Passwort vergessen?" auf Login-Seite
- PasswordResetRequest: Email + Typ-Auswahl (Mitarbeiter / Portal)
- PasswordResetConfirm: Token-basierte Bestätigung + neues Passwort (min 6 Zeichen)
- Token ist 2 Stunden gültig, dann muss neu angefordert werden
- Token ist kryptografisch sicher (crypto.randomBytes(32))
- User-Enumeration-Schutz: Backend gibt immer 200 zurück, egal ob Email existiert
- Nach erfolgreichem Reset werden ALLE bestehenden Sessions gekickt
(tokenInvalidatedAt gesetzt) – falls jemand parallel eingeloggt war
DB:
- User.passwordResetToken + passwordResetExpiresAt
- Customer.portalPasswordResetToken + portalPasswordResetExpiresAt
## 2. Rate-Limiting gegen Brute-Force
Mit express-rate-limit:
- Login: 10 Versuche pro 15 Minuten pro IP. Erfolgreiche zählen nicht mit.
- Passwort-Reset-Request: 5 Versuche pro Stunde pro IP (Mail-Flut verhindern)
Sowohl Mitarbeiter-Login als auch Portal-Login geschützt.
## 3. Auto-Geburtstagsgrüße per Cron
Das autoBirthdayGreeting-Flag hatten wir schon, aber kein Scheduler der
ihn wirklich abschickt. Jetzt:
- Läuft täglich um 08:00 Uhr
- Findet Kunden mit heutigem Geburtstag + autoBirthdayGreeting=true
- Nur Email-Kanal (Messenger brauchen Browser-Klick)
- Catch-up 30s nach Server-Start: wenn Server am Geburtstag down war, wird
beim nächsten Boot nachgeholt
- lastBirthdayGreetingYear verhindert Doppelversand
Dependencies: node-cron, @types/node-cron, express-rate-limit
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Ein neues System um Stammdaten-Kataloge zwischen Installationen zu teilen –
explizit ohne Kundendaten, Verträge oder Einstellungen.
**Was wird exportiert:**
- Anbieter + zugehörige Tarife
- Kündigungsfristen
- Vertragslaufzeiten
- Vertragskategorien
- PDF-Auftragsvorlagen (JSON + PDF-Dateien + Feldzuordnungen)
**Was NICHT:**
- Kundendaten, Verträge, Dokumente, Emails, SMTP-Einstellungen
→ dafür gibt es den Datenbank-Backup
**Neue Einstellungsseite /settings/factory-defaults:**
- Zeigt Anzahl pro Kategorie (Anbieter, Tarife, Fristen, …)
- "Exportieren"-Button lädt ZIP herunter (manifest.json + JSONs + PDFs)
- Import-Anleitung inline
**Import-Script:**
- `npm run seed:defaults` (tsx scripts/seed-factory-defaults.ts)
- Liest alle JSON-Dateien aus backend/factory-defaults/*/*.json
- Merged mehrere Dateien automatisch pro Kategorie (unique-key gewinnt zuletzt)
- Upsertet idempotent → kann mehrfach ausgeführt werden
- Kopiert PDF-Vorlagen aus factory-defaults/pdf-templates/ nach uploads/pdf-templates/
- Alte PDF-Dateien werden beim Re-Import entsorgt
Backend:
- services/factoryDefaults.service.ts: collectFactoryDefaults() + exportFactoryDefaults()
- controllers/factoryDefaults.controller.ts: preview + export
- routes/factoryDefaults.routes.ts: GET /api/factory-defaults/preview + /export
- scripts/seed-factory-defaults.ts: CLI-Import-Script
- .gitignore: factory-defaults/* außer .gitkeep und README.md
Frontend:
- pages/settings/FactoryDefaults.tsx: Übersicht + Export-Button
- Settings-Karte „Factory-Defaults" im System-Abschnitt
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- PDF-Template-Editor in Einstellungen: Vorlagen hochladen, Formularfelder automatisch auslesen, CRM-Felder zuordnen
- PDF-Vorschau mit annotierten Feldnamen, seitenweise Sortierung der Felder
- Auftrag generieren aus Vertragsdaten (Button im Vertrags-Detail)
- Dynamische Rufnummern-Felder mit Vorwahl-Extraktion und konfigurierbarer Maximalanzahl
- Nicht zugeordnete Felder bleiben editierbar im generierten PDF
- Eigentümer-Felder mit Namens-Kombinationen (Firma+Name etc.) und Fallback auf Kundendaten
- Stressfrei-E-Mail als Feld-Option im Template-Editor
- Objekttyp, Lage und Lage des Anschlusses als neue Felder bei Festnetz-Verträgen (DSL, Glasfaser, Kabel)
- Bankverbindung-Fallback: wenn keine am Vertrag verknüpft, wird automatisch die neueste aktive des Kunden genommen
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>