/* Auswertung der Passwortrichtlinie im Browser – nur für die Anzeige. * * Verbindlich prüft der Server (backend/app/services/policy.py); hier geht es * allein darum, beim Tippen sofort zu zeigen, was noch fehlt. Die Regeln müssen * deshalb mit der Backend-Fassung übereinstimmen: * Grossbuchstabe = \p{Lu}, Kleinbuchstabe = \p{Ll}, Ziffer = \p{Nd}, * Sonderzeichen = weder Buchstabe noch Ziffer noch Leerraum. */ export const DEFAULT_POLICY = { min_length: 10, require_upper: true, require_lower: true, require_digit: true, require_special: false, mfa_required: false, }; const PRUEFUNGEN = { upper: (pw) => /\p{Lu}/u.test(pw), lower: (pw) => /\p{Ll}/u.test(pw), digit: (pw) => /\p{Nd}/u.test(pw), special: (pw) => /[^\p{L}\p{N}\s]/u.test(pw), }; /** * Liefert die aktiven Regeln als Liste – jeweils mit Kennung und Erfüllt-Zustand. * `t` ist die i18n-Funktion; die Texte liegen unter `password.rule*`. */ export function ruleList(policy, password, t) { const p = policy || DEFAULT_POLICY; const pw = password || ""; const regeln = [ { id: "length", text: t("password.ruleLength", { n: p.min_length }), ok: pw.length >= p.min_length }, ]; for (const id of ["upper", "lower", "digit", "special"]) { if (p[`require_${id}`]) { regeln.push({ id, text: t(`password.rule${id[0].toUpperCase()}${id.slice(1)}`), ok: PRUEFUNGEN[id](pw), }); } } return regeln; } /** True, wenn das Passwort alle aktiven Regeln erfüllt. */ export function meetsPolicy(policy, password, t) { return ruleList(policy, password, t).every((r) => r.ok); } const GROSS = "ABCDEFGHJKMNPQRSTUVWXYZ"; // ohne I, O, L const KLEIN = "abcdefghijkmnpqrstuvwxyz"; const ZIFFERN = "23456789"; const SONDER = "!?%+*#$"; function ziehen(alphabet, anzahl) { const werte = new Uint32Array(anzahl); crypto.getRandomValues(werte); return Array.from(werte, (v) => alphabet[v % alphabet.length]); } /** * Erzeugt ein zufälliges Passwort, das die Richtlinie garantiert erfüllt. * Zeichen, die man leicht verwechselt (0/O, 1/I/l), bleiben draussen – solche * Passwörter werden vom Admin abgetippt oder weitergegeben. */ export function randomPassword(policy) { const p = policy || DEFAULT_POLICY; const laenge = Math.max(p.min_length, 12); const pflicht = []; if (p.require_upper) pflicht.push(...ziehen(GROSS, 1)); if (p.require_lower) pflicht.push(...ziehen(KLEIN, 1)); if (p.require_digit) pflicht.push(...ziehen(ZIFFERN, 1)); if (p.require_special) pflicht.push(...ziehen(SONDER, 1)); const rest = GROSS + KLEIN + ZIFFERN + (p.require_special ? SONDER : ""); const zeichen = [...pflicht, ...ziehen(rest, Math.max(0, laenge - pflicht.length))]; // Mischen, damit die Pflichtzeichen nicht immer vorne stehen (Fisher-Yates // mit kryptografischer Quelle). const misch = new Uint32Array(zeichen.length); crypto.getRandomValues(misch); for (let i = zeichen.length - 1; i > 0; i--) { const j = misch[i] % (i + 1); [zeichen[i], zeichen[j]] = [zeichen[j], zeichen[i]]; } return zeichen.join(""); }