forked from Prompt-Hash-Stellar/prompt-hash
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathrateLimiter.ts
More file actions
105 lines (88 loc) · 2.92 KB
/
Copy pathrateLimiter.ts
File metadata and controls
105 lines (88 loc) · 2.92 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
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
import { Request, Response, NextFunction } from "express";
interface RateLimitEntry {
count: number;
resetAt: number;
}
interface RateLimitOptions {
windowMs: number;
maxRequests: number;
keyGenerator?: (_req: Request) => string;
message?: string;
}
const stores = new Map<string, Map<string, RateLimitEntry>>();
function getStore(name: string): Map<string, RateLimitEntry> {
if (!stores.has(name)) {
stores.set(name, new Map());
}
return stores.get(name)!;
}
function cleanupStore(store: Map<string, RateLimitEntry>, windowMs: number) {
const now = Date.now();
for (const [key, entry] of store) {
if (now > entry.resetAt + windowMs) {
store.delete(key);
}
}
}
export function rateLimit(options: RateLimitOptions) {
const {
windowMs,
maxRequests,
keyGenerator = (req) => req.ip || req.socket.remoteAddress || "unknown",
message = "Too many requests, please try again later.",
} = options;
const storeName = `rl_${windowMs}_${maxRequests}`;
const store = getStore(storeName);
const cleanupInterval = setInterval(() => {
cleanupStore(store, windowMs);
}, windowMs);
if (cleanupInterval.unref) {
cleanupInterval.unref();
}
return (req: Request, res: Response, next: NextFunction) => {
const key = keyGenerator(req);
const now = Date.now();
const entry = store.get(key);
if (!entry || now > entry.resetAt) {
store.set(key, { count: 1, resetAt: now + windowMs });
res.setHeader("X-RateLimit-Limit", maxRequests);
res.setHeader("X-RateLimit-Remaining", maxRequests - 1);
res.setHeader("X-RateLimit-Reset", Math.ceil((now + windowMs) / 1000));
return next();
}
if (entry.count >= maxRequests) {
const retryAfter = Math.ceil((entry.resetAt - now) / 1000);
res.setHeader("Retry-After", retryAfter);
res.setHeader("X-RateLimit-Limit", maxRequests);
res.setHeader("X-RateLimit-Remaining", 0);
res.setHeader("X-RateLimit-Reset", Math.ceil(entry.resetAt / 1000));
return res.status(429).json({ error: message });
}
entry.count += 1;
res.setHeader("X-RateLimit-Limit", maxRequests);
res.setHeader("X-RateLimit-Remaining", maxRequests - entry.count);
res.setHeader("X-RateLimit-Reset", Math.ceil(entry.resetAt / 1000));
next();
};
}
// Pre-configured limiters for common use cases
export const globalLimiter = rateLimit({
windowMs: 15 * 60 * 1000,
maxRequests: 100,
message: "Too many requests from this IP, please try again later.",
});
export const authLimiter = rateLimit({
windowMs: 15 * 60 * 1000,
maxRequests: 20,
message: "Too many authentication attempts, please try again later.",
});
export const strictLimiter = rateLimit({
windowMs: 60 * 1000,
maxRequests: 10,
message: "Rate limit exceeded for this endpoint.",
});
export const chatLimiter = rateLimit({
windowMs: 60 * 1000,
maxRequests: 30,
message: "Too many chat requests, please slow down.",
});