summary refs log tree commit diff
path: root/api/src/middlewares
diff options
context:
space:
mode:
Diffstat (limited to 'api/src/middlewares')
-rw-r--r--api/src/middlewares/RateLimit.ts22
1 files changed, 16 insertions, 6 deletions
diff --git a/api/src/middlewares/RateLimit.ts b/api/src/middlewares/RateLimit.ts
index 1a38cfcf..ca6de98f 100644
--- a/api/src/middlewares/RateLimit.ts
+++ b/api/src/middlewares/RateLimit.ts
@@ -1,4 +1,4 @@
-import { Config, listenEvent } from "@fosscord/util";
+import { Config, getRights, listenEvent, Rights } from "@fosscord/util";
 import { NextFunction, Request, Response, Router } from "express";
 import { getIpAdress } from "@fosscord/api";
 import { API_PREFIX_TRAILING_SLASH } from "./Authentication";
@@ -9,6 +9,7 @@ import { API_PREFIX_TRAILING_SLASH } from "./Authentication";
 
 /*
 ? bucket limit? Max actions/sec per bucket?
+(ANSWER: a small fosscord instance might not need a complex rate limiting system)
 
 TODO: delay database requests to include multiple queries
 TODO: different for methods (GET/POST)
@@ -44,21 +45,25 @@ export default function rateLimit(opts: {
 	onlyIp?: boolean;
 }): any {
 	return async (req: Request, res: Response, next: NextFunction): Promise<any> => {
+		// exempt user? if so, immediately short circuit
+		const rights = await getRights(req.user_id);
+		if (rights.has("BYPASS_RATE_LIMITS")) return;
+		
 		const bucket_id = opts.bucket || req.originalUrl.replace(API_PREFIX_TRAILING_SLASH, "");
 		var executor_id = getIpAdress(req);
-		if (!opts.onlyIp && req.user_id) executor_id = req.user_id;
+		if (!opts.onlyIp && req.user_id) executor_id = req.user_id;		
 
 		var max_hits = opts.count;
 		if (opts.bot && req.user_bot) max_hits = opts.bot;
 		if (opts.GET && ["GET", "OPTIONS", "HEAD"].includes(req.method)) max_hits = opts.GET;
 		else if (opts.MODIFY && ["POST", "DELETE", "PATCH", "PUT"].includes(req.method)) max_hits = opts.MODIFY;
 
-		const offender = Cache.get(executor_id + bucket_id);
+		let offender = Cache.get(executor_id + bucket_id);
 
 		if (offender) {
-			const reset = offender.expires_at.getTime();
-			const resetAfterMs = reset - Date.now();
-			const resetAfterSec = resetAfterMs / 1000;
+			let reset = offender.expires_at.getTime();
+			let resetAfterMs = reset - Date.now();
+			let resetAfterSec = Math.ceil(resetAfterMs / 1000);
 
 			if (resetAfterMs <= 0) {
 				offender.hits = 0;
@@ -70,6 +75,11 @@ export default function rateLimit(opts: {
 
 			if (offender.blocked) {
 				const global = bucket_id === "global";
+				// each block violation pushes the expiry one full window further
+				reset += opts.window * 1000;
+				offender.expires_at = new Date(offender.expires_at.getTime() + opts.window * 1000);
+				resetAfterMs = reset - Date.now();
+				resetAfterSec = Math.ceil(resetAfterMs / 1000);
 
 				console.log("blocked bucket: " + bucket_id, { resetAfterMs });
 				return (