@aikidosec/firewall
Version:
Zen by Aikido is an embedded Web Application Firewall that autonomously protects Node.js apps against common and critical attacks
129 lines (128 loc) • 4.8 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) {
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();
}
}
}
};
}