@aikidosec/firewall
Version:
Zen by Aikido is an embedded Web Application Firewall that autonomously protects Node.js apps against common and critical attacks
74 lines (73 loc) • 3.55 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", { value: true });
exports.createRequestListener = createRequestListener;
const Context_1 = require("../../agent/Context");
const isPackageInstalled_1 = require("../../helpers/isPackageInstalled");
const checkIfRequestIsBlocked_1 = require("./checkIfRequestIsBlocked");
const contextFromRequest_1 = require("./contextFromRequest");
const readBodyStream_1 = require("./readBodyStream");
const shouldDiscoverRoute_1 = require("./shouldDiscoverRoute");
function createRequestListener(listener, module, agent) {
const isMicroInstalled = (0, isPackageInstalled_1.isPackageInstalled)("micro");
return async function requestListener(req, res) {
// Parse body only if next or micro is installed
// We can only read the body stream once
// This is tricky, see replaceRequestBody(...)
// e.g. Hono uses web requests and web streams
// (uses Readable.toWeb(req) to convert to a web stream)
const readBody = "NEXT_DEPLOYMENT_ID" in process.env || isMicroInstalled;
if (!readBody) {
return callListenerWithContext(listener, req, res, module, agent, "");
}
const result = await (0, readBodyStream_1.readBodyStream)(req, res, agent);
if (!result.success) {
return;
}
return callListenerWithContext(listener, req, res, module, agent, result.body);
};
}
function callListenerWithContext(listener, req, res, module, agent, body) {
const context = (0, contextFromRequest_1.contextFromRequest)(req, body, module);
return (0, Context_1.runWithContext)(context, () => {
// This method is called when the response is finished and discovers the routes for display in the dashboard
// The bindContext function is used to ensure that the context is available in the callback
// If using http2, the context is not available in the callback without this
res.on("finish", (0, Context_1.bindContext)(createOnFinishRequestHandler(req, res, agent)));
if ((0, checkIfRequestIsBlocked_1.checkIfRequestIsBlocked)(res, agent)) {
// The return is necessary to prevent the listener from being called
return;
}
return listener(req, res);
});
}
// Use symbol to avoid conflicts with other properties
const countedRequest = Symbol("__zen_request_counted__");
function createOnFinishRequestHandler(req, res, agent) {
return function onFinishRequest() {
if (req[countedRequest]) {
// The request has already been counted
// This might happen if the server has multiple listeners
return;
}
// Mark the request as counted
req[countedRequest] = true;
const context = (0, Context_1.getContext)();
if (context && context.route && context.method) {
const shouldDiscover = (0, shouldDiscoverRoute_1.shouldDiscoverRoute)({
statusCode: res.statusCode,
route: context.route,
method: context.method,
});
if (shouldDiscover) {
agent.onRouteExecute(context);
}
if (shouldDiscover || context.rateLimitedEndpoint) {
agent.getInspectionStatistics().onRequest();
}
if (context.rateLimitedEndpoint) {
agent.getInspectionStatistics().onRateLimitedRequest();
agent.onRouteRateLimited(context.rateLimitedEndpoint);
}
}
};
}