import type { PasswordPolicy } from "./types"; export interface GeneratorOptions { length: number; includeUppercase: boolean; includeLowercase: boolean; includeNumbers: boolean; includeSymbols: boolean; /** Characters to exclude from every pool — honors site forbidden-char lists. */ forbiddenChars?: string; } /** * Generate a cryptographically random password. */ export function generatePassword(options: Partial = {}): string { const { length = 20, includeUppercase = true, includeLowercase = true, includeNumbers = true, includeSymbols = true, forbiddenChars = "", } = options; const forbidden = new Set(forbiddenChars); const filter = (pool: string): string => forbidden.size === 0 ? pool : Array.from(pool).filter((c) => !forbidden.has(c)).join(""); let chars = ""; if (includeLowercase) chars += filter("abcdefghijklmnopqrstuvwxyz"); if (includeUppercase) chars += filter("ABCDEFGHIJKLMNOPQRSTUVWXYZ"); if (includeNumbers) chars += filter("0123456789"); if (includeSymbols) chars += filter("!@#$%^&*"); if (chars.length === 0) chars = filter("abcdefghijklmnopqrstuvwxyz") || "abcdefghijklmnopqrstuvwxyz"; const array = crypto.getRandomValues(new Uint32Array(length)); let password = ""; for (let i = 0; i < length; i++) { password += chars[array[i]! % chars.length]; } return password; } /** * Generate a password that satisfies a detected site policy — clamps length * to maxLength, raises to minLength, honors forbidsSymbols, and excludes * forbiddenChars. Used by the inline popup when the user clicks "regen" on a * signup form with parseable requirements. */ export function generateSmartPassword(policy: PasswordPolicy = {}): string { const defaultLength = 20; let length = defaultLength; if (policy.minLength) length = Math.max(length, policy.minLength); if (policy.maxLength) length = Math.min(length, policy.maxLength); if (length < 8) length = 8; return generatePassword({ length, includeSymbols: !policy.forbidsSymbols, forbiddenChars: policy.forbiddenChars, }); }