Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
67 changes: 30 additions & 37 deletions modules/sdk-hmac/src/hmac.ts
Original file line number Diff line number Diff line change
Expand Up @@ -69,15 +69,11 @@ export function calculateHMACSubject<T extends string | Buffer = string>(
/**
* Calculate the HMAC for an HTTP request
*/
export function calculateRequestHMAC<T extends string | Buffer = string>({
url: urlPath,
text,
timestamp,
token,
method,
authVersion,
}: CalculateRequestHmacOptions<T>): string {
const signatureSubject = calculateHMACSubject({ urlPath, text, timestamp, method, authVersion });
export function calculateRequestHMAC<T extends string | Buffer = string>(
{ url: urlPath, text, timestamp, token, method, authVersion }: CalculateRequestHmacOptions<T>,
useOriginalPath = false
): string {
const signatureSubject = calculateHMACSubject({ urlPath, text, timestamp, method, authVersion }, useOriginalPath);

// calculate the HMAC
return calculateHMAC(token, signatureSubject);
Expand All @@ -86,15 +82,12 @@ export function calculateRequestHMAC<T extends string | Buffer = string>({
/**
* Calculate request headers with HMAC
*/
export function calculateRequestHeaders<T extends string | Buffer = string>({
url,
text,
token,
method,
authVersion,
}: CalculateRequestHeadersOptions<T>): RequestHeaders {
export function calculateRequestHeaders<T extends string | Buffer = string>(
{ url, text, token, method, authVersion }: CalculateRequestHeadersOptions<T>,
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);
Expand All @@ -109,31 +102,31 @@ export function calculateRequestHeaders<T extends string | Buffer = string>({
/**
* Verify the HMAC for an HTTP response
*/
export function verifyResponse<T extends string | Buffer = string>({
url: urlPath,
statusCode,
text,
timestamp,
token,
hmac,
method,
authVersion,
}: VerifyResponseOptions<T>): VerifyResponseInfo<T> {
const signatureSubject = calculateHMACSubject({
urlPath,
text,
timestamp,
statusCode,
method,
authVersion,
});
export function verifyResponse<T extends string | Buffer = string>(
{ url: urlPath, statusCode, text, timestamp, token, hmac, method, authVersion }: VerifyResponseOptions<T>,
useOriginalPath = false
): VerifyResponseInfo<T> {
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 {
Expand Down
35 changes: 34 additions & 1 deletion modules/sdk-hmac/test/hmac.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -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');

Expand Down