diff options
Diffstat (limited to 'src')
-rw-r--r-- | src/api/routes/auth/register.ts | 34 | ||||
-rw-r--r-- | src/api/routes/channels/#channel_id/messages/index.ts | 33 |
2 files changed, 54 insertions, 13 deletions
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[]) ?? []; |