diff --git a/server/package-lock.json b/server/package-lock.json index d2ba13f13..a41a50518 100644 --- a/server/package-lock.json +++ b/server/package-lock.json @@ -13,11 +13,12 @@ "axios": "^1.7.2", "bcryptjs": "3.0.2", "bullmq": "5.41.2", - "compression": "1.8.0", + "compression": "1.8.1", "cors": "^2.8.5", "dockerode": "4.0.6", "dotenv": "^16.4.5", "express": "^4.19.2", + "express-rate-limit": "8.0.1", "handlebars": "^4.7.8", "helmet": "^8.0.0", "ioredis": "^5.4.2", @@ -2022,16 +2023,16 @@ } }, "node_modules/compression": { - "version": "1.8.0", - "resolved": "https://registry.npmjs.org/compression/-/compression-1.8.0.tgz", - "integrity": "sha512-k6WLKfunuqCYD3t6AsuPGvQWaKwuLLh2/xHNcX4qE+vIfDNXpSqnrhwA7O53R7WVQUnt8dVAIW+YHr7xTgOgGA==", + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/compression/-/compression-1.8.1.tgz", + "integrity": "sha512-9mAqGPHLakhCLeNyxPkK4xVo746zQ/czLH1Ky+vkitMnWfWZps8r0qXuwhwizagCRttsL4lfG4pIOvaWLpAP0w==", "license": "MIT", "dependencies": { "bytes": "3.1.2", "compressible": "~2.0.18", "debug": "2.6.9", "negotiator": "~0.6.4", - "on-headers": "~1.0.2", + "on-headers": "~1.1.0", "safe-buffer": "5.2.1", "vary": "~1.1.2" }, @@ -3159,6 +3160,24 @@ "url": "https://opencollective.com/express" } }, + "node_modules/express-rate-limit": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/express-rate-limit/-/express-rate-limit-8.0.1.tgz", + "integrity": "sha512-aZVCnybn7TVmxO4BtlmnvX+nuz8qHW124KKJ8dumsBsmv5ZLxE0pYu7S2nwyRBGHHCAzdmnGyrc5U/rksSPO7Q==", + "license": "MIT", + "dependencies": { + "ip-address": "10.0.1" + }, + "engines": { + "node": ">= 16" + }, + "funding": { + "url": "https://github.com/sponsors/express-rate-limit" + }, + "peerDependencies": { + "express": ">= 4.11" + } + }, "node_modules/express/node_modules/debug": { "version": "2.6.9", "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", @@ -3901,6 +3920,15 @@ "url": "https://opencollective.com/ioredis" } }, + "node_modules/ip-address": { + "version": "10.0.1", + "resolved": "https://registry.npmjs.org/ip-address/-/ip-address-10.0.1.tgz", + "integrity": "sha512-NWv9YLW4PoW2B7xtzaS3NCot75m6nK7Icdv0o3lfMceJVRfSoQwqD4wEH5rLwoKJwUiZ/rfpiVBhnaF0FK4HoA==", + "license": "MIT", + "engines": { + "node": ">= 12" + } + }, "node_modules/ipaddr.js": { "version": "1.9.1", "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz", @@ -5705,9 +5733,9 @@ } }, "node_modules/on-headers": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/on-headers/-/on-headers-1.0.2.tgz", - "integrity": "sha512-pZAE+FJLoyITytdqK0U5s+FIpjN0JP3OzFi/u8Rx+EV5/W+JTWGXG8xFzevE7AjBfDqHv/8vL8qQsIhHnqRkrA==", + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/on-headers/-/on-headers-1.1.0.tgz", + "integrity": "sha512-737ZY3yNnXy37FHkQxPzt4UZ2UWPWiCZWLvFZ4fu5cueciegX0zGPnrlY6bwRg4FdQOe9YU8MkmJwGhoMybl8A==", "license": "MIT", "engines": { "node": ">= 0.8" diff --git a/server/package.json b/server/package.json index d3d7344e9..4768fb45f 100755 --- a/server/package.json +++ b/server/package.json @@ -20,11 +20,12 @@ "axios": "^1.7.2", "bcryptjs": "3.0.2", "bullmq": "5.41.2", - "compression": "1.8.0", + "compression": "1.8.1", "cors": "^2.8.5", "dockerode": "4.0.6", "dotenv": "^16.4.5", "express": "^4.19.2", + "express-rate-limit": "8.0.1", "handlebars": "^4.7.8", "helmet": "^8.0.0", "ioredis": "^5.4.2", diff --git a/server/src/app.js b/server/src/app.js index af53dbf56..04166b313 100644 --- a/server/src/app.js +++ b/server/src/app.js @@ -8,11 +8,13 @@ import languageMiddleware from "./middleware/languageMiddleware.js"; import swaggerUi from "swagger-ui-express"; import { handleErrors } from "./middleware/handleErrors.js"; import { setupRoutes } from "./config/routes.js"; +import { generalApiLimiter } from "./middleware/rateLimiter.js"; + export const createApp = ({ services, controllers, appSettings, frontendPath, openApiSpec }) => { const allowedOrigin = appSettings.clientHost; const app = express(); - + app.use(generalApiLimiter); // Static files app.use(express.static(frontendPath)); diff --git a/server/src/config/routes.js b/server/src/config/routes.js index 64b1df474..7056393f9 100644 --- a/server/src/config/routes.js +++ b/server/src/config/routes.js @@ -1,4 +1,5 @@ import { verifyJWT } from "../middleware/verifyJWT.js"; +import { authApiLimiter } from "../middleware/rateLimiter.js"; import AuthRoutes from "../routes/authRoute.js"; import InviteRoutes from "../routes/inviteRoute.js"; @@ -25,7 +26,7 @@ export const setupRoutes = (app, controllers) => { const notificationRoutes = new NotificationRoutes(controllers.notificationController); const diagnosticRoutes = new DiagnosticRoutes(controllers.diagnosticController); - app.use("/api/v1/auth", authRoutes.getRouter()); + app.use("/api/v1/auth", authApiLimiter, authRoutes.getRouter()); app.use("/api/v1/monitors", verifyJWT, monitorRoutes.getRouter()); app.use("/api/v1/settings", verifyJWT, settingsRoutes.getRouter()); app.use("/api/v1/checks", verifyJWT, checkRoutes.getRouter()); diff --git a/server/src/middleware/rateLimiter.js b/server/src/middleware/rateLimiter.js new file mode 100644 index 000000000..2e5a34f3b --- /dev/null +++ b/server/src/middleware/rateLimiter.js @@ -0,0 +1,17 @@ +import rateLimit from "express-rate-limit"; + +export const generalApiLimiter = rateLimit({ + window: 60 * 1000, + limit: 600, + standardHeaders: true, + legacyHeaders: false, + ipv6Subnet: 64, +}); + +export const authApiLimiter = rateLimit({ + window: 60 * 1000, + limit: 5, + standardHeaders: true, + legacyHeaders: false, + ipv6Subnet: 64, +});