UNPKG

redis-sliding-rate-limiter

Version:

Flexible and performant rate limiter based on sliding window algorithm with arbitrary precision

112 lines (111 loc) 5.07 kB
"use strict"; var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } return new (P || (P = Promise))(function (resolve, reject) { function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } step((generator = generator.apply(thisArg, _arguments || [])).next()); }); }; Object.defineProperty(exports, "__esModule", { value: true }); exports.createExpressMiddleware = void 0; const normalizeOptions = (options) => { for (const e of options.limiters) { if (!e.hasOwnProperty('overrideKey')) { e.overrideKey = false; } if (!e.hasOwnProperty('overrideLimit')) { e.overrideLimit = false; } if (!e.hasOwnProperty('errorMessage') || !e.errorMessage) { e.errorMessage = 'Too many requests'; } } if (!options.hasOwnProperty('errorStatusCode')) { options.errorStatusCode = 429; } if (!options.hasOwnProperty('setHeaders')) { options.setHeaders = true; } return options; }; const validateOptions = (options) => { if (options.limiters.some(e => !e.key && !e.overrideKey)) { throw new Error(`Limiter requires a key or overrideKey enabled`); } if (options.limiters.some(e => e.overrideKey && !e.overrideKeyFn && !options.overrideKeyFn)) { throw new Error('Limiter with overrideKey enabled requires at least one of limiter-specific or middleware-specific overrideKeyFn function to be defined'); } if (options.limiters.some(e => e.overrideLimit && !e.overrideLimitFn && !options.overrideLimitFn)) { throw new Error('Limiter with overrideLimit enabled requires at least one of limiter-specific or middleware-specific overrideLimitFn function to be defined'); } }; const createExpressMiddleware = (options) => { // Normalize options options = normalizeOptions(options); // Validate options validateOptions(options); return (req, res, next) => __awaiter(void 0, void 0, void 0, function* () { // Check if request should be skipped if (options.skipFn && options.skipFn(req)) { return next(); } for (const middlewareLimiter of options.limiters) { const { limiter, key, overrideKey, overrideKeyFn, overrideLimit, overrideLimitFn, errorMessage, skipFn, } = middlewareLimiter; // Check if evaluation should be skipped for the current limiter if (skipFn && skipFn(req, limiter)) { continue; } // Get Redis key let redisKey; if (overrideKey) { redisKey = overrideKeyFn ? overrideKeyFn(req, limiter) : options.overrideKeyFn(req, limiter); } else { redisKey = key; } // Override limit if enabled if (overrideLimit) { limiter.limit = overrideLimitFn ? overrideLimitFn(req, limiter) : options.overrideLimitFn(req, limiter); } const limiterResponse = yield limiter.get(redisKey); const { allowed, remaining, firstExpireAtMs, windowExpireAtMs } = limiterResponse; // Set response headers if enabled if (options.setHeaders) { // If provided, use custom function if (options.setHeadersFn) { options.setHeadersFn(req, res, limiter, limiterResponse); } // Otherwise set default headers else { const baseKey = 'X-Rate-Limit'; res.set(`${baseKey}-Remaining-${limiter.name}`, '' + remaining); res.set(`${baseKey}-First-Expire-${limiter.name}`, '' + firstExpireAtMs); res.set(`${baseKey}-Reset-${limiter.name}`, '' + windowExpireAtMs); } } // Throttle request if (!allowed) { if (options.onThrottleRequest) { return options.onThrottleRequest(req, res, redisKey); } else { let message = ''; if (errorMessage) { if (typeof errorMessage === 'string') { message = errorMessage; } else { message = JSON.stringify(errorMessage); } } return res.status(options.errorStatusCode).send(message); } } } // Allow request return next(); }); }; exports.createExpressMiddleware = createExpressMiddleware;