Reviewed-on: #8 Co-authored-by: Patrick Mueller <patrick@mueller-patrick.tech> Co-committed-by: Patrick Mueller <patrick@mueller-patrick.tech>
This commit was merged in pull request #8.
This commit is contained in:
@@ -0,0 +1,38 @@
|
||||
import * as crypto from 'crypto';
|
||||
|
||||
// Excludes 0/O, 1/I/L to avoid look-alike confusion when a code is
|
||||
// hand-written, read aloud, or typed from a printed fallback under a QR
|
||||
// code. 31 symbols * 8 chars ≈ 39.6 bits of entropy - effectively
|
||||
// unguessable combined with rate-limiting on the redeem endpoint.
|
||||
const CODE_ALPHABET = 'ABCDEFGHJKMNPQRSTUVWXYZ23456789';
|
||||
const CODE_LENGTH = 8;
|
||||
|
||||
/**
|
||||
* Generates a single random code. Uses crypto.randomInt (uniform, no
|
||||
* modulo bias) rather than Math.random() since these gate a real-world
|
||||
* concert invitation.
|
||||
*/
|
||||
export const generateCode = (): string => {
|
||||
let code = '';
|
||||
for (let i = 0; i < CODE_LENGTH; i++) {
|
||||
code += CODE_ALPHABET[crypto.randomInt(CODE_ALPHABET.length)];
|
||||
}
|
||||
return code;
|
||||
};
|
||||
|
||||
/**
|
||||
* Generates a code guaranteed not to collide with any row `existingCodes`
|
||||
* already contains, tries up to `maxAttempts` times before giving up.
|
||||
* Collisions are astronomically unlikely at this entropy - this exists as
|
||||
* a correctness backstop, not because collisions are expected.
|
||||
*/
|
||||
export const generateUniqueCode = (existingCodes: Set<string>, maxAttempts = 20): string => {
|
||||
for (let i = 0; i < maxAttempts; i++) {
|
||||
const candidate = generateCode();
|
||||
if (!existingCodes.has(candidate)) {
|
||||
existingCodes.add(candidate);
|
||||
return candidate;
|
||||
}
|
||||
}
|
||||
throw new Error('Could not generate a unique voucher code after ' + maxAttempts + ' attempts');
|
||||
};
|
||||
Reference in New Issue
Block a user