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.

90 lines (89 loc) 4.14 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.getTenantContext = getTenantContext; exports.getTenantId = getTenantId; exports.setTenantId = setTenantId; exports.runWithTenant = runWithTenant; const async_hooks_1 = require("async_hooks"); const AgentSingleton_1 = require("../AgentSingleton"); const ContextStorage_1 = require("./ContextStorage"); // Holds the tenant set by runWithTenant, separate from the request Context. const tenantIdStorage = new async_hooks_1.AsyncLocalStorage(); function getTenantContext() { // runWithTenant wins over the request, so background work that resumes inside // another request's context is still checked against its own tenant. const scoped = tenantIdStorage.getStore(); if (scoped) { return scoped; } const context = ContextStorage_1.ContextStorage.getStore(); if ((context === null || context === void 0 ? void 0 : context.tenantId) !== undefined) { return { tenantId: context.tenantId }; } return undefined; } // Returns the tenant ID currently in effect, set via setTenantId() inside a // request or runWithTenant() for background work. Lets you forward the active // tenant explicitly instead of threading it through function parameters. function getTenantId() { var _a; return (_a = getTenantContext()) === null || _a === void 0 ? void 0 : _a.tenantId; } function setTenantId(id) { const agent = (0, AgentSingleton_1.getInstance)(); if (!agent) { return; } const context = ContextStorage_1.ContextStorage.getStore(); if (!context) { logWarningSetTenantIdCalledWithoutContext(); return; } const rawId = id; if (typeof rawId !== "string" && typeof rawId !== "number") { agent.log(`setTenantId(...) expects a string or number, found ${typeof rawId} instead.`); return; } if (typeof rawId === "string" && rawId.length === 0) { agent.log(`setTenantId(...) expects a non-empty string.`); return; } context.tenantId = rawId.toString(); } // Sets the tenant for background work (queues, schedulers, workers) that runs // outside an HTTP request. The tenant follows the callback across async // boundaries, so every query inside is checked against it. function runWithTenant(tenantId, fn) { if (typeof fn !== "function") { // eslint-disable-next-line no-console console.warn("Zen.runWithTenant: Expected a function, but received a value. Wrap your code in a closure: () => yourCode"); return fn; } const rawId = tenantId; if ((typeof rawId !== "string" && typeof rawId !== "number") || (typeof rawId === "string" && rawId.length === 0)) { // eslint-disable-next-line no-console console.warn("Zen.runWithTenant(...) expects a non-empty string or number as the tenant ID. Running the callback without a tenant."); return fn(); } return tenantIdStorage.run({ tenantId: rawId.toString() }, () => { const result = fn(); // If a sync callback returns a Promise, the await happens outside the // AsyncLocalStorage context and the tenant won't be set for the query. // Use an async callback with await to ensure the query runs inside the context. if (result instanceof Promise && fn.constructor.name !== "AsyncFunction") { // eslint-disable-next-line no-console console.warn("Zen.runWithTenant: The callback returned a Promise without awaiting it. Use an async callback: async () => { return await db.query... }"); } return result; }); } let loggedWarning = false; function logWarningSetTenantIdCalledWithoutContext() { if (loggedWarning) { return; } // eslint-disable-next-line no-console console.warn("setTenantId(...) was called without a context. Make sure to call setTenantId(...) within an HTTP request. If you're using serverless functions, make sure to use the handler wrapper provided by Zen. Also ensure you import Zen at the top of your main app file (before any other imports)."); loggedWarning = true; }