samlify
Version:
Node.js library for SAML SSO
347 lines • 10.9 kB
JavaScript
;
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;
};
var __spreadArray = (this && this.__spreadArray) || function (to, from, pack) {
if (pack || arguments.length === 2) for (var i = 0, l = from.length, ar; i < l; i++) {
if (ar || !(i in from)) {
if (!ar) ar = Array.prototype.slice.call(from, 0, i);
ar[i] = from[i];
}
}
return to.concat(ar || Array.prototype.slice.call(from));
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.zipObject = zipObject;
exports.flattenDeep = flattenDeep;
exports.last = last;
exports.uniq = uniq;
exports.get = get;
exports.isString = isString;
exports.base64Decode = base64Decode;
exports.inflateString = inflateString;
exports.readPrivateKey = readPrivateKey;
exports.isNonEmptyArray = isNonEmptyArray;
exports.castArrayOpt = castArrayOpt;
exports.notEmpty = notEmpty;
exports.escapeXPathValue = escapeXPathValue;
exports.camelCase = camelCase;
/**
* @file utility.ts
* @author tngan
* @desc Common helpers (encoding, compression, certificate / key handling).
*/
var crypto_1 = require("crypto");
var zlib_1 = require("zlib");
var BASE64_STR = 'base64';
/**
* Build an object by zipping two parallel arrays of keys and values.
* When `skipDuplicated` is false, colliding keys are aggregated into arrays
* so duplicate keys do not clobber earlier values.
*
* @param arr1 key array
* @param arr2 value array (same index as keys)
* @param skipDuplicated when true (default) later writes overwrite earlier ones
* @returns object composed from key/value pairs
*/
function zipObject(arr1, arr2, skipDuplicated) {
if (skipDuplicated === void 0) { skipDuplicated = true; }
return arr1.reduce(function (res, l, i) {
if (skipDuplicated) {
res[l] = arr2[i];
return res;
}
if (res[l] !== undefined) {
res[l] = Array.isArray(res[l])
? res[l].concat(arr2[i])
: [res[l]].concat(arr2[i]);
return res;
}
res[l] = arr2[i];
return res;
}, {});
}
/**
* Recursively flatten a nested array into a single-level array.
*
* @param input nested array input
* @returns flattened array
*/
function flattenDeep(input) {
return Array.isArray(input)
? input.reduce(function (a, b) { return a.concat(flattenDeep(b)); }, [])
: [input];
}
/**
* Return the last element of an array.
*
* @param input source array
* @returns the final element, or undefined when the array is empty
*/
function last(input) {
return input.slice(-1)[0];
}
/**
* Return a copy of a string array with duplicates removed.
*
* @param input array with possible duplicates
* @returns array in original order without duplicates
*/
function uniq(input) {
var set = new Set(input);
return __spreadArray([], __read(set), false);
}
/**
* Safely read a dotted path from an object, returning `defaultValue` when
* any segment is missing.
*
* @param obj source object
* @param path dotted path expression (e.g. "a.b.c")
* @param defaultValue fallback when the path does not resolve
* @returns resolved value or the default
*/
function get(obj, path, defaultValue) {
if (defaultValue === void 0) { defaultValue = null; }
return path
.split('.')
.reduce(function (a, c) {
if (a && typeof a === 'object' && c in a) {
var next = a[c];
return next !== null && next !== void 0 ? next : defaultValue;
}
return defaultValue;
}, obj);
}
/**
* Type guard for strings.
*
* @param input value to test
* @returns true when the input is a string primitive
*/
function isString(input) {
return typeof input === 'string';
}
/**
* Encode a string or byte array as base64.
*
* @param message plain text or raw bytes
* @returns base64 encoded string
*/
function base64Encode(message) {
return Buffer.from(message).toString(BASE64_STR);
}
/**
* Decode a base64 message. Returns either the decoded string or the raw
* Buffer depending on `isBytes`.
*
* @param base64Message base64 encoded payload
* @param isBytes when true, return a Buffer instead of a string
* @returns decoded string or Buffer
*/
function base64Decode(base64Message, isBytes) {
var bytes = Buffer.from(base64Message, BASE64_STR);
return Boolean(isBytes) ? bytes : bytes.toString();
}
/**
* Raw-deflate a UTF-8 string and return the compressed bytes.
*
* @param message plain text
* @returns compressed bytes as a number array
*/
function deflateString(message) {
var input = Buffer.from(message, 'utf8');
return Array.from((0, zlib_1.deflateRawSync)(input));
}
/**
* Raw-inflate a base64 string that was produced by {@link deflateString}.
*
* @param compressedString base64-encoded raw-deflate payload
* @returns decompressed UTF-8 string
*/
function inflateString(compressedString) {
var inputBuffer = Buffer.from(compressedString, BASE64_STR);
return (0, zlib_1.inflateRawSync)(inputBuffer).toString('utf8');
}
/**
* Strip PEM header/footer, whitespace and newlines from a PEM payload.
*/
function _normalizeCerString(bin, format) {
return bin
.toString()
.replace(/\n/g, '')
.replace(/\r/g, '')
.replace("-----BEGIN ".concat(format, "-----"), '')
.replace("-----END ".concat(format, "-----"), '')
.replace(/ /g, '')
.replace(/\t/g, '');
}
/**
* Normalise a PEM certificate string to its base64 body.
*
* @param certString PEM-encoded X.509 certificate
* @returns certificate body without headers/whitespace
*/
function normalizeCerString(certString) {
return _normalizeCerString(certString, 'CERTIFICATE');
}
/**
* Normalise a PEM RSA private key string to its base64 body.
*
* @param pemString PEM-encoded RSA private key
* @returns key body without headers/whitespace
*/
function normalizePemString(pemString) {
return _normalizeCerString(pemString.toString(), 'RSA PRIVATE KEY');
}
/**
* Reconstruct the full URL (protocol + host + path) from an Express-style
* HTTP request.
*
* @param req Express-compatible request object
* @returns absolute URL string
*/
function getFullURL(req) {
return "".concat(req.protocol, "://").concat(req.get('host')).concat(req.originalUrl);
}
/**
* Return `str` when it is truthy, otherwise the provided default.
*/
function parseString(str, defaultValue) {
if (defaultValue === void 0) { defaultValue = ''; }
return str || defaultValue;
}
/**
* Shallow-merge `obj2` on top of `obj1`, returning a new object.
*/
function applyDefault(obj1, obj2) {
return Object.assign({}, obj1, obj2);
}
/**
* Extract the SPKI PEM public key from a base64 X.509 certificate body.
*
* @param x509Certificate normalised certificate body (no PEM wrappers)
* @returns PEM-encoded public key
*/
function getPublicKeyPemFromCertificate(x509Certificate) {
var der = Buffer.from(x509Certificate, 'base64');
var cert = new crypto_1.X509Certificate(der);
return cert.publicKey.export({ type: 'spki', format: 'pem' });
}
/**
* Read a PEM private key, optionally decrypting it with a passphrase.
*
* @param keyString PEM key contents
* @param passphrase optional passphrase protecting the key
* @param isOutputString when true, always return a string
* @returns PEM key as string or Buffer
*/
function readPrivateKey(keyString, passphrase, isOutputString) {
if (isString(passphrase)) {
var key = (0, crypto_1.createPrivateKey)({ key: keyString, format: 'pem', passphrase: passphrase });
var pem = key.export({ type: 'pkcs1', format: 'pem' });
return convertToString(pem, isOutputString);
}
return keyString;
}
/**
* Coerce a value to a string when `isOutputString` is true, otherwise pass
* it through untouched.
*/
function convertToString(input, isOutputString) {
return Boolean(isOutputString) ? String(input) : input;
}
/**
* Check that the input is an array with at least one element.
*
* @param a candidate value
* @returns true when the argument is a non-empty array
*/
function isNonEmptyArray(a) {
return Array.isArray(a) && a.length > 0;
}
/**
* Wrap a single value in an array, or return the array unchanged.
* An undefined input returns an empty array.
*
* @param a scalar, array, or undefined
* @returns array form of the input
*/
function castArrayOpt(a) {
if (a === undefined)
return [];
return Array.isArray(a) ? a : [a];
}
/**
* Type guard removing `null` and `undefined` from a union.
*
* @param value value to narrow
* @returns true when the value is neither null nor undefined
*/
function notEmpty(value) {
return value !== null && value !== undefined;
}
/**
* Escape a string for safe use inside an XPath single-quoted string literal.
* Prevents XPath injection by splitting on single quotes and using concat().
*
* @param value raw string that may contain quotes
* @returns XPath-safe string expression
*/
function escapeXPathValue(value) {
if (!value.includes("'")) {
return "'" + value + "'";
}
var parts = value.split("'").map(function (part) { return "'" + part + "'"; });
return 'concat(' + parts.join(",\"'\",") + ')';
}
/**
* Convert a string to camelCase, splitting on whitespace, `-`, `_`, `.`,
* and inferred case boundaries.
*
* @param input source string
* @returns camelCased output
*/
function camelCase(input) {
var words = input
.replace(/([a-z\d])([A-Z])/g, '$1\0$2')
.replace(/([A-Z]+)([A-Z][a-z])/g, '$1\0$2')
.split(/[\0\s\-_\.]+/)
.filter(function (w) { return w.length > 0; });
return words
.map(function (word, i) {
var lower = word.toLocaleLowerCase('en-US');
return i === 0 ? lower : lower.charAt(0).toLocaleUpperCase('en-US') + lower.slice(1);
})
.join('');
}
var utility = {
isString: isString,
base64Encode: base64Encode,
base64Decode: base64Decode,
deflateString: deflateString,
inflateString: inflateString,
normalizeCerString: normalizeCerString,
normalizePemString: normalizePemString,
getFullURL: getFullURL,
parseString: parseString,
applyDefault: applyDefault,
getPublicKeyPemFromCertificate: getPublicKeyPemFromCertificate,
readPrivateKey: readPrivateKey,
convertToString: convertToString,
isNonEmptyArray: isNonEmptyArray,
};
exports.default = utility;
//# sourceMappingURL=utility.js.map