@bsv/auth-express-middleware
Version:
BSV Blockchain mutual-authentication express middleware
191 lines • 7.58 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", { value: true });
exports.isLogLevelEnabled = isLogLevelEnabled;
exports.getLogMethod = getLogMethod;
exports.writeUrlToWriter = writeUrlToWriter;
exports.writeRequestHeadersToWriter = writeRequestHeadersToWriter;
exports.writeHeaderPair = writeHeaderPair;
exports.writeBodyToWriter = writeBodyToWriter;
exports.convertValueToArray = convertValueToArray;
exports.makeDebugLogger = makeDebugLogger;
const sdk_1 = require("@bsv/sdk");
const LOG_LEVELS = ['debug', 'info', 'warn', 'error'];
/**
* Helper to determine if a given message-level log should be output
* based on the configured log level.
*/
function isLogLevelEnabled(configuredLevel, messageLevel) {
return LOG_LEVELS.indexOf(messageLevel) >= LOG_LEVELS.indexOf(configuredLevel);
}
/**
* Retrieves the appropriate logging method from the logger,
* falling back to `log` if not found.
*
* Uses an explicit switch to avoid dynamic property access on a user-influenced
* key, which prevents CodeQL js/unvalidated-dynamic-method-call alerts.
*/
function getLogMethod(logger, level) {
switch (level) {
case 'debug': return (typeof logger.debug === 'function' ? logger.debug : logger.log).bind(logger);
case 'info': return (typeof logger.info === 'function' ? logger.info : logger.log).bind(logger);
case 'warn': return (typeof logger.warn === 'function' ? logger.warn : logger.log).bind(logger);
case 'error': return (typeof logger.error === 'function' ? logger.error : logger.log).bind(logger);
default: return logger.log.bind(logger);
}
}
/**
* Write the URL pathname and search components to the binary writer.
*/
function writeUrlToWriter(parsedUrl, writer) {
if (parsedUrl.pathname.length > 0) {
const pathnameAsArray = sdk_1.Utils.toArray(parsedUrl.pathname);
writer.writeVarIntNum(pathnameAsArray.length);
writer.write(pathnameAsArray);
}
else {
writer.writeVarIntNum(-1);
}
if (parsedUrl.search.length > 0) {
const searchAsArray = sdk_1.Utils.toArray(parsedUrl.search);
writer.writeVarIntNum(searchAsArray.length);
writer.write(searchAsArray);
}
else {
writer.writeVarIntNum(-1);
}
}
/**
* Collect and write signed request headers to the binary writer.
*/
function writeRequestHeadersToWriter(req, writer) {
const includedHeaders = [];
for (let [k, v] of Object.entries(req.headers)) {
k = k.toLowerCase();
// Normalise to a single string — Express may return string[] when a header
// is repeated (e.g. `Set-Cookie`). Take the first value to avoid
// type-confusion (CodeQL js/type-confusion-through-parameter-tampering).
const vStr = Array.isArray(v) ? v[0] : (typeof v === 'string' ? v : '');
let headerValue = vStr;
if (k === 'content-type') {
headerValue = vStr.split(';')[0].trim();
}
if ((k.startsWith('x-bsv-') || k === 'content-type' || k === 'authorization') &&
!k.startsWith('x-bsv-auth')) {
includedHeaders.push([k, headerValue]);
}
}
includedHeaders.sort(([keyA], [keyB]) => keyA.localeCompare(keyB));
writer.writeVarIntNum(includedHeaders.length);
for (const [headerKey, headerValue] of includedHeaders) {
writeHeaderPair(writer, headerKey, headerValue);
}
}
/**
* Write a header pair (key + value) to the binary writer.
*/
function writeHeaderPair(writer, key, value) {
const keyBytes = sdk_1.Utils.toArray(key, 'utf8');
writer.writeVarIntNum(keyBytes.length);
writer.write(keyBytes);
const valueBytes = sdk_1.Utils.toArray(value, 'utf8');
writer.writeVarIntNum(valueBytes.length);
writer.write(valueBytes);
}
/**
* Helper: Write body to writer
*/
function writeBodyToWriter(req, writer, logger, logLevel) {
const { body, headers } = req;
const debugLog = makeDebugLogger(logger, logLevel);
// Inline-normalised content-type to a single string (Express may return string[]).
// Inline narrowing rather than a helper so CodeQL's dataflow analysis can see
// the explicit type guard (avoids js/type-confusion-through-parameter-tampering).
const rawContentType = headers['content-type'];
let contentType = '';
if (typeof rawContentType === 'string') {
contentType = rawContentType;
}
else if (Array.isArray(rawContentType) && typeof rawContentType[0] === 'string') {
contentType = rawContentType[0];
}
if (Array.isArray(body) && body.every((item) => typeof item === 'number')) {
writer.writeVarIntNum(body.length);
writer.write(body);
debugLog('[writeBodyToWriter] Body recognized as number[]', { length: body.length });
return;
}
if (body instanceof Uint8Array) {
writer.writeVarIntNum(body.length);
writer.write(Array.from(body));
debugLog('[writeBodyToWriter] Body recognized as Uint8Array', { length: body.length });
return;
}
if (contentType === 'application/json' && typeof body === 'object') {
const bodyAsArray = sdk_1.Utils.toArray(JSON.stringify(body), 'utf8');
writer.writeVarIntNum(bodyAsArray.length);
writer.write(bodyAsArray);
debugLog('[writeBodyToWriter] Body recognized as JSON', { body });
return;
}
if (contentType === 'application/x-www-form-urlencoded' &&
body !== null &&
typeof body === 'object' &&
!Array.isArray(body) &&
Object.keys(body).length > 0) {
const parsedBody = new URLSearchParams(body).toString();
const bodyAsArray = sdk_1.Utils.toArray(parsedBody, 'utf8');
writer.writeVarIntNum(bodyAsArray.length);
writer.write(bodyAsArray);
debugLog('[writeBodyToWriter] Body recognized as x-www-form-urlencoded', { parsedBody });
return;
}
if (contentType === 'text/plain' && typeof body === 'string' && body.length > 0) {
const bodyAsArray = sdk_1.Utils.toArray(body, 'utf8');
writer.writeVarIntNum(bodyAsArray.length);
writer.write(bodyAsArray);
debugLog('[writeBodyToWriter] Body recognized as text/plain', { body });
return;
}
// No valid body
writer.writeVarIntNum(-1);
debugLog('[writeBodyToWriter] No valid body to write', undefined);
}
/**
* Helper: Convert values passed to res.send(...) into byte arrays
*/
function convertValueToArray(val, responseHeaders) {
if (typeof val === 'string') {
return sdk_1.Utils.toArray(val, 'utf8');
}
if (val instanceof Buffer) {
return Array.from(val);
}
if (typeof val === 'object' && val !== null) {
if (!responseHeaders['content-type']) {
responseHeaders['content-type'] = 'application/json';
}
return sdk_1.Utils.toArray(JSON.stringify(val), 'utf8');
}
if (typeof val === 'number') {
return sdk_1.Utils.toArray(val.toString(), 'utf8');
}
return sdk_1.Utils.toArray(String(val), 'utf8');
}
/**
* Returns a no-op or a bound debug logger depending on config.
*/
function makeDebugLogger(logger, logLevel) {
if (logger && logLevel && isLogLevelEnabled(logLevel, 'debug')) {
const fn = getLogMethod(logger, 'debug');
return (msg, data) => {
if (data !== undefined) {
fn(msg, data);
}
else {
fn(msg);
}
};
}
return () => { };
}
//# sourceMappingURL=authMiddlewareHelpers.js.map