summary refs log tree commit diff
diff options
context:
space:
mode:
authorTheArcaneBrony <myrainbowdash949@gmail.com>2022-09-18 19:25:08 +0200
committerTheArcaneBrony <myrainbowdash949@gmail.com>2022-09-18 19:25:08 +0200
commit6e3d8a5d9c30fbe63b4469dd751d69379ebfd50b (patch)
treed148f1a71b5a1d77c572e5335ca62b41d6e27331
parentSane message rate limit (diff)
downloadserver-6e3d8a5d9c30fbe63b4469dd751d69379ebfd50b.tar.xz
Return proper 429's, reg tokens bypass disabled registrations, code cleanup
-rw-r--r--package.json2
-rw-r--r--src/api/routes/auth/register.ts34
-rw-r--r--src/api/routes/channels/#channel_id/messages/index.ts33
3 files changed, 54 insertions, 15 deletions
diff --git a/package.json b/package.json
index c9149afd..878c2e2d 100644
--- a/package.json
+++ b/package.json
@@ -83,7 +83,6 @@
 		"missing-native-js-functions": "^1.2.18",
 		"morgan": "^1.10.0",
 		"multer": "^1.4.5-lts.1",
-		"mysql2": "^2.3.3",
 		"node-2fa": "^2.0.3",
 		"node-fetch": "^2.6.7",
 		"patch-package": "^6.4.7",
@@ -91,7 +90,6 @@
 		"prettier": "^2.7.1",
 		"proxy-agent": "^5.0.0",
 		"reflect-metadata": "^0.1.13",
-		"sqlite3": "^5.1.1",
 		"typeorm": "^0.3.7",
 		"typescript": "^4.2.3",
 		"ws": "^8.8.1"
