From 9470ded1e8716d717c8ac340acf603095fa6e18b Mon Sep 17 00:00:00 2001 From: ChrisChrome Date: Mon, 23 Dec 2024 16:31:17 -0700 Subject: [PATCH] Impliment basic rate limiting --- index.js | 5 ++++- rateLimit.js | 40 ++++++++++++++++++++++++++++++++++++++++ routes/admin.js | 4 +++- routes/api.js | 3 +++ 4 files changed, 50 insertions(+), 2 deletions(-) create mode 100644 rateLimit.js diff --git a/index.js b/index.js index 71effe0..a8d3737 100644 --- a/index.js +++ b/index.js @@ -77,6 +77,9 @@ app.use((req, res, next) => { next() }); +// Rate Limit middleware from scratch +const rateLimit = require('./rateLimit.js'); + // The very very few direct routes app.get("/", (req, res) => { const readmePath = path.join(__dirname, 'README.md'); @@ -98,7 +101,7 @@ app.get("/", (req, res) => { var cached_invite = { code: "", expires: 0 } -app.get('/discord', async (req, res) => { +app.get('/discord', rateLimit.middleware, async (req, res) => { if (cached_invite.expires > Date.now()) { return res.redirect(`https://discord.gg/${cached_invite.code}`) } else { diff --git a/rateLimit.js b/rateLimit.js new file mode 100644 index 0000000..7d52de9 --- /dev/null +++ b/rateLimit.js @@ -0,0 +1,40 @@ +const { message } = require("noblox.js"); + +if (!global.rateLimitList) { + global.rateLimitList = {}; +} + +const middleware = (req, res, next) => { + console.log(global.rateLimitList) + // X requests per Y seconds per IP address + const maxRequests = process.env.RATE_LIMIT_MAX || 30; + const timeWindow = process.env.RATE_LIMIT_TIME || 60; + + var requestIp = req.ip; + if (process.env.TRUST_PROXY && (req.ip == `::ffff:${process.env.PROXY_IP}` || req.ip == process.env.PROXY_IP)) { + requestIp = req.headers["x-forwarded-for"]; + } + if (!global.rateLimitList[requestIp]) { + global.rateLimitList[requestIp] = { requests: 0, lastRequest: Date.now() }; + } + if (global.rateLimitList[requestIp].lastRequest + timeWindow * 1000 < Date.now()) { + global.rateLimitList[requestIp] = { requests: 0, lastRequest: Date.now() }; + } else { + if (global.rateLimitList[requestIp].requests >= maxRequests) { + return res.status(429).json({ completed: false, success: false, error: "Rate limit exceeded", message: "You have been rate limited. Please try again later.", expires: global.rateLimitList[requestIp].lastRequest + timeWindow * 1000 }); + } else { + global.rateLimitList[requestIp].lastRequest = Date.now(); + } + } + global.rateLimitList[requestIp].requests++; + next(); +} + +const getRateLimit = () => { + return global.global.rateLimitList; +} + +module.exports = { + middleware, + getRateLimit +} \ No newline at end of file diff --git a/routes/admin.js b/routes/admin.js index acd5969..d155bdc 100644 --- a/routes/admin.js +++ b/routes/admin.js @@ -235,7 +235,9 @@ router.get('/login', (req, res) => { res.render('admin/login', { env: process.env }); }); -router.post('/login', async (req, res) => { +const rateLimit = require('../rateLimit.js'); + +router.post('/login', rateLimit.middleware, async (req, res) => { const conn = await pool.getConnection(); const username = req.body.username; const password = req.body.password; diff --git a/routes/api.js b/routes/api.js index 26f0eea..23d6031 100644 --- a/routes/api.js +++ b/routes/api.js @@ -7,6 +7,9 @@ const { execSync } = require('child_process'); // Create a MariaDB connection pool const pool = global.db_pool +const rateLimit = require('../rateLimit.js'); +router.use(rateLimit.middleware); + router.get("/health", async (req,res) => { try { // Get a connection from the pool