@catbee/utils
Version:
A modular, production-grade utility toolkit for Node.js and TypeScript, designed for robust, scalable applications (including Express-based services). All utilities are tree-shakable and can be imported independently.
359 lines • 15.7 kB
JavaScript
var __assign = (this && this.__assign) || function () {
__assign = Object.assign || function(t) {
for (var s, i = 1, n = arguments.length; i < n; i++) {
s = arguments[i];
for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p))
t[p] = s[p];
}
return t;
};
return __assign.apply(this, arguments);
};
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
return new (P || (P = Promise))(function (resolve, reject) {
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
step((generator = generator.apply(thisArg, _arguments || [])).next());
});
};
var __generator = (this && this.__generator) || function (thisArg, body) {
var _ = { label: 0, sent: function() { if (t[0] & 1) throw t[1]; return t[1]; }, trys: [], ops: [] }, f, y, t, g = Object.create((typeof Iterator === "function" ? Iterator : Object).prototype);
return g.next = verb(0), g["throw"] = verb(1), g["return"] = verb(2), typeof Symbol === "function" && (g[Symbol.iterator] = function() { return this; }), g;
function verb(n) { return function (v) { return step([n, v]); }; }
function step(op) {
if (f) throw new TypeError("Generator is already executing.");
while (g && (g = 0, op[0] && (_ = 0)), _) try {
if (f = 1, y && (t = op[0] & 2 ? y["return"] : op[0] ? y["throw"] || ((t = y["return"]) && t.call(y), 0) : y.next) && !(t = t.call(y, op[1])).done) return t;
if (y = 0, t) op = [op[0] & 2, t.value];
switch (op[0]) {
case 0: case 1: t = op; break;
case 4: _.label++; return { value: op[1], done: false };
case 5: _.label++; y = op[1]; op = [0]; continue;
case 7: op = _.ops.pop(); _.trys.pop(); continue;
default:
if (!(t = _.trys, t = t.length > 0 && t[t.length - 1]) && (op[0] === 6 || op[0] === 2)) { _ = 0; continue; }
if (op[0] === 3 && (!t || (op[1] > t[0] && op[1] < t[3]))) { _.label = op[1]; break; }
if (op[0] === 6 && _.label < t[1]) { _.label = t[1]; t = op; break; }
if (t && _.label < t[2]) { _.label = t[2]; _.ops.push(op); break; }
if (t[2]) _.ops.pop();
_.trys.pop(); continue;
}
op = body.call(thisArg, _);
} catch (e) { op = [6, e]; y = 0; } finally { f = t = 0; }
if (op[0] & 5) throw op[1]; return { value: op[0] ? op[1] : void 0, done: true };
}
};
var __read = (this && this.__read) || function (o, n) {
var m = typeof Symbol === "function" && o[Symbol.iterator];
if (!m) return o;
var i = m.call(o), r, ar = [], e;
try {
while ((n === void 0 || n-- > 0) && !(r = i.next()).done) ar.push(r.value);
}
catch (error) { e = { error: error }; }
finally {
try {
if (r && !r.done && (m = i["return"])) m.call(i);
}
finally { if (e) throw e.error; }
}
return ar;
};
import { createHmac, createHash, randomUUID, randomBytes, createCipheriv, createDecipheriv, scrypt, timingSafeEqual, } from "crypto";
import { promisify } from "util";
/**
* Generates an HMAC digest using the specified algorithm and secret key.
*
* @param {string} algorithm - The hashing algorithm (e.g., 'sha256', 'sha1').
* @param {string} input - The string to hash.
* @param {string} secret - The secret key for HMAC.
* @param {BinaryToTextEncoding} [encoding='hex'] - Output encoding ('hex', 'base64', etc).
* @returns {string} HMAC digest as a string.
*/
export function hmac(algorithm, input, secret, encoding) {
if (encoding === void 0) { encoding = "hex"; }
return createHmac(algorithm, secret).update(input).digest(encoding);
}
/**
* Generates a hash digest using the specified algorithm.
*
* @param {string} algorithm - The hashing algorithm (e.g., 'sha256', 'md5').
* @param {string} input - The string to hash.
* @param {BinaryToTextEncoding} [encoding='hex'] - Output encoding ('hex', 'base64', etc).
* @returns {string} Hash digest as a string.
*/
export function hash(algorithm, input, encoding) {
if (encoding === void 0) { encoding = "hex"; }
return createHash(algorithm).update(input).digest(encoding);
}
/**
* Generates an HMAC-SHA256 digest.
*
* @param {string} input - The string to hash.
* @param {string} secret - The secret key.
* @returns {string} SHA-256 HMAC digest as a string.
*/
export function sha256Hmac(input, secret) {
return hmac("sha256", input, secret);
}
/**
* Generates a SHA1 hash digest.
*
* @param {string} input - The string to hash.
* @param {BinaryToTextEncoding} [encoding='hex'] - Output encoding.
* @returns {string} SHA-1 hash as a string.
*/
export function sha1(input, encoding) {
if (encoding === void 0) { encoding = "hex"; }
return hash("sha1", input, encoding);
}
/**
* Generates a SHA256 hash digest.
*
* @param {string} input - The string to hash.
* @param {BinaryToTextEncoding} [encoding='hex'] - Output encoding.
* @returns {string} SHA-256 hash as a string.
*/
export function sha256(input, encoding) {
if (encoding === void 0) { encoding = "hex"; }
return hash("sha256", input, encoding);
}
/**
* Generates an MD5 hash digest.
*
* @param {string} input - The string to hash.
* @returns {string} MD5 hash as a string.
*/
export function md5(input) {
return hash("md5", input);
}
/**
* Generates a cryptographically strong random string by hashing a random UUID with SHA-256.
*
* @returns {string} Random string hashed with SHA-256 (hex encoding).
*/
export function randomString() {
return sha256(randomUUID());
}
/**
* Generates a secure random buffer of specified byte length.
*
* @param {number} [byteLength=32] - Number of random bytes to generate.
* @returns {Buffer} Buffer containing random bytes.
*/
export function generateRandomBytes(byteLength) {
if (byteLength === void 0) { byteLength = 32; }
return randomBytes(byteLength);
}
/**
* Generates a secure random string of specified byte length with specified encoding.
*
* @param {number} [byteLength=32] - Number of random bytes to generate.
* @param {BinaryToTextEncoding} [encoding='hex'] - Output encoding.
* @returns {string} Random string in specified encoding.
*/
export function generateRandomBytesAsString(byteLength, encoding) {
if (byteLength === void 0) { byteLength = 32; }
if (encoding === void 0) { encoding = "hex"; }
return randomBytes(byteLength).toString(encoding);
}
/**
* Generates a secure API key with a specified format.
*
* @param {string} [prefix=''] - Optional prefix for the key.
* @param {number} [byteLength=24] - Number of random bytes to generate.
* @returns {string} Formatted API key.
*/
export function generateApiKey(prefix, byteLength) {
if (prefix === void 0) { prefix = ""; }
if (byteLength === void 0) { byteLength = 24; }
var randomString = generateRandomBytesAsString(byteLength, "base64");
var key = randomString
.replace(/[+/=]/g, "") // Remove non-URL-safe characters
.substring(0, 32); // Limit length
return prefix ? "".concat(prefix, "_").concat(key) : key;
}
/**
* Compares two strings, arrays, or buffers in constant time to prevent timing attacks.
*
* @param {string | Buffer | Uint8Array} a - First value to compare
* @param {string | Buffer | Uint8Array} b - Second value to compare
* @returns {boolean} True if values are equal
*/
export function safeCompare(a, b) {
if (typeof a === "string" && typeof b === "string") {
// Convert strings to buffers
var bufA = Buffer.from(a);
var bufB = Buffer.from(b);
// Compare lengths first (not constant time, but prevents timing attack on contents)
if (bufA.length !== bufB.length)
return false;
return timingSafeEqual(bufA, bufB);
}
else if ((a instanceof Buffer || a instanceof Uint8Array) &&
(b instanceof Buffer || b instanceof Uint8Array)) {
// Compare lengths first
if (a.length !== b.length)
return false;
return timingSafeEqual(a instanceof Buffer ? a : Buffer.from(a), b instanceof Buffer ? b : Buffer.from(b));
}
throw new Error("Cannot compare: inputs must be strings, Buffers, or Uint8Arrays");
}
// Promisified version of scrypt for key derivation
var scryptAsync = promisify(scrypt);
/**
* Encrypts data using a symmetric key with secure defaults (AES-256-GCM).
*
* @param {string | Buffer} data - Data to encrypt
* @param {string | Buffer} key - Encryption key or passphrase
* @param {EncryptionOptions} [options] - Encryption options
* @returns {Promise<EncryptionResult>} Encrypted data with metadata
*/
export function encrypt(data_1, key_1) {
return __awaiter(this, arguments, void 0, function (data, key, options) {
var algorithm, inputEncoding, outputEncoding, iv, derivedKey, _a, cipher, ciphertext, encrypted, authTag;
if (options === void 0) { options = {}; }
return __generator(this, function (_b) {
switch (_b.label) {
case 0:
algorithm = options.algorithm || "aes-256-gcm";
inputEncoding = options.inputEncoding || "utf8";
outputEncoding = options.outputEncoding || "hex";
iv = randomBytes(16);
if (!(typeof key === "string")) return [3 /*break*/, 2];
return [4 /*yield*/, scryptAsync(key, iv.slice(0, 8), 32)];
case 1:
_a = _b.sent();
return [3 /*break*/, 3];
case 2:
_a = key;
_b.label = 3;
case 3:
derivedKey = _a;
cipher = createCipheriv(algorithm, derivedKey, iv);
if (typeof data === "string") {
ciphertext = cipher.update(data, inputEncoding, outputEncoding);
ciphertext += cipher.final(outputEncoding);
}
else {
encrypted = Buffer.concat([cipher.update(data), cipher.final()]);
ciphertext = outputEncoding
? encrypted.toString(outputEncoding)
: encrypted;
}
authTag = algorithm.includes("gcm")
? cipher.getAuthTag()
: undefined;
return [2 /*return*/, {
ciphertext: ciphertext,
iv: iv,
authTag: authTag,
algorithm: algorithm,
}];
}
});
});
}
/**
* Decrypts data that was encrypted with the encrypt function.
*
* @param {EncryptionResult} encryptedData - The encrypted data and metadata
* @param {string | Buffer} key - Decryption key or passphrase
* @param {DecryptionOptions} [options] - Decryption options
* @returns {Promise<string | Buffer>} Decrypted data
*/
export function decrypt(encryptedData_1, key_1) {
return __awaiter(this, arguments, void 0, function (encryptedData, key, options) {
var algorithm, inputEncoding, outputEncoding, derivedKey, _a, decipher, decrypted, result;
if (options === void 0) { options = {}; }
return __generator(this, function (_b) {
switch (_b.label) {
case 0:
algorithm = options.algorithm || encryptedData.algorithm || "aes-256-gcm";
inputEncoding = options.inputEncoding || "hex";
outputEncoding = options.outputEncoding || "utf8";
if (!(typeof key === "string")) return [3 /*break*/, 2];
return [4 /*yield*/, scryptAsync(key, encryptedData.iv.slice(0, 8), 32)];
case 1:
_a = _b.sent();
return [3 /*break*/, 3];
case 2:
_a = key;
_b.label = 3;
case 3:
derivedKey = _a;
decipher = createDecipheriv(algorithm, derivedKey, encryptedData.iv);
// Set auth tag if using GCM mode
if (encryptedData.authTag && algorithm.includes("gcm")) {
decipher.setAuthTag(encryptedData.authTag);
}
if (typeof encryptedData.ciphertext === "string") {
decrypted = decipher.update(encryptedData.ciphertext, inputEncoding, outputEncoding);
decrypted += decipher.final(outputEncoding);
}
else {
result = Buffer.concat([
decipher.update(encryptedData.ciphertext),
decipher.final(),
]);
decrypted = outputEncoding ? result.toString(outputEncoding) : result;
}
return [2 /*return*/, decrypted];
}
});
});
}
/**
* Creates a signed token with an expiration time and payload.
*
* @param {object} payload - Data to include in the token
* @param {string} secret - Secret key for signing
* @param {number} [expiresInSeconds=3600] - Token expiration in seconds
* @returns {string} Signed token string
*/
export function createSignedToken(payload, secret, expiresInSeconds) {
if (expiresInSeconds === void 0) { expiresInSeconds = 3600; }
// Create payload with expiration
var tokenPayload = __assign(__assign({}, payload), { exp: Math.floor(Date.now() / 1000) + expiresInSeconds });
// Convert to string
var payloadStr = JSON.stringify(tokenPayload);
// Base64 encode the payload
var base64Payload = Buffer.from(payloadStr).toString("base64url");
// Create signature
var signature = hmac("sha256", base64Payload, secret, "base64url");
// Combine payload and signature
return "".concat(base64Payload, ".").concat(signature);
}
/**
* Verifies and decodes a signed token.
*
* @param {string} token - The token to verify
* @param {string} secret - Secret key for verification
* @returns {object | null} Decoded payload if valid, null if invalid
*/
export function verifySignedToken(token, secret) {
try {
// Split token into parts
var _a = __read(token.split("."), 2), payloadB64 = _a[0], signature = _a[1];
if (!payloadB64 || !signature)
return null;
// Verify signature
var expectedSignature = hmac("sha256", payloadB64, secret, "base64url");
if (!safeCompare(signature, expectedSignature))
return null;
// Decode payload
var payloadStr = Buffer.from(payloadB64, "base64url").toString("utf8");
var payload = JSON.parse(payloadStr);
// Check expiration
if (payload.exp && payload.exp < Math.floor(Date.now() / 1000)) {
return null;
}
return payload;
}
catch (_b) {
return null;
}
}
//# sourceMappingURL=crypto.utils.js.map