2024-10-02 00:04:40 -04:00
|
|
|
import { rateLimit } from "express-rate-limit";
|
|
|
|
import createHttpError from "http-errors";
|
|
|
|
import { NextFunction, Request, Response } from "express";
|
|
|
|
import logger from "@server/logger";
|
|
|
|
import HttpCode from "@server/types/HttpCode";
|
2025-06-19 16:57:54 -04:00
|
|
|
import config from "@server/lib/config";
|
|
|
|
import { RedisStore } from "rate-limit-redis";
|
|
|
|
import redisManager from "@server/db/redis";
|
|
|
|
import { Command as RedisCommand } from "ioredis";
|
2024-10-02 00:04:40 -04:00
|
|
|
|
2024-10-04 23:14:40 -04:00
|
|
|
export function rateLimitMiddleware({
|
|
|
|
windowMin,
|
|
|
|
max,
|
|
|
|
type,
|
2025-06-19 16:57:54 -04:00
|
|
|
skipCondition
|
2024-10-04 23:14:40 -04:00
|
|
|
}: {
|
|
|
|
windowMin: number;
|
|
|
|
max: number;
|
|
|
|
type: "IP_ONLY" | "IP_AND_PATH";
|
|
|
|
skipCondition?: (req: Request, res: Response) => boolean;
|
|
|
|
}) {
|
2025-06-19 16:57:54 -04:00
|
|
|
const enableRedis = config.getRawConfig().flags?.enable_redis;
|
|
|
|
|
|
|
|
let opts;
|
2024-10-04 23:14:40 -04:00
|
|
|
if (type === "IP_AND_PATH") {
|
2025-06-19 16:57:54 -04:00
|
|
|
opts = {
|
2024-10-04 23:14:40 -04:00
|
|
|
windowMs: windowMin * 60 * 1000,
|
|
|
|
max,
|
|
|
|
skip: skipCondition,
|
|
|
|
keyGenerator: (req: Request) => {
|
|
|
|
return `${req.ip}-${req.path}`;
|
|
|
|
},
|
|
|
|
handler: (req: Request, res: Response, next: NextFunction) => {
|
|
|
|
const message = `Rate limit exceeded. You can make ${max} requests every ${windowMin} minute(s).`;
|
|
|
|
logger.warn(
|
2025-06-19 16:57:54 -04:00
|
|
|
`Rate limit exceeded for IP ${req.ip} on path ${req.path}`
|
2024-10-04 23:14:40 -04:00
|
|
|
);
|
|
|
|
return next(
|
2025-06-19 16:57:54 -04:00
|
|
|
createHttpError(HttpCode.TOO_MANY_REQUESTS, message)
|
2024-10-04 23:14:40 -04:00
|
|
|
);
|
2025-06-19 16:57:54 -04:00
|
|
|
}
|
|
|
|
} as any;
|
|
|
|
} else {
|
|
|
|
opts = {
|
|
|
|
windowMs: windowMin * 60 * 1000,
|
|
|
|
max,
|
|
|
|
skip: skipCondition,
|
|
|
|
handler: (req: Request, res: Response, next: NextFunction) => {
|
|
|
|
const message = `Rate limit exceeded. You can make ${max} requests every ${windowMin} minute(s).`;
|
|
|
|
logger.warn(`Rate limit exceeded for IP ${req.ip}`);
|
|
|
|
return next(
|
|
|
|
createHttpError(HttpCode.TOO_MANY_REQUESTS, message)
|
|
|
|
);
|
|
|
|
}
|
|
|
|
};
|
|
|
|
}
|
|
|
|
|
|
|
|
if (enableRedis) {
|
|
|
|
const client = redisManager.client!;
|
|
|
|
opts.store = new RedisStore({
|
|
|
|
sendCommand: async (command: string, ...args: string[]) =>
|
|
|
|
(await client.call(command, args)) as any
|
2024-10-04 23:14:40 -04:00
|
|
|
});
|
|
|
|
}
|
2025-06-19 16:57:54 -04:00
|
|
|
|
|
|
|
return rateLimit(opts);
|
2024-10-04 23:14:40 -04:00
|
|
|
}
|
2024-10-02 00:04:40 -04:00
|
|
|
|
|
|
|
export default rateLimitMiddleware;
|