1- import type { RateLimit } from '../types/RateLimit'
2- import shieldLog from '../utils/shieldLog'
1+ import shieldLog from '../utils/shieldLog.js'
32import {
43 createError ,
54 defineEventHandler ,
@@ -12,87 +11,86 @@ import {
1211export default defineEventHandler ( async ( event ) => {
1312 const config = useRuntimeConfig ( ) . public . nuxtApiShield
1413 const url = getRequestURL ( event )
15-
16- if ( ! url ?. pathname ?. startsWith ( '/api/' ) || ( config . routes ?. length && ! config . routes . some ( route => url . pathname ?. startsWith ( route ) ) ) ) {
14+ if (
15+ ! url ?. pathname ?. startsWith ( '/api/' )
16+ || ( config . routes ?. length
17+ && ! config . routes . some ( route => url . pathname ?. startsWith ( route ) ) )
18+ ) {
1719 return
1820 }
1921
20- // console.log(
21- // `👉 Handling request for URL: ${url} from IP: ${getRequestIP(event, { xForwardedFor: true }) || "unKnownIP"
22- // }`
23- // );
24-
2522 const shieldStorage = useStorage ( 'shield' )
2623 const requestIP = getRequestIP ( event , { xForwardedFor : true } ) || 'unKnownIP'
27-
2824 const banKey = `ban:${ requestIP } `
2925 const bannedUntilRaw = await shieldStorage . getItem ( banKey )
30- const bannedUntil = typeof bannedUntilRaw === 'number' ? bannedUntilRaw : Number ( bannedUntilRaw )
31- if ( bannedUntilRaw && ! Number . isNaN ( bannedUntil ) && Date . now ( ) < bannedUntil ) {
26+ const bannedUntil
27+ = typeof bannedUntilRaw === 'number'
28+ ? bannedUntilRaw
29+ : Number ( bannedUntilRaw )
30+
31+ // Check if the user is currently banned
32+ if (
33+ bannedUntilRaw
34+ && ! Number . isNaN ( bannedUntil )
35+ && Date . now ( ) < bannedUntil
36+ ) {
3237 if ( config . retryAfterHeader ) {
33- const retryAfter = Math . ceil ( ( bannedUntil - Date . now ( ) ) / 1000 )
38+ const retryAfter = Math . ceil ( ( bannedUntil - Date . now ( ) ) / 1e3 )
3439 event . node . res . setHeader ( 'Retry-After' , retryAfter )
3540 }
3641 throw createError ( {
3742 statusCode : 429 ,
3843 message : config . errorMessage ,
3944 } )
4045 }
41- else if ( bannedUntilRaw && ! Number . isNaN ( bannedUntil ) && Date . now ( ) >= bannedUntil ) {
46+ // Unban the user if the ban has expired
47+ else if (
48+ bannedUntilRaw
49+ && ! Number . isNaN ( bannedUntil )
50+ && Date . now ( ) >= bannedUntil
51+ ) {
4252 await shieldStorage . removeItem ( banKey )
43- await shieldStorage . setItem ( `ip:${ requestIP } ` , {
44- count : 1 ,
45- time : Date . now ( ) ,
46- } )
4753 }
4854
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 } ` , {
55+ const ipKey = `ip:${ requestIP } `
56+ const req = await shieldStorage . getItem ( ipKey )
57+ const now = Date . now ( )
58+
59+ // Check if a new request is outside the duration window
60+ if ( ! req || ( now - req . time ) / 1e3 >= config . limit . duration ) {
61+ // If no record exists, or the duration has expired, reset the counter and timestamp
62+ await shieldStorage . setItem ( ipKey , {
5263 count : 1 ,
53- time : Date . now ( ) ,
64+ time : now ,
5465 } )
66+ return
5567 }
5668
57- const req = ( await shieldStorage . getItem ( `ip: ${ requestIP } ` ) ) as RateLimit
69+ // Increment the counter for a request within the duration
5870 req . count ++
59- // console.log(` Set count for IP ${requestIP}: ${req.count}`);
60-
6171 shieldLog ( req , requestIP , url . toString ( ) )
6272
63- if ( ! isRateLimited ( req ) ) {
64- // console.log(" Request not rate-limited, updating storage.");
65- return await shieldStorage . setItem ( `ip:${ requestIP } ` , {
73+ // Check if the new count triggers a rate limit
74+ if ( req . count > config . limit . max ) {
75+ const banUntil = now + config . limit . ban * 1e3
76+ await shieldStorage . setItem ( banKey , banUntil )
77+ await shieldStorage . setItem ( ipKey , {
78+ count : 1 ,
79+ time : now ,
80+ } )
81+ if ( config . retryAfterHeader ) {
82+ event . node . res . setHeader ( 'Retry-After' , config . limit . ban )
83+ }
84+ throw createError ( {
85+ statusCode : 429 ,
86+ message : config . errorMessage ,
87+ } )
88+ }
89+ else {
90+ // Update the count for the current duration
91+ await shieldStorage . setItem ( ipKey , {
6692 count : req . count ,
6793 time : req . time ,
6894 } )
6995 }
70-
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 ) {
79- event . node . res . setHeader ( 'Retry-After' , config . limit . ban )
80- }
81-
82- // console.error("Throwing 429 error");
83- throw createError ( {
84- statusCode : 429 ,
85- message : config . errorMessage ,
86- } )
8796} )
88-
89- const isRateLimited = ( req : RateLimit ) => {
90- const options = useRuntimeConfig ( ) . public . nuxtApiShield
91-
92- // console.log(` count: ${req.count} <= limit: ${options.limit.max}`);
93- if ( req . count <= options . limit . max ) {
94- return false
95- }
96- // console.log(" ", (Date.now() - req.time) / 1000, "<", options.limit.duration);
97- return ( Date . now ( ) - req . time ) / 1000 < options . limit . duration
98- }
0 commit comments