diff --git a/src/api/routes/auth/register.ts b/src/api/routes/auth/register.ts
index b87c26f6..b5d75a30 100644
--- a/src/api/routes/auth/register.ts
+++ b/src/api/routes/auth/register.ts
@@ -32,6 +32,8 @@ router.post("/", route({ body: "RegisterSchema" }), async (req: Request, res: Re
 	const body = req.body as RegisterSchema;
 	const { register, security, limits } = Config.get();
 	const ip = getIpAdress(req);
+	// tokens bypass requirements:
+	const hasToken = req.get("Referrer") && req.get("Referrer")?.includes("token=");
 
 	// email will be slightly modified version of the user supplied email -> e.g. protection against GMail Trick
 	let email = adjustEmail(body.email);
@@ -43,7 +45,7 @@ router.post("/", route({ body: "RegisterSchema" }), async (req: Request, res: Re
 		});
 	}
 
-	if (register.requireCaptcha && security.captcha.enabled) {
+	if (register.requireCaptcha && security.captcha.enabled && !hasToken) {
 		const { sitekey, service } = security.captcha;
 		if (!body.captcha_key) {
 			return res?.status(400).json({
@@ -64,7 +66,7 @@ router.post("/", route({ body: "RegisterSchema" }), async (req: Request, res: Re
 	}
 
 	// check if registration is allowed
-	if (!register.allowNewRegistration) {
+	if (!register.allowNewRegistration && !hasToken) {
 		throw FieldErrors({
 			email: { code: "REGISTRATION_DISABLED", message: req.t("auth:register.REGISTRATION_DISABLED") }
 		});
@@ -142,9 +144,9 @@ router.post("/", route({ body: "RegisterSchema" }), async (req: Request, res: Re
 		});
 	}
 
-	//check if email starts with any valid registration token
+	//check if referrer starts with any valid registration token
 	let validToken = false;
-	if (req.get("Referrer") && req.get("Referrer")?.includes("token=")) {
+	if (hasToken) {
 		let token = req.get("Referrer")?.split("token=")[1].split("&")[0];
 		if (token) {
 			await ValidRegistrationToken.delete({ expires_at: LessThan(new Date()) });
@@ -172,9 +174,29 @@ router.post("/", route({ body: "RegisterSchema" }), async (req: Request, res: Re
 				}, ${req.body.username}, ${req.body.invite ?? "No invite given"}`
 			)
 		);
-		throw FieldErrors({
-			email: { code: "TOO_MANY_REGISTRATIONS", message: req.t("auth:register.TOO_MANY_REGISTRATIONS") }
+		let oldest = await User.findOne({
+			where: { created_at: MoreThan(new Date(Date.now() - limits.absoluteRate.register.window)) },
+			order: { created_at: "ASC" }
 		});
+		if (!oldest) {
+			console.warn(
+				red(
+					`[REGISTER/WARN] Global rate limits exceeded, but no oldest user found. This should not happen. Did you misconfigure the limits?`
+				)
+			);
+		} else {
+			let retryAfterSec = Math.ceil((oldest!.created_at.getTime() - new Date(Date.now() - limits.absoluteRate.register.window).getTime())/1000);
+			return res
+				.status(429)
+				.set("X-RateLimit-Limit", `${limits.absoluteRate.register.limit}`)
+				.set("X-RateLimit-Remaining", "0")
+				.set("X-RateLimit-Reset", `${(oldest!.created_at.getTime() + limits.absoluteRate.register.window) / 1000}`)
+				.set("X-RateLimit-Reset-After", `${retryAfterSec}`)
+				.set("X-RateLimit-Global", `true`)
+				.set("Retry-After", `${retryAfterSec}`)
+				.set("X-RateLimit-Bucket", `register`)
+				.send({ message: req.t("auth:register.TOO_MANY_REGISTRATIONS"), retry_after: retryAfterSec, global: true });
+		}
 	}
 
 	const user = await User.register({ ...body, req });
diff --git a/src/api/routes/channels/#channel_id/messages/index.ts b/src/api/routes/channels/#channel_id/messages/index.ts
index b2822711..65cd42f3 100644
--- a/src/api/routes/channels/#channel_id/messages/index.ts
+++ b/src/api/routes/channels/#channel_id/messages/index.ts
@@ -6,7 +6,6 @@ import {
 	Config,
 	DmChannelDTO,
 	emitEvent,
-	FieldErrors,
 	getIpAdress,
 	getPermission,
 	getRights,
@@ -21,7 +20,7 @@ import {
 } from "@fosscord/util";
 import { Request, Response, Router } from "express";
 import multer from "multer";
-import { yellow } from "picocolors";
+import { red, yellow } from "picocolors";
 import { FindManyOptions, LessThan, MoreThan } from "typeorm";
 import { URL } from "url";
 
@@ -168,19 +167,39 @@ router.post(
 
 		if (
 			!(await getRights(req.user_id)).has(Rights.FLAGS.BYPASS_RATE_LIMITS) &&
-			limits.absoluteRate.register.enabled &&
-			(await await Message.count({
+			limits.absoluteRate.sendMessage.enabled &&
+			(await Message.count({
 				where: { channel_id, timestamp: MoreThan(new Date(Date.now() - limits.absoluteRate.sendMessage.window)) }
-			})) >= limits.absoluteRate.register.limit
+			})) >= limits.absoluteRate.sendMessage.limit
 		) {
 			console.log(
 				yellow(
 					`[MESSAGE] Global register rate limit exceeded for ${getIpAdress(req)}: ${channel_id}, ${req.user_id}, ${body.content}`
 				)
 			);
-			throw FieldErrors({
-				channel_id: { code: "TOO_MANY_MESSAGES", message: req.t("common:toomany.MESSAGE") }
+			let oldest = await Message.findOne({
+				where: { channel_id, timestamp: MoreThan(new Date(Date.now() - limits.absoluteRate.sendMessage.window)) },
+				order: { timestamp: "ASC" }
 			});
+			if (!oldest) {
+				console.warn(
+					red(
+						`[MESSAGE/WARN] Global rate limits exceeded, but no oldest message found. This should not happen. Did you misconfigure the limits?`
+					)
+				);
+			} else {
+				let retryAfterSec = Math.ceil((oldest!.timestamp.getTime() - new Date(Date.now() - limits.absoluteRate.sendMessage.window).getTime())/1000);
+				return res
+					.status(429)
+					.set("X-RateLimit-Limit", `${limits.absoluteRate.sendMessage.limit}`)
+					.set("X-RateLimit-Remaining", "0")
+					.set("X-RateLimit-Reset", `${(oldest!.timestamp.getTime() + limits.absoluteRate.sendMessage.window) / 1000}`)
+					.set("X-RateLimit-Reset-After", `${retryAfterSec}`)
+					.set("X-RateLimit-Global", `false`)
+					.set("Retry-After", `${retryAfterSec}`)
+					.set("X-RateLimit-Bucket", `chnl_${channel_id}`)
+					.send({ message: req.t("common:toomany.MESSAGE"), retry_after: retryAfterSec, global: false });
+			}
 		}
 
 		const files = (req.files as Express.Multer.File[]) ?? [];