-
Notifications
You must be signed in to change notification settings - Fork 18
Expand file tree
/
Copy pathrateLimiter.js
More file actions
81 lines (70 loc) · 2.57 KB
/
Copy pathrateLimiter.js
File metadata and controls
81 lines (70 loc) · 2.57 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
// Simple in-memory fixed-window rate limiter with per-identifier keys
// Identifier: X-Request-Key header if present, otherwise IP (req.ip or X-Forwarded-For)
import { config } from '../config.js'
import { logger } from '../logger.js'
function getIdentifier(req) {
const hdr =
req.headers['x-request-key'] || req.headers['x-api-key'] || req.headers['authorization']
if (hdr && typeof hdr === 'string') return `key:${hdr}`
// Prefer X-Forwarded-For if present
const xff = req.headers['x-forwarded-for']
if (xff && typeof xff === 'string') return `ip:${xff.split(',')[0].trim()}`
return `ip:${req.ip || 'unknown'}`
}
function createFixedWindowLimiter({ windowSec, max }) {
const windowMs = windowSec * 1000
// Map identifier -> { count, windowStart }
const store = new Map()
return function limiter(req, res, next) {
try {
const id = getIdentifier(req)
const now = Date.now()
const entry = store.get(id) || { count: 0, windowStart: now }
// Reset if window elapsed
if (now - entry.windowStart >= windowMs) {
entry.count = 0
entry.windowStart = now
}
entry.count += 1
store.set(id, entry)
if (entry.count > max) {
const retryAfterSec = Math.ceil((entry.windowStart + windowMs - now) / 1000) || 1
// Clear sensitive details in logs
logger.warn('rate_limit_exceeded', {
correlationId: req.requestId,
id,
path: req.originalUrl,
method: req.method,
max,
windowSec,
})
res.set('Retry-After', String(retryAfterSec))
return res.status(429).json({
code: 'RATE_LIMIT_EXCEEDED',
message: `Too many requests. Retry after ${retryAfterSec} seconds.`,
requestId: req.requestId,
retryAfter: retryAfterSec,
})
}
next()
} catch (err) {
// On any unexpected error in limiter, allow the request through
logger.error('rate_limiter_error', { correlationId: req.requestId, err })
next()
}
}
}
// Export preconfigured middlewares using values from config
export const orchestrateLimiter = createFixedWindowLimiter({
windowSec: config.rateLimit.orchestrateWindowSec,
max: config.rateLimit.orchestrateMax,
})
export const apikeyLimiter = createFixedWindowLimiter({
windowSec: config.rateLimit.apikeyWindowSec,
max: config.rateLimit.apikeyMax,
})
// A default limiter for other high-risk routes if needed
export const defaultLimiter = createFixedWindowLimiter({
windowSec: config.rateLimit.defaultWindowSec,
max: config.rateLimit.defaultMax,
})