UNPKG

@theroyalwhee0/ident

Version:
207 lines 8.09 kB
"use strict"; /** * @file A Unique Identifier Generator for Node * @author Adam Mill <hismajesty@theroyalwhee.com> * @copyright Copyright 2021-2022 Adam Mill * @license Apache-2.0 */ var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.validationBothFactory = exports.validationVerifyFactory = exports.validationSignFactory = exports.validationFactory = exports.identGenerator = void 0; /** * Imports. * @private */ const node_crypto_1 = __importDefault(require("node:crypto")); const istype_1 = require("@theroyalwhee0/istype"); const snowman_1 = require("@theroyalwhee0/snowman"); const base32h_1 = require("@base32h/base32h"); const constants_1 = require("./constants"); /** * Default Options. * @private */ const defaultOptions = { getRandomBytes: (size) => node_crypto_1.default.randomBytes(size), }; /** * Build options from options and defaults. * @private * @param {object} options The options. * @returns {object} The merged/modified options. */ function buildOptions(options) { const built = Object.assign({}, defaultOptions, options); const idOptions = Object.assign({}, built.idOptions); built.idOptions = idOptions; if ('node' in built) { idOptions.node = built.node; } return built; } /** * identGenerator * @generator * @function identGenerator * @param {object} options Options. * @param {number} options.node The numeric ID of the node (0-1023). * @param {string} options.signKey The key to use for signing check. * @param {string} options.verifyKey The key to use for verify check. * @param {string} options.getRandomBytes Function to provide random bytes. * @param {object} options.idOptions Options passed to snowman. * @yields {string} The created ident. */ function* identGenerator(options) { options = buildOptions(options); const { verifyKey, signKey, getRandomBytes } = options; const ids = (0, snowman_1.idSequence)(options.idOptions); while (1) { // Create the buffer. const buffer = Buffer.alloc(constants_1.ALL_SIZE, 0); // Add the id. const { value: id, done } = ids.next(); if (done) { throw new Error(`id sequence should never be done.`); } buffer.writeBigUInt64BE(id, 0); // Add the random bytes. const rnd = getRandomBytes(constants_1.RND_SIZE); rnd.copy(buffer, constants_1.ID_SIZE, 0, constants_1.RND_SIZE); // Add verification hmac. const verifyBuffer = buffer.slice(0, constants_1.ID_SIZE + constants_1.RND_SIZE); const hmacVerify = node_crypto_1.default.createHmac(constants_1.HMAC_ALGO, verifyKey); const verify = hmacVerify.update(verifyBuffer).digest(); verify.copy(buffer, constants_1.ID_SIZE + constants_1.RND_SIZE, 0, constants_1.VERIFY_SIZE); // Add signature hmac. const signBuffer = buffer.slice(0, constants_1.ID_SIZE + constants_1.RND_SIZE + constants_1.VERIFY_SIZE); const hmacSign = node_crypto_1.default.createHmac(constants_1.HMAC_ALGO, signKey); const sign = hmacSign.update(signBuffer).digest(); sign.copy(buffer, constants_1.ID_SIZE + constants_1.RND_SIZE + constants_1.VERIFY_SIZE, 0, constants_1.SIGN_SIZE); // Encode buffer and strip leading zeros. const ident = (0, base32h_1.encodeBin)(buffer).replace(/^0+/, ''); yield ident; } } exports.identGenerator = identGenerator; /** * Left trim buffer. * @private * @param {Buffer} buffer A buffer to left trim. * @param {number} byte The byte value to trim. Defaults to zero. * @returns {Buffer} The trimmed buffer. */ function leftTrimBuffer(buffer, byte = 0) { let idx; for (idx = 0; idx < buffer.length; idx++) { if (buffer[idx] !== byte) { break; } } return idx === 0 ? buffer : buffer.slice(idx); } /** * Low-level validation factory. * Use validationVerifyFactory, validationSignFactory, or * validationBothFactory instead. * @param {object} options Options. * @param {string} options.signKey The key to use for signing check. * @param {string} options.verifyKey The key to use for verify check. * @returns True if valid, false if not. */ function validationFactory(options) { options = buildOptions(options); const { verifyKey, signKey } = options; return function validation(value) { if (!(0, istype_1.isString)(value) || !constants_1.re_lax.test(value)) { return false; } const decoded = leftTrimBuffer(Buffer.from((0, base32h_1.decodeBin)(value))); const buffer = decoded.length < constants_1.ALL_SIZE ? Buffer.concat([Buffer.alloc(constants_1.ALL_SIZE - decoded.length, 0), decoded]) : decoded; if (buffer.length !== constants_1.ALL_SIZE) { return false; } let start = 0, end = constants_1.ID_SIZE; const id = buffer.readBigUInt64BE(); // Check ID. const [, , , idValid] = (0, snowman_1.explodeId)(id); if (!idValid) { return false; } start = end; end += constants_1.RND_SIZE; start = end; end += constants_1.VERIFY_SIZE; const verify = buffer.slice(start, end); start = end; end += constants_1.SIGN_SIZE; const sign = buffer.slice(start, end); if (verifyKey) { // Check verify hmac if given verify key... const hmacVerify = node_crypto_1.default.createHmac(constants_1.HMAC_ALGO, verifyKey); const verifyBuffer = buffer.slice(0, constants_1.ID_SIZE + constants_1.RND_SIZE); const verifyCheck = hmacVerify.update(verifyBuffer).digest().slice(0, constants_1.VERIFY_SIZE); if (!node_crypto_1.default.timingSafeEqual(verify, verifyCheck)) { return false; } } if (signKey) { // Check sign hmac if given sign key... const hmacSign = node_crypto_1.default.createHmac(constants_1.HMAC_ALGO, signKey); const signBuffer = buffer.slice(0, constants_1.ID_SIZE + constants_1.RND_SIZE + constants_1.VERIFY_SIZE); const signCheck = hmacSign.update(signBuffer).digest().slice(0, constants_1.SIGN_SIZE); if (!node_crypto_1.default.timingSafeEqual(sign, signCheck)) { return false; } } return true; }; } exports.validationFactory = validationFactory; /** * Validation factory requring a signKey. * @param {object} options Options. * @param {string} options.signKey The key to use for signing check. * @returns {boolean} True if valid, false if not. */ function validationSignFactory(options) { if (!options?.signKey) { throw new Error('signKey is required.'); } return validationFactory(options); } exports.validationSignFactory = validationSignFactory; /** * Validation factory requring a verifyKey. * @param {object} options Options. * @param {string} options.verifyKey The key to use for verify check. * @returns {boolean} True if valid, false if not. */ function validationVerifyFactory(options) { if (!options?.verifyKey) { throw new Error('verifyKey is required.'); } return validationFactory(options); } exports.validationVerifyFactory = validationVerifyFactory; /** * Validation factory requring both keys. * @param {object} options Options. * @param {string} options.signKey The key to use for signing check. * @param {string} options.verifyKey The key to use for verify check. * @returns {boolean} True if valid, false if not. */ function validationBothFactory(options) { if (!options?.signKey) { throw new Error('signKey is required.'); } if (!options?.verifyKey) { throw new Error('verifyKey is required.'); } return validationFactory(options); } exports.validationBothFactory = validationBothFactory; //# sourceMappingURL=index.js.map