Skip to content

Commit f06f1a1

Browse files
authored
LEB-4935 | Update to validate fetch request responses (#16)
1 parent f009e85 commit f06f1a1

File tree

13 files changed

+436
-49
lines changed

13 files changed

+436
-49
lines changed

.gitignore

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,3 @@
11
.idea
2-
node_modules
2+
node_modules
3+
lib/es5/hmac.tests.js

demo/ajax.app.js

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

demo/get.app.js

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

demo/post.app.js

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

gulpfile.js

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,15 @@ gulp.task('build-lib', gulp.series('clean-lib', () => {
3232
.pipe(gulp.dest('./lib/es5'));
3333
}));
3434

35-
gulp.task('test', gulp.series('build-lib', () => {
35+
gulp.task('build-test', () => {
36+
return gulp.src(['./qunit/hmac.tests.js'])
37+
.pipe(babel({
38+
presets: ["@babel/preset-env"],
39+
}))
40+
.pipe(gulp.dest('./lib/es5'));
41+
});
42+
43+
gulp.task('test', gulp.series('build-lib', 'build-test', () => {
3644
return gulp.src('./qunit/hmac.html')
3745
.pipe(qunit());
3846
}));

lib/es5/hmac.js

Lines changed: 108 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -179,6 +179,36 @@ if (!Array.prototype.indexOf) {
179179

180180
return -1;
181181
};
182+
} //https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/assign#Polyfill
183+
// Because PhantomJS does not fully support JS APIs
184+
185+
186+
if (typeof Object.assign != 'function') {
187+
(function () {
188+
Object.assign = function (target) {
189+
'use strict';
190+
191+
if (target === undefined || target === null) {
192+
throw new TypeError('Cannot convert undefined or null to object');
193+
}
194+
195+
var output = Object(target);
196+
197+
for (var index = 1; index < arguments.length; index++) {
198+
var source = arguments[index];
199+
200+
if (source !== undefined && source !== null) {
201+
for (var nextKey in source) {
202+
if (source.hasOwnProperty(nextKey)) {
203+
output[nextKey] = source[nextKey];
204+
}
205+
}
206+
}
207+
}
208+
209+
return output;
210+
};
211+
})();
182212
}
183213
/**
184214
* AcquiaHttpHmac - Let's you sign a XMLHttpRequest or promised-based request object (e.g. jqXHR) by Acquia's
@@ -213,6 +243,9 @@ var AcquiaHttpHmac = /*#__PURE__*/function () {
213243

214244
_classCallCheck(this, AcquiaHttpHmac);
215245

246+
Object.defineProperty(this, _hasValidResponse, {
247+
value: _hasValidResponse2
248+
});
216249
Object.defineProperty(this, _generateNonce, {
217250
value: _generateNonce2
218251
});
@@ -410,6 +443,34 @@ var AcquiaHttpHmac = /*#__PURE__*/function () {
410443
void 0;
411444
return headers;
412445
}
446+
/**
447+
* Generate signed headers using provided parameters.
448+
*
449+
* @param {Object} signParameters
450+
* The signing parameters to be signed, which include method, path, headers, content type, body.
451+
* @returns {object}
452+
* The object contains headers, nonce, and timestamp.
453+
*/
454+
455+
}, {
456+
key: "getFetchHeaders",
457+
value: function getFetchHeaders(signParameters) {
458+
var nonce = _classPrivateFieldLooseBase(this, _generateNonce)[_generateNonce]();
459+
460+
var timestamp = _classPrivateFieldLooseBase(this, _generateTimestamp)[_generateTimestamp]();
461+
462+
var copiedParameters = Object.assign({}, signParameters);
463+
Object.assign(copiedParameters, {
464+
nonce: nonce,
465+
timestamp: timestamp
466+
});
467+
var headers = this.getHeaders(copiedParameters);
468+
return {
469+
headers: headers,
470+
nonce: nonce,
471+
timestamp: timestamp
472+
};
473+
}
413474
/**
414475
* Sign the request using provided parameters.
415476
*
@@ -471,6 +532,21 @@ var AcquiaHttpHmac = /*#__PURE__*/function () {
471532
return request.setRequestHeader(headerName, headers[headerName]);
472533
});
473534
}
535+
/**
536+
* Helper method to perform the Crypto processing and comparison.
537+
*
538+
* @param {String} responseText
539+
* The content (as string) of the server's response.
540+
* @param {String} sha256Header
541+
* The `X-Server-Authorization-HMAC-SHA256` header from the server's response.
542+
* @param {String} nonce
543+
* The nonce used to sign the sent request.
544+
* @param {String} timestamp
545+
* The nonce used to sign the sent request.
546+
* @returns {boolean}
547+
* TRUE if the request is valid; FALSE otherwise.
548+
*/
549+
474550
/**
475551
* Check if the request has a valid response.
476552
*
@@ -483,13 +559,27 @@ var AcquiaHttpHmac = /*#__PURE__*/function () {
483559
}, {
484560
key: "hasValidResponse",
485561
value: function hasValidResponse(request) {
486-
var signature_base_string = "".concat(request.acquiaHttpHmac.nonce, "\n").concat(request.acquiaHttpHmac.timestamp, "\n").concat(request.responseText),
487-
signature = CryptoJS.HmacSHA256(signature_base_string, this.config.parsed_secret_key).toString(CryptoJS.enc.Base64),
488-
server_signature = request.getResponseHeader('X-Server-Authorization-HMAC-SHA256');
489-
void 0;
490-
void 0;
491-
void 0;
492-
return signature === server_signature;
562+
return _classPrivateFieldLooseBase(this, _hasValidResponse)[_hasValidResponse](request.responseText, request.getResponseHeader('X-Server-Authorization-HMAC-SHA256'), request.acquiaHttpHmac.nonce, request.acquiaHttpHmac.timestamp);
563+
}
564+
/**
565+
* Check if the Fetch Response is valid.
566+
*
567+
* @param {String} responseText
568+
* The Fetch response's text.
569+
* @param {Object} headers
570+
* The Fetch response's headers.
571+
* @param {String} nonce
572+
* The nonce used to sign the Fetch request.
573+
* @param {String} timestamp
574+
* The nonce used to sign the Fetch request.
575+
* @returns {boolean}
576+
* TRUE if the request is valid; FALSE otherwise.
577+
*/
578+
579+
}, {
580+
key: "hasValidFetchResponse",
581+
value: function hasValidFetchResponse(responseText, headers, nonce, timestamp) {
582+
return _classPrivateFieldLooseBase(this, _hasValidResponse)[_hasValidResponse](responseText, headers['X-Server-Authorization-HMAC-SHA256'], nonce, timestamp);
493583
}
494584
}], [{
495585
key: "isXMLHttpRequest",
@@ -560,6 +650,8 @@ var _generateTimestamp = _classPrivateFieldLooseKey("generateTimestamp");
560650

561651
var _generateNonce = _classPrivateFieldLooseKey("generateNonce");
562652

653+
var _hasValidResponse = _classPrivateFieldLooseKey("hasValidResponse");
654+
563655
var _generateTimestamp2 = function _generateTimestamp2() {
564656
return Math.floor(Date.now() / 1000).toString();
565657
};
@@ -573,6 +665,15 @@ var _generateNonce2 = function _generateNonce2() {
573665
});
574666
};
575667

668+
var _hasValidResponse2 = function _hasValidResponse2(responseText, sha256Header, nonce, timestamp) {
669+
var signature_base_string = "".concat(nonce, "\n").concat(timestamp, "\n").concat(responseText);
670+
var signature = CryptoJS.HmacSHA256(signature_base_string, this.config.parsed_secret_key).toString(CryptoJS.enc.Base64);
671+
void 0;
672+
void 0;
673+
void 0;
674+
return signature === sha256Header;
675+
};
676+
576677
if ((typeof exports === "undefined" ? "undefined" : _typeof(exports)) === "object") {
577678
// CommonJS
578679
var CryptoJS = require('crypto-js');

lib/es5/hmac.min.js

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

lib/es6/hmac.js

Lines changed: 106 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -161,6 +161,36 @@ if (!Array.prototype.indexOf) {
161161

162162
return -1;
163163
};
164+
} //https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/assign#Polyfill
165+
// Because PhantomJS does not fully support JS APIs
166+
167+
168+
if (typeof Object.assign != 'function') {
169+
(function () {
170+
Object.assign = function (target) {
171+
'use strict';
172+
173+
if (target === undefined || target === null) {
174+
throw new TypeError('Cannot convert undefined or null to object');
175+
}
176+
177+
var output = Object(target);
178+
179+
for (var index = 1; index < arguments.length; index++) {
180+
var source = arguments[index];
181+
182+
if (source !== undefined && source !== null) {
183+
for (var nextKey in source) {
184+
if (source.hasOwnProperty(nextKey)) {
185+
output[nextKey] = source[nextKey];
186+
}
187+
}
188+
}
189+
}
190+
191+
return output;
192+
};
193+
})();
164194
}
165195
/**
166196
* AcquiaHttpHmac - Let's you sign a XMLHttpRequest or promised-based request object (e.g. jqXHR) by Acquia's
@@ -191,6 +221,9 @@ class AcquiaHttpHmac {
191221
version = '2.0',
192222
default_content_type = 'application/json'
193223
}) {
224+
Object.defineProperty(this, _hasValidResponse, {
225+
value: _hasValidResponse2
226+
});
194227
Object.defineProperty(this, _generateNonce, {
195228
value: _generateNonce2
196229
});
@@ -433,6 +466,31 @@ class AcquiaHttpHmac {
433466
return headers;
434467
}
435468

469+
/**
470+
* Generate signed headers using provided parameters.
471+
*
472+
* @param {Object} signParameters
473+
* The signing parameters to be signed, which include method, path, headers, content type, body.
474+
* @returns {object}
475+
* The object contains headers, nonce, and timestamp.
476+
*/
477+
getFetchHeaders(signParameters) {
478+
const nonce = _classPrivateFieldLooseBase(this, _generateNonce)[_generateNonce]();
479+
480+
const timestamp = _classPrivateFieldLooseBase(this, _generateTimestamp)[_generateTimestamp]();
481+
482+
const copiedParameters = Object.assign({}, signParameters);
483+
Object.assign(copiedParameters, {
484+
nonce,
485+
timestamp
486+
});
487+
const headers = this.getHeaders(copiedParameters);
488+
return {
489+
headers,
490+
nonce,
491+
timestamp
492+
};
493+
}
436494
/**
437495
* Sign the request using provided parameters.
438496
*
@@ -450,6 +508,8 @@ class AcquiaHttpHmac {
450508
* Body.
451509
* @returns {string}
452510
*/
511+
512+
453513
sign({
454514
request,
455515
method,
@@ -486,6 +546,22 @@ class AcquiaHttpHmac {
486546
request.acquiaHttpHmac.nonce = nonce;
487547
Object.keys(headers).forEach(headerName => request.setRequestHeader(headerName, headers[headerName]));
488548
}
549+
/**
550+
* Helper method to perform the Crypto processing and comparison.
551+
*
552+
* @param {String} responseText
553+
* The content (as string) of the server's response.
554+
* @param {String} sha256Header
555+
* The `X-Server-Authorization-HMAC-SHA256` header from the server's response.
556+
* @param {String} nonce
557+
* The nonce used to sign the sent request.
558+
* @param {String} timestamp
559+
* The nonce used to sign the sent request.
560+
* @returns {boolean}
561+
* TRUE if the request is valid; FALSE otherwise.
562+
*/
563+
564+
489565
/**
490566
* Check if the request has a valid response.
491567
*
@@ -494,16 +570,26 @@ class AcquiaHttpHmac {
494570
* @returns {boolean}
495571
* TRUE if the request is valid; FALSE otherwise.
496572
*/
497-
498-
499573
hasValidResponse(request) {
500-
let signature_base_string = `${request.acquiaHttpHmac.nonce}\n${request.acquiaHttpHmac.timestamp}\n${request.responseText}`,
501-
signature = CryptoJS.HmacSHA256(signature_base_string, this.config.parsed_secret_key).toString(CryptoJS.enc.Base64),
502-
server_signature = request.getResponseHeader('X-Server-Authorization-HMAC-SHA256');
503-
void 0;
504-
void 0;
505-
void 0;
506-
return signature === server_signature;
574+
return _classPrivateFieldLooseBase(this, _hasValidResponse)[_hasValidResponse](request.responseText, request.getResponseHeader('X-Server-Authorization-HMAC-SHA256'), request.acquiaHttpHmac.nonce, request.acquiaHttpHmac.timestamp);
575+
}
576+
577+
/**
578+
* Check if the Fetch Response is valid.
579+
*
580+
* @param {String} responseText
581+
* The Fetch response's text.
582+
* @param {Object} headers
583+
* The Fetch response's headers.
584+
* @param {String} nonce
585+
* The nonce used to sign the Fetch request.
586+
* @param {String} timestamp
587+
* The nonce used to sign the Fetch request.
588+
* @returns {boolean}
589+
* TRUE if the request is valid; FALSE otherwise.
590+
*/
591+
hasValidFetchResponse(responseText, headers, nonce, timestamp) {
592+
return _classPrivateFieldLooseBase(this, _hasValidResponse)[_hasValidResponse](responseText, headers['X-Server-Authorization-HMAC-SHA256'], nonce, timestamp);
507593
}
508594

509595
}
@@ -512,6 +598,8 @@ var _generateTimestamp = _classPrivateFieldLooseKey("generateTimestamp");
512598

513599
var _generateNonce = _classPrivateFieldLooseKey("generateNonce");
514600

601+
var _hasValidResponse = _classPrivateFieldLooseKey("hasValidResponse");
602+
515603
var _generateTimestamp2 = function _generateTimestamp2() {
516604
return Math.floor(Date.now() / 1000).toString();
517605
};
@@ -525,6 +613,15 @@ var _generateNonce2 = function _generateNonce2() {
525613
});
526614
};
527615

616+
var _hasValidResponse2 = function _hasValidResponse2(responseText, sha256Header, nonce, timestamp) {
617+
const signature_base_string = `${nonce}\n${timestamp}\n${responseText}`;
618+
const signature = CryptoJS.HmacSHA256(signature_base_string, this.config.parsed_secret_key).toString(CryptoJS.enc.Base64);
619+
void 0;
620+
void 0;
621+
void 0;
622+
return signature === sha256Header;
623+
};
624+
528625
if (typeof exports === "object") {
529626
// CommonJS
530627
var CryptoJS = require('crypto-js');

0 commit comments

Comments
 (0)