-
Notifications
You must be signed in to change notification settings - Fork 425
Expand file tree
/
Copy pathratelimit.js
More file actions
60 lines (51 loc) · 1.85 KB
/
ratelimit.js
File metadata and controls
60 lines (51 loc) · 1.85 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
import { Elysia } from "elysia";
import { db } from "./db.js";
import { getClientIp } from "./ip_extraction.js";
let scopeCounter = 0;
export default function valkeyRateLimit({
max: defaultMax = 30,
duration: defaultDuration = 5000,
getLimits,
onLimited,
} = {}) {
const scope = scopeCounter++;
return new Elysia({ name: `cap-ratelimit-${scope}`, scoped: true }).onBeforeHandle(
async ({ request, set, server: srv, params }) => {
const ip = getClientIp(request, srv);
if (!ip) {
if (process.env.HIDE_RATELIMIT_IP_WARNING !== "true") {
console.warn(
`⚠️ [ratelimit] Unable to determine client IP, rate limiting disabled. If you're running locally, it should be safe \n to ignore this warning. Otherwise, make sure to set the RATELIMIT_IP_HEADER env variable to a header \n which returns the user's IP. Hide this warning with env.HIDE_RATELIMIT_IP_WARNING=true`,
);
}
return;
}
let max = defaultMax;
let duration = defaultDuration;
if (getLimits) {
const limits = await getLimits(params);
if (limits) {
max = limits.max;
duration = limits.duration;
}
}
const windowMs = duration;
const windowSecs = Math.ceil(duration / 1000);
const window = Math.floor(Date.now() / windowMs);
const key = `rl:${scope}:${ip}:${windowMs}:${window}`;
const count = await db.incr(key);
if (count === 1) {
await db.expire(key, windowSecs + 1);
}
set.headers["X-RateLimit-Limit"] = String(max);
set.headers["X-RateLimit-Remaining"] = String(Math.max(0, max - count));
if (count > max) {
if (onLimited) {
try { await onLimited(request, ip); } catch { }
}
set.status = 429;
return { error: "Rate limit exceeded" };
}
},
);
}