@bsv/auth-express-middleware
Version:
BSV Blockchain mutual-authentication express middleware
763 lines • 34.1 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 = exports.writeBodyToWriter = exports.getLogMethod = exports.isLogLevelEnabled = void 0;
exports.createAuthMiddleware = createAuthMiddleware;
const node_fs_1 = __importDefault(require("node:fs"));
const mime_types_1 = __importDefault(require("mime-types"));
const sdk_1 = require("@bsv/sdk");
const authMiddlewareHelpers_js_1 = require("./authMiddlewareHelpers.js");
var authMiddlewareHelpers_js_2 = require("./authMiddlewareHelpers.js");
Object.defineProperty(exports, "isLogLevelEnabled", { enumerable: true, get: function () { return authMiddlewareHelpers_js_2.isLogLevelEnabled; } });
Object.defineProperty(exports, "getLogMethod", { enumerable: true, get: function () { return authMiddlewareHelpers_js_2.getLogMethod; } });
var authMiddlewareHelpers_js_3 = require("./authMiddlewareHelpers.js");
Object.defineProperty(exports, "writeBodyToWriter", { enumerable: true, get: function () { return authMiddlewareHelpers_js_3.writeBodyToWriter; } });
/**
* ResponseWriterWrapper buffers response data until signing is complete.
* This pattern matches the Go implementation for cleaner response handling.
*/
class ResponseWriterWrapper {
constructor(res) {
this.statusCode = 200;
this.headers = {};
this.body = [];
this.flushed = false;
this.originalRes = res;
}
status(code) {
this.statusCode = code;
return this;
}
set(key, value) {
if (typeof key === 'object' && key !== null) {
for (const [k, v] of Object.entries(key)) {
this.headers[k.toLowerCase()] = String(v);
}
}
else if (typeof key === 'string' && value !== undefined) {
this.headers[key.toLowerCase()] = String(value);
}
return this;
}
send(data) {
this.body = (0, authMiddlewareHelpers_js_1.convertValueToArray)(data, this.headers);
return this;
}
json(data) {
if (!this.headers['content-type']) {
this.headers['content-type'] = 'application/json';
}
this.body = sdk_1.Utils.toArray(JSON.stringify(data), 'utf8');
return this;
}
text(data) {
if (!this.headers['content-type']) {
this.headers['content-type'] = 'text/plain';
}
this.body = sdk_1.Utils.toArray(data, 'utf8');
return this;
}
end() {
// No-op for buffering, actual end happens on flush
return this;
}
getStatusCode() {
return this.statusCode;
}
getHeaders() {
return this.headers;
}
getBody() {
return this.body;
}
getOriginalRes() {
return this.originalRes;
}
// Called after peer signs the response
flush() {
if (this.flushed)
return;
this.flushed = true;
this.originalRes.status(this.statusCode);
for (const [key, value] of Object.entries(this.headers)) {
this.originalRes.set(key, value);
}
if (this.body.length > 0) {
this.originalRes.send(Buffer.from(new Uint8Array(this.body)));
}
else {
this.originalRes.end();
}
}
}
/**
* 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.openNextHandlerTimeouts = {};
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 ((0, authMiddlewareHelpers_js_1.isLogLevelEnabled)(this.logLevel, level)) {
const logMethod = (0, authMiddlewareHelpers_js_1.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') {
await this.sendGeneralMessage(message);
}
else {
await this.sendNonGeneralMessage(message);
}
}
/**
* Handles a general (authenticated application) AuthMessage response.
*/
async sendGeneralMessage(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 = this.readResponseHeaders(reader);
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();
}
}
/**
* Reads response headers from a binary reader.
*/
readResponseHeaders(reader) {
const responseHeaders = {};
const nHeaders = reader.readVarIntNum();
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;
}
return responseHeaders;
}
/**
* Handles a non-general (handshake) AuthMessage response.
*/
async sendNonGeneralMessage(message) {
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!');
}
// Since this is an initial response, we can assume there's only one handle per identity
const { res, next } = handles[0];
const responseHeaders = {
'x-bsv-auth-version': message.version,
'x-bsv-auth-message-type': message.messageType,
'x-bsv-auth-identity-key': message.identityKey,
'x-bsv-auth-nonce': message.nonce,
'x-bsv-auth-your-nonce': message.yourNonce,
'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();
}
/**
* Stores the callback bound by a Peer
* @param callback
*/
async onData(callback) {
this.log('debug', 'onData callback set');
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.
*/
async 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') {
await this.handleWellKnownAuth(req, res, next, onCertificatesReceived);
}
else if (req.headers['x-bsv-auth-request-id']) {
this.handleGeneralMessage(req, res, next);
}
else {
this.handleUnauthenticated(req, res, next);
}
}
catch (error) {
this.log('error', 'Caught error in handleIncomingRequest', { error });
next(error);
}
}
/**
* Handles a request to /.well-known/auth (non-general / handshake messages).
*/
async handleWellKnownAuth(req, res, next, onCertificatesReceived) {
const message = req.body;
this.log('debug', 'Received non-general message at /.well-known/auth', { message });
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 (!await this.peer.sessionManager.hasSession(message.identityKey)) {
this.registerCertificateListener(req, res, next, requestId, message, onCertificatesReceived);
}
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.'
});
});
}
}
/**
* Registers a certificate-received listener for a non-general message.
*/
registerCertificateListener(req, res, next, requestId, message, onCertificatesReceived) {
const listenerId = this.peer.listenForCertificatesReceived((senderPublicKey, certs) => {
var _a;
try {
this.log('debug', 'Certificates received event triggered', {
senderPublicKey,
certCount: certs === null || certs === void 0 ? void 0 : certs.length,
requestId
});
if (senderPublicKey === req.body.identityKey) {
this.handleCertificatesForPeer(senderPublicKey, certs, req, res, next, message, onCertificatesReceived);
}
}
catch (error) {
this.log('error', 'Error in certificate listener callback', { error });
}
finally {
const handles = this.openNonGeneralHandles[requestId];
if (handles && handles.length > 0) {
handles.shift();
if (handles.length === 0) {
delete this.openNonGeneralHandles[requestId];
}
}
(_a = this.peer) === null || _a === void 0 ? void 0 : _a.stopListeningForCertificatesReceived(listenerId);
}
});
this.log('debug', 'listenForCertificatesReceived registered', { listenerId, requestId });
}
/**
* Processes certificates received from a peer during the handshake.
*/
handleCertificatesForPeer(senderPublicKey, certs, req, res, next, message, onCertificatesReceived) {
var _a;
if (!Array.isArray(certs) || certs.length === 0) {
this.log('warn', 'No certificates provided by peer', { senderPublicKey });
const handles = this.openNonGeneralHandles[(_a = req.headers['x-bsv-auth-request-id']) !== null && _a !== void 0 ? _a : message.initialNonce];
if (handles && handles.length > 0) {
handles[0].res.status(400).json({ status: 'No certificates provided' });
}
return;
}
this.log('info', 'Certificates successfully received from peer', { senderPublicKey, certs });
if (typeof onCertificatesReceived === 'function') {
onCertificatesReceived(senderPublicKey, certs, req, res, next);
}
// Validate that identityKey is an own property of the handler map before
// invoking, preventing CodeQL js/unvalidated-dynamic-method-call from
// flagging prototype-chain dispatch on a user-supplied key.
const identityKey = message.identityKey;
if (typeof identityKey === 'string' && Object.hasOwn(this.openNextHandlers, identityKey)) {
const nextFn = this.openNextHandlers[identityKey];
const timeoutHandle = this.openNextHandlerTimeouts[identityKey];
if (timeoutHandle != null) {
clearTimeout(timeoutHandle);
delete this.openNextHandlerTimeouts[identityKey];
}
nextFn();
delete this.openNextHandlers[identityKey];
}
}
/**
* Handles an authenticated general message (has x-bsv-auth-request-id header).
*/
handleGeneralMessage(req, res, next) {
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;
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']) {
(_a = this.peer) === null || _a === void 0 ? void 0 : _a.stopListeningForGeneralMessages(listenerId);
this.setupAuthenticatedResponse(req, res, next, senderPublicKey, requestId);
}
}
catch (error) {
this.log('error', 'Error in listenForGeneralMessages callback', { error });
next(error);
}
});
this.log('debug', 'listenForGeneralMessages registered', { listenerId });
if (this.messageCallback) {
this.log('debug', 'Invoking stored messageCallback for general message');
this.messageCallback(message).catch((err) => {
const msg = err instanceof Error ? err.message : String(err);
const isAuthError = /nonce|signature|session|auth version/i.test(msg);
this.log('error', 'Error in messageCallback (general message)', { error: msg, isAuthError });
const statusCode = isAuthError ? 401 : 500;
const code = isAuthError ? 'ERR_AUTH_FAILED' : 'ERR_INTERNAL_SERVER_ERROR';
const description = isAuthError
? (msg || 'Authentication failed.')
: (msg || 'An unexpected error occurred.');
return res.status(statusCode).json({ status: 'error', code, description });
});
}
}
/**
* Sets up the intercepted response for an authenticated general message.
*/
setupAuthenticatedResponse(req, res, next, senderPublicKey, requestId) {
this.log('debug', 'General message from the correct identity key', { requestId, senderPublicKey });
req.auth = { identityKey: senderPublicKey };
const wrapper = new ResponseWriterWrapper(res);
let responseSent = false;
const buildAndSendResponse = async () => {
var _a;
if (responseSent)
return;
responseSent = true;
try {
const responsePayload = buildResponsePayload(requestId, wrapper.getStatusCode(), wrapper.getHeaders(), wrapper.getBody(), req, this.logger, this.logLevel);
this.openGeneralHandles[requestId] = { res, next };
this.log('debug', 'Sending general message response', {
requestId,
responseStatus: wrapper.getStatusCode(),
responseHeaders: wrapper.getHeaders(),
responseBodyLength: wrapper.getBody().length
});
await ((_a = this.peer) === null || _a === void 0 ? void 0 : _a.toPeer(responsePayload, req.headers['x-bsv-auth-identity-key']));
}
catch (err) {
delete this.openGeneralHandles[requestId];
this.log('error', 'Failed to build and send authenticated response', { error: err });
try {
const restored = this.resetRes(res, next);
restored.status(500).json({
status: 'error',
code: 'ERR_RESPONSE_SIGNING_FAILED',
description: err instanceof Error ? err.message : 'Failed to sign response'
});
}
catch (_responseAlreadySent) {
// Response may already be partially sent — nothing to do
}
}
};
this.hijackResponse(res, next, wrapper, buildAndSendResponse);
void this.scheduleNextOrCertificateWait(next, senderPublicKey, wrapper, buildAndSendResponse).catch(next);
}
/**
* Overrides the response methods to intercept and buffer the response for signing.
*/
hijackResponse(res, next, wrapper, buildAndSendResponse) {
// Override methods to capture response data
this.checkRes(res, 'needs to be clear', next);
res.__status = res.status;
res.status = (n) => {
wrapper.status(n);
return res;
};
res.__set = res.set;
res.set = (keyOrHeaders, value) => {
wrapper.set(keyOrHeaders, value);
return res;
};
res.__send = res.send;
res.send = (val) => {
if (typeof val === 'object' && val !== null && !wrapper.getHeaders()['content-type']) {
wrapper.set('content-type', 'application/json');
}
wrapper.send(val);
buildAndSendResponse();
return res;
};
res.__json = res.json;
res.json = (obj) => {
wrapper.json(obj);
buildAndSendResponse();
return res;
};
res.__text = res.text;
res.text = (str) => {
wrapper.text(str);
buildAndSendResponse();
return res;
};
res.__end = res.end;
res.end = () => {
buildAndSendResponse();
return res;
};
res.__sendFile = res.sendFile;
res.sendFile = (path, options, callback) => {
node_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);
wrapper.status(500);
buildAndSendResponse();
return;
}
const mimeType = mime_types_1.default.lookup(path) || 'application/octet-stream';
wrapper.set('Content-Type', mimeType);
wrapper.send(Array.from(data));
buildAndSendResponse();
});
};
}
/**
* Either calls next() immediately or stores it pending certificate arrival.
*/
async scheduleNextOrCertificateWait(next, senderPublicKey, wrapper, buildAndSendResponse) {
var _a, _b, _c, _d, _e;
const hasSession = await ((_b = (_a = this.peer) === null || _a === void 0 ? void 0 : _a.sessionManager.hasSession(senderPublicKey)) !== null && _b !== void 0 ? _b : false);
const needsCertificates = (_e = (_d = (_c = this.peer) === null || _c === void 0 ? void 0 : _c.certificatesToRequest) === null || _d === void 0 ? void 0 : _d.certifiers) === null || _e === void 0 ? void 0 : _e.length;
this.log('debug', 'Checking if we need to wait for certificates', {
senderPublicKey,
hasSession,
needsCertificates,
openNextHandlersKeys: Object.keys(this.openNextHandlers)
});
if (!needsCertificates || hasSession) {
this.log('debug', 'Calling next() immediately - no certificate wait needed', { senderPublicKey, hasSession });
next();
return;
}
this.log('debug', 'Storing next handler to wait for certificates', { senderPublicKey });
const existingTimeout = this.openNextHandlerTimeouts[senderPublicKey];
if (existingTimeout != null) {
clearTimeout(existingTimeout);
delete this.openNextHandlerTimeouts[senderPublicKey];
}
this.openNextHandlers[senderPublicKey] = next;
const CERTIFICATE_TIMEOUT_MS = 30000;
const timeoutHandle = setTimeout(() => {
if (this.openNextHandlers[senderPublicKey]) {
this.log('warn', 'Certificate request timed out', { senderPublicKey });
delete this.openNextHandlers[senderPublicKey];
delete this.openNextHandlerTimeouts[senderPublicKey];
wrapper.status(408).json({
status: 'error',
code: 'CERTIFICATE_TIMEOUT',
message: 'Certificate request timed out'
});
buildAndSendResponse();
}
}, CERTIFICATE_TIMEOUT_MS);
this.openNextHandlerTimeouts[senderPublicKey] = timeoutHandle;
}
/**
* Handles a request with no auth headers.
*/
handleUnauthenticated(req, res, next) {
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!'
});
}
}
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) {
const debugLog = (0, authMiddlewareHelpers_js_1.makeDebugLogger)(logger, logLevel);
debugLog('[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));
const protocol = req.protocol;
const host = req.get('host');
const parsedUrl = new URL(`${protocol}://${host}${req.originalUrl}`);
(0, authMiddlewareHelpers_js_1.writeUrlToWriter)(parsedUrl, writer);
(0, authMiddlewareHelpers_js_1.writeRequestHeadersToWriter)(req, writer);
(0, authMiddlewareHelpers_js_1.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')
: []
};
debugLog('[buildAuthMessageFromRequest] AuthMessage built', { authMessage });
return authMessage;
}
/**
* Helper: Build response payload for sending back to peer
*/
function buildResponsePayload(requestId, responseStatus, responseHeaders, responseBody, req, logger, logLevel) {
const debugLog = (0, authMiddlewareHelpers_js_1.makeDebugLogger)(logger, logLevel);
debugLog('[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 (const [headerKey, headerValue] of includedHeaders) {
(0, authMiddlewareHelpers_js_1.writeHeaderPair)(writer, headerKey, headerValue);
}
if (responseBody.length > 0) {
writer.writeVarIntNum(responseBody.length);
writer.write(responseBody);
}
else {
writer.writeVarIntNum(-1);
}
return writer.toArray();
}
/**
* 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 && (0, authMiddlewareHelpers_js_1.isLogLevelEnabled)(logLevel, 'error')) {
(0, authMiddlewareHelpers_js_1.getLogMethod)(logger, 'error')('[createAuthMiddleware] No wallet provided in AuthMiddlewareOptions.');
}
throw new Error('You must configure the auth middleware with a wallet.');
}
const transport = new ExpressTransport(allowUnauthenticated !== null && allowUnauthenticated !== void 0 ? allowUnauthenticated : false, logger, logLevel);
const sessionMgr = sessionManager || new sdk_1.SessionManager();
if (logger && logLevel && (0, authMiddlewareHelpers_js_1.isLogLevelEnabled)(logLevel, 'info')) {
(0, authMiddlewareHelpers_js_1.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 (req, res, next) => {
if (logger && logLevel && (0, authMiddlewareHelpers_js_1.isLogLevelEnabled)(logLevel, 'debug')) {
(0, authMiddlewareHelpers_js_1.getLogMethod)(logger, 'debug')('[createAuthMiddleware] Incoming request to auth middleware', {
path: req.path,
headers: req.headers,
method: req.method
});
}
void transport.handleIncomingRequest(req, res, next, onCertificatesReceived).catch(next);
};
}
//# sourceMappingURL=index.js.map