/** * @typedef {object} BackupCodesOptions * @property {number} [length=10] * Total number of characters per code (excluding group separators). * 10 chars from a 32-symbol alphabet = 50 bits of entropy per code — * comfortably beyond brute-force even with weak server-side hashing. * @property {number} [groups=2] * How many dash-separated groups to visually split the code into * for readability. Set to `1` to disable grouping. * @property {string} [alphabet] * Override the character set. The default (Crockford Base32) skips * the ambiguous `0/O/1/I/L` glyphs. */ /** * Generate a batch of one-time recovery codes. * * Codes are formatted like `ABCD-1234-EF` — dash-separated for easy * transcription from a paper printout, uppercase only, no ambiguous * characters. * * The caller is responsible for **hashing** the codes before storage * (bcrypt / argon2 / a strong HMAC keyed with a server secret — do * NOT store them raw). See the README for a worked example. * * @param {number} [n=10] How many codes to generate. * @param {BackupCodesOptions} [options] * @returns {string[]} */ declare function backupCodes(n?: number, options?: BackupCodesOptions): string[]; /** * Normalize a user-supplied code to the format `backupCodes` returns * so timing-safe compare works. Strips whitespace, uppercases, and * removes dashes. * * @param {string} input * @returns {string} */ declare function normalizeBackupCode(input: string): string; /** * Timing-safe compare between a user-supplied code and a candidate. * Both sides are normalized first (whitespace / dashes / case). * * Use this to check the user's input against every unused stored code * *without* short-circuiting on mismatch length, so an attacker can't * distinguish "wrong format" from "wrong value" from timing. * * @param {string} candidate What the user submitted. * @param {string} stored One of the codes you saved at enrollment. * @returns {boolean} */ declare function compareBackupCode(candidate: string, stored: string): boolean; /** * Timing-safe scan across a list of stored codes. Returns the index of * the first matching entry, or `null` when nothing matches. **Every** * entry is compared even after a match, so an attacker can't distinguish * "wrong code" from "wrong slot" through timing. * * const idx = verifyBackupCode(userInput, user.backupCodes) * if (idx === null) return res.status(401).end() * await db.markBackupCodeUsed(userId, idx) // single-use * * Store the codes **hashed** — bcrypt / argon2 / a strong keyed HMAC. * This helper hands off the compare to {@link compareBackupCode}, so * if your `storedList` is a list of plain strings the raw input is * matched against them directly; wire it into your hash routine * yourself when you're storing digests. * * @param {string} candidate User-supplied code (any case / spacing). * @param {string[]} storedList Your saved codes (in the order you * want indices reported). * @returns {number | null} Zero-based index of the match, or null. */ declare function verifyBackupCode(candidate: string, storedList: string[]): number | null; /** * Ready-made shapes for the most common backup-code conventions. * Spread into `backupCodes` options to pick one, override individual * fields to tweak. * * backupCodes(10, backupPresets.numeric) * backupCodes(10, { ...backupPresets.long, groups: 3 }) * * @type {Readonly>} */ declare const backupPresets: Readonly>; type BackupCodesOptions = { /** * Total number of characters per code (excluding group separators). * 10 chars from a 32-symbol alphabet = 50 bits of entropy per code — * comfortably beyond brute-force even with weak server-side hashing. */ length?: number | undefined; /** * How many dash-separated groups to visually split the code into * for readability. Set to `1` to disable grouping. */ groups?: number | undefined; /** * Override the character set. The default (Crockford Base32) skips * the ambiguous `0/O/1/I/L` glyphs. */ alphabet?: string | undefined; }; export { backupCodes, backupPresets, compareBackupCode, normalizeBackupCode, verifyBackupCode }; export type { BackupCodesOptions };