@bsv/auth-express-middleware
Version:
BSV Blockchain mutual-authentication express middleware
753 lines • 37.4 kB
JavaScript
"use strict";
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.ExpressTransport = void 0;
exports.createAuthMiddleware = createAuthMiddleware;
const fs_1 = __importDefault(require("fs"));
const mime_types_1 = __importDefault(require("mime-types"));
const sdk_1 = require("@bsv/sdk");
/**
* Helper to determine if a given message-level log should be output
* based on the configured log level.
*/
function isLogLevelEnabled(configuredLevel, messageLevel) {
const levels = ['debug', 'info', 'warn', 'error'];
const configuredIndex = levels.indexOf(configuredLevel);
const messageIndex = levels.indexOf(messageLevel);
return messageIndex >= configuredIndex;
}
/**
* Retrieves the appropriate logging method from the logger,
* falling back to `log` if not found.
*/
function getLogMethod(logger, level) {
switch (level) {
case 'debug':
return typeof logger.debug === 'function' ? logger.debug.bind(logger) : logger.log.bind(logger);
case 'info':
// We'll map 'info' to console.info if available
return typeof logger.info === 'function' ? logger.info.bind(logger) : logger.log.bind(logger);
case 'warn':
return typeof logger.warn === 'function' ? logger.warn.bind(logger) : logger.log.bind(logger);
case 'error':
return typeof logger.error === 'function' ? logger.error.bind(logger) : logger.log.bind(logger);
default:
return logger.log.bind(logger);
}
}
/**
* Transport implementation for Express.
*/
class ExpressTransport {
/**
* Constructs a new ExpressTransport instance.
*
* @param {boolean} [allowUnauthenticated=false] - Whether to allow unauthenticated requests passed the auth middleware.
* If `true`, requests without authentication will be permitted, and `req.auth.identityKey`
* will be set to `"unknown"`. If `false`, unauthenticated requests will result in a `401 Unauthorized` response.
* @param {typeof console} [logger] - Logger to use (e.g., console). If omitted, logging is disabled.
* @param {'debug' | 'info' | 'warn' | 'error'} [logLevel] - Log level. If omitted, no logs are output.
*/
constructor(allowUnauthenticated = false, logger, logLevel) {
this.openNonGeneralHandles = {};
this.openGeneralHandles = {};
this.openNextHandlers = {};
this.allowAuthenticated = allowUnauthenticated;
this.logger = logger;
this.logLevel = logLevel || 'error'; // Default to 'error' if not provided
}
/**
* Internal logging method, only logs if logger is defined and log level is appropriate.
*
* @param level - The log level for this message
* @param message - The message to log
* @param data - Optional additional data to log
*/
log(level, message, data) {
if (typeof this.logger !== 'object')
return; // Logging disabled
if (!isLogLevelEnabled(this.logLevel, level))
return;
const logMethod = getLogMethod(this.logger, level);
if (data !== undefined) {
logMethod(`[ExpressTransport] [${level.toUpperCase()}] ${message}`, data);
}
else {
logMethod(`[ExpressTransport] [${level.toUpperCase()}] ${message}`);
}
}
setPeer(peer) {
this.peer = peer;
this.log('debug', 'Peer set in ExpressTransport', { peer });
}
/**
* Sends an AuthMessage to the connected Peer.
* This method uses an Express response object to deliver the message to the specified Peer.
*
* ### Parameters:
* @param {AuthMessage} message - The authenticated message to send.
*
* ### Returns:
* @returns {Promise<void>} A promise that resolves once the message has been sent successfully.
*/
async send(message) {
this.log('debug', `Attempting to send AuthMessage`, { message });
if (message.messageType !== 'general') {
const handles = this.openNonGeneralHandles[message.yourNonce];
if (!Array.isArray(handles) || handles.length === 0) {
this.log('warn', `No open handles to peer for nonce`, { yourNonce: message.yourNonce });
throw new Error('No open handles to this peer!');
}
else {
// Since this is an initial response, we can assume there's only one handle per identity
const { res, next } = handles[0];
const responseHeaders = {};
responseHeaders['x-bsv-auth-version'] = message.version;
responseHeaders['x-bsv-auth-message-type'] = message.messageType;
responseHeaders['x-bsv-auth-identity-key'] = message.identityKey;
responseHeaders['x-bsv-auth-nonce'] = message.nonce;
responseHeaders['x-bsv-auth-your-nonce'] = message.yourNonce;
responseHeaders['x-bsv-auth-signature'] = sdk_1.Utils.toHex(message.signature);
if (typeof message.requestedCertificates === 'object') {
responseHeaders['x-bsv-auth-requested-certificates'] = JSON.stringify(message.requestedCertificates);
}
if (res.__set !== undefined) {
this.resetRes(res, next);
}
for (const [k, v] of Object.entries(responseHeaders)) {
res.set(k, v);
}
this.log('info', 'Sending non-general AuthMessage response', {
status: 200,
responseHeaders,
messagePayload: message
});
res.send(message);
handles.shift();
}
}
else {
// General message
const reader = new sdk_1.Utils.Reader(message.payload);
const requestId = sdk_1.Utils.toBase64(reader.read(32));
if (typeof this.openGeneralHandles[requestId] !== 'object') {
this.log('warn', `No response handle for this requestId`, { requestId });
throw new Error('No response handle for this requestId!');
}
let { res, next } = this.openGeneralHandles[requestId];
delete this.openGeneralHandles[requestId];
const statusCode = reader.readVarIntNum();
res.__status(statusCode);
const responseHeaders = {};
const nHeaders = reader.readVarIntNum();
if (nHeaders > 0) {
for (let i = 0; i < nHeaders; i++) {
const nHeaderKeyBytes = reader.readVarIntNum();
const headerKeyBytes = reader.read(nHeaderKeyBytes);
const headerKey = sdk_1.Utils.toUTF8(headerKeyBytes);
const nHeaderValueBytes = reader.readVarIntNum();
const headerValueBytes = reader.read(nHeaderValueBytes);
const headerValue = sdk_1.Utils.toUTF8(headerValueBytes);
responseHeaders[headerKey] = headerValue;
}
}
responseHeaders['x-bsv-auth-version'] = message.version;
responseHeaders['x-bsv-auth-identity-key'] = message.identityKey;
responseHeaders['x-bsv-auth-nonce'] = message.nonce;
responseHeaders['x-bsv-auth-your-nonce'] = message.yourNonce;
responseHeaders['x-bsv-auth-signature'] = sdk_1.Utils.toHex(message.signature);
responseHeaders['x-bsv-auth-request-id'] = requestId;
if (message.requestedCertificates) {
responseHeaders['x-bsv-auth-requested-certificates'] = JSON.stringify(message.requestedCertificates);
}
for (const [k, v] of Object.entries(responseHeaders)) {
;
res.__set(k, v);
}
let responseBody;
const responseBodyBytes = reader.readVarIntNum();
if (responseBodyBytes > 0) {
responseBody = reader.read(responseBodyBytes);
}
res = this.resetRes(res, next);
this.log('info', `Sending general AuthMessage response`, {
status: statusCode,
responseHeaders,
responseBodyLength: responseBody ? responseBody.length : 0,
requestId
});
if (responseBody) {
res.send(Buffer.from(new Uint8Array(responseBody)));
}
else {
res.end();
}
}
}
/**
* Stores the callback bound by a Peer
* @param callback
*/
async onData(callback) {
this.log('debug', `onData callback set`);
// Just store the callback
this.messageCallback = callback;
}
/**
* Handles an incoming request for the Express server.
*
* This method processes both general and non-general message types,
* manages peer-to-peer certificate handling, and modifies the response object
* to enable custom behaviors like certificate requests and tailored responses.
*
* ### Behavior:
* - For `/.well-known/auth`:
* - Handles non-general messages and listens for certificates.
* - Calls the `onCertificatesReceived` callback (if provided) when certificates are received.
* - For general messages:
* - Sets up a listener for peer-to-peer general messages.
* - Overrides response methods (`send`, `json`, etc.) for custom handling.
* - Returns a 401 error if mutual authentication fails.
*
* ### Parameters:
* @param {AuthRequest} req - The incoming HTTP request.
* @param {Response} res - The HTTP response.
* @param {NextFunction} next - The Express `next` middleware function.
* @param {Function} [onCertificatesReceived] - Optional callback invoked when certificates are received.
*/
handleIncomingRequest(req, res, next, onCertificatesReceived) {
this.log('debug', `Handling incoming request`, {
path: req.path,
headers: req.headers,
method: req.method,
body: req.body
});
try {
if (!this.peer) {
this.log('error', `No Peer set in ExpressTransport! Cannot handle request.`);
throw new Error('You must set a Peer before you can handle incoming requests!');
}
if (req.path === '/.well-known/auth') {
// Non-general message
const message = req.body;
this.log('debug', `Received non-general message at /.well-known/auth`, { message });
// Get a the request id
let requestId = req.headers['x-bsv-auth-request-id'];
if (!requestId) {
requestId = message.initialNonce;
}
if (Array.isArray(this.openNonGeneralHandles[requestId])) {
this.openNonGeneralHandles[requestId].push({ res, next });
}
else {
this.openNonGeneralHandles[requestId] = [{ res, next }];
}
if (!this.peer.sessionManager.hasSession(message.identityKey)) {
const listenerId = this.peer.listenForCertificatesReceived((senderPublicKey, certs) => {
var _a;
this.log('debug', 'Certificates received event triggered', {
senderPublicKey,
certCount: certs === null || certs === void 0 ? void 0 : certs.length
});
if (senderPublicKey !== req.body.identityKey) {
return;
}
if (!Array.isArray(certs) || certs.length === 0) {
this.log('warn', 'No certificates provided by peer', { senderPublicKey });
this.openNonGeneralHandles[senderPublicKey][0].res
.status(400)
.json({ status: 'No certificates provided' });
}
else {
this.log('info', 'Certificates successfully received from peer', {
senderPublicKey,
certs
});
// this.openNonGeneralHandles[message.initialNonce!][0].res.json({ status: 'certificate received' })
if (typeof onCertificatesReceived === 'function') {
onCertificatesReceived(senderPublicKey, certs, req, res, next);
}
const nextFn = this.openNextHandlers[message.identityKey];
if (typeof nextFn === 'function') {
nextFn();
delete this.openNextHandlers[message.identityKey];
}
}
this.openNonGeneralHandles[message.initialNonce].shift();
(_a = this.peer) === null || _a === void 0 ? void 0 : _a.stopListeningForCertificatesReceived(listenerId);
});
this.log('debug', 'listenForCertificatesReceived registered', { listenerId });
}
if (this.messageCallback) {
this.log('debug', `Invoking stored messageCallback for non-general message`);
this.messageCallback(message).catch((err) => {
this.log('error', `Error in messageCallback`, { error: err.message, err });
return res.status(500).json({
status: 'error',
code: 'ERR_INTERNAL_SERVER_ERROR',
description: err.message || 'An unknown error occurred.'
});
});
}
}
else {
// Possibly general message
if (req.headers['x-bsv-auth-request-id']) {
const message = buildAuthMessageFromRequest(req, this.logger, this.logLevel);
this.log('debug', `Received general message with x-bsv-auth-request-id`, { message });
// Setup general message listener
const listenerId = this.peer.listenForGeneralMessages((senderPublicKey, payload) => {
var _a, _b, _c, _d;
try {
if (senderPublicKey !== req.headers['x-bsv-auth-identity-key'])
return;
const requestId = sdk_1.Utils.toBase64(new sdk_1.Utils.Reader(payload).read(32));
if (requestId === req.headers['x-bsv-auth-request-id']) {
this.log('debug', `General message from the correct identity key`, {
requestId,
senderPublicKey
});
(_a = this.peer) === null || _a === void 0 ? void 0 : _a.stopListeningForGeneralMessages(listenerId);
req.auth = { identityKey: senderPublicKey };
let responseStatus = 200;
let responseHeaders = {};
let responseBody = [];
// Override methods after checking res is clear
this.checkRes(res, 'needs to be clear', next);
res.__status = res.status;
res.status = (n) => {
responseStatus = n;
return res; // Return res for chaining
};
res.__set = res.set;
res.set = (keyOrHeaders, value) => {
if (typeof keyOrHeaders === 'object' && keyOrHeaders !== null) {
// Handle setting multiple headers with an object
for (const [key, val] of Object.entries(keyOrHeaders)) {
if (typeof key !== 'string') {
throw new TypeError(`Header name must be a string, received: ${typeof key}`);
}
responseHeaders[key.toLowerCase()] = String(val); // Ensure value is a string
}
}
else if (typeof keyOrHeaders === 'string') {
// Handle setting a single header
if (typeof value === 'undefined') {
throw new TypeError('Value must be provided when setting a single header');
}
responseHeaders[keyOrHeaders.toLowerCase()] = String(value); // Ensure value is a string
}
else {
throw new TypeError('Invalid arguments: res.set expects a string or an object');
}
return res; // Return res for chaining
};
const buildResponse = async () => {
var _a;
const payload = buildResponsePayload(requestId, responseStatus, responseHeaders, responseBody, req, this.logger, this.logLevel);
this.openGeneralHandles[requestId] = { res, next };
this.log('debug', `Sending general message response`, {
requestId,
responseStatus,
responseHeaders,
responseBodyLength: responseBody.length
});
await ((_a = this.peer) === null || _a === void 0 ? void 0 : _a.toPeer(payload, req.headers['x-bsv-auth-identity-key']));
};
res.__send = res.send;
res.send = (val) => {
// If the value is an object and no content-type is set, assume JSON
if (typeof val === 'object' &&
val !== null &&
!responseHeaders['content-type']) {
res.set('content-type', 'application/json');
}
responseBody = convertValueToArray(val, responseHeaders);
buildResponse();
};
res.__json = res.json;
res.json = (obj) => {
if (!responseHeaders['content-type']) {
res.set('content-type', 'application/json');
}
responseBody = sdk_1.Utils.toArray(JSON.stringify(obj), 'utf8');
buildResponse();
};
res.text = (str) => {
if (!responseHeaders['content-type']) {
res.set('content-type', 'text/plain');
}
responseBody = sdk_1.Utils.toArray(str, 'utf8');
buildResponse();
};
res.__end = res.end;
res.end = () => {
buildResponse();
};
res.__sendFile = res.sendFile;
res.sendFile = (path, options, callback) => {
fs_1.default.readFile(path, (err, data) => {
if (err) {
this.log('error', `Error reading file in sendFile`, { error: err.message });
if (callback)
return callback(err);
res.status(500);
return buildResponse();
}
const mimeType = mime_types_1.default.lookup(path) || 'application/octet-stream';
res.set('Content-Type', mimeType);
responseBody = Array.from(data);
buildResponse();
});
};
if (((_d = (_c = (_b = this.peer) === null || _b === void 0 ? void 0 : _b.certificatesToRequest) === null || _c === void 0 ? void 0 : _c.certifiers) === null || _d === void 0 ? void 0 : _d.length) &&
Object.keys(this.openNextHandlers[senderPublicKey] || {}).length > 0) {
this.openNextHandlers[senderPublicKey] = next;
}
else {
next();
}
}
}
catch (error) {
this.log('error', `Error in listenForGeneralMessages callback`, { error });
next(error);
}
});
this.log('debug', `listenForGeneralMessages registered`, { listenerId });
if (this.messageCallback) {
// Note: The requester may want more detailed error handling
this.log('debug', `Invoking stored messageCallback for general message`);
this.messageCallback(message).catch((err) => {
this.log('error', `Error in messageCallback (general message)`, { error: err.message });
return res.status(500).json({
status: 'error',
code: 'ERR_INTERNAL_SERVER_ERROR',
description: err.message || 'An unknown error occurred.'
});
});
}
}
else {
// No auth headers
this.log('warn', `No Auth headers found on request. Checking allowUnauthenticated setting.`, { allowAuthenticated: this.allowAuthenticated });
if (this.allowAuthenticated) {
req.auth = { identityKey: 'unknown' };
next();
}
else {
this.log('warn', `Mutual-authentication failed. Returning 401.`);
res.status(401).json({
status: 'error',
code: 'UNAUTHORIZED',
message: 'Mutual-authentication failed!'
});
}
}
}
}
catch (error) {
this.log('error', `Caught error in handleIncomingRequest`, { error });
next(error);
}
}
checkRes(res, test, next) {
if (test === 'needs to be clear') {
if (typeof res.__status === 'function' ||
typeof res.__set === 'function' ||
typeof res.__json === 'function' ||
typeof res.__text === 'function' ||
typeof res.__send === 'function' ||
typeof res.__end === 'function' ||
typeof res.__sendFile === 'function') {
const e = new Error('Unable to install Auth midddleware on the response object as it is not clear. Are two middleware instances installed?');
if (typeof next === 'function') {
next(e);
}
throw e;
}
}
else {
if (typeof res.__status !== 'function' ||
typeof res.__set !== 'function' ||
typeof res.__json !== 'function' ||
typeof res.__send !== 'function' ||
typeof res.__end !== 'function' ||
typeof res.__sendFile !== 'function') {
const e = new Error('Unable to restore response object. Did you tamper with hijacked properties (res.__status, __set, __json, __text, __send, __end, __sendFile) ?');
if (typeof next === 'function') {
next(e);
}
throw e;
}
}
}
resetRes(res, next) {
this.checkRes(res, 'needs to be hijacked', next);
res.status = res.__status;
res.set = res.__set;
res.json = res.__json;
res.text = res.__text;
res.send = res.__send;
res.end = res.__end;
res.sendFile = res.__sendFile;
return res;
}
}
exports.ExpressTransport = ExpressTransport;
/**
* Helper: Build AuthMessage from Request
*/
function buildAuthMessageFromRequest(req, logger, logLevel) {
// Possibly log raw request details at debug level
if (logger && logLevel && isLogLevelEnabled(logLevel, 'debug')) {
getLogMethod(logger, 'debug')(`[buildAuthMessageFromRequest] Building message from request...`, {
path: req.path,
headers: req.headers,
method: req.method,
body: req.body
});
}
const writer = new sdk_1.Utils.Writer();
const requestNonce = req.headers['x-bsv-auth-request-id'];
const requestNonceBytes = requestNonce ? sdk_1.Utils.toArray(requestNonce, 'base64') : [];
writer.write(requestNonceBytes);
writer.writeVarIntNum(req.method.length);
writer.write(sdk_1.Utils.toArray(req.method));
// Dynamically determine the base URL
const protocol = req.protocol; // Ex. 'http' or 'https'
const host = req.get('host'); // Ex. 'example.com:3000'
const baseUrl = `${protocol}://${host}`;
const parsedUrl = new URL(`${baseUrl}${req.originalUrl}`);
// Pathname
if (parsedUrl.pathname.length > 0) {
const pathnameAsArray = sdk_1.Utils.toArray(parsedUrl.pathname);
writer.writeVarIntNum(pathnameAsArray.length);
writer.write(pathnameAsArray);
}
else {
writer.writeVarIntNum(-1);
}
// Search
if (parsedUrl.search.length > 0) {
const searchAsArray = sdk_1.Utils.toArray(parsedUrl.search);
writer.writeVarIntNum(searchAsArray.length);
writer.write(searchAsArray);
}
else {
writer.writeVarIntNum(-1);
}
// Parse request headers from client and include only the signed headers:
// - Include custom headers prefixed with x-bsv (excluding those starting with x-bsv-auth)
// - Include a normalized version of the content-type header
// - Include the authorization header
// Headers
const includedHeaders = [];
for (let [k, v] of Object.entries(req.headers)) {
k = k.toLowerCase();
// Normalize the Content-Type header by removing any parameters.
if (k === 'content-type') {
v = v.split(';')[0].trim();
}
if ((k.startsWith('x-bsv-') || k === 'content-type' || k === 'authorization') && !k.startsWith('x-bsv-auth')) {
includedHeaders.push([k, v]);
}
}
includedHeaders.sort(([keyA], [keyB]) => keyA.localeCompare(keyB));
writer.writeVarIntNum(includedHeaders.length);
for (let i = 0; i < includedHeaders.length; i++) {
const headerKeyAsArray = sdk_1.Utils.toArray(includedHeaders[i][0], 'utf8');
writer.writeVarIntNum(headerKeyAsArray.length);
writer.write(headerKeyAsArray);
const headerValueAsArray = sdk_1.Utils.toArray(includedHeaders[i][1], 'utf8');
writer.writeVarIntNum(headerValueAsArray.length);
writer.write(headerValueAsArray);
}
// Body
writeBodyToWriter(req, writer, logger, logLevel);
const authMessage = {
messageType: 'general',
version: req.headers['x-bsv-auth-version'],
identityKey: req.headers['x-bsv-auth-identity-key'],
nonce: req.headers['x-bsv-auth-nonce'],
yourNonce: req.headers['x-bsv-auth-your-nonce'],
payload: writer.toArray(),
signature: req.headers['x-bsv-auth-signature']
? sdk_1.Utils.toArray(req.headers['x-bsv-auth-signature'], 'hex')
: []
};
if (logger && logLevel && isLogLevelEnabled(logLevel, 'debug')) {
getLogMethod(logger, 'debug')(`[buildAuthMessageFromRequest] AuthMessage built`, { authMessage });
}
return authMessage;
}
/**
* Helper: Write body to writer
*/
function writeBodyToWriter(req, writer, logger, logLevel) {
const { body, headers } = req;
if (Array.isArray(body) && body.every((item) => typeof item === 'number')) {
// If the body is already a number[]
writer.writeVarIntNum(body.length);
writer.write(body);
if (logger && logLevel && isLogLevelEnabled(logLevel, 'debug')) {
getLogMethod(logger, 'debug')(`[writeBodyToWriter] Body recognized as number[]`, { length: body.length });
}
}
else if (body instanceof Uint8Array) {
// If the body is a Uint8Array
writer.writeVarIntNum(body.length);
writer.write(Array.from(body)); // Convert Uint8Array to number[]
if (logger && logLevel && isLogLevelEnabled(logLevel, 'debug')) {
getLogMethod(logger, 'debug')(`[writeBodyToWriter] Body recognized as Uint8Array`, { length: body.length });
}
}
else if (headers['content-type'] === 'application/json' &&
typeof body === 'object') {
// If the body is JSON
const bodyAsArray = sdk_1.Utils.toArray(JSON.stringify(body), 'utf8');
writer.writeVarIntNum(bodyAsArray.length);
writer.write(bodyAsArray);
if (logger && logLevel && isLogLevelEnabled(logLevel, 'debug')) {
getLogMethod(logger, 'debug')(`[writeBodyToWriter] Body recognized as JSON`, { body });
}
}
else if (headers['content-type'] === 'application/x-www-form-urlencoded' &&
body &&
Object.keys(body).length > 0) {
// If the body is URL-encoded
const parsedBody = new URLSearchParams(body).toString();
const bodyAsArray = sdk_1.Utils.toArray(parsedBody, 'utf8');
writer.writeVarIntNum(bodyAsArray.length);
writer.write(bodyAsArray);
if (logger && logLevel && isLogLevelEnabled(logLevel, 'debug')) {
getLogMethod(logger, 'debug')(`[writeBodyToWriter] Body recognized as x-www-form-urlencoded`, { parsedBody });
}
}
else if (headers['content-type'] === 'text/plain' &&
typeof body === 'string' &&
body.length > 0) {
// If the body is plain text
const bodyAsArray = sdk_1.Utils.toArray(body, 'utf8');
writer.writeVarIntNum(bodyAsArray.length);
writer.write(bodyAsArray);
if (logger && logLevel && isLogLevelEnabled(logLevel, 'debug')) {
getLogMethod(logger, 'debug')(`[writeBodyToWriter] Body recognized as text/plain`, { body });
}
}
else {
// No valid body
writer.writeVarIntNum(-1);
if (logger && logLevel && isLogLevelEnabled(logLevel, 'debug')) {
getLogMethod(logger, 'debug')(`[writeBodyToWriter] No valid body to write`);
}
}
}
/**
* Helper: Build response payload for sending back to peer
*/
function buildResponsePayload(requestId, responseStatus, responseHeaders, responseBody, req, logger, logLevel) {
if (logger && logLevel && isLogLevelEnabled(logLevel, 'debug')) {
getLogMethod(logger, 'debug')(`[buildResponsePayload] Building response payload`, {
requestId,
responseStatus,
responseHeaders,
responseBodyLength: responseBody.length
});
}
const writer = new sdk_1.Utils.Writer();
writer.write(sdk_1.Utils.toArray(requestId, 'base64'));
writer.writeVarIntNum(responseStatus);
// Filter out headers that should NOT be signed:
// - Include custom headers prefixed with x-bsv (excluding those starting with x-bsv-auth)
// - Include the authorization header
const includedHeaders = [];
Object.entries(responseHeaders).forEach(([key, value]) => {
const lowerKey = key.toLowerCase();
if ((lowerKey.startsWith('x-bsv-') || lowerKey === 'authorization') && !lowerKey.startsWith('x-bsv-auth')) {
includedHeaders.push([lowerKey, value]);
}
});
// Sort the headers by key to ensure a consistent order for signing and verification.
includedHeaders.sort(([keyA], [keyB]) => keyA.localeCompare(keyB));
writer.writeVarIntNum(includedHeaders.length);
for (let i = 0; i < includedHeaders.length; i++) {
const headerKeyAsArray = sdk_1.Utils.toArray(includedHeaders[i][0], 'utf8');
writer.writeVarIntNum(headerKeyAsArray.length);
writer.write(headerKeyAsArray);
const headerValueAsArray = sdk_1.Utils.toArray(includedHeaders[i][1], 'utf8');
writer.writeVarIntNum(headerValueAsArray.length);
writer.write(headerValueAsArray);
}
if (responseBody.length > 0) {
writer.writeVarIntNum(responseBody.length);
writer.write(responseBody);
}
else {
writer.writeVarIntNum(-1);
}
return writer.toArray();
}
/**
* 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');
}
else if (val instanceof Buffer) {
return Array.from(val);
}
else if (typeof val === 'object') {
if (val !== null) {
if (!responseHeaders['content-type']) {
responseHeaders['content-type'] = 'application/json';
}
return sdk_1.Utils.toArray(JSON.stringify(val), 'utf8');
}
}
else if (typeof val === 'number') {
return sdk_1.Utils.toArray(val.toString(), 'utf8');
}
else {
return sdk_1.Utils.toArray(String(val), 'utf8');
}
return [];
}
/**
* Creates an Express middleware that handles authentication via BSV-SDK.
*
* @param {AuthMiddlewareOptions} options
* @returns {(req: Request, res: Response, next: NextFunction) => void} Express middleware
*/
function createAuthMiddleware(options) {
const { wallet, sessionManager, allowUnauthenticated, certificatesToRequest, onCertificatesReceived, logger, logLevel } = options;
if (!wallet) {
if (logger && logLevel && isLogLevelEnabled(logLevel, 'error')) {
getLogMethod(logger, 'error')(`[createAuthMiddleware] No wallet provided in AuthMiddlewareOptions.`);
}
throw new Error('You must configure the auth middleware with a wallet.');
}
// Construct transport with logging
const transport = new ExpressTransport(allowUnauthenticated !== null && allowUnauthenticated !== void 0 ? allowUnauthenticated : false, logger, logLevel);
const sessionMgr = sessionManager || new sdk_1.SessionManager();
if (logger && logLevel && isLogLevelEnabled(logLevel, 'info')) {
getLogMethod(logger, 'info')(`[createAuthMiddleware] Creating Peer with provided wallet & transport. Session Manager: ${sessionManager ? 'Custom' : 'Default'}`);
}
const peer = new sdk_1.Peer(wallet, transport, certificatesToRequest, sessionMgr);
transport.setPeer(peer);
// Return the express middleware
return (req, res, next) => {
if (logger && logLevel && isLogLevelEnabled(logLevel, 'debug')) {
getLogMethod(logger, 'debug')(`[createAuthMiddleware] Incoming request to auth middleware`, {
path: req.path,
headers: req.headers,
method: req.method
});
}
transport.handleIncomingRequest(req, res, next, onCertificatesReceived);
};
}
//# sourceMappingURL=index.js.map