@@ -5,6 +5,7 @@ const timeout = require("../timeout");
55const { cpSync, writeFileSync } = require ( "fs" ) ;
66
77const pathToApp = resolve ( __dirname , "../../sample-apps/nextjs-standalone" ) ;
8+ const testServerUrl = "http://localhost:5874" ;
89
910t . setTimeout ( 2 * 60 * 1000 ) ;
1011
@@ -100,6 +101,7 @@ t.test("it blocks in blocking mode", (t) => {
100101 t . match ( stdout , / S t a r t i n g a g e n t / ) ;
101102 t . match ( stderr , / Z e n h a s b l o c k e d a s h e l l i n j e c t i o n / ) ;
102103 t . match ( stderr , / Z e n h a s b l o c k e d a n S Q L i n j e c t i o n / ) ;
104+ t . notMatch ( stderr , / Z e n i s N O T p r o t e c t i n g y o u r a p p l i c a t i o n / ) ;
103105 }
104106 )
105107 . catch ( ( error ) => {
@@ -110,6 +112,85 @@ t.test("it blocks in blocking mode", (t) => {
110112 } ) ;
111113} ) ;
112114
115+ t . test ( "it rate limits requests" , async ( t ) => {
116+ const appResponse = await fetch ( `${ testServerUrl } /api/runtime/apps` , {
117+ method : "POST" ,
118+ signal : AbortSignal . timeout ( 5000 ) ,
119+ } ) ;
120+ const { token } = await appResponse . json ( ) ;
121+
122+ const configResponse = await fetch ( `${ testServerUrl } /api/runtime/config` , {
123+ method : "POST" ,
124+ headers : {
125+ "Content-Type" : "application/json" ,
126+ Authorization : token ,
127+ } ,
128+ body : JSON . stringify ( {
129+ endpoints : [
130+ {
131+ route : "/cats" ,
132+ method : "GET" ,
133+ forceProtectionOff : false ,
134+ rateLimiting : {
135+ enabled : true ,
136+ maxRequests : 1 ,
137+ windowSizeInMS : 60 * 1000 ,
138+ } ,
139+ } ,
140+ ] ,
141+ } ) ,
142+ signal : AbortSignal . timeout ( 5000 ) ,
143+ } ) ;
144+ t . equal ( configResponse . status , 200 ) ;
145+
146+ const server = spawn ( `node` , [ "-r" , "@aikidosec/firewall" , "server.js" ] , {
147+ env : {
148+ ...process . env ,
149+ AIKIDO_DEBUG : "true" ,
150+ AIKIDO_BLOCK : "true" ,
151+ AIKIDO_TOKEN : token ,
152+ AIKIDO_ENDPOINT : testServerUrl ,
153+ AIKIDO_REALTIME_ENDPOINT : testServerUrl ,
154+ PORT : "4003" ,
155+ } ,
156+ cwd : join ( pathToApp , ".next/standalone" ) ,
157+ } ) ;
158+
159+ server . on ( "error" , ( err ) => {
160+ t . fail ( err . message ) ;
161+ } ) ;
162+
163+ await timeout ( 5000 ) ;
164+
165+ try {
166+ const resp1 = await fetch ( "http://127.0.0.1:4003/cats" , {
167+ method : "GET" ,
168+ signal : AbortSignal . timeout ( 5000 ) ,
169+ headers : {
170+ "X-Forwarded-For" : "1.2.3.4" ,
171+ } ,
172+ } ) ;
173+ t . not ( resp1 . status , 429 , "first request should not be rate limited" ) ;
174+
175+ const resp2 = await fetch ( "http://127.0.0.1:4003/cats" , {
176+ method : "GET" ,
177+ signal : AbortSignal . timeout ( 5000 ) ,
178+ headers : {
179+ "X-Forwarded-For" : "1.2.3.4" ,
180+ } ,
181+ } ) ;
182+ t . equal ( resp2 . status , 429 , "second request should be rate limited" ) ;
183+ t . ok (
184+ resp2 . headers . get ( "retry-after" ) ,
185+ "Retry-After header should be present"
186+ ) ;
187+ } catch ( error ) {
188+ t . fail ( error . message ) ;
189+ } finally {
190+ server . kill ( ) ;
191+ }
192+ } ) ;
193+
113194t . test ( "it does not block in dry mode" , ( t ) => {
114195 const server = spawn ( `node` , [ "-r" , "@aikidosec/firewall" , "server.js" ] , {
115196 env : {
@@ -177,6 +258,7 @@ t.test("it does not block in dry mode", (t) => {
177258 t . match ( stdout , / S t a r t i n g a g e n t / ) ;
178259 t . notMatch ( stderr , / Z e n h a s b l o c k e d a s h e l l i n j e c t i o n / ) ;
179260 t . notMatch ( stderr , / Z e n h a s b l o c k e d a n S Q L i n j e c t i o n / ) ;
261+ t . notMatch ( stderr , / Z e n i s N O T p r o t e c t i n g y o u r a p p l i c a t i o n / ) ;
180262 }
181263 )
182264 . catch ( ( error ) => {
0 commit comments