// ==================== PASSWORD GENERATOR ==================== // Generiert sichere, zufällige Passwörter import { randomBytes } from 'crypto'; // Zeichensätze für Passwort-Generierung const LOWERCASE = 'abcdefghijklmnopqrstuvwxyz'; const UPPERCASE = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ'; const NUMBERS = '0123456789'; const SPECIAL = '!@#$%^&*()_+-=[]{}|;:,.<>?'; // Standard-Passwortlänge const DEFAULT_LENGTH = 16; export interface PasswordOptions { length?: number; includeLowercase?: boolean; includeUppercase?: boolean; includeNumbers?: boolean; includeSpecial?: boolean; } /** * Generiert ein kryptografisch sicheres Passwort */ export function generateSecurePassword(options: PasswordOptions = {}): string { const { length = DEFAULT_LENGTH, includeLowercase = true, includeUppercase = true, includeNumbers = true, includeSpecial = true, } = options; // Zeichensatz zusammenstellen let charset = ''; const requiredChars: string[] = []; if (includeLowercase) { charset += LOWERCASE; requiredChars.push(getRandomChar(LOWERCASE)); } if (includeUppercase) { charset += UPPERCASE; requiredChars.push(getRandomChar(UPPERCASE)); } if (includeNumbers) { charset += NUMBERS; requiredChars.push(getRandomChar(NUMBERS)); } if (includeSpecial) { charset += SPECIAL; requiredChars.push(getRandomChar(SPECIAL)); } if (charset.length === 0) { throw new Error('Mindestens ein Zeichensatz muss aktiviert sein'); } // Restliche Zeichen auffüllen const remainingLength = Math.max(0, length - requiredChars.length); const randomChars: string[] = []; for (let i = 0; i < remainingLength; i++) { randomChars.push(getRandomChar(charset)); } // Alle Zeichen mischen (Fisher-Yates Shuffle) const allChars = [...requiredChars, ...randomChars]; for (let i = allChars.length - 1; i > 0; i--) { const j = getRandomInt(i + 1); [allChars[i], allChars[j]] = [allChars[j], allChars[i]]; } return allChars.join(''); } /** * Generiert ein einfaches Passwort ohne Sonderzeichen (für APIs die das nicht mögen) */ export function generateSimplePassword(length = 12): string { return generateSecurePassword({ length, includeLowercase: true, includeUppercase: true, includeNumbers: true, includeSpecial: false, }); } // ==================== PASSWORD COMPLEXITY VALIDATION ==================== /** * Mindestanforderungen für vom User vergebene Passwörter. * Generator-Output (generateSecurePassword) erfüllt diese standardmäßig. */ export interface PasswordComplexityResult { ok: boolean; errors: string[]; } export function validatePasswordComplexity(pw: unknown): PasswordComplexityResult { 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 (!/[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'); // Sonderzeichen-Set bewusst breit – auch Leerzeichen + Unicode-Punktuation // zulassen, damit gängige Passwort-Manager-Outputs nicht abgelehnt werden. if (!/[^A-Za-z0-9]/.test(pw)) errors.push('mindestens ein Sonderzeichen'); return { ok: errors.length === 0, errors }; } /** * 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); if (!r.ok) { throw new Error('Passwort erfüllt Mindestanforderungen nicht: ' + r.errors.join(', ')); } } // Kryptografisch sichere Zufallszahl function getRandomInt(max: number): number { const bytes = randomBytes(4); const value = bytes.readUInt32BE(0); return value % max; } // Zufälliges Zeichen aus einem Zeichensatz function getRandomChar(charset: string): string { return charset[getRandomInt(charset.length)]; }