⚡ only local rate limit to prevent to much pressure on the database
This commit is contained in:
parent
bee6f71298
commit
eb6efd14c1
@ -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 { NextFunction, Request, Response, Router } from "express";
|
||||||
import { LessThan, MoreThan } from "typeorm";
|
|
||||||
import { getIpAdress } from "../util/ipAddress";
|
import { getIpAdress } from "../util/ipAddress";
|
||||||
import { API_PREFIX_TRAILING_SLASH } from "./Authentication";
|
import { API_PREFIX_TRAILING_SLASH } from "./Authentication";
|
||||||
|
|
||||||
// Docs: https://discord.com/developers/docs/topics/rate-limits
|
// 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?
|
? 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>();
|
var Cache = new Map<string, RateLimit>();
|
||||||
const EventRateLimit = "RATELIMIT";
|
const EventRateLimit = "RATELIMIT";
|
||||||
|
|
||||||
@ -46,13 +55,22 @@ export default function rateLimit(opts: {
|
|||||||
|
|
||||||
const offender = Cache.get(executor_id + bucket_id);
|
const offender = Cache.get(executor_id + bucket_id);
|
||||||
|
|
||||||
if (offender && offender.blocked) {
|
if (offender) {
|
||||||
const reset = offender.expires_at.getTime();
|
const reset = offender.expires_at.getTime();
|
||||||
const resetAfterMs = reset - Date.now();
|
const resetAfterMs = reset - Date.now();
|
||||||
const resetAfterSec = resetAfterMs / 1000;
|
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 });
|
console.log("blocked bucket: " + bucket_id, { resetAfterMs });
|
||||||
return (
|
return (
|
||||||
res
|
res
|
||||||
@ -67,15 +85,9 @@ export default function rateLimit(opts: {
|
|||||||
// TODO: error rate limit message translation
|
// TODO: error rate limit message translation
|
||||||
.send({ message: "You are being rate limited.", retry_after: resetAfterSec, global })
|
.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();
|
next();
|
||||||
const hitRouteOpts = { bucket_id, executor_id, max_hits, window: opts.window };
|
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);
|
Cache.set(event.channel_id as string, event.data);
|
||||||
event.acknowledge?.();
|
event.acknowledge?.();
|
||||||
});
|
});
|
||||||
await RateLimit.delete({ expires_at: MoreThan(new Date()) }); // cleans up if not already deleted, morethan -> older date
|
// 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 });
|
// const limits = await RateLimit.find({ blocked: true });
|
||||||
limits.forEach((limit) => {
|
// limits.forEach((limit) => {
|
||||||
Cache.set(limit.executor_id, limit);
|
// Cache.set(limit.executor_id, limit);
|
||||||
});
|
// });
|
||||||
|
|
||||||
setInterval(() => {
|
setInterval(() => {
|
||||||
Cache.forEach((x, key) => {
|
Cache.forEach((x, key) => {
|
||||||
if (new Date() > x.expires_at) {
|
if (new Date() > x.expires_at) {
|
||||||
Cache.delete(key);
|
Cache.delete(key);
|
||||||
RateLimit.delete({ executor_id: key });
|
// RateLimit.delete({ executor_id: key });
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}, 1000 * 60 * 10);
|
}, 1000 * 60);
|
||||||
|
|
||||||
app.use(
|
app.use(
|
||||||
rateLimit({
|
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 }) {
|
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 });
|
var ratelimit = await RateLimit.findOne({ id: opts.bucket_id, executor_id: opts.executor_id });
|
||||||
if (!ratelimit) {
|
if (!ratelimit) {
|
||||||
ratelimit = new RateLimit({
|
ratelimit = new RateLimit({
|
||||||
@ -167,4 +198,5 @@ async function hitRoute(opts: { executor_id: string; bucket_id: string; max_hits
|
|||||||
}
|
}
|
||||||
|
|
||||||
await ratelimit.save();
|
await ratelimit.save();
|
||||||
|
*/
|
||||||
}
|
}
|
||||||
|
@ -1,6 +1,5 @@
|
|||||||
import { Column, Entity, JoinColumn, ManyToOne, RelationId } from "typeorm";
|
import { Column, Entity } from "typeorm";
|
||||||
import { BaseClass } from "./BaseClass";
|
import { BaseClass } from "./BaseClass";
|
||||||
import { User } from "./User";
|
|
||||||
|
|
||||||
@Entity("rate_limits")
|
@Entity("rate_limits")
|
||||||
export class RateLimit extends BaseClass {
|
export class RateLimit extends BaseClass {
|
||||||
|
Loading…
x
Reference in New Issue
Block a user