Version 1.0.0: Passwort-Reset + Rate-Limiting + Auto-Geburtstagsgrüße
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>
This commit is contained in:
@@ -6,6 +6,8 @@ import { Shield } from 'lucide-react';
|
||||
import ScrollToTop from './components/ScrollToTop';
|
||||
import Layout from './components/layout/Layout';
|
||||
import Login from './pages/Login';
|
||||
import PasswordResetRequest from './pages/PasswordResetRequest';
|
||||
import PasswordResetConfirm from './pages/PasswordResetConfirm';
|
||||
import Dashboard from './pages/Dashboard';
|
||||
import CustomerList from './pages/customers/CustomerList';
|
||||
import CustomerDetail from './pages/customers/CustomerDetail';
|
||||
@@ -146,6 +148,10 @@ function App() {
|
||||
element={isAuthenticated ? <Navigate to="/" replace /> : <Login />}
|
||||
/>
|
||||
|
||||
{/* Passwort-Reset (öffentlich, kein Auth-Check) */}
|
||||
<Route path="/password-reset/request" element={<PasswordResetRequest />} />
|
||||
<Route path="/password-reset" element={<PasswordResetConfirm />} />
|
||||
|
||||
<Route
|
||||
path="/"
|
||||
element={
|
||||
|
||||
Reference in New Issue
Block a user