11const async = require ( 'async' ) ;
22const { parseString } = require ( 'xml2js' ) ;
3- const { errorInstances, errors } = require ( 'arsenal' ) ;
3+ const { errorInstances, errors, models } = require ( 'arsenal' ) ;
44
55const collectCorsHeaders = require ( '../utilities/collectCorsHeaders' ) ;
66const metadata = require ( '../metadata/wrapper' ) ;
7- const { standardMetadataValidateBucket } = require ( '../metadata/metadataUtils' ) ;
87const { isRateLimitServiceUser } = require ( './apiUtils/authorization/serviceUser' ) ;
8+ const { config } = require ( '../Config' ) ;
9+
10+ const RateLimitConfiguration = models . RateLimitConfiguration ;
911
1012function parseRequestBody ( requestBody , callback ) {
13+ // Try JSON first
14+ let jsonData ;
1115 try {
12- const jsonData = JSON . parse ( requestBody ) ;
16+ jsonData = JSON . parse ( requestBody ) ;
1317 if ( typeof jsonData !== 'object' ) {
14- throw new Error ( 'Invalid JSON' ) ;
18+ throw new Error ( 'Invalid JSON - not an object ' ) ;
1519 }
20+ // JSON succeeded - return immediately, do NOT try XML
1621 return callback ( null , jsonData ) ;
17- } catch {
18- return parseString ( requestBody , ( xmlError , xmlData ) => {
22+ } catch ( jsonError ) {
23+ // JSON failed - try XML
24+ parseString ( requestBody , ( xmlError , xmlData ) => {
1925 if ( xmlError ) {
2026 return callback ( errorInstances . InvalidArgument
2127 . customizeDescription ( 'Request body must be a JSON object' ) ) ;
@@ -25,15 +31,30 @@ function parseRequestBody(requestBody, callback) {
2531 }
2632}
2733
28- function validateRateLimitConfig ( config , callback ) {
29- const limit = parseInt ( config . RequestsPerSecond , 10 ) ;
34+ function validateRateLimitConfig ( requestConfig , callback ) {
35+ const limit = parseInt ( requestConfig . RequestsPerSecond , 10 ) ;
36+
37+ // Validate positive integer
3038 if ( Number . isNaN ( limit ) || ! Number . isInteger ( limit ) || limit <= 0 ) {
3139 return callback ( errorInstances . InvalidArgument
3240 . customizeDescription ( 'RequestsPerSecond must be a positive integer' ) ) ;
3341 }
34- return callback ( null , {
35- RequestsPerSecond : limit ,
42+
43+ // Validate minimum rate limit (must be >= number of nodes)
44+ const nodeCount = config . rateLimiting ?. nodeCount || 1 ;
45+ if ( limit < nodeCount ) {
46+ return callback ( errorInstances . InvalidArgument
47+ . customizeDescription (
48+ `RequestsPerSecond must be >= ${ nodeCount } (number of CloudServer nodes)`
49+ ) ) ;
50+ }
51+
52+ // Create RateLimitConfiguration model with flattened structure
53+ const rateLimitConfig = new RateLimitConfiguration ( {
54+ RequestsPerSecond : limit ,
3655 } ) ;
56+
57+ return callback ( null , rateLimitConfig ) ;
3758}
3859
3960/**
@@ -47,7 +68,16 @@ function validateRateLimitConfig(config, callback) {
4768function bucketPutRateLimit ( authInfo , request , log , callback ) {
4869 log . debug ( 'processing request' , { method : 'bucketPutRateLimit' } ) ;
4970
50- if ( ! isRateLimitServiceUser ( authInfo ) ) {
71+ const requestArn = authInfo . getArn ( ) ;
72+ const configArn = config . rateLimiting ?. serviceUserArn ;
73+ log . debug ( 'ARN comparison for rate limit authorization' , {
74+ requestArn,
75+ configArn,
76+ match : requestArn === configArn ,
77+ } ) ;
78+
79+ if ( ! isRateLimitServiceUser ( authInfo , log ) ) {
80+ log . warn ( 'Access denied - ARN mismatch' , { requestArn, configArn } ) ;
5181 return callback ( errors . AccessDenied ) ;
5282 }
5383
@@ -69,11 +99,9 @@ function bucketPutRateLimit(authInfo, request, log, callback) {
6999 }
70100 return next ( null , bucket , limitConfig ) ;
71101 } ) ,
72- ( bucket , limitConfig , next ) => {
73- bucket . setRateLimitConfig ( limitConfig ) ;
74- metadata . updateBucket ( bucket . getName ( ) , bucket , log ,
75- err => next ( err , bucket ) ) ;
76- } ,
102+ ( bucket , limitConfig , next ) => bucket . setRateLimitConfiguration ( limitConfig )
103+ . then ( ( ) => metadata . updateBucket ( bucket . getName ( ) , bucket , log ,
104+ err => next ( err , bucket ) ) ) ,
77105 ] , ( err , bucket ) => {
78106 const corsHeaders = collectCorsHeaders ( request . headers . origin ,
79107 request . method , bucket ) ;
0 commit comments