@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.
78 lines (77 loc) • 3.78 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);
}
if (agent.getAttackWaveDetector().check(context)) {
agent.onDetectedAttackWave({ request: context, metadata: {} });
agent.getInspectionStatistics().onAttackWaveDetected();
}
}
};
}
;