Password generator
Local-only password generator with rejection-sampled Web Crypto, lazy-loaded zxcvbn strength scoring, and a 100/100/100/100 Lighthouse profile.
Overview
A local-only password generator that never sends a password over the wire. Generation, strength scoring, and clipboard copy all run in the user’s browser; the page itself is a static Astro build that hydrates a single vanilla-TS island.
Goals
- Unbiased random output (no modulo bias on small alphabets).
- Guarantee at least one character from every enabled class.
- Honest strength signal without paying the cost of
zxcvbnon the critical path. - Pass WCAG 2.0 AA including color contrast in real Chromium, not just headless DOM.
- Lighthouse 100/100/100/100 on both mobile and desktop.
Approach
Rejection-sampled RNG
randomIntBelow(n, crypto) draws a 32-bit value from crypto.getRandomValues and rejects draws above the largest multiple of n that fits in 2^32. No modulo bias on small alphabets like the 19-symbol pool or the 8-digit “avoid ambiguous” set. The rejection branch is unit-tested with a stub Crypto that queues specific u32 values.
Class-presence guarantee
The generator seeds the buffer with one character from each enabled class pool, fills the remainder from the merged pool, then Fisher–Yates shuffles. A 100-trial test loop locks in the contract so a future refactor to a single merged-pool loop fails loudly.
Lazy strength
zxcvbn is ~700 KB minified. It loads via a dynamic import('zxcvbn') on the first strength update, with a .catch that surfaces “strength meter unavailable” through the page’s live region rather than hanging the UI on chunk-load failures.
Accessibility
The DOM is built imperatively in mount-app.ts with semantic landmarks, aria-labelledby on every section, a real <button> for copy, and a live region for strength + error feedback. Two axe gates: happy-dom for structure, real Chromium for color contrast.
Outcomes
- 19 unit tests covering the RNG rejection branch, class-presence guarantee across 100 trials, subset coverage, MAX_LEN bound, and the
avoidAmbiguousinvariant (never emits0,O,1,I,l). - 2 Playwright axe tests gate WCAG A/AA including color contrast.
- Lighthouse mobile + desktop: 100/100/100/100.
What’s next
- CSP headers via Hostinger
.htaccess(Phase 7 deploy). - A 301 from the legacy
/pw-gen/path to/password-generatorat cutover.