@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.
130 lines (129 loc) • 4.84 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", { value: true });
exports.createLambdaWrapper = createLambdaWrapper;
const AgentSingleton_1 = require("../agent/AgentSingleton");
const Context_1 = require("../agent/Context");
const isJsonContentType_1 = require("../helpers/isJsonContentType");
const isPlainObject_1 = require("../helpers/isPlainObject");
const parseCookies_1 = require("../helpers/parseCookies");
function isAsyncHandler(handler) {
return handler.length <= 2;
}
function convertToAsyncFunction(originalHandler) {
// oxlint-disable-next-line require-await
return async (event, context) => {
if (isAsyncHandler(originalHandler)) {
return originalHandler(event, context);
}
return new Promise((resolve, reject) => {
try {
originalHandler(event, context, (error, result) => {
if (error) {
reject(error);
}
else {
resolve(result);
}
});
}
catch (error) {
reject(error);
}
});
};
}
function normalizeHeaders(headers) {
const normalized = {};
for (const key in headers) {
normalized[key.toLowerCase()] = headers[key];
}
return normalized;
}
function tryParseAsJSON(json) {
try {
return JSON.parse(json);
}
catch {
return undefined;
}
}
function parseBody(event) {
const headers = event.headers ? normalizeHeaders(event.headers) : {};
if (!event.body || !(0, isJsonContentType_1.isJsonContentType)(headers["content-type"] || "")) {
return undefined;
}
return tryParseAsJSON(event.body);
}
function isGatewayEvent(event) {
return (0, isPlainObject_1.isPlainObject)(event) && "httpMethod" in event && "headers" in event;
}
function isSQSEvent(event) {
return (0, isPlainObject_1.isPlainObject)(event) && "Records" in event;
}
// eslint-disable-next-line max-lines-per-function
function createLambdaWrapper(handler) {
const asyncHandler = convertToAsyncFunction(handler);
const agent = (0, AgentSingleton_1.getInstance)();
let lastFlushStatsAt = undefined;
const flushEveryMS = 10 * 60 * 1000;
// eslint-disable-next-line max-lines-per-function
return async (event, context) => {
var _a, _b, _c;
let agentContext = undefined;
if (isSQSEvent(event)) {
const body = event.Records.map((record) => tryParseAsJSON(record.body)).filter((body) => body);
agentContext = {
url: undefined,
method: undefined,
remoteAddress: undefined,
body: {
Records: body.map((record) => ({
body: record,
})),
},
routeParams: {},
headers: {},
query: {},
cookies: {},
source: "lambda/sqs",
route: undefined,
};
}
else if (isGatewayEvent(event)) {
agentContext = {
url: undefined,
method: event.httpMethod,
remoteAddress: (_b = (_a = event.requestContext) === null || _a === void 0 ? void 0 : _a.identity) === null || _b === void 0 ? void 0 : _b.sourceIp,
body: parseBody(event),
headers: event.headers,
routeParams: event.pathParameters ? event.pathParameters : {},
query: event.queryStringParameters ? event.queryStringParameters : {},
cookies: ((_c = event.headers) === null || _c === void 0 ? void 0 : _c.cookie) ? (0, parseCookies_1.parse)(event.headers.cookie) : {},
source: "lambda/gateway",
route: event.resource ? event.resource : undefined,
};
}
if (!agentContext) {
// We don't know what the type of the event is
// We can't provide any context for the underlying sinks
// So we just run the handler without any context
return await asyncHandler(event, context);
}
try {
return await (0, Context_1.runWithContext)(agentContext, async () => {
return await asyncHandler(event, context);
});
}
finally {
if (agent) {
const stats = agent.getInspectionStatistics();
stats.onRequest();
if (lastFlushStatsAt === undefined ||
lastFlushStatsAt + flushEveryMS < performance.now()) {
await agent.flushStats(1000);
lastFlushStatsAt = performance.now();
}
}
}
};
}