Mitarbeiter-Passwörter auf 25 Zeichen (BSI-Empfehlung)

Portal-Customer-Schwellwert bleibt 12 (Handy-Eingabe → längere PWs
erhöhen Reuse-Risiko). Mitarbeiter/Admin nutzen Passwort-Manager,
für die kostet die Länge nichts.

passwordGenerator.ts:
- STAFF_MIN_PASSWORD_LENGTH = 25, PORTAL_MIN_PASSWORD_LENGTH = 12
- validatePasswordComplexity({ minLength }) parametrisiert

Mitarbeiter-Pfade auf 25:
- createUser, register, setUserPassword
- confirmPasswordReset: Audience aus Token bestimmen
  (getPasswordResetAudience), User → 25, Customer → 12. Kein
  Body-Hint, damit kein Downgrade-Trick möglich.

Portal-Pfade unverändert (default 12):
- setPortalPassword, changeInitialPortalPassword

Seed-Admin:
- 28-char Zufallspasswort (statt 16) mit allen 4 Klassen garantiert
- SEED_ADMIN_PASSWORD-ENV nur akzeptiert wenn ≥ 25 Zeichen,
  sonst Log-Warnung + Random-Fallback

Frontend:
- UserList: Hinweis "Mind. 25 Zeichen". Update + PW gleichzeitig →
  zwei API-Calls (PUT + POST /users/:id/password) statt
  Password im Body durchzuschmuggeln (Backend strippt es eh)
- PasswordResetConfirm: Hinweis "Mind. 12 (Mitarbeiter: 25)"
- userApi.setPassword(id, password) neu

Live-verifiziert:
- POST /users/6/password "Hallo123!Test" (12) → 400 "mindestens 25"
- POST /users/6/password "MeinExtremLangesPW2026!Test" → 200,
  Login mit neuem PW → success
- POST /customers/3/portal/password "Hallo123!Test" (12) → 200
- POST /users createUser mit 12-char-PW → 400 "mindestens 25"

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-05-18 15:19:58 +02:00
parent cf8c6c84c2
commit 3e1fc3eab2
9 changed files with 127 additions and 25 deletions
+16 -4
View File
@@ -99,12 +99,24 @@ export interface PasswordComplexityResult {
errors: string[];
}
export function validatePasswordComplexity(pw: unknown): PasswordComplexityResult {
// Mindestlängen nach Kontext (Pentest Runde 13 / 2026-05-18):
// Endkunden tippen ihr Portal-Passwort auch auf dem Handy ein 12 ist hier
// der Endkunden-Floor. Mitarbeiter/Admin nutzen Passwort-Manager → 25
// Zeichen entsprechen der aktuellen BSI-Empfehlung für lange Passphrasen
// mit Komplexität.
export const PORTAL_MIN_PASSWORD_LENGTH = 12;
export const STAFF_MIN_PASSWORD_LENGTH = 25;
export function validatePasswordComplexity(
pw: unknown,
opts: { minLength?: number } = {},
): PasswordComplexityResult {
const minLength = opts.minLength ?? PORTAL_MIN_PASSWORD_LENGTH;
const errors: string[] = [];
if (typeof pw !== 'string') {
return { ok: false, errors: ['Passwort fehlt oder ist kein Text'] };
}
if (pw.length < 12) errors.push('mindestens 12 Zeichen');
if (pw.length < minLength) errors.push(`mindestens ${minLength} Zeichen`);
if (!/[a-z]/.test(pw)) errors.push('mindestens einen Kleinbuchstaben');
if (!/[A-Z]/.test(pw)) errors.push('mindestens einen Großbuchstaben');
if (!/[0-9]/.test(pw)) errors.push('mindestens eine Ziffer');
@@ -118,8 +130,8 @@ export function validatePasswordComplexity(pw: unknown): PasswordComplexityResul
* Wirft mit sprechender Fehlermeldung, wenn das Passwort die Komplexität
* nicht erfüllt. Für Aufruf direkt im Controller, der die Exception fängt.
*/
export function assertPasswordComplexity(pw: unknown): void {
const r = validatePasswordComplexity(pw);
export function assertPasswordComplexity(pw: unknown, opts: { minLength?: number } = {}): void {
const r = validatePasswordComplexity(pw, opts);
if (!r.ok) {
throw new Error('Passwort erfüllt Mindestanforderungen nicht: ' + r.errors.join(', '));
}