This commit is contained in:
2025-10-24 17:06:14 -05:00
parent 12d0690b91
commit df8c75603f
11289 changed files with 1209053 additions and 318 deletions

View File

@@ -0,0 +1,327 @@
# Changelog
All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to
[Semantic Versioning](https://semver.org/spec/v2.0.0.html).
## [6.11.2](https://github.com/express-rate-limit/express-rate-limit/releases/tag/v6.11.2)
Fixed:
- Restored `IncrementResponse ` TypeScript type (See
[#397](https://github.com/express-rate-limit/express-rate-limit/pull/397))
## [6.11.1](https://github.com/express-rate-limit/express-rate-limit/releases/tag/v6.11.1)
### Fixed
- Check for prefixed keys when validating that the stores have single counted
keys (See
[#395](https://github.com/express-rate-limit/express-rate-limit/issues/395)).
## [6.11.0](https://github.com/express-rate-limit/express-rate-limit/releases/tag/v6.11.0)
### Added
- Support for retrieving the current hit count and reset time for a given key
from a store (See
[#390](https://github.com/express-rate-limit/express-rate-limit/issues/389)).
## [6.10.0](https://github.com/express-rate-limit/express-rate-limit/releases/tag/v6.10.0)
### Added
- Support for combined `RateLimit` header from the
[RateLimit header fields for HTTP standardization draft](https://github.com/ietf-wg-httpapi/ratelimit-headers)
adopted by the IETF. Enable by setting `standardHeaders: 'draft-7'`
- New `standardHeaders: 'draft-6'` option, treated equivalent to
`standardHeaders: true` from previous releases. (`true` and `false` are still
supported.)
- New `RateLimit-Policy` header added when `standardHeaders` is set to
`'draft-6'`, `'draft-7'`, or `true`
- Warning when using deprecated `draft_polli_ratelimit_headers` option
- Warning when using deprecated `onLimitReached` option
- Warning when `totalHits` value returned from Store is invalid
## [6.9.0](https://github.com/express-rate-limit/express-rate-limit/releases/tag/v6.9.0)
### Added
- New validaion check for double-counted requests
- Added help link to each ValidationError, directing users to the appropriate
wiki page for more info
### Changed
- Miscaleanous documenation improvements
## [6.8.1](https://github.com/express-rate-limit/express-rate-limit/releases/tag/v6.8.0) & [6.7.2](https://github.com/express-rate-limit/express-rate-limit/releases/tag/v6.8.0)
### Changed
- Revert 6.7.1 change that bumped typescript from 5.x to 4.x and
dts-bundle-generator from 8.x to 7.x (See
[#360](https://github.com/express-rate-limit/express-rate-limit/issues/360))
## [6.8.0](https://github.com/express-rate-limit/express-rate-limit/releases/tag/v6.8.0)
### Added
- Added a set of validation checks that will log an error if failed. See
https://github.com/express-rate-limit/express-rate-limit/wiki/Error-Codes for
a list of potential errors. Can be disabled by setting `validate: false` in
the configuration. Automatically disables after the first request. (See
[#358](https://github.com/express-rate-limit/express-rate-limit/issues/358))
## [6.7.1](https://github.com/express-rate-limit/express-rate-limit/releases/tag/v6.7.1)
### Fixed
- Fixed compatibility with TypeScript's TypeScript new `node16` module
resolution strategy (See
[#355](https://github.com/express-rate-limit/express-rate-limit/issues/355))
### Changed
- Bumped development dependencies
- This initially include bumping typescript from 4.x to 5.x and
dts-bundle-generator from 7.x to 8.x
- Added `node` 20 to list of versions the CI jobs run on.
No functional changes.
## [6.7.0](https://github.com/express-rate-limit/express-rate-limit/releases/tag/v6.7.0)
### Changed
- Updated links to point to the new `express-rate-limit` organization on GitHub.
- Added advertisement to `readme.md` for project sponsor
[Zuplo](https://zuplo.link/express-rate-limit).
- Updated to `typescript` version 5 and bumped other dependencies.
- Dropped `node` 12, and added `node` 19 to the list of versions the CI jobs run
on.
No functional changes.
## [6.6.0](https://github.com/nfriedly/express-rate-limit/releases/tag/v6.6.0)
### Added
- Added `shutdown` method to the Store interface and the MemoryStore.
## [6.5.2](https://github.com/nfriedly/express-rate-limit/releases/tag/v6.5.2)
### Fixed
- Fixed an issue with missing types in ESM monorepos.
## [6.5.1](https://github.com/nfriedly/express-rate-limit/releases/tag/v6.5.1)
### Added
- The message option can now be a (sync/asynx) function that returns a value
(#311)
### Changed
- Updated all dependencies
Note: 6.5.0 was not released due to CI automation issues.
## [6.4.0](https://github.com/nfriedly/express-rate-limit/releases/tag/v6.3.0)
### Added
- Adds Express 5 (`5.0.0-beta.1`) as a supported peer dependency (#304)
### Changed
- Tests are now run on Node 12, 14, 16 and 18 on CI (#305)
- Updated all development dependencies (#306)
## [6.3.0](https://github.com/nfriedly/express-rate-limit/releases/tag/v6.3.0)
### Changed
- Changes the build target to es2019 so that ESBuild outputs code that can run
with Node 12.
- Changes the minimum required Node version to 12.9.0.
## [6.2.1](https://github.com/nfriedly/express-rate-limit/releases/tag/v6.2.1)
### Fixed
- Use the default value for an option when `undefined` is passed to the rate
limiter.
## [6.2.0](https://github.com/nfriedly/express-rate-limit/releases/tag/v6.2.0)
### Added
- Export the `MemoryStore`, so it can now be imported as a named import
(`import { MemoryStore } from 'express-rate-limit'`).
### Fixed
- Deprecate the `onLimitReached` option (this was supposed to be deprecated in
v6.0.0 itself); developers should use a custom handler function that checks if
the rate limit has been exceeded instead.
## [6.1.0](https://github.com/nfriedly/express-rate-limit/releases/tag/v6.1.0)
### Added
- Added a named export `rateLimit` in case the default import does not work.
### Fixed
- Added a named export `default`, so Typescript CommonJS developers can
default-import the library (`import rateLimit from 'express-rate-limit'`).
## [6.0.5](https://github.com/nfriedly/express-rate-limit/releases/tag/v6.0.5)
### Fixed
- Use named imports for ExpressJS types so users do not need to enable the
`esModuleInterop` flag in their Typescript compiler configuration.
## [6.0.4](https://github.com/nfriedly/express-rate-limit/releases/tag/v6.0.4)
### Fixed
- Upload the built package as a `.tgz` to GitHub releases.
### Changed
- Add ` main` and `module` fields to `package.json`. This helps tools such as
ESLint that do not yet support the `exports` field.
- Bumped the minimum node.js version in `package-lock.json` to match
`package.json`
## [6.0.3](https://github.com/nfriedly/express-rate-limit/releases/tag/v6.0.3)
### Changed
- Bumped minimum Node version from 12.9 to 14.5 in `package.json` because the
transpiled output uses the nullish coalescing operator (`??`), which
[isn't supported in node.js prior to 14.x](https://node.green/#ES2020-features--nullish-coalescing-operator-----).
## [6.0.2](https://github.com/nfriedly/express-rate-limit/releases/v6.0.2)
### Fixed
- Ensure CommonJS projects can import the module.
### Added
- Add additional tests that test:
- importing the library in `js-cjs`, `js-esm`, `ts-cjs`, `ts-esm`
environments.
- usage of the library with external stores (`redis`, `mongo`, `memcached`,
`precise`).
### Changed
- Use [`esbuild`](https://esbuild.github.io/) to generate ESM and CJS output.
This reduces the size of the built package from 138 kb to 13kb and build time
to 4 ms! :rocket:
- Use [`dts-bundle-generator`](https://github.com/timocov/dts-bundle-generator)
to generate a single Typescript declaration file.
## [6.0.1](https://github.com/nfriedly/express-rate-limit/releases/v6.0.1)
### Fixed
- Ensure CommonJS projects can import the module.
## [6.0.0](https://github.com/nfriedly/express-rate-limit/releases/v6.0.0)
### Added
- `express` 4.x as a peer dependency.
- Better Typescript support (the library was rewritten in Typescript).
- Export the package as both ESM and CJS.
- Publish the built package (`.tgz` file) on GitHub releases as well as the npm
registry.
- Issue and PR templates.
- A contributing guide.
### Changed
- Rename the `draft_polli_ratelimit_headers` option to `standardHeaders`.
- Rename the `headers` option to `legacyHeaders`.
- `Retry-After` header is now sent if either `legacyHeaders` or
`standardHeaders` is set.
- Allow `keyGenerator` to be an async function/return a promise.
- Change the way custom stores are defined.
- Add the `init` method for stores to set themselves up using options passed
to the middleware.
- Rename the `incr` method to `increment`.
- Allow the `increment`, `decrement`, `resetKey` and `resetAll` methods to
return a promise.
- Old stores will automatically be promisified and used.
- The package can now only be used with NodeJS version 12.9.0 or greater.
- The `onLimitReached` configuration option is now deprecated. Replace it with a
custom `handler` that checks the number of hits.
### Removed
- Remove the deprecated `limiter.resetIp` method (use the `limiter.resetKey`
method instead).
- Remove the deprecated options `delayMs`, `delayAfter` (the delay functionality
was moved to the
[`express-slow-down`](https://github.com/nfriedly/express-slow-down) package)
and `global` (use a key generator that returns a constant value).
## [5.x](https://github.com/nfriedly/express-rate-limit/releases/tag/v5.5.1)
### Added
- The middleware ~throws~ logs an error if `request.ip` is undefined.
### Removed
- Removes typescript typings. (See
[#138](https://github.com/nfriedly/express-rate-limit/issues/138))
## [4.x](https://github.com/nfriedly/express-rate-limit/releases/tag/v4.0.4)
### Changed
- The library no longer modifies the passed-in options object, it instead makes
a clone of it.
## [3.x](https://github.com/nfriedly/express-rate-limit/releases/tag/v3.5.2)
### Added
- Simplifies the default `handler` function so that it no longer changes the
response format. The default handler also uses
[response.send](https://expressjs.com/en/4x/api.html#response.send).
### Changes
- `onLimitReached` now only triggers once for a client and window. However, the
`handle` method is called for every blocked request.
### Removed
- The `delayAfter` and `delayMs` options; they were moved to the
[express-slow-down](https://npmjs.org/package/express-slow-down) package.
## [2.x](https://github.com/nfriedly/express-rate-limit/releases/tag/v2.14.2)
### Added
- A `limiter.resetKey()` method to reset the hit counter for a particular client
### Changes
- The rate limiter now uses a less precise but less resource intensive method of
tracking hits from a client.
### Removed
- The `global` option.

View File

@@ -0,0 +1,674 @@
"use strict";
var __defProp = Object.defineProperty;
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
var __getOwnPropNames = Object.getOwnPropertyNames;
var __hasOwnProp = Object.prototype.hasOwnProperty;
var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
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);
var __publicField = (obj, key, value) => {
__defNormalProp(obj, typeof key !== "symbol" ? key + "" : key, value);
return value;
};
// source/index.ts
var source_exports = {};
__export(source_exports, {
MemoryStore: () => MemoryStore,
default: () => lib_default,
rateLimit: () => lib_default
});
module.exports = __toCommonJS(source_exports);
// source/headers.ts
var getResetSeconds = (resetTime, windowMs) => {
let resetSeconds = void 0;
if (resetTime) {
const deltaSeconds = Math.ceil((resetTime.getTime() - Date.now()) / 1e3);
resetSeconds = Math.max(0, deltaSeconds);
} else if (windowMs) {
resetSeconds = Math.ceil(windowMs / 1e3);
}
return resetSeconds;
};
var setLegacyHeaders = (response, info) => {
if (response.headersSent)
return;
response.setHeader("X-RateLimit-Limit", info.limit);
response.setHeader("X-RateLimit-Remaining", info.remaining);
if (info.resetTime instanceof Date) {
response.setHeader("Date", (/* @__PURE__ */ new Date()).toUTCString());
response.setHeader(
"X-RateLimit-Reset",
Math.ceil(info.resetTime.getTime() / 1e3)
);
}
};
var setDraft6Headers = (response, info, windowMs) => {
if (response.headersSent)
return;
const windowSeconds = Math.ceil(windowMs / 1e3);
const resetSeconds = getResetSeconds(info.resetTime);
response.setHeader("RateLimit-Policy", `${info.limit};w=${windowSeconds}`);
response.setHeader("RateLimit-Limit", info.limit);
response.setHeader("RateLimit-Remaining", info.remaining);
if (resetSeconds)
response.setHeader("RateLimit-Reset", resetSeconds);
};
var setDraft7Headers = (response, info, windowMs) => {
if (response.headersSent)
return;
const windowSeconds = Math.ceil(windowMs / 1e3);
const resetSeconds = getResetSeconds(info.resetTime, windowMs);
response.setHeader("RateLimit-Policy", `${info.limit};w=${windowSeconds}`);
response.setHeader(
"RateLimit",
`limit=${info.limit}, remaining=${info.remaining}, reset=${resetSeconds}`
);
};
var setRetryAfterHeader = (response, info, windowMs) => {
if (response.headersSent)
return;
const resetSeconds = getResetSeconds(info.resetTime, windowMs);
response.setHeader("Retry-After", resetSeconds);
};
// source/validations.ts
var import_node_net = require("net");
var ValidationError = class extends Error {
/**
* The code must be a string, in snake case and all capital, that starts with
* the substring `ERR_ERL_`.
*
* The message must be a string, starting with an uppercase character,
* describing the issue in detail.
*/
constructor(code, message) {
const url = `https://express-rate-limit.github.io/${code}/`;
super(`${message} See ${url} for more information.`);
__publicField(this, "name");
__publicField(this, "code");
__publicField(this, "help");
this.name = this.constructor.name;
this.code = code;
this.help = url;
}
};
var ChangeWarning = class extends ValidationError {
};
var _Validations = class _Validations {
constructor(enabled) {
// eslint-disable-next-line @typescript-eslint/parameter-properties
__publicField(this, "enabled");
this.enabled = enabled;
}
enable() {
this.enabled = true;
}
disable() {
this.enabled = false;
}
/**
* Checks whether the IP address is valid, and that it does not have a port
* number in it.
*
* See https://github.com/express-rate-limit/express-rate-limit/wiki/Error-Codes#err_erl_invalid_ip_address.
*
* @param ip {string | undefined} - The IP address provided by Express as request.ip.
*
* @returns {void}
*/
ip(ip) {
this.wrap(() => {
if (ip === void 0) {
throw new ValidationError(
"ERR_ERL_UNDEFINED_IP_ADDRESS",
`An undefined 'request.ip' was detected. This might indicate a misconfiguration or the connection being destroyed prematurely.`
);
}
if (!(0, import_node_net.isIP)(ip)) {
throw new ValidationError(
"ERR_ERL_INVALID_IP_ADDRESS",
`An invalid 'request.ip' (${ip}) was detected. Consider passing a custom 'keyGenerator' function to the rate limiter.`
);
}
});
}
/**
* Makes sure the trust proxy setting is not set to `true`.
*
* See https://github.com/express-rate-limit/express-rate-limit/wiki/Error-Codes#err_erl_permissive_trust_proxy.
*
* @param request {Request} - The Express request object.
*
* @returns {void}
*/
trustProxy(request) {
this.wrap(() => {
if (request.app.get("trust proxy") === true) {
throw new ValidationError(
"ERR_ERL_PERMISSIVE_TRUST_PROXY",
`The Express 'trust proxy' setting is true, which allows anyone to trivially bypass IP-based rate limiting.`
);
}
});
}
/**
* Makes sure the trust proxy setting is set in case the `X-Forwarded-For`
* header is present.
*
* See https://github.com/express-rate-limit/express-rate-limit/wiki/Error-Codes#err_erl_unset_trust_proxy.
*
* @param request {Request} - The Express request object.
*
* @returns {void}
*/
xForwardedForHeader(request) {
this.wrap(() => {
if (request.headers["x-forwarded-for"] && request.app.get("trust proxy") === false) {
throw new ValidationError(
"ERR_ERL_UNEXPECTED_X_FORWARDED_FOR",
`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.`
);
}
});
}
/**
* Ensures totalHits value from store is a positive integer.
*
* @param hits {any} - The `totalHits` returned by the store.
*/
positiveHits(hits) {
this.wrap(() => {
if (typeof hits !== "number" || hits < 1 || hits !== Math.round(hits)) {
throw new ValidationError(
"ERR_ERL_INVALID_HITS",
`The totalHits value returned from the store must be a positive integer, got ${hits}`
// eslint-disable-line @typescript-eslint/restrict-template-expressions
);
}
});
}
/**
* Ensures a given key is incremented only once per request.
*
* @param request {Request} - The Express request object.
* @param store {Store} - The store class.
* @param key {string} - The key used to store the client's hit count.
*
* @returns {void}
*/
singleCount(request, store, key) {
this.wrap(() => {
var _a;
let storeKeys = _Validations.singleCountKeys.get(request);
if (!storeKeys) {
storeKeys = /* @__PURE__ */ new Map();
_Validations.singleCountKeys.set(request, storeKeys);
}
const storeKey = store.localKeys ? store : store.constructor.name;
let keys = storeKeys.get(storeKey);
if (!keys) {
keys = [];
storeKeys.set(storeKey, keys);
}
const prefixedKey = `${(_a = store.prefix) != null ? _a : ""}${key}`;
if (keys.includes(prefixedKey)) {
throw new ValidationError(
"ERR_ERL_DOUBLE_COUNT",
`The hit count for ${key} was incremented more than once for a single request.`
);
}
keys.push(prefixedKey);
});
}
/**
* Warns the user that the behaviour for `max: 0` is changing in the next
* major release.
*
* @param max {number} - The maximum number of hits per client.
*
* @returns {void}
*/
max(max) {
this.wrap(() => {
if (max === 0) {
throw new ChangeWarning(
"WRN_ERL_MAX_ZERO",
`Setting max to 0 disables rate limiting in express-rate-limit v6 and older, but will cause all requests to be blocked in v7`
);
}
});
}
/**
* Warns the user that the `draft_polli_ratelimit_headers` option is deprecated
* and will be removed in the next major release.
*
* @param draft_polli_ratelimit_headers {boolean|undefined} - The now-deprecated setting that was used to enable standard headers.
*
* @returns {void}
*/
draftPolliHeaders(draft_polli_ratelimit_headers) {
this.wrap(() => {
if (draft_polli_ratelimit_headers) {
throw new ChangeWarning(
"WRN_ERL_DEPRECATED_DRAFT_POLLI_HEADERS",
`The draft_polli_ratelimit_headers configuration option is deprecated and will be removed in express-rate-limit v7, please set standardHeaders: 'draft-6' instead.`
);
}
});
}
/**
* Warns the user that the `onLimitReached` option is deprecated and will be removed in the next
* major release.
*
* @param onLimitReached {function|undefined} - The maximum number of hits per client.
*
* @returns {void}
*/
onLimitReached(onLimitReached) {
this.wrap(() => {
if (onLimitReached) {
throw new ChangeWarning(
"WRN_ERL_DEPRECATED_ON_LIMIT_REACHED",
`The onLimitReached configuration option is deprecated and will be removed in express-rate-limit v7.`
);
}
});
}
/**
* Warns the user when the selected headers option requires a reset time but
* the store does not provide one.
*
* @param resetTime {Date | undefined} - The timestamp when the client's hit count will be reset.
*
* @returns {void}
*/
headersResetTime(resetTime) {
this.wrap(() => {
if (!resetTime) {
throw new ValidationError(
"ERR_ERL_HEADERS_NO_RESET",
`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.`
);
}
});
}
wrap(validation) {
if (!this.enabled) {
return;
}
try {
validation.call(this);
} catch (error) {
if (error instanceof ChangeWarning)
console.warn(error);
else
console.error(error);
}
}
};
/**
* Maps the key used in a store for a certain request, and ensures that the
* same key isn't used more than once per request.
*
* The store can be any one of the following:
* - An instance, for stores like the MemoryStore where two instances do not
* share state.
* - A string (class name), for stores where multiple instances
* typically share state, such as the Redis store.
*/
__publicField(_Validations, "singleCountKeys", /* @__PURE__ */ new WeakMap());
var Validations = _Validations;
// source/memory-store.ts
var calculateNextResetTime = (windowMs) => {
const resetTime = /* @__PURE__ */ new Date();
resetTime.setMilliseconds(resetTime.getMilliseconds() + windowMs);
return resetTime;
};
var MemoryStore = class {
constructor() {
/**
* The duration of time before which all hit counts are reset (in milliseconds).
*/
__publicField(this, "windowMs");
/**
* The map that stores the number of hits for each client in memory.
*/
__publicField(this, "hits");
/**
* The time at which all hit counts will be reset.
*/
__publicField(this, "resetTime");
/**
* Reference to the active timer.
*/
__publicField(this, "interval");
/**
* Confirmation that the keys incremented in once instance of MemoryStore
* cannot affect other instances.
*/
__publicField(this, "localKeys", true);
}
/**
* Method that initializes the store.
*
* @param options {Options} - The options used to setup the middleware.
*/
init(options) {
this.windowMs = options.windowMs;
this.resetTime = calculateNextResetTime(this.windowMs);
this.hits = {};
this.interval = setInterval(async () => {
await this.resetAll();
}, this.windowMs);
if (this.interval.unref)
this.interval.unref();
}
/**
* Method to fetch a client's hit count and reset time.
*
* @param key {string} - The identifier for a client.
*
* @returns {ClientRateLimitInfo | undefined} - The number of hits and reset time for that client.
*
* @public
*/
async get(key) {
if (this.hits[key] !== void 0)
return {
totalHits: this.hits[key],
resetTime: this.resetTime
};
return void 0;
}
/**
* Method to increment a client's hit counter.
*
* @param key {string} - The identifier for a client.
*
* @returns {ClientRateLimitInfo} - The number of hits and reset time for that client.
*
* @public
*/
async increment(key) {
var _a;
const totalHits = ((_a = this.hits[key]) != null ? _a : 0) + 1;
this.hits[key] = totalHits;
return {
totalHits,
resetTime: this.resetTime
};
}
/**
* Method to decrement a client's hit counter.
*
* @param key {string} - The identifier for a client.
*
* @public
*/
async decrement(key) {
const current = this.hits[key];
if (current)
this.hits[key] = current - 1;
}
/**
* Method to reset a client's hit counter.
*
* @param key {string} - The identifier for a client.
*
* @public
*/
async resetKey(key) {
delete this.hits[key];
}
/**
* Method to reset everyone's hit counter.
*
* @public
*/
async resetAll() {
this.hits = {};
this.resetTime = calculateNextResetTime(this.windowMs);
}
/**
* Method to stop the timer (if currently running) and prevent any memory
* leaks.
*
* @public
*/
shutdown() {
clearInterval(this.interval);
}
};
// source/lib.ts
var isLegacyStore = (store) => (
// Check that `incr` exists but `increment` does not - store authors might want
// to keep both around for backwards compatibility.
typeof store.incr === "function" && typeof store.increment !== "function"
);
var promisifyStore = (passedStore) => {
if (!isLegacyStore(passedStore)) {
return passedStore;
}
const legacyStore = passedStore;
class PromisifiedStore {
/* istanbul ignore next */
async get(key) {
return void 0;
}
async increment(key) {
return new Promise((resolve, reject) => {
legacyStore.incr(
key,
(error, totalHits, resetTime) => {
if (error)
reject(error);
resolve({ totalHits, resetTime });
}
);
});
}
async decrement(key) {
return legacyStore.decrement(key);
}
async resetKey(key) {
return legacyStore.resetKey(key);
}
/* istanbul ignore next */
async resetAll() {
if (typeof legacyStore.resetAll === "function")
return legacyStore.resetAll();
}
}
return new PromisifiedStore();
};
var getOptionsFromConfig = (config) => {
const { validations, ...directlyPassableEntries } = config;
return {
...directlyPassableEntries,
validate: validations.enabled
};
};
var omitUndefinedOptions = (passedOptions) => {
const omittedOptions = {};
for (const k of Object.keys(passedOptions)) {
const key = k;
if (passedOptions[key] !== void 0) {
omittedOptions[key] = passedOptions[key];
}
}
return omittedOptions;
};
var parseOptions = (passedOptions) => {
var _a, _b, _c, _d;
const notUndefinedOptions = omitUndefinedOptions(passedOptions);
const validations = new Validations((_a = notUndefinedOptions == null ? void 0 : notUndefinedOptions.validate) != null ? _a : true);
validations.draftPolliHeaders(
notUndefinedOptions.draft_polli_ratelimit_headers
);
validations.onLimitReached(notUndefinedOptions.onLimitReached);
let standardHeaders = (_b = notUndefinedOptions.standardHeaders) != null ? _b : false;
if (standardHeaders === true || standardHeaders === void 0 && notUndefinedOptions.draft_polli_ratelimit_headers) {
standardHeaders = "draft-6";
}
const config = {
windowMs: 60 * 1e3,
max: 5,
message: "Too many requests, please try again later.",
statusCode: 429,
legacyHeaders: (_c = passedOptions.headers) != null ? _c : true,
requestPropertyName: "rateLimit",
skipFailedRequests: false,
skipSuccessfulRequests: false,
requestWasSuccessful: (_request, response) => response.statusCode < 400,
skip: (_request, _response) => false,
keyGenerator(request, _response) {
validations.ip(request.ip);
validations.trustProxy(request);
validations.xForwardedForHeader(request);
return request.ip;
},
async handler(request, response, _next, _optionsUsed) {
response.status(config.statusCode);
const message = typeof config.message === "function" ? await config.message(
request,
response
) : config.message;
if (!response.writableEnded) {
response.send(message);
}
},
onLimitReached(_request, _response, _optionsUsed) {
},
// Allow the default options to be overriden by the options passed to the middleware.
...notUndefinedOptions,
// `standardHeaders` is resolved into a draft version above, use that.
standardHeaders,
// Note that this field is declared after the user's options are spread in,
// so that this field doesn't get overriden with an un-promisified store!
store: promisifyStore((_d = notUndefinedOptions.store) != null ? _d : new MemoryStore()),
// Print an error to the console if a few known misconfigurations are detected.
validations
};
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") {
throw new TypeError(
"An invalid store was passed. Please ensure that the store is a class that implements the `Store` interface."
);
}
return config;
};
var handleAsyncErrors = (fn) => async (request, response, next) => {
try {
await Promise.resolve(fn(request, response, next)).catch(next);
} catch (error) {
next(error);
}
};
var rateLimit = (passedOptions) => {
var _a;
const config = parseOptions(passedOptions != null ? passedOptions : {});
const options = getOptionsFromConfig(config);
if (typeof config.store.init === "function")
config.store.init(options);
const middleware = handleAsyncErrors(
async (request, response, next) => {
const skip = await config.skip(request, response);
if (skip) {
next();
return;
}
const augmentedRequest = request;
const key = await config.keyGenerator(request, response);
const { totalHits, resetTime } = await config.store.increment(key);
config.validations.positiveHits(totalHits);
config.validations.singleCount(request, config.store, key);
const retrieveQuota = typeof config.max === "function" ? config.max(request, response) : config.max;
const maxHits = await retrieveQuota;
config.validations.max(maxHits);
const info = {
limit: maxHits,
current: totalHits,
remaining: Math.max(maxHits - totalHits, 0),
resetTime
};
augmentedRequest[config.requestPropertyName] = info;
if (config.legacyHeaders && !response.headersSent) {
setLegacyHeaders(response, info);
}
if (config.standardHeaders && !response.headersSent) {
if (config.standardHeaders === "draft-6") {
setDraft6Headers(response, info, config.windowMs);
} else if (config.standardHeaders === "draft-7") {
config.validations.headersResetTime(info.resetTime);
setDraft7Headers(response, info, config.windowMs);
}
}
if (config.skipFailedRequests || config.skipSuccessfulRequests) {
let decremented = false;
const decrementKey = async () => {
if (!decremented) {
await config.store.decrement(key);
decremented = true;
}
};
if (config.skipFailedRequests) {
response.on("finish", async () => {
if (!config.requestWasSuccessful(request, response))
await decrementKey();
});
response.on("close", async () => {
if (!response.writableEnded)
await decrementKey();
});
response.on("error", async () => {
await decrementKey();
});
}
if (config.skipSuccessfulRequests) {
response.on("finish", async () => {
if (config.requestWasSuccessful(request, response))
await decrementKey();
});
}
}
if (maxHits && totalHits === maxHits + 1) {
config.onLimitReached(request, response, options);
}
config.validations.disable();
if (maxHits && totalHits > maxHits) {
if (config.legacyHeaders || config.standardHeaders) {
setRetryAfterHeader(response, info, config.windowMs);
}
config.handler(request, response, next, options);
return;
}
next();
}
);
middleware.resetKey = config.store.resetKey.bind(config.store);
middleware.getKey = (_a = config.store.get) == null ? void 0 : _a.bind(
config.store
);
return middleware;
};
var lib_default = rateLimit;
// Annotate the CommonJS export names for ESM import in node:
0 && (module.exports = {
MemoryStore,
rateLimit
});
module.exports = rateLimit; module.exports.default = rateLimit; module.exports.rateLimit = rateLimit; module.exports.MemoryStore = MemoryStore;

View File

@@ -0,0 +1,410 @@
// Generated by dts-bundle-generator v7.0.0
import { NextFunction, Request, RequestHandler, Response } from 'express';
/**
* Callback that fires when a client's hit counter is incremented.
*
* @param error {Error | undefined} - The error that occurred, if any.
* @param totalHits {number} - The number of hits for that client so far.
* @param resetTime {Date | undefined} - The time when the counter resets.
*/
export type IncrementCallback = (error: Error | undefined, totalHits: number, resetTime: Date | undefined) => void;
/**
* Method (in the form of middleware) to generate/retrieve a value based on the
* incoming request.
*
* @param request {Request} - The Express request object.
* @param response {Response} - The Express response object.
*
* @returns {T} - The value needed.
*/
export type ValueDeterminingMiddleware<T> = (request: Request, response: Response) => T | Promise<T>;
/**
* Express request handler that sends back a response when a client is
* rate-limited.
*
* @param request {Request} - The Express request object.
* @param response {Response} - The Express response object.
* @param next {NextFunction} - The Express `next` function, can be called to skip responding.
* @param optionsUsed {Options} - The options used to set up the middleware.
*/
export type RateLimitExceededEventHandler = (request: Request, response: Response, next: NextFunction, optionsUsed: Options) => void;
/**
* Event callback that is triggered on a client's first request that exceeds the limit
* but not for subsequent requests. May be used for logging, etc. Should *not*
* send a response.
*
* @param request {Request} - The Express request object.
* @param response {Response} - The Express response object.
* @param optionsUsed {Options} - The options used to set up the middleware.
*/
export type RateLimitReachedEventHandler = (request: Request, response: Response, optionsUsed: Options) => void;
/**
* Data returned from the `Store` when a client's hit counter is incremented.
*
* @property totalHits {number} - The number of hits for that client so far.
* @property resetTime {Date | undefined} - The time when the counter resets.
*/
export type ClientRateLimitInfo = {
totalHits: number;
resetTime: Date | undefined;
};
export type IncrementResponse = ClientRateLimitInfo;
/**
* A modified Express request handler with the rate limit functions.
*/
export type RateLimitRequestHandler = RequestHandler & {
/**
* Method to reset a client's hit counter.
*
* @param key {string} - The identifier for a client.
*/
resetKey: (key: string) => void;
/**
* Method to fetch a client's hit count and reset time.
*
* @param key {string} - The identifier for a client.
*
* @returns {ClientRateLimitInfo} - The number of hits and reset time for that client.
*/
getKey?: (key: string) => Promise<ClientRateLimitInfo | undefined> | ClientRateLimitInfo | undefined;
};
/**
* An interface that all hit counter stores must implement.
*
* @deprecated 6.x - Implement the `Store` interface instead.
*/
export type LegacyStore = {
/**
* Method to increment a client's hit counter.
*
* @param key {string} - The identifier for a client.
* @param callback {IncrementCallback} - The callback to call once the counter is incremented.
*/
incr: (key: string, callback: IncrementCallback) => void;
/**
* Method to decrement a client's hit counter.
*
* @param key {string} - The identifier for a client.
*/
decrement: (key: string) => void;
/**
* Method to reset a client's hit counter.
*
* @param key {string} - The identifier for a client.
*/
resetKey: (key: string) => void;
/**
* Method to reset everyone's hit counter.
*/
resetAll?: () => void;
};
/**
* An interface that all hit counter stores must implement.
*/
export type Store = {
/**
* Method that initializes the store, and has access to the options passed to
* the middleware too.
*
* @param options {Options} - The options used to setup the middleware.
*/
init?: (options: Options) => void;
/**
* Method to fetch a client's hit count and reset time.
*
* @param key {string} - The identifier for a client.
*
* @returns {ClientRateLimitInfo} - The number of hits and reset time for that client.
*/
get?: (key: string) => Promise<ClientRateLimitInfo | undefined> | ClientRateLimitInfo | undefined;
/**
* Method to increment a client's hit counter.
*
* @param key {string} - The identifier for a client.
*
* @returns {IncrementResponse | undefined} - The number of hits and reset time for that client.
*/
increment: (key: string) => Promise<IncrementResponse> | IncrementResponse;
/**
* Method to decrement a client's hit counter.
*
* @param key {string} - The identifier for a client.
*/
decrement: (key: string) => Promise<void> | void;
/**
* Method to reset a client's hit counter.
*
* @param key {string} - The identifier for a client.
*/
resetKey: (key: string) => Promise<void> | void;
/**
* Method to reset everyone's hit counter.
*/
resetAll?: () => Promise<void> | void;
/**
* Method to shutdown the store, stop timers, and release all resources.
*/
shutdown?: () => Promise<void> | void;
/**
* Flag to indicate that keys incremented in one instance of this store can
* not affect other instances. Typically false if a database is used, true for
* MemoryStore.
*
* Used to help detect double-counting misconfigurations.
*/
localKeys?: boolean;
/**
* Optional value that the store prepends to keys
*
* Used by the double-count check to avoid false-positives when a key is counted twice, but with different prefixes
*/
prefix?: string;
};
export type DraftHeadersVersion = "draft-6" | "draft-7";
/**
* The configuration options for the rate limiter.
*/
export type Options = {
/**
* How long we should remember the requests.
*
* Defaults to `60000` ms (= 1 minute).
*/
windowMs: number;
/**
* The maximum number of connections to allow during the `window` before
* rate limiting the client.
*
* Can be the limit itself as a number or express middleware that parses
* the request and then figures out the limit.
*
* Defaults to `5`.
*/
max: number | ValueDeterminingMiddleware<number>;
/**
* The response body to send back when a client is rate limited.
*
* Defaults to `'Too many requests, please try again later.'`
*/
message: any | ValueDeterminingMiddleware<any>;
/**
* The HTTP status code to send back when a client is rate limited.
*
* Defaults to `HTTP 429 Too Many Requests` (RFC 6585).
*/
statusCode: number;
/**
* Whether to send `X-RateLimit-*` headers with the rate limit and the number
* of requests.
*
* Defaults to `true` (for backward compatibility).
*/
legacyHeaders: boolean;
/**
* Whether to enable support for the standardized rate limit headers (`RateLimit-*`).
*
* Defaults to `false` (for backward compatibility, but its use is recommended).
*/
standardHeaders: boolean | DraftHeadersVersion;
/**
* The name of the property on the request object to store the rate limit info.
*
* Defaults to `rateLimit`.
*/
requestPropertyName: string;
/**
* If `true`, the library will (by default) skip all requests that have a 4XX
* or 5XX status.
*
* Defaults to `false`.
*/
skipFailedRequests: boolean;
/**
* If `true`, the library will (by default) skip all requests that have a
* status code less than 400.
*
* Defaults to `false`.
*/
skipSuccessfulRequests: boolean;
/**
* Method to generate custom identifiers for clients.
*
* By default, the client's IP address is used.
*/
keyGenerator: ValueDeterminingMiddleware<string>;
/**
* Express request handler that sends back a response when a client is
* rate-limited.
*
* By default, sends back the `statusCode` and `message` set via the options.
*/
handler: RateLimitExceededEventHandler;
/**
* Express request handler that sends back a response when a client has
* reached their rate limit, and will be rate limited on their next request.
*
* @deprecated 6.x - Please use a custom `handler` that checks the number of
* hits instead.
*/
onLimitReached: RateLimitReachedEventHandler;
/**
* Method (in the form of middleware) to determine whether or not this request
* counts towards a client's quota.
*
* By default, skips no requests.
*/
skip: ValueDeterminingMiddleware<boolean>;
/**
* Method to determine whether or not the request counts as 'succesful'. Used
* when either `skipSuccessfulRequests` or `skipFailedRequests` is set to true.
*
* By default, requests with a response status code less than 400 are considered
* successful.
*/
requestWasSuccessful: ValueDeterminingMiddleware<boolean>;
/**
* The `Store` to use to store the hit count for each client.
*
* By default, the built-in `MemoryStore` will be used.
*/
store: Store | LegacyStore;
/**
* Whether or not the validation checks should run.
*/
validate: boolean;
/**
* Whether to send `X-RateLimit-*` headers with the rate limit and the number
* of requests.
*
* @deprecated 6.x - This option was renamed to `legacyHeaders`.
*/
headers?: boolean;
/**
* Whether to send `RateLimit-*` headers with the rate limit and the number
* of requests.
*
* @deprecated 6.x - This option was renamed to `standardHeaders`.
*/
draft_polli_ratelimit_headers?: boolean;
};
/**
* The extended request object that includes information about the client's
* rate limit.
*/
export type AugmentedRequest = Request & {
[key: string]: RateLimitInfo;
};
/**
* The rate limit related information for each client included in the
* Express request object.
*/
export type RateLimitInfo = {
limit: number;
current: number;
remaining: number;
resetTime: Date | undefined;
};
/**
*
* Create an instance of IP rate-limiting middleware for Express.
*
* @param passedOptions {Options} - Options to configure the rate limiter.
*
* @returns {RateLimitRequestHandler} - The middleware that rate-limits clients based on your configuration.
*
* @public
*/
export declare const rateLimit: (passedOptions?: Partial<Options>) => RateLimitRequestHandler;
/**
* A `Store` that stores the hit count for each client in memory.
*
* @public
*/
export declare class MemoryStore implements Store {
/**
* The duration of time before which all hit counts are reset (in milliseconds).
*/
windowMs: number;
/**
* The map that stores the number of hits for each client in memory.
*/
hits: {
[key: string]: number | undefined;
};
/**
* The time at which all hit counts will be reset.
*/
resetTime: Date;
/**
* Reference to the active timer.
*/
interval?: NodeJS.Timer;
/**
* Confirmation that the keys incremented in once instance of MemoryStore
* cannot affect other instances.
*/
localKeys: boolean;
/**
* Method that initializes the store.
*
* @param options {Options} - The options used to setup the middleware.
*/
init(options: Options): void;
/**
* Method to fetch a client's hit count and reset time.
*
* @param key {string} - The identifier for a client.
*
* @returns {ClientRateLimitInfo | undefined} - The number of hits and reset time for that client.
*
* @public
*/
get(key: string): Promise<ClientRateLimitInfo | undefined>;
/**
* Method to increment a client's hit counter.
*
* @param key {string} - The identifier for a client.
*
* @returns {ClientRateLimitInfo} - The number of hits and reset time for that client.
*
* @public
*/
increment(key: string): Promise<ClientRateLimitInfo>;
/**
* Method to decrement a client's hit counter.
*
* @param key {string} - The identifier for a client.
*
* @public
*/
decrement(key: string): Promise<void>;
/**
* Method to reset a client's hit counter.
*
* @param key {string} - The identifier for a client.
*
* @public
*/
resetKey(key: string): Promise<void>;
/**
* Method to reset everyone's hit counter.
*
* @public
*/
resetAll(): Promise<void>;
/**
* Method to stop the timer (if currently running) and prevent any memory
* leaks.
*
* @public
*/
shutdown(): void;
}
export {
rateLimit as default,
};
export {};

View File

@@ -0,0 +1,410 @@
// Generated by dts-bundle-generator v7.0.0
import { NextFunction, Request, RequestHandler, Response } from 'express';
/**
* Callback that fires when a client's hit counter is incremented.
*
* @param error {Error | undefined} - The error that occurred, if any.
* @param totalHits {number} - The number of hits for that client so far.
* @param resetTime {Date | undefined} - The time when the counter resets.
*/
export type IncrementCallback = (error: Error | undefined, totalHits: number, resetTime: Date | undefined) => void;
/**
* Method (in the form of middleware) to generate/retrieve a value based on the
* incoming request.
*
* @param request {Request} - The Express request object.
* @param response {Response} - The Express response object.
*
* @returns {T} - The value needed.
*/
export type ValueDeterminingMiddleware<T> = (request: Request, response: Response) => T | Promise<T>;
/**
* Express request handler that sends back a response when a client is
* rate-limited.
*
* @param request {Request} - The Express request object.
* @param response {Response} - The Express response object.
* @param next {NextFunction} - The Express `next` function, can be called to skip responding.
* @param optionsUsed {Options} - The options used to set up the middleware.
*/
export type RateLimitExceededEventHandler = (request: Request, response: Response, next: NextFunction, optionsUsed: Options) => void;
/**
* Event callback that is triggered on a client's first request that exceeds the limit
* but not for subsequent requests. May be used for logging, etc. Should *not*
* send a response.
*
* @param request {Request} - The Express request object.
* @param response {Response} - The Express response object.
* @param optionsUsed {Options} - The options used to set up the middleware.
*/
export type RateLimitReachedEventHandler = (request: Request, response: Response, optionsUsed: Options) => void;
/**
* Data returned from the `Store` when a client's hit counter is incremented.
*
* @property totalHits {number} - The number of hits for that client so far.
* @property resetTime {Date | undefined} - The time when the counter resets.
*/
export type ClientRateLimitInfo = {
totalHits: number;
resetTime: Date | undefined;
};
export type IncrementResponse = ClientRateLimitInfo;
/**
* A modified Express request handler with the rate limit functions.
*/
export type RateLimitRequestHandler = RequestHandler & {
/**
* Method to reset a client's hit counter.
*
* @param key {string} - The identifier for a client.
*/
resetKey: (key: string) => void;
/**
* Method to fetch a client's hit count and reset time.
*
* @param key {string} - The identifier for a client.
*
* @returns {ClientRateLimitInfo} - The number of hits and reset time for that client.
*/
getKey?: (key: string) => Promise<ClientRateLimitInfo | undefined> | ClientRateLimitInfo | undefined;
};
/**
* An interface that all hit counter stores must implement.
*
* @deprecated 6.x - Implement the `Store` interface instead.
*/
export type LegacyStore = {
/**
* Method to increment a client's hit counter.
*
* @param key {string} - The identifier for a client.
* @param callback {IncrementCallback} - The callback to call once the counter is incremented.
*/
incr: (key: string, callback: IncrementCallback) => void;
/**
* Method to decrement a client's hit counter.
*
* @param key {string} - The identifier for a client.
*/
decrement: (key: string) => void;
/**
* Method to reset a client's hit counter.
*
* @param key {string} - The identifier for a client.
*/
resetKey: (key: string) => void;
/**
* Method to reset everyone's hit counter.
*/
resetAll?: () => void;
};
/**
* An interface that all hit counter stores must implement.
*/
export type Store = {
/**
* Method that initializes the store, and has access to the options passed to
* the middleware too.
*
* @param options {Options} - The options used to setup the middleware.
*/
init?: (options: Options) => void;
/**
* Method to fetch a client's hit count and reset time.
*
* @param key {string} - The identifier for a client.
*
* @returns {ClientRateLimitInfo} - The number of hits and reset time for that client.
*/
get?: (key: string) => Promise<ClientRateLimitInfo | undefined> | ClientRateLimitInfo | undefined;
/**
* Method to increment a client's hit counter.
*
* @param key {string} - The identifier for a client.
*
* @returns {IncrementResponse | undefined} - The number of hits and reset time for that client.
*/
increment: (key: string) => Promise<IncrementResponse> | IncrementResponse;
/**
* Method to decrement a client's hit counter.
*
* @param key {string} - The identifier for a client.
*/
decrement: (key: string) => Promise<void> | void;
/**
* Method to reset a client's hit counter.
*
* @param key {string} - The identifier for a client.
*/
resetKey: (key: string) => Promise<void> | void;
/**
* Method to reset everyone's hit counter.
*/
resetAll?: () => Promise<void> | void;
/**
* Method to shutdown the store, stop timers, and release all resources.
*/
shutdown?: () => Promise<void> | void;
/**
* Flag to indicate that keys incremented in one instance of this store can
* not affect other instances. Typically false if a database is used, true for
* MemoryStore.
*
* Used to help detect double-counting misconfigurations.
*/
localKeys?: boolean;
/**
* Optional value that the store prepends to keys
*
* Used by the double-count check to avoid false-positives when a key is counted twice, but with different prefixes
*/
prefix?: string;
};
export type DraftHeadersVersion = "draft-6" | "draft-7";
/**
* The configuration options for the rate limiter.
*/
export type Options = {
/**
* How long we should remember the requests.
*
* Defaults to `60000` ms (= 1 minute).
*/
windowMs: number;
/**
* The maximum number of connections to allow during the `window` before
* rate limiting the client.
*
* Can be the limit itself as a number or express middleware that parses
* the request and then figures out the limit.
*
* Defaults to `5`.
*/
max: number | ValueDeterminingMiddleware<number>;
/**
* The response body to send back when a client is rate limited.
*
* Defaults to `'Too many requests, please try again later.'`
*/
message: any | ValueDeterminingMiddleware<any>;
/**
* The HTTP status code to send back when a client is rate limited.
*
* Defaults to `HTTP 429 Too Many Requests` (RFC 6585).
*/
statusCode: number;
/**
* Whether to send `X-RateLimit-*` headers with the rate limit and the number
* of requests.
*
* Defaults to `true` (for backward compatibility).
*/
legacyHeaders: boolean;
/**
* Whether to enable support for the standardized rate limit headers (`RateLimit-*`).
*
* Defaults to `false` (for backward compatibility, but its use is recommended).
*/
standardHeaders: boolean | DraftHeadersVersion;
/**
* The name of the property on the request object to store the rate limit info.
*
* Defaults to `rateLimit`.
*/
requestPropertyName: string;
/**
* If `true`, the library will (by default) skip all requests that have a 4XX
* or 5XX status.
*
* Defaults to `false`.
*/
skipFailedRequests: boolean;
/**
* If `true`, the library will (by default) skip all requests that have a
* status code less than 400.
*
* Defaults to `false`.
*/
skipSuccessfulRequests: boolean;
/**
* Method to generate custom identifiers for clients.
*
* By default, the client's IP address is used.
*/
keyGenerator: ValueDeterminingMiddleware<string>;
/**
* Express request handler that sends back a response when a client is
* rate-limited.
*
* By default, sends back the `statusCode` and `message` set via the options.
*/
handler: RateLimitExceededEventHandler;
/**
* Express request handler that sends back a response when a client has
* reached their rate limit, and will be rate limited on their next request.
*
* @deprecated 6.x - Please use a custom `handler` that checks the number of
* hits instead.
*/
onLimitReached: RateLimitReachedEventHandler;
/**
* Method (in the form of middleware) to determine whether or not this request
* counts towards a client's quota.
*
* By default, skips no requests.
*/
skip: ValueDeterminingMiddleware<boolean>;
/**
* Method to determine whether or not the request counts as 'succesful'. Used
* when either `skipSuccessfulRequests` or `skipFailedRequests` is set to true.
*
* By default, requests with a response status code less than 400 are considered
* successful.
*/
requestWasSuccessful: ValueDeterminingMiddleware<boolean>;
/**
* The `Store` to use to store the hit count for each client.
*
* By default, the built-in `MemoryStore` will be used.
*/
store: Store | LegacyStore;
/**
* Whether or not the validation checks should run.
*/
validate: boolean;
/**
* Whether to send `X-RateLimit-*` headers with the rate limit and the number
* of requests.
*
* @deprecated 6.x - This option was renamed to `legacyHeaders`.
*/
headers?: boolean;
/**
* Whether to send `RateLimit-*` headers with the rate limit and the number
* of requests.
*
* @deprecated 6.x - This option was renamed to `standardHeaders`.
*/
draft_polli_ratelimit_headers?: boolean;
};
/**
* The extended request object that includes information about the client's
* rate limit.
*/
export type AugmentedRequest = Request & {
[key: string]: RateLimitInfo;
};
/**
* The rate limit related information for each client included in the
* Express request object.
*/
export type RateLimitInfo = {
limit: number;
current: number;
remaining: number;
resetTime: Date | undefined;
};
/**
*
* Create an instance of IP rate-limiting middleware for Express.
*
* @param passedOptions {Options} - Options to configure the rate limiter.
*
* @returns {RateLimitRequestHandler} - The middleware that rate-limits clients based on your configuration.
*
* @public
*/
export declare const rateLimit: (passedOptions?: Partial<Options>) => RateLimitRequestHandler;
/**
* A `Store` that stores the hit count for each client in memory.
*
* @public
*/
export declare class MemoryStore implements Store {
/**
* The duration of time before which all hit counts are reset (in milliseconds).
*/
windowMs: number;
/**
* The map that stores the number of hits for each client in memory.
*/
hits: {
[key: string]: number | undefined;
};
/**
* The time at which all hit counts will be reset.
*/
resetTime: Date;
/**
* Reference to the active timer.
*/
interval?: NodeJS.Timer;
/**
* Confirmation that the keys incremented in once instance of MemoryStore
* cannot affect other instances.
*/
localKeys: boolean;
/**
* Method that initializes the store.
*
* @param options {Options} - The options used to setup the middleware.
*/
init(options: Options): void;
/**
* Method to fetch a client's hit count and reset time.
*
* @param key {string} - The identifier for a client.
*
* @returns {ClientRateLimitInfo | undefined} - The number of hits and reset time for that client.
*
* @public
*/
get(key: string): Promise<ClientRateLimitInfo | undefined>;
/**
* Method to increment a client's hit counter.
*
* @param key {string} - The identifier for a client.
*
* @returns {ClientRateLimitInfo} - The number of hits and reset time for that client.
*
* @public
*/
increment(key: string): Promise<ClientRateLimitInfo>;
/**
* Method to decrement a client's hit counter.
*
* @param key {string} - The identifier for a client.
*
* @public
*/
decrement(key: string): Promise<void>;
/**
* Method to reset a client's hit counter.
*
* @param key {string} - The identifier for a client.
*
* @public
*/
resetKey(key: string): Promise<void>;
/**
* Method to reset everyone's hit counter.
*
* @public
*/
resetAll(): Promise<void>;
/**
* Method to stop the timer (if currently running) and prevent any memory
* leaks.
*
* @public
*/
shutdown(): void;
}
export {
rateLimit as default,
};
export {};

View File

@@ -0,0 +1,410 @@
// Generated by dts-bundle-generator v7.0.0
import { NextFunction, Request, RequestHandler, Response } from 'express';
/**
* Callback that fires when a client's hit counter is incremented.
*
* @param error {Error | undefined} - The error that occurred, if any.
* @param totalHits {number} - The number of hits for that client so far.
* @param resetTime {Date | undefined} - The time when the counter resets.
*/
export type IncrementCallback = (error: Error | undefined, totalHits: number, resetTime: Date | undefined) => void;
/**
* Method (in the form of middleware) to generate/retrieve a value based on the
* incoming request.
*
* @param request {Request} - The Express request object.
* @param response {Response} - The Express response object.
*
* @returns {T} - The value needed.
*/
export type ValueDeterminingMiddleware<T> = (request: Request, response: Response) => T | Promise<T>;
/**
* Express request handler that sends back a response when a client is
* rate-limited.
*
* @param request {Request} - The Express request object.
* @param response {Response} - The Express response object.
* @param next {NextFunction} - The Express `next` function, can be called to skip responding.
* @param optionsUsed {Options} - The options used to set up the middleware.
*/
export type RateLimitExceededEventHandler = (request: Request, response: Response, next: NextFunction, optionsUsed: Options) => void;
/**
* Event callback that is triggered on a client's first request that exceeds the limit
* but not for subsequent requests. May be used for logging, etc. Should *not*
* send a response.
*
* @param request {Request} - The Express request object.
* @param response {Response} - The Express response object.
* @param optionsUsed {Options} - The options used to set up the middleware.
*/
export type RateLimitReachedEventHandler = (request: Request, response: Response, optionsUsed: Options) => void;
/**
* Data returned from the `Store` when a client's hit counter is incremented.
*
* @property totalHits {number} - The number of hits for that client so far.
* @property resetTime {Date | undefined} - The time when the counter resets.
*/
export type ClientRateLimitInfo = {
totalHits: number;
resetTime: Date | undefined;
};
export type IncrementResponse = ClientRateLimitInfo;
/**
* A modified Express request handler with the rate limit functions.
*/
export type RateLimitRequestHandler = RequestHandler & {
/**
* Method to reset a client's hit counter.
*
* @param key {string} - The identifier for a client.
*/
resetKey: (key: string) => void;
/**
* Method to fetch a client's hit count and reset time.
*
* @param key {string} - The identifier for a client.
*
* @returns {ClientRateLimitInfo} - The number of hits and reset time for that client.
*/
getKey?: (key: string) => Promise<ClientRateLimitInfo | undefined> | ClientRateLimitInfo | undefined;
};
/**
* An interface that all hit counter stores must implement.
*
* @deprecated 6.x - Implement the `Store` interface instead.
*/
export type LegacyStore = {
/**
* Method to increment a client's hit counter.
*
* @param key {string} - The identifier for a client.
* @param callback {IncrementCallback} - The callback to call once the counter is incremented.
*/
incr: (key: string, callback: IncrementCallback) => void;
/**
* Method to decrement a client's hit counter.
*
* @param key {string} - The identifier for a client.
*/
decrement: (key: string) => void;
/**
* Method to reset a client's hit counter.
*
* @param key {string} - The identifier for a client.
*/
resetKey: (key: string) => void;
/**
* Method to reset everyone's hit counter.
*/
resetAll?: () => void;
};
/**
* An interface that all hit counter stores must implement.
*/
export type Store = {
/**
* Method that initializes the store, and has access to the options passed to
* the middleware too.
*
* @param options {Options} - The options used to setup the middleware.
*/
init?: (options: Options) => void;
/**
* Method to fetch a client's hit count and reset time.
*
* @param key {string} - The identifier for a client.
*
* @returns {ClientRateLimitInfo} - The number of hits and reset time for that client.
*/
get?: (key: string) => Promise<ClientRateLimitInfo | undefined> | ClientRateLimitInfo | undefined;
/**
* Method to increment a client's hit counter.
*
* @param key {string} - The identifier for a client.
*
* @returns {IncrementResponse | undefined} - The number of hits and reset time for that client.
*/
increment: (key: string) => Promise<IncrementResponse> | IncrementResponse;
/**
* Method to decrement a client's hit counter.
*
* @param key {string} - The identifier for a client.
*/
decrement: (key: string) => Promise<void> | void;
/**
* Method to reset a client's hit counter.
*
* @param key {string} - The identifier for a client.
*/
resetKey: (key: string) => Promise<void> | void;
/**
* Method to reset everyone's hit counter.
*/
resetAll?: () => Promise<void> | void;
/**
* Method to shutdown the store, stop timers, and release all resources.
*/
shutdown?: () => Promise<void> | void;
/**
* Flag to indicate that keys incremented in one instance of this store can
* not affect other instances. Typically false if a database is used, true for
* MemoryStore.
*
* Used to help detect double-counting misconfigurations.
*/
localKeys?: boolean;
/**
* Optional value that the store prepends to keys
*
* Used by the double-count check to avoid false-positives when a key is counted twice, but with different prefixes
*/
prefix?: string;
};
export type DraftHeadersVersion = "draft-6" | "draft-7";
/**
* The configuration options for the rate limiter.
*/
export type Options = {
/**
* How long we should remember the requests.
*
* Defaults to `60000` ms (= 1 minute).
*/
windowMs: number;
/**
* The maximum number of connections to allow during the `window` before
* rate limiting the client.
*
* Can be the limit itself as a number or express middleware that parses
* the request and then figures out the limit.
*
* Defaults to `5`.
*/
max: number | ValueDeterminingMiddleware<number>;
/**
* The response body to send back when a client is rate limited.
*
* Defaults to `'Too many requests, please try again later.'`
*/
message: any | ValueDeterminingMiddleware<any>;
/**
* The HTTP status code to send back when a client is rate limited.
*
* Defaults to `HTTP 429 Too Many Requests` (RFC 6585).
*/
statusCode: number;
/**
* Whether to send `X-RateLimit-*` headers with the rate limit and the number
* of requests.
*
* Defaults to `true` (for backward compatibility).
*/
legacyHeaders: boolean;
/**
* Whether to enable support for the standardized rate limit headers (`RateLimit-*`).
*
* Defaults to `false` (for backward compatibility, but its use is recommended).
*/
standardHeaders: boolean | DraftHeadersVersion;
/**
* The name of the property on the request object to store the rate limit info.
*
* Defaults to `rateLimit`.
*/
requestPropertyName: string;
/**
* If `true`, the library will (by default) skip all requests that have a 4XX
* or 5XX status.
*
* Defaults to `false`.
*/
skipFailedRequests: boolean;
/**
* If `true`, the library will (by default) skip all requests that have a
* status code less than 400.
*
* Defaults to `false`.
*/
skipSuccessfulRequests: boolean;
/**
* Method to generate custom identifiers for clients.
*
* By default, the client's IP address is used.
*/
keyGenerator: ValueDeterminingMiddleware<string>;
/**
* Express request handler that sends back a response when a client is
* rate-limited.
*
* By default, sends back the `statusCode` and `message` set via the options.
*/
handler: RateLimitExceededEventHandler;
/**
* Express request handler that sends back a response when a client has
* reached their rate limit, and will be rate limited on their next request.
*
* @deprecated 6.x - Please use a custom `handler` that checks the number of
* hits instead.
*/
onLimitReached: RateLimitReachedEventHandler;
/**
* Method (in the form of middleware) to determine whether or not this request
* counts towards a client's quota.
*
* By default, skips no requests.
*/
skip: ValueDeterminingMiddleware<boolean>;
/**
* Method to determine whether or not the request counts as 'succesful'. Used
* when either `skipSuccessfulRequests` or `skipFailedRequests` is set to true.
*
* By default, requests with a response status code less than 400 are considered
* successful.
*/
requestWasSuccessful: ValueDeterminingMiddleware<boolean>;
/**
* The `Store` to use to store the hit count for each client.
*
* By default, the built-in `MemoryStore` will be used.
*/
store: Store | LegacyStore;
/**
* Whether or not the validation checks should run.
*/
validate: boolean;
/**
* Whether to send `X-RateLimit-*` headers with the rate limit and the number
* of requests.
*
* @deprecated 6.x - This option was renamed to `legacyHeaders`.
*/
headers?: boolean;
/**
* Whether to send `RateLimit-*` headers with the rate limit and the number
* of requests.
*
* @deprecated 6.x - This option was renamed to `standardHeaders`.
*/
draft_polli_ratelimit_headers?: boolean;
};
/**
* The extended request object that includes information about the client's
* rate limit.
*/
export type AugmentedRequest = Request & {
[key: string]: RateLimitInfo;
};
/**
* The rate limit related information for each client included in the
* Express request object.
*/
export type RateLimitInfo = {
limit: number;
current: number;
remaining: number;
resetTime: Date | undefined;
};
/**
*
* Create an instance of IP rate-limiting middleware for Express.
*
* @param passedOptions {Options} - Options to configure the rate limiter.
*
* @returns {RateLimitRequestHandler} - The middleware that rate-limits clients based on your configuration.
*
* @public
*/
export declare const rateLimit: (passedOptions?: Partial<Options>) => RateLimitRequestHandler;
/**
* A `Store` that stores the hit count for each client in memory.
*
* @public
*/
export declare class MemoryStore implements Store {
/**
* The duration of time before which all hit counts are reset (in milliseconds).
*/
windowMs: number;
/**
* The map that stores the number of hits for each client in memory.
*/
hits: {
[key: string]: number | undefined;
};
/**
* The time at which all hit counts will be reset.
*/
resetTime: Date;
/**
* Reference to the active timer.
*/
interval?: NodeJS.Timer;
/**
* Confirmation that the keys incremented in once instance of MemoryStore
* cannot affect other instances.
*/
localKeys: boolean;
/**
* Method that initializes the store.
*
* @param options {Options} - The options used to setup the middleware.
*/
init(options: Options): void;
/**
* Method to fetch a client's hit count and reset time.
*
* @param key {string} - The identifier for a client.
*
* @returns {ClientRateLimitInfo | undefined} - The number of hits and reset time for that client.
*
* @public
*/
get(key: string): Promise<ClientRateLimitInfo | undefined>;
/**
* Method to increment a client's hit counter.
*
* @param key {string} - The identifier for a client.
*
* @returns {ClientRateLimitInfo} - The number of hits and reset time for that client.
*
* @public
*/
increment(key: string): Promise<ClientRateLimitInfo>;
/**
* Method to decrement a client's hit counter.
*
* @param key {string} - The identifier for a client.
*
* @public
*/
decrement(key: string): Promise<void>;
/**
* Method to reset a client's hit counter.
*
* @param key {string} - The identifier for a client.
*
* @public
*/
resetKey(key: string): Promise<void>;
/**
* Method to reset everyone's hit counter.
*
* @public
*/
resetAll(): Promise<void>;
/**
* Method to stop the timer (if currently running) and prevent any memory
* leaks.
*
* @public
*/
shutdown(): void;
}
export {
rateLimit as default,
};
export {};

View File

@@ -0,0 +1,647 @@
var __defProp = Object.defineProperty;
var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
var __publicField = (obj, key, value) => {
__defNormalProp(obj, typeof key !== "symbol" ? key + "" : key, value);
return value;
};
// source/headers.ts
var getResetSeconds = (resetTime, windowMs) => {
let resetSeconds = void 0;
if (resetTime) {
const deltaSeconds = Math.ceil((resetTime.getTime() - Date.now()) / 1e3);
resetSeconds = Math.max(0, deltaSeconds);
} else if (windowMs) {
resetSeconds = Math.ceil(windowMs / 1e3);
}
return resetSeconds;
};
var setLegacyHeaders = (response, info) => {
if (response.headersSent)
return;
response.setHeader("X-RateLimit-Limit", info.limit);
response.setHeader("X-RateLimit-Remaining", info.remaining);
if (info.resetTime instanceof Date) {
response.setHeader("Date", (/* @__PURE__ */ new Date()).toUTCString());
response.setHeader(
"X-RateLimit-Reset",
Math.ceil(info.resetTime.getTime() / 1e3)
);
}
};
var setDraft6Headers = (response, info, windowMs) => {
if (response.headersSent)
return;
const windowSeconds = Math.ceil(windowMs / 1e3);
const resetSeconds = getResetSeconds(info.resetTime);
response.setHeader("RateLimit-Policy", `${info.limit};w=${windowSeconds}`);
response.setHeader("RateLimit-Limit", info.limit);
response.setHeader("RateLimit-Remaining", info.remaining);
if (resetSeconds)
response.setHeader("RateLimit-Reset", resetSeconds);
};
var setDraft7Headers = (response, info, windowMs) => {
if (response.headersSent)
return;
const windowSeconds = Math.ceil(windowMs / 1e3);
const resetSeconds = getResetSeconds(info.resetTime, windowMs);
response.setHeader("RateLimit-Policy", `${info.limit};w=${windowSeconds}`);
response.setHeader(
"RateLimit",
`limit=${info.limit}, remaining=${info.remaining}, reset=${resetSeconds}`
);
};
var setRetryAfterHeader = (response, info, windowMs) => {
if (response.headersSent)
return;
const resetSeconds = getResetSeconds(info.resetTime, windowMs);
response.setHeader("Retry-After", resetSeconds);
};
// source/validations.ts
import { isIP } from "net";
var ValidationError = class extends Error {
/**
* The code must be a string, in snake case and all capital, that starts with
* the substring `ERR_ERL_`.
*
* The message must be a string, starting with an uppercase character,
* describing the issue in detail.
*/
constructor(code, message) {
const url = `https://express-rate-limit.github.io/${code}/`;
super(`${message} See ${url} for more information.`);
__publicField(this, "name");
__publicField(this, "code");
__publicField(this, "help");
this.name = this.constructor.name;
this.code = code;
this.help = url;
}
};
var ChangeWarning = class extends ValidationError {
};
var _Validations = class _Validations {
constructor(enabled) {
// eslint-disable-next-line @typescript-eslint/parameter-properties
__publicField(this, "enabled");
this.enabled = enabled;
}
enable() {
this.enabled = true;
}
disable() {
this.enabled = false;
}
/**
* Checks whether the IP address is valid, and that it does not have a port
* number in it.
*
* See https://github.com/express-rate-limit/express-rate-limit/wiki/Error-Codes#err_erl_invalid_ip_address.
*
* @param ip {string | undefined} - The IP address provided by Express as request.ip.
*
* @returns {void}
*/
ip(ip) {
this.wrap(() => {
if (ip === void 0) {
throw new ValidationError(
"ERR_ERL_UNDEFINED_IP_ADDRESS",
`An undefined 'request.ip' was detected. This might indicate a misconfiguration or the connection being destroyed prematurely.`
);
}
if (!isIP(ip)) {
throw new ValidationError(
"ERR_ERL_INVALID_IP_ADDRESS",
`An invalid 'request.ip' (${ip}) was detected. Consider passing a custom 'keyGenerator' function to the rate limiter.`
);
}
});
}
/**
* Makes sure the trust proxy setting is not set to `true`.
*
* See https://github.com/express-rate-limit/express-rate-limit/wiki/Error-Codes#err_erl_permissive_trust_proxy.
*
* @param request {Request} - The Express request object.
*
* @returns {void}
*/
trustProxy(request) {
this.wrap(() => {
if (request.app.get("trust proxy") === true) {
throw new ValidationError(
"ERR_ERL_PERMISSIVE_TRUST_PROXY",
`The Express 'trust proxy' setting is true, which allows anyone to trivially bypass IP-based rate limiting.`
);
}
});
}
/**
* Makes sure the trust proxy setting is set in case the `X-Forwarded-For`
* header is present.
*
* See https://github.com/express-rate-limit/express-rate-limit/wiki/Error-Codes#err_erl_unset_trust_proxy.
*
* @param request {Request} - The Express request object.
*
* @returns {void}
*/
xForwardedForHeader(request) {
this.wrap(() => {
if (request.headers["x-forwarded-for"] && request.app.get("trust proxy") === false) {
throw new ValidationError(
"ERR_ERL_UNEXPECTED_X_FORWARDED_FOR",
`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.`
);
}
});
}
/**
* Ensures totalHits value from store is a positive integer.
*
* @param hits {any} - The `totalHits` returned by the store.
*/
positiveHits(hits) {
this.wrap(() => {
if (typeof hits !== "number" || hits < 1 || hits !== Math.round(hits)) {
throw new ValidationError(
"ERR_ERL_INVALID_HITS",
`The totalHits value returned from the store must be a positive integer, got ${hits}`
// eslint-disable-line @typescript-eslint/restrict-template-expressions
);
}
});
}
/**
* Ensures a given key is incremented only once per request.
*
* @param request {Request} - The Express request object.
* @param store {Store} - The store class.
* @param key {string} - The key used to store the client's hit count.
*
* @returns {void}
*/
singleCount(request, store, key) {
this.wrap(() => {
var _a;
let storeKeys = _Validations.singleCountKeys.get(request);
if (!storeKeys) {
storeKeys = /* @__PURE__ */ new Map();
_Validations.singleCountKeys.set(request, storeKeys);
}
const storeKey = store.localKeys ? store : store.constructor.name;
let keys = storeKeys.get(storeKey);
if (!keys) {
keys = [];
storeKeys.set(storeKey, keys);
}
const prefixedKey = `${(_a = store.prefix) != null ? _a : ""}${key}`;
if (keys.includes(prefixedKey)) {
throw new ValidationError(
"ERR_ERL_DOUBLE_COUNT",
`The hit count for ${key} was incremented more than once for a single request.`
);
}
keys.push(prefixedKey);
});
}
/**
* Warns the user that the behaviour for `max: 0` is changing in the next
* major release.
*
* @param max {number} - The maximum number of hits per client.
*
* @returns {void}
*/
max(max) {
this.wrap(() => {
if (max === 0) {
throw new ChangeWarning(
"WRN_ERL_MAX_ZERO",
`Setting max to 0 disables rate limiting in express-rate-limit v6 and older, but will cause all requests to be blocked in v7`
);
}
});
}
/**
* Warns the user that the `draft_polli_ratelimit_headers` option is deprecated
* and will be removed in the next major release.
*
* @param draft_polli_ratelimit_headers {boolean|undefined} - The now-deprecated setting that was used to enable standard headers.
*
* @returns {void}
*/
draftPolliHeaders(draft_polli_ratelimit_headers) {
this.wrap(() => {
if (draft_polli_ratelimit_headers) {
throw new ChangeWarning(
"WRN_ERL_DEPRECATED_DRAFT_POLLI_HEADERS",
`The draft_polli_ratelimit_headers configuration option is deprecated and will be removed in express-rate-limit v7, please set standardHeaders: 'draft-6' instead.`
);
}
});
}
/**
* Warns the user that the `onLimitReached` option is deprecated and will be removed in the next
* major release.
*
* @param onLimitReached {function|undefined} - The maximum number of hits per client.
*
* @returns {void}
*/
onLimitReached(onLimitReached) {
this.wrap(() => {
if (onLimitReached) {
throw new ChangeWarning(
"WRN_ERL_DEPRECATED_ON_LIMIT_REACHED",
`The onLimitReached configuration option is deprecated and will be removed in express-rate-limit v7.`
);
}
});
}
/**
* Warns the user when the selected headers option requires a reset time but
* the store does not provide one.
*
* @param resetTime {Date | undefined} - The timestamp when the client's hit count will be reset.
*
* @returns {void}
*/
headersResetTime(resetTime) {
this.wrap(() => {
if (!resetTime) {
throw new ValidationError(
"ERR_ERL_HEADERS_NO_RESET",
`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.`
);
}
});
}
wrap(validation) {
if (!this.enabled) {
return;
}
try {
validation.call(this);
} catch (error) {
if (error instanceof ChangeWarning)
console.warn(error);
else
console.error(error);
}
}
};
/**
* Maps the key used in a store for a certain request, and ensures that the
* same key isn't used more than once per request.
*
* The store can be any one of the following:
* - An instance, for stores like the MemoryStore where two instances do not
* share state.
* - A string (class name), for stores where multiple instances
* typically share state, such as the Redis store.
*/
__publicField(_Validations, "singleCountKeys", /* @__PURE__ */ new WeakMap());
var Validations = _Validations;
// source/memory-store.ts
var calculateNextResetTime = (windowMs) => {
const resetTime = /* @__PURE__ */ new Date();
resetTime.setMilliseconds(resetTime.getMilliseconds() + windowMs);
return resetTime;
};
var MemoryStore = class {
constructor() {
/**
* The duration of time before which all hit counts are reset (in milliseconds).
*/
__publicField(this, "windowMs");
/**
* The map that stores the number of hits for each client in memory.
*/
__publicField(this, "hits");
/**
* The time at which all hit counts will be reset.
*/
__publicField(this, "resetTime");
/**
* Reference to the active timer.
*/
__publicField(this, "interval");
/**
* Confirmation that the keys incremented in once instance of MemoryStore
* cannot affect other instances.
*/
__publicField(this, "localKeys", true);
}
/**
* Method that initializes the store.
*
* @param options {Options} - The options used to setup the middleware.
*/
init(options) {
this.windowMs = options.windowMs;
this.resetTime = calculateNextResetTime(this.windowMs);
this.hits = {};
this.interval = setInterval(async () => {
await this.resetAll();
}, this.windowMs);
if (this.interval.unref)
this.interval.unref();
}
/**
* Method to fetch a client's hit count and reset time.
*
* @param key {string} - The identifier for a client.
*
* @returns {ClientRateLimitInfo | undefined} - The number of hits and reset time for that client.
*
* @public
*/
async get(key) {
if (this.hits[key] !== void 0)
return {
totalHits: this.hits[key],
resetTime: this.resetTime
};
return void 0;
}
/**
* Method to increment a client's hit counter.
*
* @param key {string} - The identifier for a client.
*
* @returns {ClientRateLimitInfo} - The number of hits and reset time for that client.
*
* @public
*/
async increment(key) {
var _a;
const totalHits = ((_a = this.hits[key]) != null ? _a : 0) + 1;
this.hits[key] = totalHits;
return {
totalHits,
resetTime: this.resetTime
};
}
/**
* Method to decrement a client's hit counter.
*
* @param key {string} - The identifier for a client.
*
* @public
*/
async decrement(key) {
const current = this.hits[key];
if (current)
this.hits[key] = current - 1;
}
/**
* Method to reset a client's hit counter.
*
* @param key {string} - The identifier for a client.
*
* @public
*/
async resetKey(key) {
delete this.hits[key];
}
/**
* Method to reset everyone's hit counter.
*
* @public
*/
async resetAll() {
this.hits = {};
this.resetTime = calculateNextResetTime(this.windowMs);
}
/**
* Method to stop the timer (if currently running) and prevent any memory
* leaks.
*
* @public
*/
shutdown() {
clearInterval(this.interval);
}
};
// source/lib.ts
var isLegacyStore = (store) => (
// Check that `incr` exists but `increment` does not - store authors might want
// to keep both around for backwards compatibility.
typeof store.incr === "function" && typeof store.increment !== "function"
);
var promisifyStore = (passedStore) => {
if (!isLegacyStore(passedStore)) {
return passedStore;
}
const legacyStore = passedStore;
class PromisifiedStore {
/* istanbul ignore next */
async get(key) {
return void 0;
}
async increment(key) {
return new Promise((resolve, reject) => {
legacyStore.incr(
key,
(error, totalHits, resetTime) => {
if (error)
reject(error);
resolve({ totalHits, resetTime });
}
);
});
}
async decrement(key) {
return legacyStore.decrement(key);
}
async resetKey(key) {
return legacyStore.resetKey(key);
}
/* istanbul ignore next */
async resetAll() {
if (typeof legacyStore.resetAll === "function")
return legacyStore.resetAll();
}
}
return new PromisifiedStore();
};
var getOptionsFromConfig = (config) => {
const { validations, ...directlyPassableEntries } = config;
return {
...directlyPassableEntries,
validate: validations.enabled
};
};
var omitUndefinedOptions = (passedOptions) => {
const omittedOptions = {};
for (const k of Object.keys(passedOptions)) {
const key = k;
if (passedOptions[key] !== void 0) {
omittedOptions[key] = passedOptions[key];
}
}
return omittedOptions;
};
var parseOptions = (passedOptions) => {
var _a, _b, _c, _d;
const notUndefinedOptions = omitUndefinedOptions(passedOptions);
const validations = new Validations((_a = notUndefinedOptions == null ? void 0 : notUndefinedOptions.validate) != null ? _a : true);
validations.draftPolliHeaders(
notUndefinedOptions.draft_polli_ratelimit_headers
);
validations.onLimitReached(notUndefinedOptions.onLimitReached);
let standardHeaders = (_b = notUndefinedOptions.standardHeaders) != null ? _b : false;
if (standardHeaders === true || standardHeaders === void 0 && notUndefinedOptions.draft_polli_ratelimit_headers) {
standardHeaders = "draft-6";
}
const config = {
windowMs: 60 * 1e3,
max: 5,
message: "Too many requests, please try again later.",
statusCode: 429,
legacyHeaders: (_c = passedOptions.headers) != null ? _c : true,
requestPropertyName: "rateLimit",
skipFailedRequests: false,
skipSuccessfulRequests: false,
requestWasSuccessful: (_request, response) => response.statusCode < 400,
skip: (_request, _response) => false,
keyGenerator(request, _response) {
validations.ip(request.ip);
validations.trustProxy(request);
validations.xForwardedForHeader(request);
return request.ip;
},
async handler(request, response, _next, _optionsUsed) {
response.status(config.statusCode);
const message = typeof config.message === "function" ? await config.message(
request,
response
) : config.message;
if (!response.writableEnded) {
response.send(message);
}
},
onLimitReached(_request, _response, _optionsUsed) {
},
// Allow the default options to be overriden by the options passed to the middleware.
...notUndefinedOptions,
// `standardHeaders` is resolved into a draft version above, use that.
standardHeaders,
// Note that this field is declared after the user's options are spread in,
// so that this field doesn't get overriden with an un-promisified store!
store: promisifyStore((_d = notUndefinedOptions.store) != null ? _d : new MemoryStore()),
// Print an error to the console if a few known misconfigurations are detected.
validations
};
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") {
throw new TypeError(
"An invalid store was passed. Please ensure that the store is a class that implements the `Store` interface."
);
}
return config;
};
var handleAsyncErrors = (fn) => async (request, response, next) => {
try {
await Promise.resolve(fn(request, response, next)).catch(next);
} catch (error) {
next(error);
}
};
var rateLimit = (passedOptions) => {
var _a;
const config = parseOptions(passedOptions != null ? passedOptions : {});
const options = getOptionsFromConfig(config);
if (typeof config.store.init === "function")
config.store.init(options);
const middleware = handleAsyncErrors(
async (request, response, next) => {
const skip = await config.skip(request, response);
if (skip) {
next();
return;
}
const augmentedRequest = request;
const key = await config.keyGenerator(request, response);
const { totalHits, resetTime } = await config.store.increment(key);
config.validations.positiveHits(totalHits);
config.validations.singleCount(request, config.store, key);
const retrieveQuota = typeof config.max === "function" ? config.max(request, response) : config.max;
const maxHits = await retrieveQuota;
config.validations.max(maxHits);
const info = {
limit: maxHits,
current: totalHits,
remaining: Math.max(maxHits - totalHits, 0),
resetTime
};
augmentedRequest[config.requestPropertyName] = info;
if (config.legacyHeaders && !response.headersSent) {
setLegacyHeaders(response, info);
}
if (config.standardHeaders && !response.headersSent) {
if (config.standardHeaders === "draft-6") {
setDraft6Headers(response, info, config.windowMs);
} else if (config.standardHeaders === "draft-7") {
config.validations.headersResetTime(info.resetTime);
setDraft7Headers(response, info, config.windowMs);
}
}
if (config.skipFailedRequests || config.skipSuccessfulRequests) {
let decremented = false;
const decrementKey = async () => {
if (!decremented) {
await config.store.decrement(key);
decremented = true;
}
};
if (config.skipFailedRequests) {
response.on("finish", async () => {
if (!config.requestWasSuccessful(request, response))
await decrementKey();
});
response.on("close", async () => {
if (!response.writableEnded)
await decrementKey();
});
response.on("error", async () => {
await decrementKey();
});
}
if (config.skipSuccessfulRequests) {
response.on("finish", async () => {
if (config.requestWasSuccessful(request, response))
await decrementKey();
});
}
}
if (maxHits && totalHits === maxHits + 1) {
config.onLimitReached(request, response, options);
}
config.validations.disable();
if (maxHits && totalHits > maxHits) {
if (config.legacyHeaders || config.standardHeaders) {
setRetryAfterHeader(response, info, config.windowMs);
}
config.handler(request, response, next, options);
return;
}
next();
}
);
middleware.resetKey = config.store.resetKey.bind(config.store);
middleware.getKey = (_a = config.store.get) == null ? void 0 : _a.bind(
config.store
);
return middleware;
};
var lib_default = rateLimit;
export {
MemoryStore,
lib_default as default,
lib_default as rateLimit
};

20
qwen/nodejs/node_modules/express-rate-limit/license.md generated vendored Normal file
View File

@@ -0,0 +1,20 @@
# MIT License
Copyright 2021 Nathan Friedly
Permission is hereby granted, free of charge, to any person obtaining a copy of
this software and associated documentation files (the "Software"), to deal in
the Software without restriction, including without limitation the rights to
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
the Software, and to permit persons to whom the Software is furnished to do so,
subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

View File

@@ -0,0 +1,132 @@
{
"name": "express-rate-limit",
"version": "6.11.2",
"description": "Basic IP rate-limiting middleware for Express. Use to limit repeated requests to public APIs and/or endpoints such as password reset.",
"author": {
"name": "Nathan Friedly",
"url": "http://nfriedly.com/"
},
"license": "MIT",
"homepage": "https://github.com/express-rate-limit/express-rate-limit",
"repository": "https://github.com/express-rate-limit/express-rate-limit",
"keywords": [
"express-rate-limit",
"express",
"rate",
"limit",
"ratelimit",
"rate-limit",
"middleware",
"ip",
"auth",
"authorization",
"security",
"brute",
"force",
"bruteforce",
"brute-force",
"attack"
],
"type": "module",
"exports": {
".": {
"import": {
"types": "./dist/index.d.mts",
"default": "./dist/index.mjs"
},
"require": {
"types": "./dist/index.d.cts",
"default": "./dist/index.cjs"
}
}
},
"main": "./dist/index.cjs",
"module": "./dist/index.mjs",
"types": "./dist/index.d.ts",
"files": [
"dist/",
"tsconfig.json",
"package.json",
"readme.md",
"license.md",
"changelog.md"
],
"engines": {
"node": ">= 14"
},
"scripts": {
"clean": "del-cli dist/ coverage/ *.log *.tmp *.bak *.tgz",
"build:cjs": "esbuild --platform=node --bundle --target=es2019 --format=cjs --outfile=dist/index.cjs --footer:js=\"module.exports = rateLimit; module.exports.default = rateLimit; module.exports.rateLimit = rateLimit; module.exports.MemoryStore = MemoryStore;\" source/index.ts",
"build:esm": "esbuild --platform=node --bundle --target=es2019 --format=esm --outfile=dist/index.mjs source/index.ts",
"build:types": "dts-bundle-generator --out-file=dist/index.d.ts source/index.ts && cp dist/index.d.ts dist/index.d.cts && cp dist/index.d.ts dist/index.d.mts",
"compile": "run-s clean build:*",
"lint:code": "xo",
"lint:rest": "prettier --check .",
"lint": "run-s lint:*",
"format:code": "xo --fix",
"format:rest": "prettier --write .",
"format": "run-s format:*",
"test:lib": "cross-env NODE_NO_WARNINGS=1 NODE_OPTIONS=--experimental-vm-modules jest --config config/jest.json",
"test:ext": "cd test/external/ && bash run-all-tests",
"test": "run-s lint test:lib",
"pre-commit": "lint-staged",
"prepare": "run-s compile && husky install config/husky"
},
"peerDependencies": {
"express": "^4 || ^5"
},
"devDependencies": {
"@express-rate-limit/prettier": "1.0.0",
"@express-rate-limit/tsconfig": "1.0.0",
"@jest/globals": "29.6.2",
"@types/express": "4.17.17",
"@types/jest": "29.5.3",
"@types/node": "20.4.0",
"@types/supertest": "2.0.12",
"cross-env": "7.0.3",
"del-cli": "5.0.0",
"dts-bundle-generator": "7.0.0",
"esbuild": "0.18.11",
"express": "4.18.2",
"husky": "8.0.3",
"jest": "29.6.2",
"lint-staged": "13.2.3",
"npm-run-all": "4.1.5",
"ratelimit-header-parser": "0.1.0",
"supertest": "6.3.3",
"ts-jest": "29.1.1",
"ts-node": "10.9.1",
"typescript": "4.9.5",
"xo": "0.54.2"
},
"xo": {
"prettier": true,
"rules": {
"@typescript-eslint/no-empty-function": 0,
"@typescript-eslint/no-dynamic-delete": 0,
"@typescript-eslint/no-confusing-void-expression": 0,
"@typescript-eslint/consistent-indexed-object-style": [
"error",
"index-signature"
],
"n/no-unsupported-features/es-syntax": 0
},
"overrides": [
{
"files": "test/library/*.ts",
"rules": {
"@typescript-eslint/no-unsafe-argument": 0,
"@typescript-eslint/no-unsafe-assignment": 0
}
}
],
"ignore": [
"test/external"
]
},
"prettier": "@express-rate-limit/prettier",
"lint-staged": {
"{source,test}/**/*.ts": "xo --fix",
"**/*.{json,yaml,md}": "prettier --write "
}
}

579
qwen/nodejs/node_modules/express-rate-limit/readme.md generated vendored Normal file
View File

@@ -0,0 +1,579 @@
# <div align="center"> Express Rate Limit </div>
---
Sponsored by [Zuplo](https://zuplo.link/express-rate-limit) a fully-managed API
Gateway for developers. Add
[dynamic rate-limiting](https://zuplo.link/dynamic-rate-limiting),
authentication and more to any API in minutes. Learn more at
[zuplo.com](https://zuplo.link/express-rate-limit)
---
<div align="center">
[![tests](https://github.com/express-rate-limit/express-rate-limit/actions/workflows/ci.yaml/badge.svg)](https://github.com/express-rate-limit/express-rate-limit/actions/workflows/ci.yaml)
[![npm version](https://img.shields.io/npm/v/express-rate-limit.svg)](https://npmjs.org/package/express-rate-limit 'View this project on NPM')
[![npm downloads](https://img.shields.io/npm/dm/express-rate-limit)](https://www.npmjs.com/package/express-rate-limit)
Basic rate-limiting middleware for [Express](http://expressjs.com/). Use to
limit repeated requests to public APIs and/or endpoints such as password reset.
Plays nice with
[express-slow-down](https://www.npmjs.com/package/express-slow-down).
</div>
## Use Cases
Depending on your use case, you may need to switch to a different
[store](#store).
#### Abuse Prevention
The default `MemoryStore` is probably fine.
#### API Rate Limit Enforcement
You likely want to switch to a different [store](#store). As a performance
optimization, the default `MemoryStore` uses a global time window, so if your
limit is 10 requests per minute, a single user might be able to get an initial
burst of up to 20 requests in a row if they happen to get the first 10 in at the
end of one minute and the next 10 in at the start of the next minute. (After the
initial burst, they will be limited to the expected 10 requests per minute.) All
other stores use per-user time windows, so a user will get exactly 10 requests
regardless.
Additionally, if you have multiple servers or processes (for example, with the
[node:cluster](https://nodejs.org/api/cluster.html) module), you'll likely want
to use an external data store to syhcnronize hits
([redis](https://npmjs.com/package/rate-limit-redis),
[memcached](https://npmjs.org/package/rate-limit-memcached), [etc.](#store))
This will guarentee the expected result even if some requests get handled by
different servers/processes.
### Alternate Rate Limiters
This module was designed to only handle the basics and didn't even support
external stores initially. These other options all are excellent pieces of
software and may be more appropriate for some situations:
- [`rate-limiter-flexible`](https://www.npmjs.com/package/rate-limiter-flexible)
- [`express-brute`](https://www.npmjs.com/package/express-brute)
- [`rate-limiter`](https://www.npmjs.com/package/express-limiter)
## Installation
From the npm registry:
```sh
# Using npm
> npm install express-rate-limit
# Using yarn or pnpm
> yarn/pnpm add express-rate-limit
```
From Github Releases:
```sh
# Using npm
> npm install https://github.com/express-rate-limit/express-rate-limit/releases/download/v{version}/express-rate-limit.tgz
# Using yarn or pnpm
> yarn/pnpm add https://github.com/express-rate-limit/express-rate-limit/releases/download/v{version}/express-rate-limit.tgz
```
Replace `{version}` with the version of the package that you want to your, e.g.:
`6.0.0`.
## Usage
### Importing
This library is provided in ESM as well as CJS forms, and works with both
Javascript and Typescript projects.
**This package requires you to use Node 14 or above.**
Import it in a CommonJS project (`type: commonjs` or no `type` field in
`package.json`) as follows:
```ts
const { rateLimit } = require('express-rate-limit')
```
Import it in a ESM project (`type: module` in `package.json`) as follows:
```ts
import { rateLimit } from 'express-rate-limit'
```
### Examples
To use it in an API-only server where the rate-limiter should be applied to all
requests:
```ts
import { rateLimit } from 'express-rate-limit'
const limiter = rateLimit({
windowMs: 15 * 60 * 1000, // 15 minutes
max: 100, // Limit each IP to 100 requests per `window` (here, per 15 minutes)
standardHeaders: 'draft-7', // draft-6: RateLimit-* headers; draft-7: combined RateLimit header
legacyHeaders: false, // X-RateLimit-* headers
// store: ... , // Use an external store for more precise rate limiting
})
// Apply the rate limiting middleware to all requests
app.use(limiter)
```
To use it in a 'regular' web server (e.g. anything that uses
`express.static()`), where the rate-limiter should only apply to certain
requests:
```ts
import { rateLimit } from 'express-rate-limit'
const apiLimiter = rateLimit({
windowMs: 15 * 60 * 1000, // 15 minutes
max: 100, // Limit each IP to 100 requests per `window` (here, per 15 minutes)
standardHeaders: 'draft-7', // Set `RateLimit` and `RateLimit-Policy`` headers
legacyHeaders: false, // Disable the `X-RateLimit-*` headers
// store: ... , // Use an external store for more precise rate limiting
})
// Apply the rate limiting middleware to API calls only
app.use('/api', apiLimiter)
```
To create multiple instances to apply different rules to different endpoints:
```ts
import { rateLimit } from 'express-rate-limit'
const apiLimiter = rateLimit({
windowMs: 15 * 60 * 1000, // 15 minutes
max: 100, // Limit each IP to 100 requests per `window` (here, per 15 minutes)
standardHeaders: 'draft-7', // draft-6: RateLimit-* headers; draft-7: combined RateLimit header
legacyHeaders: false, // X-RateLimit-* headers
// store: ... , // Use an external store for more precise rate limiting
})
app.use('/api/', apiLimiter)
const createAccountLimiter = rateLimit({
windowMs: 60 * 60 * 1000, // 1 hour
max: 5, // Limit each IP to 5 create account requests per `window` (here, per hour)
message:
'Too many accounts created from this IP, please try again after an hour',
standardHeaders: 'draft-7', // draft-6: RateLimit-* headers; draft-7: combined RateLimit header
legacyHeaders: false, // X-RateLimit-* headers
})
app.post('/create-account', createAccountLimiter, (request, response) => {
//...
})
```
To use a custom store:
```ts
import { rateLimit } from 'express-rate-limit'
import RedisStore from 'rate-limit-redis'
import RedisClient from 'ioredis'
const redisClient = new RedisClient()
const rateLimiter = rateLimit({
windowMs: 15 * 60 * 1000, // 15 minutes
max: 100, // Limit each IP to 100 requests per `window` (here, per 15 minutes)
standardHeaders: 'draft-7', // draft-6: RateLimit-* headers; draft-7: combined RateLimit header
legacyHeaders: false, // X-RateLimit-* headers
store: new RedisStore({
/* ... */
}), // Use the external store
})
// Apply the rate limiting middleware to all requests
app.use(rateLimiter)
```
> **Note:** most stores will require additional configuration, such as custom
> prefixes, when using multiple instances. The default built-in memory store is
> an exception to this rule.
### Troubleshooting Proxy Issues
If you are behind a proxy/load balancer (usually the case with most hosting
services, e.g. Heroku, Bluemix, AWS ELB, Nginx, Cloudflare, Akamai, Fastly,
Firebase Hosting, Rackspace LB, Riverbed Stingray, etc.), the IP address of the
request might be the IP of the load balancer/reverse proxy (making the rate
limiter effectively a global one and blocking all requests once the limit is
reached) or `undefined`. To solve this issue, add the following line to your
code (right after you create the express application):
```ts
app.set('trust proxy', numberOfProxies)
```
Where `numberOfProxies` is the number of proxies between the user and the
server. To find the correct number, create a test endpoint that returns the
client IP:
```ts
app.set('trust proxy', 1)
app.get('/ip', (request, response) => response.send(request.ip))
```
Go to `/ip` and see the IP address returned in the response. If it matches your
public IP address, then the number of proxies is correct and the rate limiter
should now work correctly. If not, then keep increasing the number until it
does.
For more information about the `trust proxy` setting, take a look at the
[official Express documentation](https://expressjs.com/en/guide/behind-proxies.html).
## Configuration
### `windowMs`
> `number`
Time frame for which requests are checked/remembered. Also used in the
`Retry-After` header when the limit is reached.
Note: with stores that do not implement the `init` function (see the table in
the [`stores` section below](#stores)), you may need to configure this value
twice, once here and once on the store. In some cases the units also differ
(e.g. seconds vs miliseconds).
Defaults to `60000` ms (= 1 minute).
### `max`
> `number | function`
The maximum number of connections to allow during the `window` before rate
limiting the client.
Can be the limit itself as a number or a (sync/async) function that accepts the
Express `request` and `response` objects and then returns a number.
Defaults to `5`. Set it to `0` to disable the rate limiter.
An example of using a function:
```ts
const isPremium = async (user) => {
// ...
}
const limiter = rateLimit({
// ...
max: async (request, response) => {
if (await isPremium(request.user)) return 10
else return 5
},
})
```
### `message`
> `any`
The response body to send back when a client is rate limited.
May be a `string`, JSON object, or any other value that Express's
[`response.send`](https://expressjs.com/en/4x/api.html#res.send) method
supports. It can also be a (sync/async) function that accepts the Express
request and response objects and then returns a `string`, JSON object or any
other value the Express `response.send` function accepts.
Defaults to `'Too many requests, please try again later.'`
An example of using a function:
```ts
const isPremium = async (user) => {
// ...
}
const limiter = rateLimit({
// ...
message: async (request, response) => {
if (await isPremium(request.user))
return 'You can only make 10 requests every hour.'
else return 'You can only make 5 requests every hour.'
},
})
```
### `statusCode`
> `number`
The HTTP status code to send back when a client is rate limited.
Defaults to `429` (HTTP 429 Too Many Requests - RFC 6585).
### `legacyHeaders`
> `boolean`
Whether to send the legacy rate limit headers for the limit
(`X-RateLimit-Limit`), current usage (`X-RateLimit-Remaining`) and reset time
(if the store provides it) (`X-RateLimit-Reset`) on all responses. If set to
`true`, the middleware also sends the `Retry-After` header on all blocked
requests.
Defaults to `true` (for backward compatibility).
> Renamed in `6.x` from `headers` to `legacyHeaders`.
### `standardHeaders`
> `boolean` | `'draft-6'` | `'draft-7'`
Whether to enable support for headers conforming to the
[RateLimit header fields for HTTP standardization draft](https://github.com/ietf-wg-httpapi/ratelimit-headers)
adopted by the IETF.
If set to `draft-6`, separate `RateLimit-Policy` `RateLimit-Limit`,
`RateLimit-Remaining`, and, if the store supports it, `RateLimit-Reset` headers
are set on the response, in accordance with
[draft-ietf-httpapi-ratelimit-headers-06](https://datatracker.ietf.org/doc/html/draft-ietf-httpapi-ratelimit-headers-06).
If set to `draft-7`, a combined `RateLimit` header is set containing limit,
remaining, and reset values, and a `RateLimit-Policy` header is set, in
accordiance with
[draft-ietf-httpapi-ratelimit-headers-07](https://datatracker.ietf.org/doc/html/draft-ietf-httpapi-ratelimit-headers-07).
`windowMs` is used for the reset value if the store does not provide a reset
time.
If set to `true`, it is treated as `draft-6`, however this behavior may change
in a future semver major release.
If set to any truthy value, the middleware also sends the `Retry-After` header
on all blocked requests.
The `standardHeaders` option may be used in conjunction with, or instead of the
`legacyHeaders` option.
Tip: use
[ratelimit-header-parser](https://www.npmjs.com/package/ratelimit-header-parser)
in clients to read/parse any form of express-rate-limit's headers.
Defaults to `false`.
> Renamed in `6.x` from `draft_polli_ratelimit_headers` to `standardHeaders`.
### `requestPropertyName`
> `string`
The name of the property on the Express `request` object to store the rate limit
info.
Defaults to `'rateLimit'`.
### `skipFailedRequests`
> `boolean`
When set to `true`, failed requests won't be counted. Request considered failed
when the `requestWasSuccessful` option returns `false`. By default, this means
requests fail when:
- the response status >= 400
- the request was cancelled before last chunk of data was sent (response `close`
event triggered)
- the response `error` event was triggered by response
(Technically they are counted and then un-counted, so a large number of slow
requests all at once could still trigger a rate-limit. This may be fixed in a
future release. PRs welcome!)
Defaults to `false`.
### `skipSuccessfulRequests`
> `boolean`
If `true`, the library will (by default) skip all requests that are considered
'failed' by the `requestWasSuccessful` function. By default, this means requests
succeed when the response status code < 400.
(Technically they are counted and then un-counted, so a large number of slow
requests all at once could still trigger a rate-limit. This may be fixed in a
future release. PRs welcome!)
Defaults to `false`.
### `keyGenerator`
> `function`
Method to retrieve custom identifiers for clients, such as their IP address,
username, or API Key.
Should be a (sync/async) function that accepts the Express `request` and
`response` objects and then returns a string.
By default, the client's IP address is used:
```ts
const limiter = rateLimit({
// ...
keyGenerator: (request, response) => request.ip,
})
```
> **Note** If a `keyGenerator` returns the same value for every user, it becomes
> a global rate limiter. This could be combined with a second instance of
> `express-rate-limit` to have both global and per-user limits.
### `handler`
> `function`
Express request handler that sends back a response when a client is
rate-limited.
By default, sends back the `statusCode` and `message` set via the `options`,
similar to this:
```ts
const limiter = rateLimit({
// ...
handler: (request, response, next, options) =>
response.status(options.statusCode).send(options.message),
})
```
### `onLimitReached`
> `function`
A (sync/async) function that accepts the Express `request` and `response`
objects that is called the on the request where a client has just exceeded their
rate limit.
This method was
[deprecated in v6](https://github.com/express-rate-limit/express-rate-limit/releases/v6.0.0) -
Please use a custom `handler` that checks the number of hits instead.
### `skip`
> `function`
Function to determine whether or not this request counts towards a client's
quota. Should be a (sync/async) function that accepts the Express `request` and
`response` objects and then returns `true` or `false`.
Could also act as an allowlist for certain keys:
```ts
const allowlist = ['192.168.0.56', '192.168.0.21']
const limiter = rateLimit({
// ...
skip: (request, response) => allowlist.includes(request.ip),
})
```
By default, it skips no requests:
```ts
const limiter = rateLimit({
// ...
skip: (request, response) => false,
})
```
### `requestWasSuccessful`
> `function`
Method to determine whether or not the request counts as 'succesful'. Used when
either `skipSuccessfulRequests` or `skipFailedRequests` is set to true. Should
be a (sync/async) function that accepts the Express `request` and `response`
objects and then returns `true` or `false`.
By default, requests with a response status code less than 400 are considered
successful:
```ts
const limiter = rateLimit({
// ...
requestWasSuccessful: (request, response) => response.statusCode < 400,
})
```
### `validate`
> `boolean`
When enabled, a set of validation checks are run on the first request to detect
common misconfigurations with proxies, etc. Prints an error to the console if
any issue is detected.
Automatically disables after the first request is processed.
See https://github.com/express-rate-limit/express-rate-limit/wiki/Error-Codes
for more info.
Defaults to true.
### `store`
> `Store`
The `Store` to use to store the hit count for each client.
By default, the [`memory-store`](source/memory-store.ts) is used.
Here is a list of external stores:
| Name | Description | Legacy/Modern |
| -------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------- | ------------------- |
| [`memory-store`](source/memory-store.ts) | _(default)_ Simple in-memory option. Does not share state when app has multiple processes or servers. | Modern as of v6.0.0 |
| [`rate-limit-redis`](https://npmjs.com/package/rate-limit-redis) | A [Redis](http://redis.io/)-backed store, more suitable for large or demanding deployments. | Modern as of v3.0.0 |
| [`rate-limit-memcached`](https://npmjs.org/package/rate-limit-memcached) | A [Memcached](https://memcached.org/)-backed store. | Legacy |
| [`rate-limit-mongo`](https://www.npmjs.com/package/rate-limit-mongo) | A [MongoDB](https://www.mongodb.com/)-backed store. | Legacy |
| [`precise-memory-rate-limit`](https://www.npmjs.com/package/precise-memory-rate-limit) | A memory store similar to the built-in one, except that it stores a distinct timestamp for each key. | Modern as of v2.0.0 |
Take a look at
[this guide](https://github.com/express-rate-limit/express-rate-limit/wiki/Creating-Your-Own-Store)
if you wish to create your own store.
## Request API
A `request.rateLimit` property is added to all requests with the `limit`,
`current`, and `remaining` number of requests and, if the store provides it, a
`resetTime` Date object. These may be used in your application code to take
additional actions or inform the user of their status.
The property name can be configured with the configuration option
`requestPropertyName`.
## Instance API
### `resetKey(key)`
Resets the rate limiting for a given key. An example use case is to allow users
to complete a captcha or whatever to reset their rate limit, then call this
method.
## Issues and Contributing
If you encounter a bug or want to see something added/changed, please go ahead
and
[open an issue](https://github.com/nfriexpress-rate-limitedly/express-rate-limit/issues/new)!
If you need help with something, feel free to
[start a discussion](https://github.com/express-rate-limit/express-rate-limit/discussions/new)!
If you wish to contribute to the library, thanks! First, please read
[the contributing guide](contributing.md). Then you can pick up any issue and
fix/implement it!
## License
MIT © [Nathan Friedly](http://nfriedly.com/)

View File

@@ -0,0 +1,5 @@
{
"include": ["source/"],
"exclude": ["node_modules/"],
"extends": "@express-rate-limit/tsconfig"
}