@aikidosec/firewall
Version:
Zen by Aikido is an embedded Application Firewall that autonomously protects Node.js apps against common and critical attacks, provides rate limiting, detects malicious traffic (including bots), and more.
85 lines (84 loc) • 3.81 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);
};
}
// Use symbol to avoid conflicts with other properties
const countedRequest = Symbol("__zen_request_counted__");
function callListenerWithContext(listener, req, res, module, agent, body) {
const context = (0, contextFromRequest_1.contextFromRequest)(req, body, module);
return (0, Context_1.runWithContext)(context, () => {
const context = (0, Context_1.getContext)();
if (context) {
res.on("finish", () => {
// Don't use `getContext()` in this callback
// The context won't be available when using http2
// We want the latest context (`runWithContext` updates context if there is already one)
// Since context is an object, the reference will point to the latest one
onFinishRequestHandler(req, res, agent, context);
});
}
if ((0, checkIfRequestIsBlocked_1.checkIfRequestIsBlocked)(res, agent)) {
// The return is necessary to prevent the listener from being called
return;
}
return listener(req, res);
});
}
function onFinishRequestHandler(req, res, agent, context) {
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;
if (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);
}
if (context.remoteAddress &&
!agent.getConfig().isBypassedIP(context.remoteAddress) &&
agent.getAttackWaveDetector().check(context)) {
agent.onDetectedAttackWave({
request: context,
});
agent.getInspectionStatistics().onAttackWaveDetected();
}
}
}