3c4f3331d8
Jenkins Production Deployment
Reviewed-on: #8 Co-authored-by: Patrick Mueller <patrick@mueller-patrick.tech> Co-committed-by: Patrick Mueller <patrick@mueller-patrick.tech>
39 lines
1.4 KiB
TypeScript
39 lines
1.4 KiB
TypeScript
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');
|
|
};
|