Skip to content

Commit 01f3ecf

Browse files
committed
fix: failing test
1 parent f901e1d commit 01f3ecf

File tree

1 file changed

+26
-66
lines changed

1 file changed

+26
-66
lines changed

src/runtime/server/middleware/shield.ts

Lines changed: 26 additions & 66 deletions
Original file line numberDiff line numberDiff line change
@@ -23,11 +23,7 @@ export default defineEventHandler(async (event) => {
2323
// );
2424

2525
const shieldStorage = useStorage('shield')
26-
// Determine whether to trust X-Forwarded-For header based on config.
27-
// The module's default for `config.security.trustXForwardedFor` is true.
28-
const trustXForwardedFor = config.security?.trustXForwardedFor;
29-
30-
const requestIP = getRequestIP(event, { xForwardedFor: trustXForwardedFor }) || 'unKnownIP'
26+
const requestIP = getRequestIP(event, { xForwardedFor: true }) || 'unKnownIP'
3127

3228
const banKey = `ban:${requestIP}`
3329
const bannedUntilRaw = await shieldStorage.getItem(banKey)
@@ -43,79 +39,43 @@ export default defineEventHandler(async (event) => {
4339
})
4440
}
4541
else if (bannedUntilRaw && !Number.isNaN(bannedUntil) && Date.now() >= bannedUntil) {
46-
// Ban expired, remove ban and reset IP count
4742
await shieldStorage.removeItem(banKey)
48-
// When a ban expires, we reset the IP's rate limit data as if it's a new IP.
49-
// This ensures that the count doesn't carry over from before the ban.
50-
await shieldStorage.setItem(`ip:${requestIP}`, { count: 1, time: Date.now() })
51-
// Note: The request that triggers this ban removal is counted as the first request in the new window.
52-
// Thus, we don't 'return' here but proceed to process this request.
43+
await shieldStorage.setItem(`ip:${requestIP}`, {
44+
count: 1,
45+
time: Date.now(),
46+
})
5347
}
5448

55-
// IP Rate Limiting Logic
56-
const ipKey = `ip:${requestIP}`
57-
let currentRateLimitData = (await shieldStorage.getItem(ipKey)) as RateLimit | null
58-
const currentTime = Date.now()
59-
60-
if (!currentRateLimitData) {
61-
// First request from this IP (or first after a ban was just cleared above)
62-
currentRateLimitData = { count: 1, time: currentTime }
63-
}
64-
else {
65-
// Existing IP, check if window needs reset
66-
if ((currentTime - currentRateLimitData.time) / 1000 >= config.limit.duration) {
67-
// Window expired, reset
68-
currentRateLimitData.count = 1
69-
currentRateLimitData.time = currentTime
70-
}
71-
else {
72-
// Window still active, increment count
73-
currentRateLimitData.count++
74-
}
49+
if (!(await shieldStorage.hasItem(`ip:${requestIP}`))) {
50+
// console.log(" IP not found in storage, setting initial count.", requestIP);
51+
return await shieldStorage.setItem(`ip:${requestIP}`, {
52+
count: 1,
53+
time: Date.now(),
54+
})
7555
}
7656

77-
const req = currentRateLimitData // This is the state *after* accounting for the current request
78-
79-
shieldLog(req, requestIP, url.toString()) // Log the current state
80-
81-
if (isRateLimited(req)) { // isRateLimited now checks if this current state (req) warrants a ban
82-
const banUntilTimestamp = Date.now() + config.limit.ban * 1000
83-
await shieldStorage.setItem(banKey, banUntilTimestamp)
84-
// When a user is banned, their IP tracking data should also be reset
85-
// so that after the ban, they start with a fresh count.
86-
await shieldStorage.setItem(ipKey, { count: 1, time: Date.now() })
57+
const req = (await shieldStorage.getItem(`ip:${requestIP}`)) as RateLimit
58+
req.count++
59+
// console.log(` Set count for IP ${requestIP}: ${req.count}`);
8760

88-
if (config.retryAfterHeader) {
89-
event.node.res.setHeader('Retry-After', config.limit.ban) // Duration of ban in seconds
90-
}
61+
shieldLog(req, requestIP, url.toString())
9162

92-
// console.error("Throwing 429 error due to rate limit ban");
93-
throw createError({
94-
statusCode: 429,
95-
message: config.errorMessage,
96-
})
97-
}
98-
else {
99-
// Not banned, just update the storage with the new count/time
100-
await shieldStorage.setItem(ipKey, {
63+
if (!isRateLimited(req)) {
64+
// console.log(" Request not rate-limited, updating storage.");
65+
return await shieldStorage.setItem(`ip:${requestIP}`, {
10166
count: req.count,
10267
time: req.time,
10368
})
104-
// Request is allowed, proceed to the actual API handler
105-
return
10669
}
107-
})
10870

109-
const isRateLimited = (req: RateLimit) => { // req here is the state *after* current request is counted
110-
const options = useRuntimeConfig().public.nuxtApiShield
111-
// If count is over limit AND it happened within the duration since req.time
112-
if (req.count > options.limit.max) {
113-
// Check if these excess requests occurred within the defined duration
114-
// from the start of the current window (req.time)
115-
return (Date.now() - req.time) / 1000 < options.limit.duration
116-
}
117-
return false // Count is not over max, so not rate limited
118-
}
71+
const banUntil = Date.now() + config.limit.ban * 1000
72+
await shieldStorage.setItem(banKey, banUntil)
73+
await shieldStorage.setItem(`ip:${requestIP}`, {
74+
count: 1,
75+
time: Date.now(),
76+
})
77+
78+
if (config.retryAfterHeader) {
11979
event.node.res.setHeader('Retry-After', config.limit.ban)
12080
}
12181

0 commit comments

Comments
 (0)