From 04a7c5ee93ff26a25c4f7ccc956990249d7c6cbb Mon Sep 17 00:00:00 2001 From: arsam Date: Fri, 18 Sep 2026 23:12:27 +0330 Subject: [PATCH] Import source: backend/src/middleware/rateLimit Import the existing local project snapshot. This commit organizes existing files; it does not represent newly developed functionality. --- backend/src/middleware/rateLimit.ts | 38 +++++++++++++++++++++++++++++ 1 file changed, 38 insertions(+) create mode 100644 backend/src/middleware/rateLimit.ts diff --git a/backend/src/middleware/rateLimit.ts b/backend/src/middleware/rateLimit.ts new file mode 100644 index 0000000..765d053 --- /dev/null +++ b/backend/src/middleware/rateLimit.ts @@ -0,0 +1,38 @@ +import type { RequestHandler } from 'express'; +import { HttpError } from './errorHandler.js'; + +const log = new Map(); + +// Prune entries older than 1 hour every 10 minutes +setInterval(() => { + const cutoff = Date.now() - 3_600_000; + for (const [ip, ts] of log) { + const fresh = ts.filter((t) => t > cutoff); + if (fresh.length === 0) log.delete(ip); + else log.set(ip, fresh); + } +}, 600_000).unref(); + +/** + * Sliding-window rate limiter — in-memory, per IP. + * maxRequests allowed within windowMs milliseconds. + */ +export function rateLimit(maxRequests: number, windowMs: number): RequestHandler { + return (req, res, next) => { + const ip = req.ip ?? req.socket.remoteAddress ?? 'unknown'; + const now = Date.now(); + const windowStart = now - windowMs; + + const timestamps = (log.get(ip) ?? []).filter((t) => t > windowStart); + + if (timestamps.length >= maxRequests) { + const retryAfterSecs = Math.ceil((timestamps[0] - windowStart) / 1000); + res.setHeader('Retry-After', String(retryAfterSecs)); + return next(new HttpError(429, 'RATE_LIMITED', `Too many requests — retry after ${retryAfterSecs}s`)); + } + + timestamps.push(now); + log.set(ip, timestamps); + next(); + }; +}