Einmalpasswort-Flow für Portal-Credentials
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>
This commit is contained in:
@@ -1036,16 +1036,20 @@ export async function sendPortalCredentials(req: AuthRequest, res: Response): Pr
|
||||
password: plaintextPassword,
|
||||
});
|
||||
|
||||
// Versendetes Passwort ist ein Einmalpasswort → beim ersten Login muss
|
||||
// der Kunde sich ein eigenes setzen.
|
||||
await authService.markPortalPasswordForChange(customerId);
|
||||
|
||||
await logChange({
|
||||
req,
|
||||
action: 'UPDATE',
|
||||
resourceType: 'PortalSettings',
|
||||
resourceId: customerId.toString(),
|
||||
label: `Portal-Zugangsdaten per E-Mail versendet an ${targetEmail}`,
|
||||
label: `Portal-Zugangsdaten per E-Mail versendet an ${targetEmail} (Einmalpasswort)`,
|
||||
customerId,
|
||||
});
|
||||
|
||||
res.json({ success: true, message: `Zugangsdaten an ${targetEmail} versendet` } as ApiResponse);
|
||||
res.json({ success: true, message: `Zugangsdaten an ${targetEmail} versendet (Einmalpasswort)` } as ApiResponse);
|
||||
} catch (error) {
|
||||
res.status(500).json({
|
||||
success: false,
|
||||
|
||||
Reference in New Issue
Block a user