summary refs log tree commit diff
diff options
context:
space:
mode:
authorFlam3rboy <34555296+Flam3rboy@users.noreply.github.com>2021-08-30 12:14:32 +0200
committerFlam3rboy <34555296+Flam3rboy@users.noreply.github.com>2021-08-30 12:14:32 +0200
commit954700b2d5c4090fdf9d7d25beef3d6529afa8d3 (patch)
tree4d98ac01cabc73337b8b2654715ee8f445edd719
parent:bug: convert bigint -> string (diff)
downloadserver-954700b2d5c4090fdf9d7d25beef3d6529afa8d3.tar.xz
:zap: only local rate limit to prevent to much pressure on the database
-rw-r--r--api/src/middlewares/RateLimit.ts70
-rw-r--r--util/src/entities/RateLimit.ts3
2 files changed, 52 insertions, 21 deletions
diff --git a/api/src/middlewares/RateLimit.ts b/api/src/middlewares/RateLimit.ts
index ed6b951a..dffbc0d9 100644
--- a/api/src/middlewares/RateLimit.ts
+++ b/api/src/middlewares/RateLimit.ts
@@ -1,11 +1,12 @@
-import { Config, listenEvent, emitEvent, RateLimit } from "@fosscord/util";
+import { Config, listenEvent } from "@fosscord/util";
 import { NextFunction, Request, Response, Router } from "express";
-import { LessThan, MoreThan } from "typeorm";
 import { getIpAdress } from "../util/ipAddress";
 import { API_PREFIX_TRAILING_SLASH } from "./Authentication";
 
 // Docs: https://discord.com/developers/docs/topics/rate-limits
 
+// TODO: use better caching (e.g. redis) as else it creates to much pressure on the database
+
 /*
 ? bucket limit? Max actions/sec per bucket?
 
@@ -18,6 +19,14 @@ TODO: different for methods (GET/POST)
 
 */
 
+type RateLimit = {
+	id: "global" | "error" | string;
+	executor_id: string;
+	hits: number;
+	blocked: boolean;
+	expires_at: Date;
+};
+
 var Cache = new Map<string, RateLimit>();
 const EventRateLimit = "RATELIMIT";
 
@@ -46,13 +55,22 @@ export default function rateLimit(opts: {
 
 		const offender = Cache.get(executor_id + bucket_id);
 
-		if (offender && offender.blocked) {
+		if (offender) {
 			const reset = offender.expires_at.getTime();
 			const resetAfterMs = reset - Date.now();
 			const resetAfterSec = resetAfterMs / 1000;
-			const global = bucket_id === "global";
 
-			if (resetAfterMs > 0) {
+			if (resetAfterMs <= 0) {
+				offender.hits = 0;
+				offender.expires_at = new Date(Date.now() + opts.window * 1000);
+				offender.blocked = false;
+
+				Cache.delete(executor_id + bucket_id);
+			}
+
+			if (offender.blocked) {
+				const global = bucket_id === "global";
+
 				console.log("blocked bucket: " + bucket_id, { resetAfterMs });
 				return (
 					res
@@ -67,15 +85,9 @@ export default function rateLimit(opts: {
 						// TODO: error rate limit message translation
 						.send({ message: "You are being rate limited.", retry_after: resetAfterSec, global })
 				);
-			} else {
-				offender.hits = 0;
-				offender.expires_at = new Date(Date.now() + opts.window * 1000);
-				offender.blocked = false;
-				// mongodb ttl didn't update yet -> manually update/delete
-				RateLimit.delete({ id: bucket_id, executor_id });
-				Cache.delete(executor_id + bucket_id);
 			}
 		}
+
 		next();
 		const hitRouteOpts = { bucket_id, executor_id, max_hits, window: opts.window };
 
@@ -100,20 +112,20 @@ export async function initRateLimits(app: Router) {
 		Cache.set(event.channel_id as string, event.data);
 		event.acknowledge?.();
 	});
-	await RateLimit.delete({ expires_at: MoreThan(new Date()) }); // cleans up if not already deleted, morethan -> older date
-	const limits = await RateLimit.find({ blocked: true });
-	limits.forEach((limit) => {
-		Cache.set(limit.executor_id, limit);
-	});
+	// await RateLimit.delete({ expires_at: LessThan(new Date().toISOString()) }); // cleans up if not already deleted, morethan -> older date
+	// const limits = await RateLimit.find({ blocked: true });
+	// limits.forEach((limit) => {
+	// 	Cache.set(limit.executor_id, limit);
+	// });
 
 	setInterval(() => {
 		Cache.forEach((x, key) => {
 			if (new Date() > x.expires_at) {
 				Cache.delete(key);
-				RateLimit.delete({ executor_id: key });
+				// RateLimit.delete({ executor_id: key });
 			}
 		});
-	}, 1000 * 60 * 10);
+	}, 1000 * 60);
 
 	app.use(
 		rateLimit({
@@ -139,6 +151,25 @@ export async function initRateLimits(app: Router) {
 }
 
 async function hitRoute(opts: { executor_id: string; bucket_id: string; max_hits: number; window: number }) {
+	const id = opts.executor_id + opts.bucket_id;
+	var limit = Cache.get(id);
+	if (!limit) {
+		limit = {
+			id: opts.bucket_id,
+			executor_id: opts.executor_id,
+			expires_at: new Date(Date.now() + opts.window * 1000),
+			hits: 0,
+			blocked: false
+		};
+		Cache.set(id, limit);
+	}
+
+	limit.hits++;
+	if (limit.hits >= opts.max_hits) {
+		limit.blocked = true;
+	}
+
+	/*
 	var ratelimit = await RateLimit.findOne({ id: opts.bucket_id, executor_id: opts.executor_id });
 	if (!ratelimit) {
 		ratelimit = new RateLimit({
@@ -167,4 +198,5 @@ async function hitRoute(opts: { executor_id: string; bucket_id: string; max_hits
 	}
 
 	await ratelimit.save();
+	*/
 }
diff --git a/util/src/entities/RateLimit.ts b/util/src/entities/RateLimit.ts
index 49af0416..fa9c32c1 100644
--- a/util/src/entities/RateLimit.ts
+++ b/util/src/entities/RateLimit.ts
@@ -1,6 +1,5 @@
-import { Column, Entity, JoinColumn, ManyToOne, RelationId } from "typeorm";
+import { Column, Entity } from "typeorm";
 import { BaseClass } from "./BaseClass";
-import { User } from "./User";
 
 @Entity("rate_limits")
 export class RateLimit extends BaseClass {