UNPKG

express-limiter-pro

Version:

A TypeScript library for safe display and sanitization to prevent XSS attacks.

231 lines (220 loc) 8.51 kB
"use strict"; var __defProp = Object.defineProperty; var __getOwnPropDesc = Object.getOwnPropertyDescriptor; var __getOwnPropNames = Object.getOwnPropertyNames; var __hasOwnProp = Object.prototype.hasOwnProperty; var __name = (target, value) => __defProp(target, "name", { value, configurable: true }); var __export = (target, all) => { for (var name in all) __defProp(target, name, { get: all[name], enumerable: true }); }; var __copyProps = (to, from, except, desc) => { if (from && typeof from === "object" || typeof from === "function") { for (let key of __getOwnPropNames(from)) if (!__hasOwnProp.call(to, key) && key !== except) __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable }); } return to; }; var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod); // src/index.ts var index_exports = {}; __export(index_exports, { configManager: () => configManager, configureRateLimits: () => configureRateLimits }); module.exports = __toCommonJS(index_exports); // src/limits/limiters.ts var import_express_rate_limit2 = require("express-rate-limit"); // src/utils/ipKeyHelpers.ts var import_express_rate_limit = require("express-rate-limit"); function getIpFromRequest(req) { const xff = req.headers["x-forwarded-for"] ?? ""; const forwarded = xff.split(",")[0]?.trim(); const stripPort = /* @__PURE__ */ __name((ip) => ip.replace(/:\d+$/, ""), "stripPort"); if (req.ip) return stripPort(req.ip); if (forwarded) return stripPort(forwarded); return "127.0.0.1"; } __name(getIpFromRequest, "getIpFromRequest"); function ipKeyGeneratorFromReq(req, ipv6Subnet = 64) { const ip = getIpFromRequest(req); return (0, import_express_rate_limit.ipKeyGenerator)(ip, ipv6Subnet); } __name(ipKeyGeneratorFromReq, "ipKeyGeneratorFromReq"); // src/configs/rateLimitConfigs.ts var rateLimitConfigs = { development: { api: { windowMs: 15 * 60 * 1e3, max: 1e3, standardHeaders: true, legacyHeaders: false }, auth: { windowMs: 15 * 60 * 1e3, max: 50, standardHeaders: true, legacyHeaders: false }, sensitive: { windowMs: 15 * 60 * 1e3, max: 20, standardHeaders: true, legacyHeaders: false } }, production: { api: { windowMs: 15 * 60 * 1e3, max: 300, standardHeaders: true, legacyHeaders: false, keyGenerator: /* @__PURE__ */ __name((req) => ipKeyGeneratorFromReq(req), "keyGenerator") }, auth: { windowMs: 15 * 60 * 1e3, max: 20, standardHeaders: true, legacyHeaders: false, keyGenerator: /* @__PURE__ */ __name((req) => ipKeyGeneratorFromReq(req), "keyGenerator") }, sensitive: { windowMs: 60 * 60 * 1e3, max: 10, standardHeaders: true, legacyHeaders: false, keyGenerator: /* @__PURE__ */ __name((req) => ipKeyGeneratorFromReq(req), "keyGenerator") } } }; // src/logger/logger.ts var logger = { info: /* @__PURE__ */ __name((...args) => console.info("[info]", ...args), "info"), warn: /* @__PURE__ */ __name((...args) => console.warn("[warn]", ...args), "warn"), error: /* @__PURE__ */ __name((...args) => console.error("[error]", ...args), "error"), debug: /* @__PURE__ */ __name((...args) => console.debug("[debug]", ...args), "debug") }; // src/configs/configManager.ts var import_events = require("events"); // src/errors/error.ts var _ConfigNotFoundException = class _ConfigNotFoundException extends Error { constructor(message) { super(message ?? "Configuration not found"); this.name = "ConfigNotFoundException"; } }; __name(_ConfigNotFoundException, "ConfigNotFoundException"); var ConfigNotFoundException = _ConfigNotFoundException; // src/configs/configManager.ts var _ConfigManager = class _ConfigManager extends import_events.EventEmitter { constructor() { super(); } setConfigSource(source) { this.configSource = source; } setConfig(cfg) { this.config = { ...cfg }; this.emit("configAvailable", this.config); } getConfig() { return this.config; } async loadConfig() { if (!this.configSource) { throw new ConfigNotFoundException("Config source not set"); } try { const config = await this.configSource(); this.setConfig(config); return config; } catch (error) { throw new ConfigNotFoundException("Error loading configuration"); } } }; __name(_ConfigManager, "ConfigManager"); var ConfigManager = _ConfigManager; var configManager = new ConfigManager(); // src/limits/limiters.ts var apiLimiter = null; var authLimiter = null; var sensitiveLimiter = null; var createLimiter = /* @__PURE__ */ __name((options) => (0, import_express_rate_limit2.rateLimit)({ ...options, skipSuccessfulRequests: false, handler: /* @__PURE__ */ __name((req, res) => { logger.warn(`Rate limit exceeded for ${req.ip} on ${req.originalUrl}`); return res.status(429).json({ success: false, error: options.errorName || "Rate Limit Exceeded", message: options.message || "Too many requests, please try again later." }); }, "handler") }), "createLimiter"); var initializeRateLimiters = /* @__PURE__ */ __name(async () => { try { const config = await configManager.getConfig(); const rateLimitConfig = rateLimitConfigs[config?.isDevelopment ? "development" : "production"]; apiLimiter = createLimiter({ ...rateLimitConfig.api, errorName: "API Rate Limit Exceeded", message: "Too many requests, please try again later." }); authLimiter = createLimiter({ ...rateLimitConfig.auth, errorName: "Authentication Rate Limit Exceeded", message: "Too many authentication attempts, please try again later." }); sensitiveLimiter = createLimiter({ ...rateLimitConfig.sensitive, errorName: "Operation Rate Limit Exceeded", message: "Too many sensitive operations attempted, please try again later." }); logger.info("Rate limiters initialized successfully"); } catch (error) { logger.error("Failed to initialize rate limiters:", error); throw error; } }, "initializeRateLimiters"); var getApiLimiter = /* @__PURE__ */ __name(() => { if (!apiLimiter) throw new Error("Rate limiters not initialized. Call initializeRateLimiters() first."); return apiLimiter; }, "getApiLimiter"); var getAuthLimiter = /* @__PURE__ */ __name(() => { if (!authLimiter) throw new Error("Rate limiters not initialized. Call initializeRateLimiters() first."); return authLimiter; }, "getAuthLimiter"); var getSensitiveLimiter = /* @__PURE__ */ __name(() => { if (!sensitiveLimiter) throw new Error("Rate limiters not initialized. Call initializeRateLimiters() first."); return sensitiveLimiter; }, "getSensitiveLimiter"); // src/configs/apiEndpoints.ts var API_ENDPOINT_HEADER = "X-API-Endpoint"; // src/setup/rateLimitSetup.ts async function configureRateLimits(app) { try { await initializeRateLimiters(); const apiLimiter2 = getApiLimiter(); const authLimiter2 = getAuthLimiter(); const sensitiveLimiter2 = getSensitiveLimiter(); const rateLimitRules = [ { paths: ["/api", "/api/v1"], limiter: apiLimiter2 }, { paths: ["/api/auth", "/api/v1/auth"], limiter: authLimiter2, condition: /* @__PURE__ */ __name((req) => ["/login", "/register", "/authenticate"].some((p) => req.path.includes(p)), "condition") }, { paths: ["/api"], limiter: sensitiveLimiter2, condition: /* @__PURE__ */ __name((req) => req.path.includes("/2fa") || ["auth:2fa-setup", "auth:2fa-enable", "auth:2fa-disable", "auth:2fa-verify"].includes(req.headers[API_ENDPOINT_HEADER] || req.headers[API_ENDPOINT_HEADER.toLowerCase()]), "condition") } ]; rateLimitRules.forEach((rule) => { if (rule.condition) { const middleware = /* @__PURE__ */ __name((req, res, next) => rule.condition(req) ? rule.limiter(req, res, next) : next(), "middleware"); app.use(rule.paths, middleware); } else { app.use(rule.paths, rule.limiter); } }); logger.info("Rate limiting configured successfully"); } catch (error) { logger.error("Failed to configure rate limiting:", error); throw error; } } __name(configureRateLimits, "configureRateLimits"); // Annotate the CommonJS export names for ESM import in node: 0 && (module.exports = { configManager, configureRateLimits }); //# sourceMappingURL=index.js.map