// Cryptographically random password generator for the user-create // and reset-password flows. PiCloud has no email yet, so the admin // invites a user by generating a password locally, posting it to the // backend, and copying the cleartext out of the one-time reveal panel // to share through whatever channel they trust. // // Charset is alphanumeric plus a small printable symbol set — enough // entropy at 16 chars (~95 bits) to be uncopyable by hand mistakes, // avoidant of characters that ship awkwardly through chat clients // (no quotes, slashes, or backticks). // // Sampling: rejection sampling against a Uint8 stream. The naive // `byte % CHARSET.length` would slightly overweight the first // (256 mod N) chars; with N = 71 that's ~16 ppm of bias which is // safe at 16 chars but easy to remove. const CHARSET = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789!#$%&*+-?@'; export function generatePassword(length = 16): string { if (length < 8) { throw new Error('password length must be at least 8'); } const n = CHARSET.length; // Largest multiple of `n` that fits in a Uint8 — bytes ≥ MAX get // rejected to remove modulo bias. const max = 256 - (256 % n); const buf = new Uint8Array(length); let out = ''; while (out.length < length) { crypto.getRandomValues(buf); for (let i = 0; i < buf.length && out.length < length; i++) { const byte = buf[i]; if (byte < max) out += CHARSET[byte % n]; } } return out; }