UNPKG

@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.

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;