mirror of
https://github.com/fosrl/pangolin.git
synced 2025-08-16 07:28:06 +02:00
add redis rate limiter
This commit is contained in:
parent
377eb2b851
commit
494b54ac32
6 changed files with 56 additions and 22 deletions
13
package-lock.json
generated
13
package-lock.json
generated
|
@ -74,6 +74,7 @@
|
|||
"oslo": "1.2.1",
|
||||
"pg": "^8.16.0",
|
||||
"qrcode.react": "4.2.0",
|
||||
"rate-limit-redis": "^4.2.1",
|
||||
"react": "19.1.0",
|
||||
"react-dom": "19.1.0",
|
||||
"react-easy-sort": "^1.6.0",
|
||||
|
@ -11506,6 +11507,18 @@
|
|||
"node": ">= 0.6"
|
||||
}
|
||||
},
|
||||
"node_modules/rate-limit-redis": {
|
||||
"version": "4.2.1",
|
||||
"resolved": "https://registry.npmjs.org/rate-limit-redis/-/rate-limit-redis-4.2.1.tgz",
|
||||
"integrity": "sha512-JsUsVmRVI6G/XrlYtfGV1NMCbGS/CVYayHkxD5Ism5FaL8qpFHCXbFkUeIi5WJ/onJOKWCgtB/xtCLa6qSXb4g==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">= 16"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"express-rate-limit": ">= 6"
|
||||
}
|
||||
},
|
||||
"node_modules/raw-body": {
|
||||
"version": "2.5.2",
|
||||
"resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.5.2.tgz",
|
||||
|
|
|
@ -92,6 +92,7 @@
|
|||
"oslo": "1.2.1",
|
||||
"pg": "^8.16.0",
|
||||
"qrcode.react": "4.2.0",
|
||||
"rate-limit-redis": "^4.2.1",
|
||||
"react": "19.1.0",
|
||||
"react-dom": "19.1.0",
|
||||
"react-easy-sort": "^1.6.0",
|
||||
|
@ -107,9 +108,9 @@
|
|||
"winston": "3.17.0",
|
||||
"winston-daily-rotate-file": "5.0.0",
|
||||
"ws": "8.18.2",
|
||||
"yargs": "18.0.0",
|
||||
"zod": "3.25.56",
|
||||
"zod-validation-error": "3.4.1",
|
||||
"yargs": "18.0.0"
|
||||
"zod-validation-error": "3.4.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@dotenvx/dotenvx": "1.44.1",
|
||||
|
|
|
@ -4,7 +4,7 @@ import config from "@server/lib/config";
|
|||
|
||||
class RedisManager {
|
||||
private static instance: RedisManager;
|
||||
private client: Redis | null = null;
|
||||
public client: Redis | null = null;
|
||||
private subscriber: Redis | null = null;
|
||||
private publisher: Redis | null = null;
|
||||
private isEnabled: boolean = false;
|
||||
|
|
|
@ -3,20 +3,27 @@ import createHttpError from "http-errors";
|
|||
import { NextFunction, Request, Response } from "express";
|
||||
import logger from "@server/logger";
|
||||
import HttpCode from "@server/types/HttpCode";
|
||||
import config from "@server/lib/config";
|
||||
import { RedisStore } from "rate-limit-redis";
|
||||
import redisManager from "@server/db/redis";
|
||||
import { Command as RedisCommand } from "ioredis";
|
||||
|
||||
export function rateLimitMiddleware({
|
||||
windowMin,
|
||||
max,
|
||||
type,
|
||||
skipCondition,
|
||||
skipCondition
|
||||
}: {
|
||||
windowMin: number;
|
||||
max: number;
|
||||
type: "IP_ONLY" | "IP_AND_PATH";
|
||||
skipCondition?: (req: Request, res: Response) => boolean;
|
||||
}) {
|
||||
const enableRedis = config.getRawConfig().flags?.enable_redis;
|
||||
|
||||
let opts;
|
||||
if (type === "IP_AND_PATH") {
|
||||
return rateLimit({
|
||||
opts = {
|
||||
windowMs: windowMin * 60 * 1000,
|
||||
max,
|
||||
skip: skipCondition,
|
||||
|
@ -26,24 +33,37 @@ export function rateLimitMiddleware({
|
|||
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} on path ${req.path}`,
|
||||
`Rate limit exceeded for IP ${req.ip} on path ${req.path}`
|
||||
);
|
||||
return next(
|
||||
createHttpError(HttpCode.TOO_MANY_REQUESTS, message),
|
||||
createHttpError(HttpCode.TOO_MANY_REQUESTS, message)
|
||||
);
|
||||
},
|
||||
});
|
||||
}
|
||||
return rateLimit({
|
||||
} 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));
|
||||
},
|
||||
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
|
||||
});
|
||||
}
|
||||
|
||||
return rateLimit(opts);
|
||||
}
|
||||
|
||||
export default rateLimitMiddleware;
|
||||
|
|
|
@ -398,7 +398,7 @@ if (redisManager.isRedisEnabled()) {
|
|||
});
|
||||
logger.info(`WebSocket handler initialized with Redis support - Node ID: ${NODE_ID}`);
|
||||
} else {
|
||||
logger.info('WebSocket handler initialized in local mode (Redis disabled)');
|
||||
logger.debug('WebSocket handler initialized in local mode (Redis disabled)');
|
||||
}
|
||||
|
||||
// Cleanup function for graceful shutdown
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue