// API keys and HMAC secrets: random bytes from the CSPRNG first, then encoded as text. import { type ByteEncoding, encodeBytes } from './encoding'; import { randomBytes } from './random'; export const API_KEY_BYTES = { min: 16, max: 128 } as const; export const JWT_SECRET_BYTES = { min: 32, max: 256 } as const; export const KEY_PREFIX_PATTERN = /^[A-Za-z0-9_-]{0,16}$/; /** HMAC JWT algorithms whose key-size rule this many bytes meets (RFC 7518, section 3.2). */ export const HMAC_MIN_BYTES = { HS256: 32, HS384: 48, HS512: 64 } as const; function validateBytes(bytes: number, range: { min: number; max: number }) { if (!Number.isInteger(bytes) || bytes < range.min || bytes > range.max) { throw new RangeError(`Byte length must be between ${range.min} and ${range.max}.`); } } export function generateApiKey(options: { bytes: number; encoding: Exclude; prefix: string; }): string { validateBytes(options.bytes, API_KEY_BYTES); if (!KEY_PREFIX_PATTERN.test(options.prefix)) { throw new RangeError('The prefix may use up to 16 letters, digits, "_" or "-".'); } return options.prefix + encodeBytes(randomBytes(options.bytes), options.encoding); } export function generateJwtSecret(options: { bytes: number; encoding: ByteEncoding }): string { validateBytes(options.bytes, JWT_SECRET_BYTES); return encodeBytes(randomBytes(options.bytes), options.encoding); } export function hmacAlgorithmsFor(bytes: number): (keyof typeof HMAC_MIN_BYTES)[] { return (Object.keys(HMAC_MIN_BYTES) as (keyof typeof HMAC_MIN_BYTES)[]).filter( (name) => bytes >= HMAC_MIN_BYTES[name], ); }