diff --git a/modules/sdk-hmac/src/hmac.ts b/modules/sdk-hmac/src/hmac.ts index 2de379b0ae..993ba94c73 100644 --- a/modules/sdk-hmac/src/hmac.ts +++ b/modules/sdk-hmac/src/hmac.ts @@ -69,15 +69,11 @@ export function calculateHMACSubject( /** * Calculate the HMAC for an HTTP request */ -export function calculateRequestHMAC({ - url: urlPath, - text, - timestamp, - token, - method, - authVersion, -}: CalculateRequestHmacOptions): string { - const signatureSubject = calculateHMACSubject({ urlPath, text, timestamp, method, authVersion }); +export function calculateRequestHMAC( + { url: urlPath, text, timestamp, token, method, authVersion }: CalculateRequestHmacOptions, + useOriginalPath = false +): string { + const signatureSubject = calculateHMACSubject({ urlPath, text, timestamp, method, authVersion }, useOriginalPath); // calculate the HMAC return calculateHMAC(token, signatureSubject); @@ -86,15 +82,12 @@ export function calculateRequestHMAC({ /** * Calculate request headers with HMAC */ -export function calculateRequestHeaders({ - url, - text, - token, - method, - authVersion, -}: CalculateRequestHeadersOptions): RequestHeaders { +export function calculateRequestHeaders( + { url, text, token, method, authVersion }: CalculateRequestHeadersOptions, + useOriginalPath = false +): RequestHeaders { const timestamp = Date.now(); - const hmac = calculateRequestHMAC({ url, text, timestamp, token, method, authVersion }); + const hmac = calculateRequestHMAC({ url, text, timestamp, token, method, authVersion }, useOriginalPath); // calculate the SHA256 hash of the token const hashDigest = sjcl.hash.sha256.hash(token); @@ -109,31 +102,31 @@ export function calculateRequestHeaders({ /** * Verify the HMAC for an HTTP response */ -export function verifyResponse({ - url: urlPath, - statusCode, - text, - timestamp, - token, - hmac, - method, - authVersion, -}: VerifyResponseOptions): VerifyResponseInfo { - const signatureSubject = calculateHMACSubject({ - urlPath, - text, - timestamp, - statusCode, - method, - authVersion, - }); +export function verifyResponse( + { url: urlPath, statusCode, text, timestamp, token, hmac, method, authVersion }: VerifyResponseOptions, + useOriginalPath = false +): VerifyResponseInfo { + const signatureSubject = calculateHMACSubject( + { + urlPath, + text, + timestamp, + statusCode, + method, + authVersion, + }, + useOriginalPath + ); // calculate the HMAC const expectedHmac = calculateHMAC(token, signatureSubject); - // determine if the response is still within the validity window (5 minute window) + // determine if the response is still within the validity window (5-minute backwards window, 1-minute forward window) const now = Date.now(); - const isInResponseValidityWindow = timestamp >= now - 1000 * 60 * 5 && timestamp <= now; + const backwardValidityWindow = 1000 * 60 * 5; + const forwardValidityWindow = 1000 * 60; + const isInResponseValidityWindow = + timestamp >= now - backwardValidityWindow && timestamp <= now + forwardValidityWindow; // verify the HMAC and timestamp return { diff --git a/modules/sdk-hmac/test/hmac.ts b/modules/sdk-hmac/test/hmac.ts index fb2d6a009a..65d9e080aa 100644 --- a/modules/sdk-hmac/test/hmac.ts +++ b/modules/sdk-hmac/test/hmac.ts @@ -249,7 +249,7 @@ describe('HMAC Utility Functions', () => { expect(result.isValid).to.be.false; }); - it('should return invalid if timestamp is outside the validity window', () => { + it('should return invalid if timestamp is outside the validity window (backwards check)', () => { const result = verifyResponse({ url: '/api/test', statusCode: 200, @@ -264,6 +264,39 @@ describe('HMAC Utility Functions', () => { expect(result.isInResponseValidityWindow).to.be.false; }); + it('should return invalid if timestamp is outside the validity window (forwards check)', () => { + const result = verifyResponse({ + url: '/api/test', + statusCode: 200, + text: 'response-body', + timestamp: MOCK_TIMESTAMP + 1000 * 60 * 2, // 2 minutes in the future + token: 'test-token', + hmac: '8f6a2d183e4c4f2bd2023202486e1651292c84573a31b3829d394f1763a6ec6c', + method: 'post', + authVersion: 3, + }); + + expect(result.isInResponseValidityWindow).to.be.false; + }); + + it('should verify if timestamp is inside the forward validity window', () => { + const result = verifyResponse({ + url: '/api/test', + statusCode: 200, + text: 'response-body', + timestamp: MOCK_TIMESTAMP + 1000 * 30, // 30 seconds in the future + token: 'test-token', + hmac: '5e08c494691951ee45a6fc0e3fbfce3a76fcb5b9ee37e9bf9f2ac66690466dc7', + method: 'post', + authVersion: 3, + }); + + expect(result).to.include({ + isValid: true, + isInResponseValidityWindow: true, + }); + }); + it('should verify response with Buffer data', () => { const responseData = Buffer.from('binary-response-data');