From 1a547c819cbd6f9c1f4bc7d7c16f46023ea0f7ad Mon Sep 17 00:00:00 2001 From: Akhileshwar Shriram <112577383+AkhilTheBoss@users.noreply.github.com> Date: Fri, 19 Sep 2025 08:55:40 -0700 Subject: [PATCH] feat: added rate limiter --- package-lock.json | 45 +++++++++++++++++++++++++++++++++++++++++++-- package.json | 1 + server/index.ts | 7 +++++++ 3 files changed, 51 insertions(+), 2 deletions(-) diff --git a/package-lock.json b/package-lock.json index c2a5a34..9441ff6 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "@libretexts/libreone", - "version": "1.50.0", + "version": "1.51.1", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "@libretexts/libreone", - "version": "1.50.0", + "version": "1.51.1", "license": "ISC", "dependencies": { "@aws-sdk/client-s3": "^3.624.0", @@ -33,6 +33,7 @@ "cors": "^2.8.5", "dotenv": "^16.0.3", "express": "^4.21.0", + "express-rate-limit": "^8.1.0", "fast-xml-parser": "^4.5.1", "helmet": "^6.0.0", "http-cas-client": "^0.4.3", @@ -7733,6 +7734,24 @@ "node": ">= 0.10.0" } }, + "node_modules/express-rate-limit": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/express-rate-limit/-/express-rate-limit-8.1.0.tgz", + "integrity": "sha512-4nLnATuKupnmwqiJc27b4dCFmB/T60ExgmtDD7waf4LdrbJ8CPZzZRHYErDYNhoz+ql8fUdYwM/opf90PoPAQA==", + "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", @@ -8831,6 +8850,15 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "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", @@ -22212,6 +22240,14 @@ } } }, + "express-rate-limit": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/express-rate-limit/-/express-rate-limit-8.1.0.tgz", + "integrity": "sha512-4nLnATuKupnmwqiJc27b4dCFmB/T60ExgmtDD7waf4LdrbJ8CPZzZRHYErDYNhoz+ql8fUdYwM/opf90PoPAQA==", + "requires": { + "ip-address": "10.0.1" + } + }, "fast-deep-equal": { "version": "3.1.3", "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", @@ -22972,6 +23008,11 @@ "p-is-promise": "^3.0.0" } }, + "ip-address": { + "version": "10.0.1", + "resolved": "https://registry.npmjs.org/ip-address/-/ip-address-10.0.1.tgz", + "integrity": "sha512-NWv9YLW4PoW2B7xtzaS3NCot75m6nK7Icdv0o3lfMceJVRfSoQwqD4wEH5rLwoKJwUiZ/rfpiVBhnaF0FK4HoA==" + }, "ipaddr.js": { "version": "1.9.1", "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz", diff --git a/package.json b/package.json index f329b85..6ea838c 100644 --- a/package.json +++ b/package.json @@ -41,6 +41,7 @@ "cors": "^2.8.5", "dotenv": "^16.0.3", "express": "^4.21.0", + "express-rate-limit": "^8.1.0", "fast-xml-parser": "^4.5.1", "helmet": "^6.0.0", "http-cas-client": "^0.4.3", diff --git a/server/index.ts b/server/index.ts index d7bd2c4..e928486 100644 --- a/server/index.ts +++ b/server/index.ts @@ -18,6 +18,12 @@ import { getProductionURL } from './helpers'; import swaggerUi from 'swagger-ui-express'; import swaggerSpec from './swagger/swagger.json' import { PageContext } from '@renderer/types'; +import rateLimit from "express-rate-limit"; + +const apiLimiter = rateLimit({ + windowMs: 15 * 60 * 1000, // 15 minutes + limit: 100, // Limit each IP to 100 requests per windowMs +}); const __filename = fileURLToPath(import.meta.url); const __dirname = dirname(__filename); @@ -25,6 +31,7 @@ const isProduction = process.env.NODE_ENV === 'production'; const root = `${__dirname}/..`; const app = express(); +app.use(apiLimiter); app.use(helmet.hidePoweredBy()); // TODO: Improve helmet utilization app.use(compression()); app.use(bodyParser.json());