File size: 1,721 Bytes
dcedb25 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 | import crypto from 'crypto';
// Shared primitive for generating and verifying one-time alphanumeric codes.
//
// Uppercase letters + digits, minus visually ambiguous characters (I, O, 0, 1),
// so codes are easy to read from a terminal log and type by hand.
//
// Usage: call createOneTimeCode() to get an independent code instance; each
// instance keeps its own state and can be generated / cleared / compared
// without affecting any other instance.
const ALPHABET = 'ABCDEFGHJKLMNPQRSTUVWXYZ23456789';
const CODE_LENGTH = 10;
export interface OneTimeCode {
/** Mint a new code, replacing any existing one. Returns the raw code string. */
generate(): string;
/** Return the current code, or null if none is active. */
get(): string | null;
/** Invalidate the current code. */
clear(): void;
/**
* Constant-time comparison against the active code.
* Returns false if no code is active or the input is not a matching string.
*/
matches(provided: unknown): boolean;
}
export function createOneTimeCode(): OneTimeCode {
let current: string | null = null;
return {
generate() {
const bytes = crypto.randomBytes(CODE_LENGTH);
let out = '';
for (let i = 0; i < CODE_LENGTH; i++) {
out += ALPHABET[bytes[i]! % ALPHABET.length];
}
current = out;
return current;
},
get() {
return current;
},
clear() {
current = null;
},
matches(provided: unknown): boolean {
if (!current || typeof provided !== 'string') return false;
const a = Buffer.from(current);
const b = Buffer.from(provided);
if (a.length !== b.length) return false;
return crypto.timingSafeEqual(a, b);
},
};
}
|