mirror of
https://github.com/xfarrow/blink
synced 2025-06-27 09:03:02 +02:00
Change endpoint from persons to people
This commit is contained in:
838
backend/apis/nodejs/node_modules/express-rate-limit/dist/index.cjs
generated
vendored
Normal file
838
backend/apis/nodejs/node_modules/express-rate-limit/dist/index.cjs
generated
vendored
Normal file
@ -0,0 +1,838 @@
|
||||
"use strict";
|
||||
var __defProp = Object.defineProperty;
|
||||
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
||||
var __getOwnPropNames = Object.getOwnPropertyNames;
|
||||
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
||||
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);
|
||||
|
||||
// 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 import_node_buffer = require("buffer");
|
||||
var import_node_crypto = require("crypto");
|
||||
var SUPPORTED_DRAFT_VERSIONS = ["draft-6", "draft-7", "draft-8"];
|
||||
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 getPartitionKey = (key) => {
|
||||
const hash = (0, import_node_crypto.createHash)("sha256");
|
||||
hash.update(key);
|
||||
const partitionKey = hash.digest("hex").slice(0, 12);
|
||||
return import_node_buffer.Buffer.from(partitionKey).toString("base64");
|
||||
};
|
||||
var setLegacyHeaders = (response, info) => {
|
||||
if (response.headersSent)
|
||||
return;
|
||||
response.setHeader("X-RateLimit-Limit", info.limit.toString());
|
||||
response.setHeader("X-RateLimit-Remaining", info.remaining.toString());
|
||||
if (info.resetTime instanceof Date) {
|
||||
response.setHeader("Date", (/* @__PURE__ */ new Date()).toUTCString());
|
||||
response.setHeader(
|
||||
"X-RateLimit-Reset",
|
||||
Math.ceil(info.resetTime.getTime() / 1e3).toString()
|
||||
);
|
||||
}
|
||||
};
|
||||
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.toString());
|
||||
response.setHeader("RateLimit-Remaining", info.remaining.toString());
|
||||
if (resetSeconds)
|
||||
response.setHeader("RateLimit-Reset", resetSeconds.toString());
|
||||
};
|
||||
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 setDraft8Headers = (response, info, windowMs, name, key) => {
|
||||
if (response.headersSent)
|
||||
return;
|
||||
const windowSeconds = Math.ceil(windowMs / 1e3);
|
||||
const resetSeconds = getResetSeconds(info.resetTime, windowMs);
|
||||
const partitionKey = getPartitionKey(key);
|
||||
const policy = `q=${info.limit}; w=${windowSeconds}; pk=:${partitionKey}:`;
|
||||
const header = `r=${info.remaining}; t=${resetSeconds}`;
|
||||
response.append("RateLimit-Policy", `"${name}"; ${policy}`);
|
||||
response.append("RateLimit", `"${name}"; ${header}`);
|
||||
};
|
||||
var setRetryAfterHeader = (response, info, windowMs) => {
|
||||
if (response.headersSent)
|
||||
return;
|
||||
const resetSeconds = getResetSeconds(info.resetTime, windowMs);
|
||||
response.setHeader("Retry-After", resetSeconds.toString());
|
||||
};
|
||||
|
||||
// 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.`);
|
||||
this.name = this.constructor.name;
|
||||
this.code = code;
|
||||
this.help = url;
|
||||
}
|
||||
};
|
||||
var ChangeWarning = class extends ValidationError {
|
||||
};
|
||||
var usedStores = /* @__PURE__ */ new Set();
|
||||
var singleCountKeys = /* @__PURE__ */ new WeakMap();
|
||||
var validations = {
|
||||
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
|
||||
enabled: {
|
||||
default: true
|
||||
},
|
||||
// Should be EnabledValidations type, but that's a circular reference
|
||||
disable() {
|
||||
for (const k of Object.keys(this.enabled))
|
||||
this.enabled[k] = 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) {
|
||||
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) {
|
||||
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) {
|
||||
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) {
|
||||
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}`
|
||||
);
|
||||
}
|
||||
},
|
||||
/**
|
||||
* Ensures a single store instance is not used with multiple express-rate-limit instances
|
||||
*/
|
||||
unsharedStore(store) {
|
||||
if (usedStores.has(store)) {
|
||||
const maybeUniquePrefix = store?.localKeys ? "" : " (with a unique prefix)";
|
||||
throw new ValidationError(
|
||||
"ERR_ERL_STORE_REUSE",
|
||||
`A Store instance must not be shared across multiple rate limiters. Create a new instance of ${store.constructor.name}${maybeUniquePrefix} for each limiter instead.`
|
||||
);
|
||||
}
|
||||
usedStores.add(store);
|
||||
},
|
||||
/**
|
||||
* 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) {
|
||||
let storeKeys = singleCountKeys.get(request);
|
||||
if (!storeKeys) {
|
||||
storeKeys = /* @__PURE__ */ new Map();
|
||||
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 = `${store.prefix ?? ""}${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` / `limit: 0` is
|
||||
* changing in the next major release.
|
||||
*
|
||||
* @param limit {number} - The maximum number of hits per client.
|
||||
*
|
||||
* @returns {void}
|
||||
*/
|
||||
limit(limit) {
|
||||
if (limit === 0) {
|
||||
throw new ChangeWarning(
|
||||
"WRN_ERL_MAX_ZERO",
|
||||
`Setting limit or max to 0 disables rate limiting in express-rate-limit v6 and older, but will cause all requests to be blocked in v7`
|
||||
);
|
||||
}
|
||||
},
|
||||
/**
|
||||
* 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 {any | undefined} - The now-deprecated setting that was used to enable standard headers.
|
||||
*
|
||||
* @returns {void}
|
||||
*/
|
||||
draftPolliHeaders(draft_polli_ratelimit_headers) {
|
||||
if (draft_polli_ratelimit_headers) {
|
||||
throw new ChangeWarning(
|
||||
"WRN_ERL_DEPRECATED_DRAFT_POLLI_HEADERS",
|
||||
`The draft_polli_ratelimit_headers configuration option is deprecated and has been 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 {any | undefined} - The maximum number of hits per client.
|
||||
*
|
||||
* @returns {void}
|
||||
*/
|
||||
onLimitReached(onLimitReached) {
|
||||
if (onLimitReached) {
|
||||
throw new ChangeWarning(
|
||||
"WRN_ERL_DEPRECATED_ON_LIMIT_REACHED",
|
||||
`The onLimitReached configuration option is deprecated and has been removed in express-rate-limit v7.`
|
||||
);
|
||||
}
|
||||
},
|
||||
/**
|
||||
* Warns the user when an invalid/unsupported version of the draft spec is passed.
|
||||
*
|
||||
* @param version {any | undefined} - The version passed by the user.
|
||||
*
|
||||
* @returns {void}
|
||||
*/
|
||||
headersDraftVersion(version) {
|
||||
if (typeof version !== "string" || !SUPPORTED_DRAFT_VERSIONS.includes(version)) {
|
||||
const versionString = SUPPORTED_DRAFT_VERSIONS.join(", ");
|
||||
throw new ValidationError(
|
||||
"ERR_ERL_HEADERS_UNSUPPORTED_DRAFT_VERSION",
|
||||
`standardHeaders: only the following versions of the IETF draft specification are supported: ${versionString}.`
|
||||
);
|
||||
}
|
||||
},
|
||||
/**
|
||||
* 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) {
|
||||
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.`
|
||||
);
|
||||
}
|
||||
},
|
||||
/**
|
||||
* Checks the options.validate setting to ensure that only recognized
|
||||
* validations are enabled or disabled.
|
||||
*
|
||||
* If any unrecognized values are found, an error is logged that
|
||||
* includes the list of supported vaidations.
|
||||
*/
|
||||
validationsConfig() {
|
||||
const supportedValidations = Object.keys(this).filter(
|
||||
(k) => !["enabled", "disable"].includes(k)
|
||||
);
|
||||
supportedValidations.push("default");
|
||||
for (const key of Object.keys(this.enabled)) {
|
||||
if (!supportedValidations.includes(key)) {
|
||||
throw new ValidationError(
|
||||
"ERR_ERL_UNKNOWN_VALIDATION",
|
||||
`options.validate.${key} is not recognized. Supported validate options are: ${supportedValidations.join(
|
||||
", "
|
||||
)}.`
|
||||
);
|
||||
}
|
||||
}
|
||||
},
|
||||
/**
|
||||
* Checks to see if the instance was created inside of a request handler,
|
||||
* which would prevent it from working correctly, with the default memory
|
||||
* store (or any other store with localKeys.)
|
||||
*/
|
||||
creationStack(store) {
|
||||
const { stack } = new Error(
|
||||
"express-rate-limit validation check (set options.validate.creationStack=false to disable)"
|
||||
);
|
||||
if (stack?.includes("Layer.handle [as handle_request]")) {
|
||||
if (!store.localKeys) {
|
||||
throw new ValidationError(
|
||||
"ERR_ERL_CREATED_IN_REQUEST_HANDLER",
|
||||
"express-rate-limit instance should *usually* be created at app initialization, not when responding to a request."
|
||||
);
|
||||
}
|
||||
throw new ValidationError(
|
||||
"ERR_ERL_CREATED_IN_REQUEST_HANDLER",
|
||||
`express-rate-limit instance should be created at app initialization, not when responding to a request.`
|
||||
);
|
||||
}
|
||||
}
|
||||
};
|
||||
var getValidations = (_enabled) => {
|
||||
let enabled;
|
||||
if (typeof _enabled === "boolean") {
|
||||
enabled = {
|
||||
default: _enabled
|
||||
};
|
||||
} else {
|
||||
enabled = {
|
||||
default: true,
|
||||
..._enabled
|
||||
};
|
||||
}
|
||||
const wrappedValidations = {
|
||||
enabled
|
||||
};
|
||||
for (const [name, validation] of Object.entries(validations)) {
|
||||
if (typeof validation === "function")
|
||||
wrappedValidations[name] = (...args) => {
|
||||
if (!(enabled[name] ?? enabled.default)) {
|
||||
return;
|
||||
}
|
||||
try {
|
||||
;
|
||||
validation.apply(
|
||||
wrappedValidations,
|
||||
args
|
||||
);
|
||||
} catch (error) {
|
||||
if (error instanceof ChangeWarning)
|
||||
console.warn(error);
|
||||
else
|
||||
console.error(error);
|
||||
}
|
||||
};
|
||||
}
|
||||
return wrappedValidations;
|
||||
};
|
||||
|
||||
// source/memory-store.ts
|
||||
var MemoryStore = class {
|
||||
constructor() {
|
||||
/**
|
||||
* These two maps store usage (requests) and reset time by key (for example, IP
|
||||
* addresses or API keys).
|
||||
*
|
||||
* They are split into two to avoid having to iterate through the entire set to
|
||||
* determine which ones need reset. Instead, `Client`s are moved from `previous`
|
||||
* to `current` as they hit the endpoint. Once `windowMs` has elapsed, all clients
|
||||
* left in `previous`, i.e., those that have not made any recent requests, are
|
||||
* known to be expired and can be deleted in bulk.
|
||||
*/
|
||||
this.previous = /* @__PURE__ */ new Map();
|
||||
this.current = /* @__PURE__ */ new Map();
|
||||
/**
|
||||
* Confirmation that the keys incremented in once instance of MemoryStore
|
||||
* cannot affect other instances.
|
||||
*/
|
||||
this.localKeys = true;
|
||||
}
|
||||
/**
|
||||
* Method that initializes the store.
|
||||
*
|
||||
* @param options {Options} - The options used to setup the middleware.
|
||||
*/
|
||||
init(options) {
|
||||
this.windowMs = options.windowMs;
|
||||
if (this.interval)
|
||||
clearInterval(this.interval);
|
||||
this.interval = setInterval(() => {
|
||||
this.clearExpired();
|
||||
}, 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) {
|
||||
return this.current.get(key) ?? this.previous.get(key);
|
||||
}
|
||||
/**
|
||||
* 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) {
|
||||
const client = this.getClient(key);
|
||||
const now = Date.now();
|
||||
if (client.resetTime.getTime() <= now) {
|
||||
this.resetClient(client, now);
|
||||
}
|
||||
client.totalHits++;
|
||||
return client;
|
||||
}
|
||||
/**
|
||||
* Method to decrement a client's hit counter.
|
||||
*
|
||||
* @param key {string} - The identifier for a client.
|
||||
*
|
||||
* @public
|
||||
*/
|
||||
async decrement(key) {
|
||||
const client = this.getClient(key);
|
||||
if (client.totalHits > 0)
|
||||
client.totalHits--;
|
||||
}
|
||||
/**
|
||||
* Method to reset a client's hit counter.
|
||||
*
|
||||
* @param key {string} - The identifier for a client.
|
||||
*
|
||||
* @public
|
||||
*/
|
||||
async resetKey(key) {
|
||||
this.current.delete(key);
|
||||
this.previous.delete(key);
|
||||
}
|
||||
/**
|
||||
* Method to reset everyone's hit counter.
|
||||
*
|
||||
* @public
|
||||
*/
|
||||
async resetAll() {
|
||||
this.current.clear();
|
||||
this.previous.clear();
|
||||
}
|
||||
/**
|
||||
* Method to stop the timer (if currently running) and prevent any memory
|
||||
* leaks.
|
||||
*
|
||||
* @public
|
||||
*/
|
||||
shutdown() {
|
||||
clearInterval(this.interval);
|
||||
void this.resetAll();
|
||||
}
|
||||
/**
|
||||
* Recycles a client by setting its hit count to zero, and reset time to
|
||||
* `windowMs` milliseconds from now.
|
||||
*
|
||||
* NOT to be confused with `#resetKey()`, which removes a client from both the
|
||||
* `current` and `previous` maps.
|
||||
*
|
||||
* @param client {Client} - The client to recycle.
|
||||
* @param now {number} - The current time, to which the `windowMs` is added to get the `resetTime` for the client.
|
||||
*
|
||||
* @return {Client} - The modified client that was passed in, to allow for chaining.
|
||||
*/
|
||||
resetClient(client, now = Date.now()) {
|
||||
client.totalHits = 0;
|
||||
client.resetTime.setTime(now + this.windowMs);
|
||||
return client;
|
||||
}
|
||||
/**
|
||||
* Retrieves or creates a client, given a key. Also ensures that the client being
|
||||
* returned is in the `current` map.
|
||||
*
|
||||
* @param key {string} - The key under which the client is (or is to be) stored.
|
||||
*
|
||||
* @returns {Client} - The requested client.
|
||||
*/
|
||||
getClient(key) {
|
||||
if (this.current.has(key))
|
||||
return this.current.get(key);
|
||||
let client;
|
||||
if (this.previous.has(key)) {
|
||||
client = this.previous.get(key);
|
||||
this.previous.delete(key);
|
||||
} else {
|
||||
client = { totalHits: 0, resetTime: /* @__PURE__ */ new Date() };
|
||||
this.resetClient(client);
|
||||
}
|
||||
this.current.set(key, client);
|
||||
return client;
|
||||
}
|
||||
/**
|
||||
* Move current clients to previous, create a new map for current.
|
||||
*
|
||||
* This function is called every `windowMs`.
|
||||
*/
|
||||
clearExpired() {
|
||||
this.previous = this.current;
|
||||
this.current = /* @__PURE__ */ new Map();
|
||||
}
|
||||
};
|
||||
|
||||
// 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 {
|
||||
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: validations2, ...directlyPassableEntries } = config;
|
||||
return {
|
||||
...directlyPassableEntries,
|
||||
validate: validations2.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) => {
|
||||
const notUndefinedOptions = omitUndefinedOptions(passedOptions);
|
||||
const validations2 = getValidations(notUndefinedOptions?.validate ?? true);
|
||||
validations2.validationsConfig();
|
||||
validations2.draftPolliHeaders(
|
||||
// @ts-expect-error see the note above.
|
||||
notUndefinedOptions.draft_polli_ratelimit_headers
|
||||
);
|
||||
validations2.onLimitReached(notUndefinedOptions.onLimitReached);
|
||||
let standardHeaders = notUndefinedOptions.standardHeaders ?? false;
|
||||
if (standardHeaders === true)
|
||||
standardHeaders = "draft-6";
|
||||
const config = {
|
||||
windowMs: 60 * 1e3,
|
||||
limit: passedOptions.max ?? 5,
|
||||
// `max` is deprecated, but support it anyways.
|
||||
message: "Too many requests, please try again later.",
|
||||
statusCode: 429,
|
||||
legacyHeaders: passedOptions.headers ?? true,
|
||||
identifier(request, _response) {
|
||||
let duration = "";
|
||||
const property = config.requestPropertyName;
|
||||
const { limit } = request[property];
|
||||
const seconds = config.windowMs / 1e3;
|
||||
const minutes = config.windowMs / (1e3 * 60);
|
||||
const hours = config.windowMs / (1e3 * 60 * 60);
|
||||
const days = config.windowMs / (1e3 * 60 * 60 * 24);
|
||||
if (seconds < 60)
|
||||
duration = `${seconds}sec`;
|
||||
else if (minutes < 60)
|
||||
duration = `${minutes}min`;
|
||||
else if (hours < 24)
|
||||
duration = `${hours}hr${hours > 1 ? "s" : ""}`;
|
||||
else
|
||||
duration = `${days}day${days > 1 ? "s" : ""}`;
|
||||
return `${limit}-in-${duration}`;
|
||||
},
|
||||
requestPropertyName: "rateLimit",
|
||||
skipFailedRequests: false,
|
||||
skipSuccessfulRequests: false,
|
||||
requestWasSuccessful: (_request, response) => response.statusCode < 400,
|
||||
skip: (_request, _response) => false,
|
||||
keyGenerator(request, _response) {
|
||||
validations2.ip(request.ip);
|
||||
validations2.trustProxy(request);
|
||||
validations2.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);
|
||||
}
|
||||
},
|
||||
passOnStoreError: false,
|
||||
// Allow the default options to be overriden by the passed options.
|
||||
...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(notUndefinedOptions.store ?? new MemoryStore()),
|
||||
// Print an error to the console if a few known misconfigurations are detected.
|
||||
validations: validations2
|
||||
};
|
||||
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) => {
|
||||
const config = parseOptions(passedOptions ?? {});
|
||||
const options = getOptionsFromConfig(config);
|
||||
config.validations.creationStack(config.store);
|
||||
config.validations.unsharedStore(config.store);
|
||||
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);
|
||||
let totalHits = 0;
|
||||
let resetTime;
|
||||
try {
|
||||
const incrementResult = await config.store.increment(key);
|
||||
totalHits = incrementResult.totalHits;
|
||||
resetTime = incrementResult.resetTime;
|
||||
} catch (error) {
|
||||
if (config.passOnStoreError) {
|
||||
console.error(
|
||||
"express-rate-limit: error from store, allowing request without rate-limiting.",
|
||||
error
|
||||
);
|
||||
next();
|
||||
return;
|
||||
}
|
||||
throw error;
|
||||
}
|
||||
config.validations.positiveHits(totalHits);
|
||||
config.validations.singleCount(request, config.store, key);
|
||||
const retrieveLimit = typeof config.limit === "function" ? config.limit(request, response) : config.limit;
|
||||
const limit = await retrieveLimit;
|
||||
config.validations.limit(limit);
|
||||
const info = {
|
||||
limit,
|
||||
used: totalHits,
|
||||
remaining: Math.max(limit - totalHits, 0),
|
||||
resetTime
|
||||
};
|
||||
Object.defineProperty(info, "current", {
|
||||
configurable: false,
|
||||
enumerable: false,
|
||||
value: totalHits
|
||||
});
|
||||
augmentedRequest[config.requestPropertyName] = info;
|
||||
if (config.legacyHeaders && !response.headersSent) {
|
||||
setLegacyHeaders(response, info);
|
||||
}
|
||||
if (config.standardHeaders && !response.headersSent) {
|
||||
switch (config.standardHeaders) {
|
||||
case "draft-6": {
|
||||
setDraft6Headers(response, info, config.windowMs);
|
||||
break;
|
||||
}
|
||||
case "draft-7": {
|
||||
config.validations.headersResetTime(info.resetTime);
|
||||
setDraft7Headers(response, info, config.windowMs);
|
||||
break;
|
||||
}
|
||||
case "draft-8": {
|
||||
const retrieveName = typeof config.identifier === "function" ? config.identifier(request, response) : config.identifier;
|
||||
const name = await retrieveName;
|
||||
config.validations.headersResetTime(info.resetTime);
|
||||
setDraft8Headers(response, info, config.windowMs, name, key);
|
||||
break;
|
||||
}
|
||||
default: {
|
||||
config.validations.headersDraftVersion(config.standardHeaders);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
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 (!await 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 (await config.requestWasSuccessful(request, response))
|
||||
await decrementKey();
|
||||
});
|
||||
}
|
||||
}
|
||||
config.validations.disable();
|
||||
if (totalHits > limit) {
|
||||
if (config.legacyHeaders || config.standardHeaders) {
|
||||
setRetryAfterHeader(response, info, config.windowMs);
|
||||
}
|
||||
config.handler(request, response, next, options);
|
||||
return;
|
||||
}
|
||||
next();
|
||||
}
|
||||
);
|
||||
const getThrowFn = () => {
|
||||
throw new Error("The current store does not support the get/getKey method");
|
||||
};
|
||||
middleware.resetKey = config.store.resetKey.bind(config.store);
|
||||
middleware.getKey = typeof config.store.get === "function" ? config.store.get.bind(config.store) : getThrowFn;
|
||||
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;
|
Reference in New Issue
Block a user