basant307/AI_Governance_Project
048
1// source/ip-key-generator.ts2import { isIPv6 } from "node:net";3import { Address6 } from "ip-address";4function ipKeyGenerator(ip, ipv6Subnet = 56) {5 if (isIPv6(ip)) {6 const address = new Address6(ip);7 if (address.is4()) return address.to4().correctForm();8 if (ipv6Subnet) {9 const subnet = new Address6(`${ip}/${ipv6Subnet}`);10 return subnet.networkForm();11 }12 }13 return ip;14}15 16// source/memory-store.ts17var MemoryStore = class {18 constructor(validations2) {19 this.validations = validations2;20 /**21 * These two maps store usage (requests) and reset time by key (for example, IP22 * addresses or API keys).23 *24 * They are split into two to avoid having to iterate through the entire set to25 * determine which ones need reset. Instead, `Client`s are moved from `previous`26 * to `current` as they hit the endpoint. Once `windowMs` has elapsed, all clients27 * left in `previous`, i.e., those that have not made any recent requests, are28 * known to be expired and can be deleted in bulk.29 */30 this.previous = /* @__PURE__ */ new Map();31 this.current = /* @__PURE__ */ new Map();32 /**33 * Confirmation that the keys incremented in once instance of MemoryStore34 * cannot affect other instances.35 */36 this.localKeys = true;37 }38 /**39 * Method that initializes the store.40 *41 * @param options {Options} - The options used to setup the middleware.42 */43 init(options) {44 this.windowMs = options.windowMs;45 this.validations?.windowMs(this.windowMs);46 if (this.interval) clearInterval(this.interval);47 this.interval = setInterval(() => {48 this.clearExpired();49 }, this.windowMs);50 this.interval.unref?.();51 }52 /**53 * Method to fetch a client's hit count and reset time.54 *55 * @param key {string} - The identifier for a client.56 *57 * @returns {ClientRateLimitInfo | undefined} - The number of hits and reset time for that client.58 *59 * @public60 */61 async get(key) {62 return this.current.get(key) ?? this.previous.get(key);63 }64 /**65 * Method to increment a client's hit counter.66 *67 * @param key {string} - The identifier for a client.68 *69 * @returns {ClientRateLimitInfo} - The number of hits and reset time for that client.70 *71 * @public72 */73 async increment(key) {74 const client = this.getClient(key);75 const now = Date.now();76 if (client.resetTime.getTime() <= now) {77 this.resetClient(client, now);78 }79 client.totalHits++;80 return client;81 }82 /**83 * Method to decrement a client's hit counter.84 *85 * @param key {string} - The identifier for a client.86 *87 * @public88 */89 async decrement(key) {90 const client = this.getClient(key);91 if (client.totalHits > 0) client.totalHits--;92 }93 /**94 * Method to reset a client's hit counter.95 *96 * @param key {string} - The identifier for a client.97 *98 * @public99 */100 async resetKey(key) {101 this.current.delete(key);102 this.previous.delete(key);103 }104 /**105 * Method to reset everyone's hit counter.106 *107 * @public108 */109 async resetAll() {110 this.current.clear();111 this.previous.clear();112 }113 /**114 * Method to stop the timer (if currently running) and prevent any memory115 * leaks.116 *117 * @public118 */119 shutdown() {120 clearInterval(this.interval);121 void this.resetAll();122 }123 /**124 * Recycles a client by setting its hit count to zero, and reset time to125 * `windowMs` milliseconds from now.126 *127 * NOT to be confused with `#resetKey()`, which removes a client from both the128 * `current` and `previous` maps.129 *130 * @param client {Client} - The client to recycle.131 * @param now {number} - The current time, to which the `windowMs` is added to get the `resetTime` for the client.132 *133 * @return {Client} - The modified client that was passed in, to allow for chaining.134 */135 resetClient(client, now = Date.now()) {136 client.totalHits = 0;137 client.resetTime.setTime(now + this.windowMs);138 return client;139 }140 /**141 * Retrieves or creates a client, given a key. Also ensures that the client being142 * returned is in the `current` map.143 *144 * @param key {string} - The key under which the client is (or is to be) stored.145 *146 * @returns {Client} - The requested client.147 */148 getClient(key) {149 if (this.current.has(key)) return this.current.get(key);150 let client;151 if (this.previous.has(key)) {152 client = this.previous.get(key);153 this.previous.delete(key);154 } else {155 client = { totalHits: 0, resetTime: /* @__PURE__ */ new Date() };156 this.resetClient(client);157 }158 this.current.set(key, client);159 return client;160 }161 /**162 * Move current clients to previous, create a new map for current.163 *164 * This function is called every `windowMs`.165 */166 clearExpired() {167 this.previous = this.current;168 this.current = /* @__PURE__ */ new Map();169 }170};171 172// source/rate-limit.ts173import { isIPv6 as isIPv62 } from "node:net";174 175// source/console-logger.ts176var ConsoleLogger = {177 warn(...args) {178 console.warn(...args.reverse());179 },180 error(...args) {181 console.error(...args.reverse());182 }183};184 185// source/headers.ts186import { Buffer } from "node:buffer";187import { createHash } from "node:crypto";188var SUPPORTED_DRAFT_VERSIONS = [189 "draft-6",190 "draft-7",191 "draft-8"192];193var getResetSeconds = (windowMs, resetTime) => {194 let resetSeconds;195 if (resetTime) {196 const deltaSeconds = Math.ceil((resetTime.getTime() - Date.now()) / 1e3);197 resetSeconds = Math.max(0, deltaSeconds);198 } else {199 resetSeconds = Math.ceil(windowMs / 1e3);200 }201 return resetSeconds;202};203var getPartitionKey = (key) => {204 const hash = createHash("sha256");205 hash.update(key);206 const partitionKey = hash.digest("hex").slice(0, 12);207 return Buffer.from(partitionKey).toString("base64");208};209var setLegacyHeaders = (response, info) => {210 if (response.headersSent) return;211 response.setHeader("X-RateLimit-Limit", info.limit.toString());212 response.setHeader("X-RateLimit-Remaining", info.remaining.toString());213 if (info.resetTime instanceof Date) {214 response.setHeader("Date", (/* @__PURE__ */ new Date()).toUTCString());215 response.setHeader(216 "X-RateLimit-Reset",217 Math.ceil(info.resetTime.getTime() / 1e3).toString()218 );219 }220};221var setDraft6Headers = (response, info, windowMs) => {222 if (response.headersSent) return;223 const windowSeconds = Math.ceil(windowMs / 1e3);224 const resetSeconds = getResetSeconds(windowMs, info.resetTime);225 response.setHeader("RateLimit-Policy", `${info.limit};w=${windowSeconds}`);226 response.setHeader("RateLimit-Limit", info.limit.toString());227 response.setHeader("RateLimit-Remaining", info.remaining.toString());228 if (typeof resetSeconds === "number")229 response.setHeader("RateLimit-Reset", resetSeconds.toString());230};231var setDraft7Headers = (response, info, windowMs) => {232 if (response.headersSent) return;233 const windowSeconds = Math.ceil(windowMs / 1e3);234 const resetSeconds = getResetSeconds(windowMs, info.resetTime);235 response.setHeader("RateLimit-Policy", `${info.limit};w=${windowSeconds}`);236 response.setHeader(237 "RateLimit",238 `limit=${info.limit}, remaining=${info.remaining}, reset=${resetSeconds}`239 );240};241var setDraft8Headers = (response, info, windowMs, name, key) => {242 if (response.headersSent) return;243 const windowSeconds = Math.ceil(windowMs / 1e3);244 const resetSeconds = getResetSeconds(windowMs, info.resetTime);245 const partitionKey = getPartitionKey(key);246 const header = `r=${info.remaining}; t=${resetSeconds}`;247 const policy = `q=${info.limit}; w=${windowSeconds}; pk=:${partitionKey}:`;248 response.append("RateLimit", `"${name}"; ${header}`);249 response.append("RateLimit-Policy", `"${name}"; ${policy}`);250};251var setRetryAfterHeader = (response, info, windowMs) => {252 if (response.headersSent) return;253 const resetSeconds = getResetSeconds(windowMs, info.resetTime);254 response.setHeader("Retry-After", resetSeconds.toString());255};256 257// source/utils.ts258var omitUndefinedProperties = (passedOptions) => {259 const omittedOptions = {};260 for (const k of Object.keys(passedOptions)) {261 const key = k;262 if (passedOptions[key] !== void 0) {263 omittedOptions[key] = passedOptions[key];264 }265 }266 return omittedOptions;267};268 269// source/validations.ts270import { isIP } from "node:net";271var ValidationError = class extends Error {272 /**273 * The code must be a string, in snake case and all capital, that starts with274 * the substring `ERR_ERL_`.275 *276 * The message must be a string, starting with an uppercase character,277 * describing the issue in detail.278 */279 constructor(code, message) {280 const url = `https://express-rate-limit.github.io/${code}/`;281 super(`${message} See ${url} for more information.`);282 this.name = this.constructor.name;283 this.code = code;284 this.help = url;285 }286};287var ChangeWarning = class extends ValidationError {288};289var usedStores = /* @__PURE__ */ new Set();290var singleCountKeys = /* @__PURE__ */ new WeakMap();291var validations = {292 enabled: {293 default: true294 },295 // Should be EnabledValidations type, but that's a circular reference296 disable() {297 for (const k of Object.keys(this.enabled)) this.enabled[k] = false;298 },299 /**300 * Checks whether the IP address is valid, and that it does not have a port301 * number in it.302 *303 * See https://github.com/express-rate-limit/express-rate-limit/wiki/Error-Codes#err_erl_invalid_ip_address.304 *305 * @param ip {string | undefined} - The IP address provided by Express as request.ip.306 *307 * @returns {void}308 */309 ip(ip) {310 if (ip === void 0) {311 throw new ValidationError(312 "ERR_ERL_UNDEFINED_IP_ADDRESS",313 `An undefined 'request.ip' was detected. This might indicate a misconfiguration or the connection being destroyed prematurely.`314 );315 }316 if (!isIP(ip)) {317 throw new ValidationError(318 "ERR_ERL_INVALID_IP_ADDRESS",319 `An invalid 'request.ip' (${ip}) was detected. Consider passing a custom 'keyGenerator' function to the rate limiter.`320 );321 }322 },323 /**324 * Makes sure the trust proxy setting is not set to `true`.325 *326 * See https://github.com/express-rate-limit/express-rate-limit/wiki/Error-Codes#err_erl_permissive_trust_proxy.327 *328 * @param request {Request} - The Express request object.329 *330 * @returns {void}331 */332 trustProxy(request) {333 if (request.app.get("trust proxy") === true) {334 throw new ValidationError(335 "ERR_ERL_PERMISSIVE_TRUST_PROXY",336 `The Express 'trust proxy' setting is true, which allows anyone to trivially bypass IP-based rate limiting.`337 );338 }339 },340 /**341 * Makes sure the trust proxy setting is set in case the `X-Forwarded-For`342 * header is present.343 *344 * See https://github.com/express-rate-limit/express-rate-limit/wiki/Error-Codes#err_erl_unset_trust_proxy.345 *346 * @param request {Request} - The Express request object.347 *348 * @returns {void}349 */350 xForwardedForHeader(request) {351 if (request.headers["x-forwarded-for"] && request.app.get("trust proxy") === false) {352 throw new ValidationError(353 "ERR_ERL_UNEXPECTED_X_FORWARDED_FOR",354 `The 'X-Forwarded-For' header is set but the Express 'trust proxy' setting is false (default). This could indicate a misconfiguration which would prevent express-rate-limit from accurately identifying users.`355 );356 }357 },358 /**359 * Alert the user if the Forwarded header is set (standardized version of X-Forwarded-For - not supported by express as of version 5.1.0)360 *361 * @param request {Request} - The Express request object.362 *363 * @returns {void}364 */365 forwardedHeader(request) {366 if (request.headers.forwarded && request.ip === request.socket?.remoteAddress) {367 throw new ValidationError(368 "ERR_ERL_FORWARDED_HEADER",369 `The 'Forwarded' header (standardized X-Forwarded-For) is set but currently being ignored. Add a custom keyGenerator to use a value from this header.`370 );371 }372 },373 /**374 * Ensures totalHits value from store is a positive integer.375 *376 * @param hits {any} - The `totalHits` returned by the store.377 */378 positiveHits(hits) {379 if (typeof hits !== "number" || hits < 1 || hits !== Math.round(hits)) {380 throw new ValidationError(381 "ERR_ERL_INVALID_HITS",382 `The totalHits value returned from the store must be a positive integer, got ${hits}`383 );384 }385 },386 /**387 * Ensures a single store instance is not used with multiple express-rate-limit instances388 */389 unsharedStore(store) {390 if (usedStores.has(store)) {391 const maybeUniquePrefix = store?.localKeys ? "" : " (with a unique prefix)";392 throw new ValidationError(393 "ERR_ERL_STORE_REUSE",394 `A Store instance must not be shared across multiple rate limiters. Create a new instance of ${store.constructor.name}${maybeUniquePrefix} for each limiter instead.`395 );396 }397 usedStores.add(store);398 },399 /**400 * Ensures a given key is incremented only once per request.401 *402 * @param request {Request} - The Express request object.403 * @param store {Store} - The store class.404 * @param key {string} - The key used to store the client's hit count.405 *406 * @returns {void}407 */408 singleCount(request, store, key) {409 let storeKeys = singleCountKeys.get(request);410 if (!storeKeys) {411 storeKeys = /* @__PURE__ */ new Map();412 singleCountKeys.set(request, storeKeys);413 }414 const storeKey = store.localKeys ? store : store.constructor.name;415 let keys = storeKeys.get(storeKey);416 if (!keys) {417 keys = [];418 storeKeys.set(storeKey, keys);419 }420 const prefixedKey = `${store.prefix ?? ""}${key}`;421 if (keys.includes(prefixedKey)) {422 throw new ValidationError(423 "ERR_ERL_DOUBLE_COUNT",424 `The hit count for ${key} was incremented more than once for a single request.`425 );426 }427 keys.push(prefixedKey);428 },429 /**430 * Warns the user that the behaviour for `max: 0` / `limit: 0` is431 * changing in the next major release.432 *433 * @param limit {number} - The maximum number of hits per client.434 *435 * @returns {void}436 */437 limit(limit) {438 if (limit === 0) {439 throw new ChangeWarning(440 "WRN_ERL_MAX_ZERO",441 "Setting limit or max to 0 disables rate limiting in express-rate-limit v6 and older, but will cause all requests to be blocked in v7"442 );443 }444 },445 /**446 * Warns the user that the `draft_polli_ratelimit_headers` option is deprecated447 * and will be removed in the next major release.448 *449 * @param draft_polli_ratelimit_headers {any | undefined} - The now-deprecated setting that was used to enable standard headers.450 *451 * @returns {void}452 */453 draftPolliHeaders(draft_polli_ratelimit_headers) {454 if (draft_polli_ratelimit_headers) {455 throw new ChangeWarning(456 "WRN_ERL_DEPRECATED_DRAFT_POLLI_HEADERS",457 `The draft_polli_ratelimit_headers configuration option is deprecated and has been removed in express-rate-limit v7, please set standardHeaders: 'draft-6' instead.`458 );459 }460 },461 /**462 * Warns the user that the `onLimitReached` option is deprecated and463 * will be removed in the next major release.464 *465 * @param onLimitReached {any | undefined} - The maximum number of hits per client.466 *467 * @returns {void}468 */469 onLimitReached(onLimitReached) {470 if (onLimitReached) {471 throw new ChangeWarning(472 "WRN_ERL_DEPRECATED_ON_LIMIT_REACHED",473 "The onLimitReached configuration option is deprecated and has been removed in express-rate-limit v7."474 );475 }476 },477 /**478 * Warns the user when an invalid/unsupported version of the draft spec is passed.479 *480 * @param version {any | undefined} - The version passed by the user.481 *482 * @returns {void}483 */484 headersDraftVersion(version) {485 if (typeof version !== "string" || // @ts-expect-error This is fine. If version is not in the array, it will just return false.486 !SUPPORTED_DRAFT_VERSIONS.includes(version)) {487 const versionString = SUPPORTED_DRAFT_VERSIONS.join(", ");488 throw new ValidationError(489 "ERR_ERL_HEADERS_UNSUPPORTED_DRAFT_VERSION",490 `standardHeaders: only the following versions of the IETF draft specification are supported: ${versionString}.`491 );492 }493 },494 /**495 * Warns the user when the selected headers option requires a reset time but496 * the store does not provide one.497 *498 * @param resetTime {Date | undefined} - The timestamp when the client's hit count will be reset.499 *500 * @returns {void}501 */502 headersResetTime(resetTime) {503 if (!resetTime) {504 throw new ValidationError(505 "ERR_ERL_HEADERS_NO_RESET",506 `standardHeaders: 'draft-7' requires a 'resetTime', but the store did not provide one. The 'windowMs' value will be used instead, which may cause clients to wait longer than necessary.`507 );508 }509 },510 knownOptions(passedOptions) {511 if (!passedOptions) return;512 const optionsMap = {513 windowMs: true,514 limit: true,515 message: true,516 statusCode: true,517 legacyHeaders: true,518 standardHeaders: true,519 identifier: true,520 requestPropertyName: true,521 skipFailedRequests: true,522 skipSuccessfulRequests: true,523 keyGenerator: true,524 ipv6Subnet: true,525 handler: true,526 skip: true,527 requestWasSuccessful: true,528 store: true,529 validate: true,530 headers: true,531 max: true,532 passOnStoreError: true,533 logger: true534 };535 const validOptions = Object.keys(optionsMap).concat(536 "draft_polli_ratelimit_headers",537 // not a valid option anymore, but we have a more specific check for this one, so don't warn for it here538 // from express-slow-down - https://github.com/express-rate-limit/express-slow-down/blob/main/source/types.ts#L65539 "delayAfter",540 "delayMs",541 "maxDelayMs"542 );543 for (const key of Object.keys(passedOptions)) {544 if (!validOptions.includes(key)) {545 throw new ValidationError(546 "ERR_ERL_UNKNOWN_OPTION",547 `Unexpected configuration option: ${key}`548 // todo: suggest a valid option with a short levenstein distance?549 );550 }551 }552 },553 /**554 * Checks the options.validate setting to ensure that only recognized555 * validations are enabled or disabled.556 *557 * If any unrecognized values are found, an error is logged that558 * includes the list of supported validations.559 */560 validationsConfig() {561 const supportedValidations = Object.keys(this).filter(562 (k) => !["enabled", "disable"].includes(k)563 );564 supportedValidations.push("default");565 for (const key of Object.keys(this.enabled)) {566 if (!supportedValidations.includes(key)) {567 throw new ValidationError(568 "ERR_ERL_UNKNOWN_VALIDATION",569 `options.validate.${key} is not recognized. Supported validate options are: ${supportedValidations.join(570 ", "571 )}.`572 );573 }574 }575 },576 /**577 * Checks to see if the instance was created inside of a request handler,578 * which would prevent it from working correctly, with the default memory579 * store (or any other store with localKeys.)580 */581 creationStack(store) {582 const { stack } = new Error(583 "express-rate-limit validation check (set options.validate.creationStack=false to disable)"584 );585 if (stack?.includes("Layer.handle [as handle_request]") || // express v4586 stack?.includes("Layer.handleRequest")) {587 if (!store.localKeys) {588 throw new ValidationError(589 "ERR_ERL_CREATED_IN_REQUEST_HANDLER",590 "express-rate-limit instance should *usually* be created at app initialization, not when responding to a request."591 );592 }593 throw new ValidationError(594 "ERR_ERL_CREATED_IN_REQUEST_HANDLER",595 "express-rate-limit instance should be created at app initialization, not when responding to a request."596 );597 }598 },599 ipv6Subnet(ipv6Subnet) {600 if (ipv6Subnet === false) {601 return;602 }603 if (!Number.isInteger(ipv6Subnet) || ipv6Subnet < 32 || ipv6Subnet > 64) {604 throw new ValidationError(605 "ERR_ERL_IPV6_SUBNET",606 `Unexpected ipv6Subnet value: ${ipv6Subnet}. Expected an integer between 32 and 64 (usually 48-64).`607 );608 }609 },610 ipv6SubnetOrKeyGenerator(options) {611 if (options.ipv6Subnet !== void 0 && options.keyGenerator) {612 throw new ValidationError(613 "ERR_ERL_IPV6SUBNET_OR_KEYGENERATOR",614 `Incompatible options: the 'ipv6Subnet' option is ignored when a custom 'keyGenerator' function is also set.`615 );616 }617 },618 keyGeneratorIpFallback(keyGenerator) {619 if (!keyGenerator) {620 return;621 }622 const src = keyGenerator.toString();623 if ((src.includes("req.ip") || src.includes("request.ip")) && !src.includes("ipKeyGenerator")) {624 throw new ValidationError(625 "ERR_ERL_KEY_GEN_IPV6",626 "Custom keyGenerator appears to use request IP without calling the ipKeyGenerator helper function for IPv6 addresses. This could allow IPv6 users to bypass limits."627 );628 }629 },630 /**631 * Checks to see if the window duration is greater than 2^32 - 1. This is only632 * called by the default MemoryStore, since it uses Node's setInterval method.633 *634 * See https://nodejs.org/api/timers.html#setintervalcallback-delay-args.635 */636 windowMs(windowMs) {637 const SET_TIMEOUT_MAX = 2 ** 31 - 1;638 if (typeof windowMs !== "number" || Number.isNaN(windowMs) || windowMs < 1 || windowMs > SET_TIMEOUT_MAX) {639 throw new ValidationError(640 "ERR_ERL_WINDOW_MS",641 `Invalid windowMs value: ${windowMs}${typeof windowMs !== "number" ? ` (${typeof windowMs})` : ""}, must be a number between 1 and ${SET_TIMEOUT_MAX} when using the default MemoryStore`642 );643 }644 }645};646function validateLogger(logger) {647 if (typeof logger !== "object" || typeof logger.error !== "function" || typeof logger.warn !== "function") {648 throw new TypeError(649 "Provided logger does not implement the Logger interface"650 );651 }652}653var getValidations = (_enabled, logger) => {654 validateLogger(logger);655 let enabled;656 if (typeof _enabled === "boolean") {657 enabled = {658 default: _enabled659 };660 } else {661 enabled = {662 default: true,663 ..._enabled664 };665 }666 const wrappedValidations = { enabled };667 for (const [name, validation] of Object.entries(validations)) {668 if (typeof validation === "function")669 wrappedValidations[name] = (...args) => {670 if (!(enabled[name] ?? enabled.default)) {671 return;672 }673 try {674 ;675 validation.apply(676 wrappedValidations,677 args678 );679 } catch (error) {680 if (error instanceof ChangeWarning) logger.warn(error);681 else logger.error(error);682 }683 };684 }685 return wrappedValidations;686};687 688// source/rate-limit.ts689var isLegacyStore = (store) => (690 // Check that `incr` exists but `increment` does not - store authors might want691 // to keep both around for backwards compatibility.692 typeof store.incr === "function" && typeof store.increment !== "function"693);694var promisifyStore = (passedStore) => {695 if (!isLegacyStore(passedStore)) {696 return passedStore;697 }698 const legacyStore = passedStore;699 class PromisifiedStore {700 async increment(key) {701 return new Promise((resolve, reject) => {702 legacyStore.incr(703 key,704 (error, totalHits, resetTime) => {705 if (error) reject(error);706 resolve({ totalHits, resetTime });707 }708 );709 });710 }711 async decrement(key) {712 return legacyStore.decrement(key);713 }714 async resetKey(key) {715 return legacyStore.resetKey(key);716 }717 /* istanbul ignore next */718 async resetAll() {719 if (typeof legacyStore.resetAll === "function")720 return legacyStore.resetAll();721 }722 }723 return new PromisifiedStore();724};725var getOptionsFromConfig = (config) => {726 const { validations: validations2, ...directlyPassableEntries } = config;727 return {728 ...directlyPassableEntries,729 validate: validations2.enabled730 };731};732var parseOptions = (passedOptions) => {733 const notUndefinedOptions = omitUndefinedProperties(passedOptions);734 const logger = passedOptions.logger ?? ConsoleLogger;735 const validations2 = getValidations(736 notUndefinedOptions?.validate ?? true,737 logger738 );739 validations2.validationsConfig();740 validations2.knownOptions(passedOptions);741 validations2.draftPolliHeaders(742 // @ts-expect-error see the note above.743 notUndefinedOptions.draft_polli_ratelimit_headers744 );745 validations2.onLimitReached(notUndefinedOptions.onLimitReached);746 if (notUndefinedOptions.ipv6Subnet !== void 0 && typeof notUndefinedOptions.ipv6Subnet !== "function") {747 validations2.ipv6Subnet(notUndefinedOptions.ipv6Subnet);748 }749 validations2.keyGeneratorIpFallback(notUndefinedOptions.keyGenerator);750 validations2.ipv6SubnetOrKeyGenerator(notUndefinedOptions);751 let standardHeaders = notUndefinedOptions.standardHeaders ?? false;752 if (standardHeaders === true) standardHeaders = "draft-6";753 const config = {754 windowMs: 60 * 1e3,755 limit: passedOptions.max ?? 5,756 // `max` is deprecated, but support it anyways.757 message: "Too many requests, please try again later.",758 statusCode: 429,759 legacyHeaders: passedOptions.headers ?? true,760 identifier(request, _response) {761 let duration = "";762 const property = config.requestPropertyName;763 const { limit } = request[property];764 const seconds = config.windowMs / 1e3;765 const minutes = config.windowMs / (1e3 * 60);766 const hours = config.windowMs / (1e3 * 60 * 60);767 const days = config.windowMs / (1e3 * 60 * 60 * 24);768 if (seconds < 60) duration = `${seconds}sec`;769 else if (minutes < 60) duration = `${minutes}min`;770 else if (hours < 24) duration = `${hours}hr${hours > 1 ? "s" : ""}`;771 else duration = `${days}day${days > 1 ? "s" : ""}`;772 return `${limit}-in-${duration}`;773 },774 requestPropertyName: "rateLimit",775 skipFailedRequests: false,776 skipSuccessfulRequests: false,777 requestWasSuccessful: (_request, response) => response.statusCode < 400,778 skip: (_request, _response) => false,779 async keyGenerator(request, response) {780 validations2.ip(request.ip);781 validations2.trustProxy(request);782 validations2.xForwardedForHeader(request);783 validations2.forwardedHeader(request);784 const ip = request.ip;785 let subnet = 56;786 if (isIPv62(ip)) {787 subnet = typeof config.ipv6Subnet === "function" ? await config.ipv6Subnet(request, response) : config.ipv6Subnet;788 if (typeof config.ipv6Subnet === "function")789 validations2.ipv6Subnet(subnet);790 }791 return ipKeyGenerator(ip, subnet);792 },793 ipv6Subnet: 56,794 async handler(request, response, _next, _optionsUsed) {795 response.status(config.statusCode);796 const message = typeof config.message === "function" ? await config.message(797 request,798 response799 ) : config.message;800 if (!response.writableEnded) response.send(message);801 },802 passOnStoreError: false,803 // Allow the default options to be overridden by the passed options.804 ...notUndefinedOptions,805 // `standardHeaders` is resolved into a draft version above, use that.806 standardHeaders,807 // Note that this field is declared after the user's options are spread in,808 // so that this field doesn't get overridden with an un-promisified store!809 store: promisifyStore(810 notUndefinedOptions.store ?? new MemoryStore(validations2)811 ),812 // Print an error to the console if a few known misconfigurations are detected.813 validations: validations2,814 logger815 };816 if (typeof config.store.increment !== "function" || typeof config.store.decrement !== "function" || typeof config.store.resetKey !== "function" || config.store.resetAll !== void 0 && typeof config.store.resetAll !== "function" || config.store.init !== void 0 && typeof config.store.init !== "function") {817 throw new TypeError(818 "An invalid store was passed. Please ensure that the store is a class that implements the `Store` interface."819 );820 }821 return config;822};823var handleAsyncErrors = (fn) => async (request, response, next) => {824 try {825 await Promise.resolve(fn(request, response, next)).catch(next);826 } catch (error) {827 next(error);828 }829};830var rateLimit = (passedOptions) => {831 const config = parseOptions(passedOptions ?? {});832 const options = getOptionsFromConfig(config);833 config.validations.creationStack(config.store);834 config.validations.unsharedStore(config.store);835 if (typeof config.store.init === "function") {836 try {837 const storeInit = config.store.init(options);838 if (storeInit instanceof Promise) {839 storeInit.catch(840 (error) => config.logger.error(841 error,842 "express-rate-limit: async error during store initialization."843 )844 );845 }846 } catch (error) {847 config.logger.error(848 error,849 "express-rate-limit: error during store initialization."850 );851 }852 }853 const middleware = handleAsyncErrors(854 async (request, response, next) => {855 const closePromise = config.skipFailedRequests && new Promise((resolve) => response.once("close", resolve));856 const finishPromise = (config.skipFailedRequests || config.skipSuccessfulRequests) && new Promise((resolve) => response.once("finish", resolve));857 const errorPromise = config.skipFailedRequests && new Promise((resolve) => response.once("error", resolve));858 const skip = await config.skip(request, response);859 if (skip) {860 next();861 return;862 }863 const augmentedRequest = request;864 const key = await config.keyGenerator(request, response);865 let totalHits = 0;866 let resetTime;867 try {868 const incrementResult = await config.store.increment(key);869 totalHits = incrementResult.totalHits;870 resetTime = incrementResult.resetTime;871 } catch (error) {872 if (config.passOnStoreError) {873 config.logger.error(874 error,875 "express-rate-limit: error from store, allowing request without rate-limiting."876 );877 next();878 return;879 }880 throw error;881 }882 config.validations.positiveHits(totalHits);883 config.validations.singleCount(request, config.store, key);884 const retrieveLimit = typeof config.limit === "function" ? config.limit(request, response) : config.limit;885 const limit = await retrieveLimit;886 config.validations.limit(limit);887 const info = {888 limit,889 used: totalHits,890 remaining: Math.max(limit - totalHits, 0),891 resetTime,892 key893 };894 Object.defineProperty(info, "current", {895 configurable: false,896 enumerable: false,897 value: totalHits898 });899 augmentedRequest[config.requestPropertyName] = info;900 if (config.legacyHeaders && !response.headersSent) {901 setLegacyHeaders(response, info);902 }903 if (config.standardHeaders && !response.headersSent) {904 switch (config.standardHeaders) {905 case "draft-6": {906 setDraft6Headers(response, info, config.windowMs);907 break;908 }909 case "draft-7": {910 config.validations.headersResetTime(info.resetTime);911 setDraft7Headers(response, info, config.windowMs);912 break;913 }914 case "draft-8": {915 const retrieveName = typeof config.identifier === "function" ? config.identifier(request, response) : config.identifier;916 const name = await retrieveName;917 config.validations.headersResetTime(info.resetTime);918 setDraft8Headers(response, info, config.windowMs, name, key);919 break;920 }921 default: {922 config.validations.headersDraftVersion(config.standardHeaders);923 break;924 }925 }926 }927 if (config.skipFailedRequests || config.skipSuccessfulRequests) {928 let decremented = false;929 const decrementKey = async () => {930 if (!decremented) {931 await config.store.decrement(key);932 decremented = true;933 }934 };935 if (config.skipFailedRequests) {936 if (finishPromise) {937 void finishPromise.then(async () => {938 if (!await config.requestWasSuccessful(request, response))939 await decrementKey();940 });941 }942 if (closePromise) {943 void closePromise.then(async () => {944 if (!response.writableEnded) await decrementKey();945 });946 }947 if (errorPromise) {948 void errorPromise.then(async () => {949 await decrementKey();950 });951 }952 }953 if (config.skipSuccessfulRequests) {954 if (finishPromise) {955 void finishPromise.then(async () => {956 if (await config.requestWasSuccessful(request, response))957 await decrementKey();958 });959 }960 }961 }962 config.validations.disable();963 if (totalHits > limit) {964 if (config.legacyHeaders || config.standardHeaders) {965 setRetryAfterHeader(response, info, config.windowMs);966 }967 config.handler(request, response, next, options);968 return;969 }970 next();971 }972 );973 const getThrowFn = () => {974 throw new Error("The current store does not support the get/getKey method");975 };976 middleware.resetKey = config.store.resetKey.bind(config.store);977 middleware.getKey = typeof config.store.get === "function" ? config.store.get.bind(config.store) : getThrowFn;978 return middleware;979};980var rate_limit_default = rateLimit;981export {982 MemoryStore,983 rate_limit_default as default,984 ipKeyGenerator,985 rate_limit_default as rateLimit986};987 