Skip to content

Commit d955751

Browse files
feat(sdk-hmac): added Buffer support for HMAC subject text
TICKET: ANT-1025
1 parent 3068f37 commit d955751

File tree

3 files changed

+96
-15
lines changed

3 files changed

+96
-15
lines changed

modules/sdk-hmac/src/hmac.ts

Lines changed: 31 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -21,13 +21,34 @@ export function calculateHMAC(key: string | BinaryLike | KeyObject, message: str
2121
}
2222

2323
/**
24-
* Calculate the subject string that is to be HMAC'ed for a HTTP request or response
24+
* Utility to join segments with '|' delimiter
25+
* Returns Buffer when text segment is Buffer, string otherwise
26+
*/
27+
function joinSegments(segments: Array<string | number | Buffer>): string | Buffer {
28+
const hasBuffer = segments.some((s) => Buffer.isBuffer(s));
29+
30+
if (hasBuffer) {
31+
const parts: Buffer[] = [];
32+
const sep = Buffer.from('|', 'utf8');
33+
for (let i = 0; i < segments.length; i++) {
34+
if (i > 0) parts.push(sep);
35+
const seg = segments[i];
36+
parts.push(Buffer.isBuffer(seg) ? seg : Buffer.from(String(seg), 'utf8'));
37+
}
38+
return Buffer.concat(parts);
39+
}
40+
41+
return segments.join('|');
42+
}
43+
44+
/**
45+
* Calculate the subject that is to be HMAC'ed for a HTTP request or response
2546
* @param urlPath request url, including query params
26-
* @param text request body text
47+
* @param text request body text (string or Buffer)
2748
* @param timestamp request timestamp from `Date.now()`
2849
* @param statusCode Only set for HTTP responses, leave blank for requests
2950
* @param method request method
30-
* @returns {string}
51+
* @returns {string | Buffer} - returns Buffer when text is Buffer, string otherwise
3152
*/
3253
export function calculateHMACSubject({
3354
urlPath,
@@ -36,23 +57,24 @@ export function calculateHMACSubject({
3657
statusCode,
3758
method,
3859
authVersion,
39-
}: CalculateHmacSubjectOptions): string {
60+
}: CalculateHmacSubjectOptions): string | Buffer {
4061
/* Normalize legacy 'del' to 'delete' for backward compatibility */
4162
if (method === 'del') {
4263
method = 'delete';
4364
}
4465
const urlDetails = urlLib.parse(urlPath);
45-
const queryPath = urlDetails.query && urlDetails.query.length > 0 ? urlDetails.path : urlDetails.pathname;
66+
const queryPath = (urlDetails.query && urlDetails.query.length > 0 ? urlDetails.path : urlDetails.pathname) || '';
67+
4668
if (statusCode !== undefined && isFinite(statusCode) && Number.isInteger(statusCode)) {
4769
if (authVersion === 3) {
48-
return [method.toUpperCase(), timestamp, queryPath, statusCode, text].join('|');
70+
return joinSegments([method.toUpperCase(), timestamp, queryPath, statusCode, text]);
4971
}
50-
return [timestamp, queryPath, statusCode, text].join('|');
72+
return joinSegments([timestamp, queryPath, statusCode, text]);
5173
}
5274
if (authVersion === 3) {
53-
return [method.toUpperCase(), timestamp, '3.0', queryPath, text].join('|');
75+
return joinSegments([method.toUpperCase(), timestamp, '3.0', queryPath, text]);
5476
}
55-
return [timestamp, queryPath, text].join('|');
77+
return joinSegments([timestamp, queryPath, text]);
5678
}
5779

5880
/**

modules/sdk-hmac/src/types.ts

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ export type AuthVersion = 2 | 3;
44

55
export interface CalculateHmacSubjectOptions {
66
urlPath: string;
7-
text: string;
7+
text: string | Buffer;
88
timestamp: number;
99
method: (typeof supportedRequestMethods)[number];
1010
statusCode?: number;
@@ -13,7 +13,7 @@ export interface CalculateHmacSubjectOptions {
1313

1414
export interface CalculateRequestHmacOptions {
1515
url: string;
16-
text: string;
16+
text: string | Buffer;
1717
timestamp: number;
1818
token: string;
1919
method: (typeof supportedRequestMethods)[number];
@@ -22,7 +22,7 @@ export interface CalculateRequestHmacOptions {
2222

2323
export interface CalculateRequestHeadersOptions {
2424
url: string;
25-
text: string;
25+
text: string | Buffer;
2626
token: string;
2727
method: (typeof supportedRequestMethods)[number];
2828
authVersion: AuthVersion;
@@ -37,7 +37,7 @@ export interface RequestHeaders {
3737
export interface VerifyResponseOptions extends CalculateRequestHeadersOptions {
3838
hmac: string;
3939
url: string;
40-
text: string;
40+
text: string | Buffer;
4141
timestamp: number;
4242
method: (typeof supportedRequestMethods)[number];
4343
statusCode?: number;
@@ -47,7 +47,7 @@ export interface VerifyResponseOptions extends CalculateRequestHeadersOptions {
4747
export interface VerifyResponseInfo {
4848
isValid: boolean;
4949
expectedHmac: string;
50-
signatureSubject: string;
50+
signatureSubject: string | Buffer;
5151
isInResponseValidityWindow: boolean;
5252
verificationTime: number;
5353
}

modules/sdk-hmac/test/hmac.ts

Lines changed: 60 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,46 @@ describe('HMAC Utility Functions', () => {
7474
})
7575
).to.equal(expectedSubject);
7676
});
77+
78+
it('should return Buffer when text is Buffer (request)', () => {
79+
const expectedSubject = 'GET|1672531200000|3.0|/api/test?query=123|body-content';
80+
const result = calculateHMACSubject({
81+
urlPath: '/api/test?query=123',
82+
text: Buffer.from('body-content', 'utf8'),
83+
timestamp: MOCK_TIMESTAMP,
84+
method: 'get',
85+
authVersion: 3,
86+
});
87+
expect(Buffer.isBuffer(result)).to.be.true;
88+
expect(result.toString('utf8')).to.equal(expectedSubject);
89+
});
90+
91+
it('should return Buffer when text is Buffer (response with statusCode)', () => {
92+
const expectedSubject = 'GET|1672531200000|/api/test|200|response-body';
93+
const result = calculateHMACSubject({
94+
urlPath: '/api/test',
95+
text: Buffer.from('response-body', 'utf8'),
96+
timestamp: MOCK_TIMESTAMP,
97+
statusCode: 200,
98+
method: 'get',
99+
authVersion: 3,
100+
});
101+
expect(Buffer.isBuffer(result)).to.be.true;
102+
expect(result.toString('utf8')).to.equal(expectedSubject);
103+
});
104+
105+
it('should handle Buffer text with authVersion 2', () => {
106+
const expectedSubject = '1672531200000|/api/test|request-body';
107+
const result = calculateHMACSubject({
108+
urlPath: '/api/test',
109+
text: Buffer.from('request-body', 'utf8'),
110+
timestamp: MOCK_TIMESTAMP,
111+
method: 'post',
112+
authVersion: 2,
113+
});
114+
expect(Buffer.isBuffer(result)).to.be.true;
115+
expect(result.toString('utf8')).to.equal(expectedSubject);
116+
});
77117
});
78118

79119
describe('calculateRequestHMAC', () => {
@@ -113,7 +153,7 @@ describe('HMAC Utility Functions', () => {
113153
});
114154

115155
describe('verifyResponse', () => {
116-
it('should verify the HMAC and timestamp validity', () => {
156+
it('should verify the HMAC and timestamp validity with string text', () => {
117157
const result = verifyResponse({
118158
url: '/api/test',
119159
statusCode: 200,
@@ -130,6 +170,7 @@ describe('HMAC Utility Functions', () => {
130170
expectedHmac: 'a16c08b1fa8bff1e2e58d1831855e1745361f78bd6eb6e18b5b7ee17ae0a3bb7',
131171
isInResponseValidityWindow: true,
132172
});
173+
expect(typeof result.signatureSubject).to.equal('string');
133174
});
134175

135176
it('should return invalid if HMAC does not match', () => {
@@ -161,5 +202,23 @@ describe('HMAC Utility Functions', () => {
161202

162203
expect(result.isInResponseValidityWindow).to.be.false;
163204
});
205+
206+
it('should return Buffer signatureSubject when text is Buffer', () => {
207+
const textBuffer = Buffer.from('response-body', 'utf8');
208+
const result = verifyResponse({
209+
url: '/api/test',
210+
statusCode: 200,
211+
text: textBuffer,
212+
timestamp: MOCK_TIMESTAMP,
213+
token: 'test-token',
214+
hmac: 'a16c08b1fa8bff1e2e58d1831855e1745361f78bd6eb6e18b5b7ee17ae0a3bb7',
215+
method: 'post',
216+
authVersion: 3,
217+
});
218+
219+
expect(result.isValid).to.be.true;
220+
expect(Buffer.isBuffer(result.signatureSubject)).to.be.true;
221+
expect(result.signatureSubject.toString('utf8')).to.equal('POST|1672531200000|/api/test|200|response-body');
222+
});
164223
});
165224
});

0 commit comments

Comments
 (0)