From 68895bdca2baa09b7c21572ebfd6aa6a8e34d303 Mon Sep 17 00:00:00 2001 From: Trevor Buckner Date: Sun, 29 Sep 2024 23:37:26 -0400 Subject: [PATCH] Rate limit `/api` requests from each IP address 100 requests each 5 minutes. --- client/homebrew/navbar/error-navitem.jsx | 11 +++++++++++ package-lock.json | 16 ++++++++++++++++ package.json | 1 + server/homebrew.api.js | 12 ++++++++++++ 4 files changed, 40 insertions(+) diff --git a/client/homebrew/navbar/error-navitem.jsx b/client/homebrew/navbar/error-navitem.jsx index 5dd5c1eb9..264d528ef 100644 --- a/client/homebrew/navbar/error-navitem.jsx +++ b/client/homebrew/navbar/error-navitem.jsx @@ -116,6 +116,17 @@ const ErrorNavItem = createClass({ ; } + if(HBErrorCode === '55') { + return + Oops! +
+ Looks like there are too many requests + from this IP address in a short time. + Please try again after a few minutes. +
+
; + } + return Oops!
diff --git a/package-lock.json b/package-lock.json index ed0cbf1f1..701592890 100644 --- a/package-lock.json +++ b/package-lock.json @@ -25,6 +25,7 @@ "expr-eval": "^2.0.2", "express": "^4.19.2", "express-async-handler": "^1.2.0", + "express-rate-limit": "^7.4.0", "express-static-gzip": "2.1.7", "fs-extra": "11.2.0", "js-yaml": "^4.1.0", @@ -6352,6 +6353,21 @@ "integrity": "sha512-rCSVtPXRmQSW8rmik/AIb2P0op6l7r1fMW538yyvTMltCO4xQEWMmobfrIxN2V1/mVrgxB8Az3reYF6yUZw37w==", "license": "MIT" }, + "node_modules/express-rate-limit": { + "version": "7.4.0", + "resolved": "https://registry.npmjs.org/express-rate-limit/-/express-rate-limit-7.4.0.tgz", + "integrity": "sha512-v1204w3cXu5gCDmAvgvzI6qjzZzoMWKnyVDk3ACgfswTQLYiGen+r8w0VnXnGMmzEN/g8fwIQ4JrFFd4ZP6ssg==", + "license": "MIT", + "engines": { + "node": ">= 16" + }, + "funding": { + "url": "https://github.com/sponsors/express-rate-limit" + }, + "peerDependencies": { + "express": "4 || 5 || ^5.0.0-beta.1" + } + }, "node_modules/express-static-gzip": { "version": "2.1.7", "resolved": "https://registry.npmjs.org/express-static-gzip/-/express-static-gzip-2.1.7.tgz", diff --git a/package.json b/package.json index 8248f4304..6926097a9 100644 --- a/package.json +++ b/package.json @@ -100,6 +100,7 @@ "expr-eval": "^2.0.2", "express": "^4.19.2", "express-async-handler": "^1.2.0", + "express-rate-limit": "^7.4.0", "express-static-gzip": "2.1.7", "fs-extra": "11.2.0", "js-yaml": "^4.1.0", diff --git a/server/homebrew.api.js b/server/homebrew.api.js index 0f2283c2b..1891e2791 100644 --- a/server/homebrew.api.js +++ b/server/homebrew.api.js @@ -9,6 +9,7 @@ const yaml = require('js-yaml'); const asyncHandler = require('express-async-handler'); const { nanoid } = require('nanoid'); const { splitTextStyleAndMetadata } = require('../shared/helpers.js'); +const rateLimit = require('express-rate-limit'); const { DEFAULT_BREW, DEFAULT_BREW_LOAD } = require('./brewDefaults.js'); @@ -24,6 +25,16 @@ const isStaticTheme = (renderer, themeName)=>{ // }); // }; +// Define rate limiter options +const rateLimiter = rateLimit({ + timeWindow : 5 * 60 * 1000, // 5 minutes window + max : 100, // limit each IP to 100 requests per timeWindow + handler: (req, res, next) => { + console.log(`Rate limiting user ${req.account?.username}`); + throw { HBErrorCode: '55', status: 429, message: 'Too many requests from this IP, please try again after 5 minutes'}; + } +}); + const MAX_TITLE_LENGTH = 100; const api = { @@ -473,6 +484,7 @@ const api = { } }; +router.use('/api', rateLimiter); router.use('/api', require('./middleware/check-client-version.js')); router.post('/api', asyncHandler(api.newBrew)); router.put('/api/:id', asyncHandler(api.getBrew('edit', true)), asyncHandler(api.updateBrew));