@@ -216,42 +216,73 @@ func sanitizeLocation(loc string) (string, error) {
216216}
217217
218218// ProcessImageUploadFromPath processes image upload parameters from path
219- func ProcessImageUploadFromPath (logger * zap.Logger , pathParams string , config * config.Config ) (bool , int , error , * ImageContext ) {
219+ // Validation: Either validate token OR if location and signature provided, validate signature
220+ func ProcessImageUploadFromPath (logger * zap.Logger , pathParams string , config * config.Config ) (bool , int , * ImageContext , error ) {
220221 params , err := ParsePathParams (pathParams )
221222 if err != nil {
222- return false , fiber .StatusBadRequest , fmt .Errorf ("invalid path parameters: %w" , err ), nil
223+ return false , fiber .StatusBadRequest , nil , fmt .Errorf ("invalid path parameters: %w" , err )
223224 }
224225
225- if params .Token != config .Token {
226- return false , fiber .StatusForbidden , fmt .Errorf ("invalid token" ), nil
226+ // Handle optional S3 location with signature validation (if provided, use signature validation instead of token)
227+ var customObjectKey string
228+ if params .Location != "" && params .Signature != "" {
229+ // Signature validation mode for S3 upload
230+ if config .HmacKey == "" {
231+ return false , fiber .StatusInternalServerError , nil , fmt .Errorf ("hmac key not configured" )
232+ }
233+
234+ // Decode location
235+ decodedLocation , err := DecodeBase64URL (params .Location )
236+ if err != nil {
237+ return false , fiber .StatusBadRequest , nil , fmt .Errorf ("invalid location encoding: %w" , err )
238+ }
239+
240+ // Sanitize location
241+ sanitized , err := sanitizeLocation (decodedLocation )
242+ if err != nil {
243+ return false , fiber .StatusBadRequest , nil , fmt .Errorf ("invalid location: %w" , err )
244+ }
245+
246+ // Validate signature: HMAC(location)
247+ if ! compareHmacForMessage (sanitized , params .Signature , config .HmacKey ) {
248+ return false , fiber .StatusForbidden , nil , fmt .Errorf ("invalid signature" )
249+ }
250+
251+ customObjectKey = sanitized
252+ } else {
253+ // Token validation mode (default)
254+ if params .Token != config .Token {
255+ return false , fiber .StatusForbidden , nil , fmt .Errorf ("invalid token" )
256+ }
227257 }
228258
229259 if params .Quality < 1 || params .Quality > 100 {
230- return false , fiber .StatusBadRequest , fmt .Errorf ("quality must be between 1 and 100" ), nil
260+ return false , fiber .StatusBadRequest , nil , fmt .Errorf ("quality must be between 1 and 100" )
231261 }
232262
233263 if params .Width > 0 && params .Height > 0 && (params .Width < 1 || params .Height < 1 ) {
234- return false , fiber .StatusBadRequest , fmt .Errorf ("width and height must be greater than 0" ), nil
264+ return false , fiber .StatusBadRequest , nil , fmt .Errorf ("width and height must be greater than 0" )
235265 }
236266
237267 if params .Scale < 0 || params .Scale > 1 {
238- return false , fiber .StatusBadRequest , fmt .Errorf ("scale must be between 0 and 1" ), nil
268+ return false , fiber .StatusBadRequest , nil , fmt .Errorf ("scale must be between 0 and 1" )
239269 }
240270
241271 // Apply default webp setting if not specified
242272 if ! params .Webp && config .Webp {
243273 params .Webp = config .Webp
244274 }
245275
246- return true , fiber .StatusOK , nil , & ImageContext {
247- Quality : params .Quality ,
248- Width : params .Width ,
249- Height : params .Height ,
250- Scale : params .Scale ,
251- Interpolation : params .Interpolation ,
252- Webp : params .Webp ,
253- FramePosition : params .FramePosition ,
254- }
276+ return true , fiber .StatusOK , & ImageContext {
277+ Quality : params .Quality ,
278+ Width : params .Width ,
279+ Height : params .Height ,
280+ Scale : params .Scale ,
281+ Interpolation : params .Interpolation ,
282+ Webp : params .Webp ,
283+ FramePosition : params .FramePosition ,
284+ CustomObjectKey : customObjectKey ,
285+ }, nil
255286}
256287
257288func ProcessImageUpload (logger * zap.Logger , c * fiber.Ctx , config * config.Config ) (ok bool , status int , err error , params * ImageContext ) {
@@ -294,35 +325,35 @@ func ProcessImageUpload(logger *zap.Logger, c *fiber.Ctx, config *config.Config)
294325}
295326
296327// ProcessImageContextFromPath processes image context from path parameters
297- func ProcessImageContextFromPath (logger * zap.Logger , pathParams string , config * config.Config ) (bool , int , error , * ImageContext ) {
328+ func ProcessImageContextFromPath (logger * zap.Logger , pathParams string , config * config.Config ) (bool , int , * ImageContext , error ) {
298329 params , err := ParsePathParams (pathParams )
299330 if err != nil {
300- return false , fiber .StatusBadRequest , fmt .Errorf ("invalid path parameters: %w" , err ), nil
331+ return false , fiber .StatusBadRequest , nil , fmt .Errorf ("invalid path parameters: %w" , err )
301332 }
302333
303334 // URL is optional if location is provided
304335 urlParam := ""
305336 if params .EncodedURL != "" {
306337 urlParam , err = DecodeURL (params .EncodedURL )
307338 if err != nil {
308- return false , fiber .StatusBadRequest , fmt .Errorf ("failed to decode URL: %w" , err ), nil
339+ return false , fiber .StatusBadRequest , nil , fmt .Errorf ("failed to decode URL: %w" , err )
309340 }
310341 }
311342
312343 // If custom location is requested, require signature and validate location
313344 customObjectKey := ""
314345 if params .Location != "" {
315346 if config .HmacKey == "" || params .Signature == "" {
316- return false , fiber .StatusForbidden , fmt .Errorf ("signature required for custom location" ), nil
347+ return false , fiber .StatusForbidden , nil , fmt .Errorf ("signature required for custom location" )
317348 }
318349 // Expect location to be base64 URL-safe encoded
319350 decodedLocation , derr := DecodeBase64URL (params .Location )
320351 if derr != nil {
321- return false , fiber .StatusBadRequest , derr , nil
352+ return false , fiber .StatusBadRequest , nil , derr
322353 }
323354 sanitized , serr := sanitizeLocation (decodedLocation )
324355 if serr != nil {
325- return false , fiber .StatusBadRequest , serr , nil
356+ return false , fiber .StatusBadRequest , nil , serr
326357 }
327358
328359 // If URL is provided, sign URL|location, otherwise just sign location
@@ -334,23 +365,23 @@ func ProcessImageContextFromPath(logger *zap.Logger, pathParams string, config *
334365 }
335366
336367 if ! compareHmacForMessage (signedMsg , params .Signature , config .HmacKey ) {
337- return false , fiber .StatusForbidden , fmt .Errorf ("invalid signature for location" ), nil
368+ return false , fiber .StatusForbidden , nil , fmt .Errorf ("invalid signature for location" )
338369 }
339370 customObjectKey = sanitized
340371 } else if params .Signature != "" { // normal signature over URL only
341372 if config .HmacKey == "" {
342- return false , fiber .StatusForbidden , fmt .Errorf ("hmac key is not set" ), nil
373+ return false , fiber .StatusForbidden , nil , fmt .Errorf ("hmac key is not set" )
343374 }
344375 if urlParam == "" {
345- return false , fiber .StatusBadRequest , fmt .Errorf ("url is required when signature is provided without location" ), nil
376+ return false , fiber .StatusBadRequest , nil , fmt .Errorf ("url is required when signature is provided without location" )
346377 }
347378 if ! compareHmac (urlParam , params .Signature , config .HmacKey ) {
348- return false , fiber .StatusForbidden , fmt .Errorf ("invalid signature" ), nil
379+ return false , fiber .StatusForbidden , nil , fmt .Errorf ("invalid signature" )
349380 }
350381 } else {
351382 // Neither location nor signature provided, URL is required
352383 if urlParam == "" {
353- return false , fiber .StatusBadRequest , fmt .Errorf ("url or location is required" ), nil
384+ return false , fiber .StatusBadRequest , nil , fmt .Errorf ("url or location is required" )
354385 }
355386 }
356387
@@ -359,29 +390,29 @@ func ProcessImageContextFromPath(logger *zap.Logger, pathParams string, config *
359390 if urlParam != "" {
360391 validOrigin , validHostname := pool .ValidateUrl (logger , urlParam , config .AllowedOrigins )
361392 if ! validOrigin {
362- return false , fiber .StatusForbidden , fmt .Errorf ("url is not allowed" ), nil
393+ return false , fiber .StatusForbidden , nil , fmt .Errorf ("url is not allowed" )
363394 }
364395 hostname = validHostname
365396 }
366397
367398 if params .Quality < 1 || params .Quality > 100 {
368- return false , fiber .StatusBadRequest , fmt .Errorf ("quality must be between 1 and 100" ), nil
399+ return false , fiber .StatusBadRequest , nil , fmt .Errorf ("quality must be between 1 and 100" )
369400 }
370401
371402 if params .Width > 0 && params .Height > 0 && (params .Width < 1 || params .Height < 1 ) {
372- return false , fiber .StatusBadRequest , fmt .Errorf ("width and height must be greater than 0" ), nil
403+ return false , fiber .StatusBadRequest , nil , fmt .Errorf ("width and height must be greater than 0" )
373404 }
374405
375406 if params .Scale < 0 || params .Scale > 1 {
376- return false , fiber .StatusBadRequest , fmt .Errorf ("scale must be between 0 and 1" ), nil
407+ return false , fiber .StatusBadRequest , nil , fmt .Errorf ("scale must be between 0 and 1" )
377408 }
378409
379410 // Apply default webp setting if not specified
380411 if ! params .Webp && config .Webp {
381412 params .Webp = config .Webp
382413 }
383414
384- return true , fiber .StatusOK , nil , & ImageContext {
415+ return true , fiber .StatusOK , & ImageContext {
385416 Url : urlParam ,
386417 Quality : params .Quality ,
387418 Width : params .Width ,
@@ -392,7 +423,7 @@ func ProcessImageContextFromPath(logger *zap.Logger, pathParams string, config *
392423 FramePosition : params .FramePosition ,
393424 Hostname : hostname ,
394425 CustomObjectKey : customObjectKey ,
395- }
426+ }, nil
396427}
397428
398429func ProcessImageContext (logger * zap.Logger , c * fiber.Ctx , config * config.Config ) (ok bool , status int , err error , params * ImageContext ) {
0 commit comments