@quell/server
Version:
Quell is an open-source NPM package providing a light-weight caching layer implementation and cache invalidation for GraphQL responses on both the client- and server-side. Use Quell to prevent redundant client-side API requests and to minimize costly serv
72 lines (71 loc) • 4.52 kB
JavaScript
;
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.createRateLimiter = void 0;
const cacheUtils_1 = require("../helpers/cacheUtils");
/**
* A redis-based IP rate limiter middleware function that limits the number of requests per second based on IP address using Redis.
* @param {Request} req - Express request object, including request body with GraphQL query string.
* @param {Response} res - Express response object, will carry query response to next middleware.
* @param {NextFunction} next - Express next middleware function, invoked when QuellCache completes its work.
* @returns {void} Passes an error to Express if no query was included in the request or if the number of requests by the current IP
* exceeds the IP rate limit.
*/
function createRateLimiter(config) {
return (req, res, next) => __awaiter(this, void 0, void 0, function* () {
var _a, _b, _c;
// Set ipRate to the ipRate limit from the request body or use default.
const ipRateLimit = (_b = (_a = req.body.costOptions) === null || _a === void 0 ? void 0 : _a.ipRate) !== null && _b !== void 0 ? _b : config.ipRate;
// Get the IP address from the request.
const ipAddress = req.ip;
// Get the current time in seconds.
const currentTimeSeconds = Math.floor(Date.now() / 1000);
// Create a Redis IP key using the IP address and current time.
const redisIpTimeKey = `${ipAddress}:${currentTimeSeconds}`;
// Return an error if no query is found on the request.
if (!req.body.query) {
const err = {
log: "Error: no GraphQL query found on request body, inside rateLimiter",
status: 400,
message: {
err: "Error in rateLimiter: Bad Request. Check server log for more details.",
},
};
return next(err);
}
try {
// Create a Redis multi command queue.
const redisRunQueue = config.redisCache.multi();
// Add to queue: increment the key associated with the current IP address and time in Redis.
redisRunQueue.incr(redisIpTimeKey);
// Add to queue: set the key to expire after 1 second.
redisRunQueue.expire(redisIpTimeKey, 1);
// Execute the Redis multi command queue.
const redisResponse = (yield redisRunQueue.exec()).map((result) => JSON.stringify(result));
// Save result of increment command, which will be the number of requests made by the current IP address in the last second.
// Since the increment command was the first command in the queue, it will be the first result in the returned array.
const numRequestsString = (_c = redisResponse[0]) !== null && _c !== void 0 ? _c : "0";
const numRequests = parseInt(numRequestsString, 10);
// If the number of requests is greater than the IP rate limit, throw an error.
if (numRequests > ipRateLimit) {
next((0, cacheUtils_1.createServerError)(`Redis cache error: Express error handler caught too many requests from this IP address (${ipAddress}): limit is: ${ipRateLimit} requests per second, inside rateLimiter`, 429, "Error in rateLimiter middleware. Check server log for more details."));
return;
}
console.log(`IP ${ipAddress} made a request. Limit is: ${ipRateLimit} requests per second. Result: OK.`);
return next();
}
catch (error) {
next((0, cacheUtils_1.createServerError)(`Catch block in rateLimiter middleware, ${error}`, 500, "IPRate Limiting Error. Check server log for more details."));
return;
}
});
}
exports.createRateLimiter = createRateLimiter;