summary refs log tree commit diff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/Server.ts9
-rw-r--r--src/middlewares/RateLimit.ts36
-rw-r--r--src/routes/auth/login.ts2
-rw-r--r--src/routes/auth/register.ts2
4 files changed, 30 insertions, 19 deletions
diff --git a/src/Server.ts b/src/Server.ts

index 326fcc5c..94aab0f5 100644 --- a/src/Server.ts +++ b/src/Server.ts
@@ -93,10 +93,11 @@ export class FosscordServer extends Server { const prefix = Router(); // @ts-ignore this.app = prefix; - prefix.use(RateLimit({ bucket: "global", count: 10, error: 10, window: 5, bot: 250 })); - prefix.use("/guilds/:id", RateLimit({ count: 10, window: 5 })); - prefix.use("/webhooks/:id", RateLimit({ count: 10, window: 5 })); - prefix.use("/channels/:id", RateLimit({ count: 10, window: 5 })); + prefix.use(RateLimit({ bucket: "global", count: 10, window: 5, bot: 250 })); + prefix.use(RateLimit({ bucket: "error", count: 5, error: true, window: 5, bot: 15, onylIp: true })); + prefix.use("/guilds/:id", RateLimit({ count: 5, window: 5 })); + prefix.use("/webhooks/:id", RateLimit({ count: 5, window: 5 })); + prefix.use("/channels/:id", RateLimit({ count: 5, window: 5 })); this.routes = await this.registerRoutes(path.join(__dirname, "routes", "/")); app.use("/api", prefix); // allow unversioned requests diff --git a/src/middlewares/RateLimit.ts b/src/middlewares/RateLimit.ts
index 93d69236..89e002df 100644 --- a/src/middlewares/RateLimit.ts +++ b/src/middlewares/RateLimit.ts
@@ -36,17 +36,21 @@ export default function RateLimit(opts: { window: number; count: number; bot?: number; - error?: number; webhook?: number; oauth?: number; GET?: number; MODIFY?: number; + error?: boolean; + success?: boolean; + onylIp?: boolean; }) { Cache.init(); // will only initalize it once return async (req: Request, res: Response, next: NextFunction) => { - const bucket_id = opts.bucket || req.path.replace(API_PREFIX_TRAILING_SLASH, ""); - const user_id = req.user_id || getIpAdress(req); + const bucket_id = opts.bucket || req.originalUrl.replace(API_PREFIX_TRAILING_SLASH, ""); + var user_id = getIpAdress(req); + if (!opts.onylIp && req.user_id) user_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; @@ -59,9 +63,9 @@ export default function RateLimit(opts: { const resetAfterMs = reset - Date.now(); const resetAfterSec = resetAfterMs / 1000; const global = bucket_id === "global"; - console.log("blocked", { resetAfterMs }); if (resetAfterMs > 0) { + console.log("blocked", { resetAfterMs }); return ( res .status(429) @@ -76,26 +80,28 @@ export default function RateLimit(opts: { .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 - db.collection("ratelimits").updateOne( - { id: bucket_id, user_id }, - { $set: { hits: 0, expires_at: new Date(Date.now() + opts.window * 1000), blocked: false } } - ); + db.collection("ratelimits").updateOne({ id: bucket_id, user_id }, { $set: offender }); } } next(); + const hitRouteOpts = { bucket_id, user_id, max_hits, window: opts.window }; - if (opts.error) { + if (opts.error || opts.success) { res.once("finish", () => { // check if error and increment error rate limit - if (res.statusCode >= 400) { - // TODO: use config rate limit values - return hitRoute({ bucket_id: "error", user_id, max_hits: opts.error as number, window: opts.window }); + if (res.statusCode >= 400 && opts.error) { + return hitRoute(hitRouteOpts); + } else if (res.statusCode >= 200 && res.statusCode < 300 && opts.success) { + return hitRoute(hitRouteOpts); } }); + } else { + return hitRoute(hitRouteOpts); } - - return hitRoute({ user_id, bucket_id, max_hits, window: opts.window }); }; } @@ -121,7 +127,7 @@ function hitRoute(opts: { user_id: string; bucket_id: string; max_hits: number; { $set: { hits: { $sum: [{ $ifNull: ["$hits", 0] }, 1] }, - blocked: { $gt: ["$hits", opts.max_hits] } + blocked: { $gte: ["$hits", opts.max_hits] } } } ], diff --git a/src/routes/auth/login.ts b/src/routes/auth/login.ts
index 2c4084ea..547d115b 100644 --- a/src/routes/auth/login.ts +++ b/src/routes/auth/login.ts
@@ -4,12 +4,14 @@ import bcrypt from "bcrypt"; import jwt from "jsonwebtoken"; import { Config, UserModel } from "@fosscord/server-util"; import { adjustEmail } from "./register"; +import RateLimit from "../../middlewares/RateLimit"; const router: Router = Router(); export default router; router.post( "/", + RateLimit({ count: 5, window: 60, onylIp: true }), check({ login: new Length(String, 2, 100), // email or telephone password: new Length(String, 8, 64), diff --git a/src/routes/auth/register.ts b/src/routes/auth/register.ts
index f39206f2..83f8dc8c 100644 --- a/src/routes/auth/register.ts +++ b/src/routes/auth/register.ts
@@ -6,11 +6,13 @@ import "missing-native-js-functions"; import { generateToken } from "./login"; import { getIpAdress, IPAnalysis, isProxy } from "../../util/ipAddress"; import { HTTPError } from "lambert-server"; +import RateLimit from "../../middlewares/RateLimit"; const router: Router = Router(); router.post( "/", + RateLimit({ count: 2, window: 60 * 60 * 12, onylIp: true, success: true }), check({ username: new Length(String, 2, 32), // TODO: check min password length in config