@@ -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