@@ -1027,6 +1027,122 @@ describe('rate limit', () => {
10271027 } ) ;
10281028
10291029 describe ( 'batch method bypass' , ( ) => {
1030+ it ( 'should use IP-based keying for batch login sub-requests with session zone' , async ( ) => {
1031+ await reconfigureServer ( {
1032+ rateLimit : [
1033+ {
1034+ requestPath : '/login' ,
1035+ requestTimeWindow : 10000 ,
1036+ requestCount : 1 ,
1037+ errorResponseMessage : 'Too many requests' ,
1038+ includeInternalRequests : true ,
1039+ zone : Parse . Server . RateLimitZone . session ,
1040+ } ,
1041+ ] ,
1042+ } ) ;
1043+ // Create two users and get their session tokens
1044+ const res1 = await request ( {
1045+ method : 'POST' ,
1046+ headers,
1047+ url : 'http://localhost:8378/1/users' ,
1048+ body : JSON . stringify ( { username : 'user1' , password : 'password1' } ) ,
1049+ } ) ;
1050+ const sessionToken1 = res1 . data . sessionToken ;
1051+ const res2 = await request ( {
1052+ method : 'POST' ,
1053+ headers,
1054+ url : 'http://localhost:8378/1/users' ,
1055+ body : JSON . stringify ( { username : 'user2' , password : 'password2' } ) ,
1056+ } ) ;
1057+ const sessionToken2 = res2 . data . sessionToken ;
1058+ // First batch login with TOKEN1 — should succeed
1059+ const batch1 = await request ( {
1060+ method : 'POST' ,
1061+ headers : { ...headers , 'X-Parse-Session-Token' : sessionToken1 } ,
1062+ url : 'http://localhost:8378/1/batch' ,
1063+ body : JSON . stringify ( {
1064+ requests : [
1065+ { method : 'POST' , path : '/1/login' , body : { username : 'user1' , password : 'password1' } } ,
1066+ ] ,
1067+ } ) ,
1068+ } ) ;
1069+ expect ( batch1 . status ) . toBe ( 200 ) ;
1070+ // Second batch login with TOKEN2 — should be rate limited because
1071+ // login rate limit must use IP-based keying, not session-token keying;
1072+ // rotating session tokens must not create independent rate limit counters
1073+ const batch2 = await request ( {
1074+ method : 'POST' ,
1075+ headers : { ...headers , 'X-Parse-Session-Token' : sessionToken2 } ,
1076+ url : 'http://localhost:8378/1/batch' ,
1077+ body : JSON . stringify ( {
1078+ requests : [
1079+ { method : 'POST' , path : '/1/login' , body : { username : 'user1' , password : 'password1' } } ,
1080+ ] ,
1081+ } ) ,
1082+ } ) . catch ( e => e ) ;
1083+ expect ( batch2 . data ) . toEqual ( {
1084+ code : Parse . Error . CONNECTION_FAILED ,
1085+ error : 'Too many requests' ,
1086+ } ) ;
1087+ } ) ;
1088+
1089+ it ( 'should use IP-based keying for batch login sub-requests with user zone' , async ( ) => {
1090+ await reconfigureServer ( {
1091+ rateLimit : [
1092+ {
1093+ requestPath : '/login' ,
1094+ requestTimeWindow : 10000 ,
1095+ requestCount : 1 ,
1096+ errorResponseMessage : 'Too many requests' ,
1097+ includeInternalRequests : true ,
1098+ zone : Parse . Server . RateLimitZone . user ,
1099+ } ,
1100+ ] ,
1101+ } ) ;
1102+ // Create two users and get their session tokens
1103+ const res1 = await request ( {
1104+ method : 'POST' ,
1105+ headers,
1106+ url : 'http://localhost:8378/1/users' ,
1107+ body : JSON . stringify ( { username : 'user1' , password : 'password1' } ) ,
1108+ } ) ;
1109+ const sessionToken1 = res1 . data . sessionToken ;
1110+ const res2 = await request ( {
1111+ method : 'POST' ,
1112+ headers,
1113+ url : 'http://localhost:8378/1/users' ,
1114+ body : JSON . stringify ( { username : 'user2' , password : 'password2' } ) ,
1115+ } ) ;
1116+ const sessionToken2 = res2 . data . sessionToken ;
1117+ // First batch login with TOKEN1 — should succeed
1118+ const batch1 = await request ( {
1119+ method : 'POST' ,
1120+ headers : { ...headers , 'X-Parse-Session-Token' : sessionToken1 } ,
1121+ url : 'http://localhost:8378/1/batch' ,
1122+ body : JSON . stringify ( {
1123+ requests : [
1124+ { method : 'POST' , path : '/1/login' , body : { username : 'user1' , password : 'password1' } } ,
1125+ ] ,
1126+ } ) ,
1127+ } ) ;
1128+ expect ( batch1 . status ) . toBe ( 200 ) ;
1129+ // Second batch login with TOKEN2 — should be rate limited
1130+ const batch2 = await request ( {
1131+ method : 'POST' ,
1132+ headers : { ...headers , 'X-Parse-Session-Token' : sessionToken2 } ,
1133+ url : 'http://localhost:8378/1/batch' ,
1134+ body : JSON . stringify ( {
1135+ requests : [
1136+ { method : 'POST' , path : '/1/login' , body : { username : 'user1' , password : 'password1' } } ,
1137+ ] ,
1138+ } ) ,
1139+ } ) . catch ( e => e ) ;
1140+ expect ( batch2 . data ) . toEqual ( {
1141+ code : Parse . Error . CONNECTION_FAILED ,
1142+ error : 'Too many requests' ,
1143+ } ) ;
1144+ } ) ;
1145+
10301146 it ( 'should enforce POST rate limit on batch sub-requests using GET method for login' , async ( ) => {
10311147 Parse . Cloud . beforeLogin ( ( ) => { } , {
10321148 rateLimit : {
0 commit comments