UNPKG

@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
"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;