@aikidosec/firewall
Version:
Zen by Aikido is an embedded Web Application Firewall that autonomously protects Node.js apps against common and critical attacks
138 lines (137 loc) • 4.62 kB
JavaScript
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.Routes = void 0;
const getMaxApiDiscoverySamples_1 = require("../helpers/getMaxApiDiscoverySamples");
const getApiInfo_1 = require("./api-discovery/getApiInfo");
const updateApiInfo_1 = require("./api-discovery/updateApiInfo");
const AikidoDAST_1 = require("./AikidoDAST");
class Routes {
constructor(maxEntries = 1000, maxGraphQLSchemas = 10) {
this.maxEntries = maxEntries;
this.maxGraphQLSchemas = maxGraphQLSchemas;
// Routes are only registered at the end of the request, so we need to store the schema in a separate map
this.graphQLSchemas = new Map();
this.routes = new Map();
}
addRoute(context) {
if ((0, AikidoDAST_1.isAikidoDASTRequest)(context)) {
return;
}
const { method, route: path } = context;
if (!method || !path) {
return;
}
const key = this.getKey(method, path);
const existing = this.routes.get(key);
const maxSamples = (0, getMaxApiDiscoverySamples_1.getMaxApiDiscoverySamples)();
if (existing) {
(0, updateApiInfo_1.updateApiInfo)(context, existing, maxSamples);
existing.hits++;
return;
}
// Get info about body and query schema
let apispec = {};
if (maxSamples > 0) {
apispec = (0, getApiInfo_1.getApiInfo)(context) || {};
}
this.evictLeastUsedRouteIfNecessary();
this.routes.set(key, {
method,
path,
hits: 1,
apispec,
rateLimitedCount: 0,
});
}
evictLeastUsedRouteIfNecessary() {
if (this.routes.size >= this.maxEntries) {
this.evictLeastUsedRoute();
}
}
getKey(method, path) {
return `${method}:${path}`;
}
hasGraphQLSchema(method, path) {
const key = this.getKey(method, path);
return this.graphQLSchemas.has(key);
}
setGraphQLSchema(method, path, schema) {
if (schema.length > 0 &&
this.graphQLSchemas.size < this.maxGraphQLSchemas) {
const key = this.getKey(method, path);
this.graphQLSchemas.set(key, schema);
}
}
getGraphQLKey(method, path, type, name) {
return `${method}:${path}:${type}:${name}`;
}
addGraphQLField(method, path, type, name) {
const key = this.getGraphQLKey(method, path, type, name);
const existing = this.routes.get(key);
if (existing) {
existing.hits++;
return;
}
this.evictLeastUsedRouteIfNecessary();
this.routes.set(key, {
method,
path,
hits: 1,
graphql: { type, name },
apispec: {},
rateLimitedCount: 0,
});
}
evictLeastUsedRoute() {
let leastUsedKey = null;
let leastHits = Infinity;
for (const [key, route] of this.routes.entries()) {
if (route.hits < leastHits) {
leastHits = route.hits;
leastUsedKey = key;
}
}
if (leastUsedKey !== null) {
this.routes.delete(leastUsedKey);
}
}
countRouteRateLimited(route) {
let key = this.getKey(route.method, route.route);
if (route.graphql) {
key = this.getGraphQLKey(route.method, route.route, route.graphql.type, route.graphql.name);
}
let existing = this.routes.get(key);
if (!existing) {
this.evictLeastUsedRouteIfNecessary();
existing = {
method: route.method,
path: route.route,
graphql: route.graphql
? { type: route.graphql.type, name: route.graphql.name }
: undefined,
hits: 0,
apispec: {},
rateLimitedCount: 0,
};
this.routes.set(key, existing);
}
existing.rateLimitedCount++;
}
clear() {
this.routes.clear();
}
asArray() {
return Array.from(this.routes.entries()).map(([key, route]) => {
return {
method: route.method,
path: route.path,
hits: route.hits,
rateLimitedCount: route.rateLimitedCount,
graphql: route.graphql,
apispec: route.apispec,
graphQLSchema: this.graphQLSchemas.get(key),
};
});
}
}
exports.Routes = Routes;