diff --git a/app.js b/app.js index a0f898903..68cae4eec 100644 --- a/app.js +++ b/app.js @@ -97,6 +97,8 @@ const markdown = require("markdown-it")(); const v8 = require("v8"); const compression = require("compression"); const jayson = require('jayson/promise'); +const { rateLimit } = require("express-rate-limit"); + const appUtils = require("@janoside/app-utils"); const s3Utils = appUtils.s3Utils; @@ -237,6 +239,38 @@ expressApp.use(config.baseUrl, express.static(path.join(__dirname, 'public'), { })); +// https://www.npmjs.com/package/express-rate-limit +const rateLimitWindowMinutes = 15; +const rateLimitWindowMaxRequests = 200; +const rateLimiter = rateLimit({ + windowMs: rateLimitWindowMinutes * 60 * 1000, // 15 minutes + limit: rateLimitWindowMaxRequests, // Limit each IP to 100 requests per `window` (here, per 15 minutes). + standardHeaders: 'draft-7', // draft-6: `RateLimit-*` headers; draft-7: combined `RateLimit` header + legacyHeaders: false, // Disable the `X-RateLimit-*` headers. + skip: function (req, res) { + if (req.originalUrl.includes("/snippet/")) { + return true; + } + + if (req.originalUrl.includes("/api/")) { + return true; + } + + return false; + }, + handler: function (req, res, next) { + debugErrorLog(`Rate-limiting request: ip=${req.ip}, req=${req.originalUrl}`) + res.status(429).json({ + message: "Too many requests, please try again later.", + }); + } +}); + +// Apply the rate limiting middleware to all requests. +expressApp.use(rateLimiter); + + + if (config.baseUrl != '/') { expressApp.get('/', (req, res) => res.redirect(config.baseUrl)); } diff --git a/package.json b/package.json index 3d5637d1c..c8e851066 100644 --- a/package.json +++ b/package.json @@ -10,20 +10,17 @@ "miners": "node ./bin/refresh-mining-pool-configs.js", "integrity": "node ./bin/frontend-resource-integrity.js", "lint": "eslint app routes", - "css-light-debug": "sass --style expanded ./public/scss/light.scss ./public/style/light.css", "css-dark-debug": "sass --style expanded ./public/scss/dark.scss ./public/style/dark.css", "css-dark-v1-debug": "sass --style expanded ./public/scss/dark-v1.scss ./public/style/dark-v1.css", - "css-debug": "npm-run-all css-light-debug css-dark-debug css-dark-v1-debug", - "css-light": "sass --style compressed ./public/scss/light.scss ./public/style/light.min.css", "css-dark": "sass --style compressed ./public/scss/dark.scss ./public/style/dark.min.css", "css-dark-v1": "sass --style compressed ./public/scss/dark-v1.scss ./public/style/dark-v1.min.css", - "css": "npm-run-all css-light css-dark css-dark-v1 integrity" }, - "keywords": [ + "keywords": + [ "groestlcoin", "grs", "blockchain" @@ -37,7 +34,7 @@ "dependencies": { "@janoside/app-utils": "github:janoside/app-utils#ba4c23d3f", "async": "^3.2.4", - "axios": "^1.4.0", + "axios": "^1.7.4", "basic-auth": "^2.0.1", "bech32": "2.0.0", "bip32grs": "^3.0.0", @@ -54,8 +51,9 @@ "decimal.js": "^10.4.3", "dotenv": "^13.0.1", "electrum-client": "github:janoside/electrum-client", - "express": "^4.18.2", + "express": "^4.19.2", "express-async-handler": "^1.2.0", + "express-rate-limit": "7.4.0", "express-session": "^1.17.3", "jayson": "^4.1.0", "lru-cache": "^10.0.0",