1import effShortWordList from "./eff-short-wordlist.txt?raw";23export interface GeneratorOptions {4 length: number;5 includeUppercase: boolean;6 includeLowercase: boolean;7 includeNumbers: boolean;8 includeSymbols: boolean;9 10 forbiddenChars?: string;11}1213export const MIN_PASSPHRASE_WORDS = 4;14export const DEFAULT_PASSPHRASE_WORDS = 5;15export const MAX_PASSPHRASE_WORDS = 12;1617export type PassphraseSeparator = "-" | " " | "." | "_";1819export interface PassphraseOptions {20 wordCount: number;21 separator: PassphraseSeparator;22}2324const PASSPHRASE_WORDS = effShortWordList.trim().split("\n");2526272829export function generatePassword(options: Partial<GeneratorOptions> = {}): string {30 const {31 length = 20,32 includeUppercase = true,33 includeLowercase = true,34 includeNumbers = true,35 includeSymbols = true,36 forbiddenChars = "",37 } = options;3839 const forbidden = new Set(forbiddenChars);40 const filter = (pool: string): string =>41 forbidden.size === 042 ? pool43 : Array.from(pool).filter((c) => !forbidden.has(c)).join("");4445 let chars = "";46 if (includeLowercase) chars += filter("abcdefghijklmnopqrstuvwxyz");47 if (includeUppercase) chars += filter("ABCDEFGHIJKLMNOPQRSTUVWXYZ");48 if (includeNumbers) chars += filter("0123456789");49 if (includeSymbols) chars += filter("!@#$%^&*");5051 if (chars.length === 0) chars = filter("abcdefghijklmnopqrstuvwxyz") || "abcdefghijklmnopqrstuvwxyz";5253 54 55 56 57 const limit = Math.floor(0x1_0000_0000 / chars.length) * chars.length;58 let password = "";59 while (password.length < length) {60 const batch = crypto.getRandomValues(new Uint32Array(length - password.length));61 for (let i = 0; i < batch.length && password.length < length; i++) {62 const v = batch[i]!;63 if (v >= limit) continue; 64 password += chars[v % chars.length];65 }66 }67 return password;68}697071export function generatePassphrase(options: Partial<PassphraseOptions> = {}): string {72 const wordCount = Math.max(73 MIN_PASSPHRASE_WORDS,74 Math.min(MAX_PASSPHRASE_WORDS, Math.floor(options.wordCount ?? DEFAULT_PASSPHRASE_WORDS)),75 );76 const separator = options.separator ?? "-";77 const limit = Math.floor(0x1_0000_0000 / PASSPHRASE_WORDS.length) * PASSPHRASE_WORDS.length;78 const words: string[] = [];7980 while (words.length < wordCount) {81 const value = crypto.getRandomValues(new Uint32Array(1))[0]!;82 if (value < limit) words.push(PASSPHRASE_WORDS[value % PASSPHRASE_WORDS.length]!);83 }8485 return words.join(separator);86}