File size: 2,665 Bytes
077865a
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
// Per-IP fixed-window rate limiter for the public /v1 proxy (#35, item #6).
//
// The /v1 surface authenticates with the unified API key but has no password
// login like the dashboard does, so without this an attacker who can reach the
// server could brute-force the key or flood upstream providers. This caps how
// many requests a single client IP can make per minute and returns a standard
// OpenAI-shaped 429 once the cap is exceeded.
//
// FreeLLMAPI is a single-user tool, so the default ceiling is generous. Tune it
// with PROXY_RATE_LIMIT_RPM (requests per minute per IP); set it to 0 to turn
// rate limiting off entirely.
const WINDOW_MS = 60_000;
const DEFAULT_RPM = 120;
// Bound the IP map so a flood of distinct (e.g. spoofed) source addresses can't
// grow it without limit; expired entries are pruned opportunistically.
const MAX_TRACKED_IPS = 10_000;
function parseLimit() {
    const raw = process.env.PROXY_RATE_LIMIT_RPM;
    if (raw === undefined || raw.trim() === '')
        return DEFAULT_RPM;
    const n = Number(raw);
    if (!Number.isFinite(n) || n < 0)
        return DEFAULT_RPM;
    return Math.floor(n);
}
export function createProxyRateLimiter() {
    const limit = parseLimit();
    const windows = new Map();
    return function proxyRateLimit(req, res, next) {
        if (limit === 0) {
            next();
            return;
        }
        const now = Date.now();
        const ip = req.ip ?? req.socket.remoteAddress ?? 'unknown';
        let state = windows.get(ip);
        if (!state || now >= state.resetAt) {
            state = { count: 0, resetAt: now + WINDOW_MS };
            windows.set(ip, state);
        }
        state.count += 1;
        if (windows.size > MAX_TRACKED_IPS) {
            for (const [key, value] of windows) {
                if (now >= value.resetAt)
                    windows.delete(key);
            }
        }
        res.setHeader('X-RateLimit-Limit', String(limit));
        res.setHeader('X-RateLimit-Remaining', String(Math.max(0, limit - state.count)));
        res.setHeader('X-RateLimit-Reset', String(Math.ceil(state.resetAt / 1000)));
        if (state.count > limit) {
            const retryAfter = Math.max(1, Math.ceil((state.resetAt - now) / 1000));
            res.setHeader('Retry-After', String(retryAfter));
            res.status(429).json({
                error: {
                    message: `Rate limit exceeded: more than ${limit} requests per minute. Retry in ${retryAfter}s.`,
                    type: 'rate_limit_error',
                },
            });
            return;
        }
        next();
    };
}
//# sourceMappingURL=rateLimit.js.map