From 931c3a60c413b5a833518a66106819395b101c87 Mon Sep 17 00:00:00 2001 From: Christos Labrou Date: Tue, 13 Feb 2024 10:36:28 +0200 Subject: [PATCH] PROD-35940 Expand isExpiredURL (#362) * expand isExpiredUrl to validate both types of S3 presigned and add unit tests --- src/initializers/joi.ts | 11 +++++++++++ test/initializers/joi.test.ts | 28 ++++++++++++++++++++++++++++ 2 files changed, 39 insertions(+) diff --git a/src/initializers/joi.ts b/src/initializers/joi.ts index 1a5f2a97..4294b9f7 100644 --- a/src/initializers/joi.ts +++ b/src/initializers/joi.ts @@ -34,6 +34,17 @@ export const isExpiredUrl = (val: string): boolean => { if (isNaN(expiresAt)) return false; return Math.round(Date.now() / 1000) > expiresAt; } + if (parsed.searchParams.has('X-Amz-Expires') && parsed.searchParams.has('X-Amz-Date')) { + const xAmzExpires = Number(parsed.searchParams.get('X-Amz-Expires')); + // If X-Amz-Expires is not a number do not validate + if (isNaN(xAmzExpires)) return false; + const xAmzDate = parsed.searchParams.get('X-Amz-Date'); + // The URL contains date in format YYYYMMDDTHHMMSSZ. We format it to ISO (YYYY-MM-DDTHH:MM:SSZ) + const formattedDate = `${xAmzDate!.slice(0, 4)}-${xAmzDate!.slice(4, 6)}-${xAmzDate!.slice(6, 8)}T${xAmzDate!.slice(9, 11)}:${xAmzDate!.slice(11, 13)}:${xAmzDate!.slice(13, 15)}Z`; + const expirationDate = new Date(formattedDate); + expirationDate.setTime(expirationDate.getTime() + xAmzExpires * 1000); + return Date.now() > expirationDate.getTime(); + } return false; } catch (e) { logger.error(`Failed to parse url: ${val}`, e); diff --git a/test/initializers/joi.test.ts b/test/initializers/joi.test.ts index 4595c9ba..fe716342 100644 --- a/test/initializers/joi.test.ts +++ b/test/initializers/joi.test.ts @@ -1,5 +1,7 @@ import should = require('should'); import Joi from '../../src/initializers/joi'; +import { isExpiredUrl } from '../../src/initializers/joi'; +import * as sinon from 'sinon'; describe('joi extensions', function () { describe('string', function () { @@ -231,4 +233,30 @@ describe('joi extensions', function () { Joi.objectId().validate('627b825fb99b51cc16df1b41').value.should.equal('627b825fb99b51cc16df1b41'); }); }); + + describe('isExpiredUrl', function () { + afterEach(() => { + sinon.restore(); + }); + + it('returns true if expired', function () { + const expirationDate = new Date('2023-01-08T10:00:00Z'); + sinon.useFakeTimers(new Date('2023-01-10T10:00:00Z')); + const isExpired = [ + isExpiredUrl(`https://bucket.s3.amazonaws.com?Expires=${(expirationDate.getTime() / 1000)}`), + isExpiredUrl('https://bucket.s3.amazonaws.com?X-Amz-Date=20230101T100000Z&X-Amz-Expires=604800') + ]; + isExpired.map((x) => x.should.be.true()); + }); + + it('returns false if not expired', function () { + const expirationDate = new Date('2023-01-08T10:00:00Z'); + sinon.useFakeTimers(new Date('2023-01-05T10:00:00Z')); + const isExpired = [ + isExpiredUrl(`https://bucket.s3.amazonaws.com?Expires=${(expirationDate.getTime() / 1000)}`), + isExpiredUrl('https://bucket.s3.amazonaws.com?X-Amz-Date=20230101T100000Z&X-Amz-Expires=604800') + ]; + isExpired.map((x) => x.should.be.false()); + }); + }); });