From c40a9d3e815351031e513c7112f3239133e0069d Mon Sep 17 00:00:00 2001 From: Somesh Date: Sun, 5 Jul 2020 03:08:44 +0530 Subject: [PATCH 01/25] Adds auth request list to fixtures - adds request {apikey,key,val,header} --- test/fixtures/auth_requests.js | 38 ++++++++++++++++++++++++++++++++++ 1 file changed, 38 insertions(+) create mode 100644 test/fixtures/auth_requests.js diff --git a/test/fixtures/auth_requests.js b/test/fixtures/auth_requests.js new file mode 100644 index 0000000..ec127ac --- /dev/null +++ b/test/fixtures/auth_requests.js @@ -0,0 +1,38 @@ +const sdk = require('postman-collection'); + +module.exports = { + 'API_KEY': { + 'KEY_VAL_HEADER': new sdk.Request({ + 'url': { + 'protocol': 'https', + 'path': ['get'], + 'host': ['postman-echo', 'com'], + 'query': [{ + 'key': 'foo1', + 'value': 'bar1' + }, { + 'key': 'foo2', + 'value': 'aa' + }], + 'variable': [] + }, + 'method': 'GET', + 'auth': { + 'type': 'apikey', + 'apikey': [{ + 'type': 'any', + 'value': 'header', + 'key': 'in' + }, { + 'type': 'any', + 'value': 'key', + 'key': 'key' + }, { + 'type': 'any', + 'value': 'value', + 'key': 'value' + }] + } + }) + } +}; From ef47db82b47b93802386cbba8cfee8260c164ddf Mon Sep 17 00:00:00 2001 From: Somesh Date: Sun, 5 Jul 2020 03:09:33 +0530 Subject: [PATCH 02/25] Adds test for request authorizer - added test for apikey {key,val,header} --- test/unit/auth.test.js | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) create mode 100644 test/unit/auth.test.js diff --git a/test/unit/auth.test.js b/test/unit/auth.test.js new file mode 100644 index 0000000..a028ed2 --- /dev/null +++ b/test/unit/auth.test.js @@ -0,0 +1,18 @@ +const expect = require('chai').expect, + sdk = require('postman-collection'), + authRequests = require('../fixtures/auth_requests'), + authorize = require('../../lib/auth'); + +describe('Postman Request Authentication', function () { + + describe('API KEY authentication', function () { + it('should add api header to request when in type is header', function () { + var request = authRequests.API_KEY.KEY_VAL_HEADER; + // TODO pass this request to authorize function + request = authorize(request); + expect(request).to.be.of.instanceOf(sdk.Request); + expect(request.headers.has('key')).to.be.true; + expect(request.getHeaders('key').key).to.be.equal('value'); + }); + }); +}); From 39e496726490399120fd45bef5373f207491540b Mon Sep 17 00:00:00 2001 From: Somesh Date: Sun, 5 Jul 2020 03:51:45 +0530 Subject: [PATCH 03/25] Completes API key authorizer - adds all test cases {key,val,query,header} - adds auth more methods to authRequests --- test/fixtures/auth_requests.js | 160 +++++++++++++++++++++++++++++++++ test/unit/auth.test.js | 38 +++++++- 2 files changed, 197 insertions(+), 1 deletion(-) diff --git a/test/fixtures/auth_requests.js b/test/fixtures/auth_requests.js index ec127ac..06c6b26 100644 --- a/test/fixtures/auth_requests.js +++ b/test/fixtures/auth_requests.js @@ -33,6 +33,166 @@ module.exports = { 'key': 'value' }] } + }), + 'KEY_HEADER': new sdk.Request({ + 'url': { + 'protocol': 'https', + 'path': ['get'], + 'host': ['postman-echo', 'com'], + 'query': [{ + 'key': 'foo1', + 'value': 'bar1' + }, { + 'key': 'foo2', + 'value': 'aa' + }], + 'variable': [] + }, + 'method': 'GET', + 'auth': { + 'type': 'apikey', + 'apikey': [{ + 'type': 'any', + 'value': 'header', + 'key': 'in' + }, { + 'type': 'any', + 'value': 'key', + 'key': 'key' + }, { + 'type': 'any', + 'value': '', + 'key': 'value' + }] + } + }), + 'HEADER': new sdk.Request({ + 'url': { + 'protocol': 'https', + 'path': ['get'], + 'host': ['postman-echo', 'com'], + 'query': [{ + 'key': 'foo1', + 'value': 'bar1' + }, { + 'key': 'foo2', + 'value': 'aa' + }], + 'variable': [] + }, + 'method': 'GET', + 'auth': { + 'type': 'apikey', + 'apikey': [{ + 'type': 'any', + 'value': 'query', + 'key': 'in' + }, { + 'type': 'any', + 'value': '', + 'key': 'key' + }, { + 'type': 'any', + 'value': 'val', + 'key': 'value' + }] + } + }), + 'KEY_VAL_QUERY': new sdk.Request({ + 'url': { + 'protocol': 'https', + 'path': ['get'], + 'host': ['postman-echo', 'com'], + 'query': [{ + 'key': 'foo1', + 'value': 'bar1' + }, { + 'key': 'foo2', + 'value': 'aa' + }], + 'variable': [] + }, + 'method': 'GET', + 'auth': { + 'type': 'apikey', + 'apikey': [{ + 'type': 'any', + 'value': 'query', + 'key': 'in' + }, { + 'type': 'any', + 'value': 'key', + 'key': 'key' + }, { + 'type': 'any', + 'value': 'value', + 'key': 'value' + }] + } + }), + 'KEY_QUERY': new sdk.Request({ + 'url': { + 'protocol': 'https', + 'path': ['get'], + 'host': ['postman-echo', 'com'], + 'query': [{ + 'key': 'foo1', + 'value': 'bar1' + }, { + 'key': 'foo2', + 'value': 'aa' + }], + 'variable': [] + }, + 'method': 'GET', + 'auth': { + 'type': 'apikey', + 'apikey': [{ + 'type': 'any', + 'value': 'query', + 'key': 'in' + }, { + 'type': 'any', + 'value': 'key', + 'key': 'key' + }, { + 'type': 'any', + 'value': '', + 'key': 'value' + }] + } + }), + 'QUERY': new sdk.Request({ + 'url': { + 'protocol': 'https', + 'path': ['get'], + 'host': ['postman-echo', 'com'], + 'query': [{ + 'key': 'foo1', + 'value': 'bar1' + }, { + 'key': 'foo2', + 'value': 'aa' + }], + 'variable': [] + }, + 'method': 'GET', + 'auth': { + 'type': 'apikey', + 'apikey': [{ + 'type': 'any', + 'value': 'query', + 'key': 'in' + }, { + 'type': 'any', + 'value': '', + 'key': 'key' + }, { + 'type': 'any', + 'value': '', + 'key': 'value' + }] + } }) } }; diff --git a/test/unit/auth.test.js b/test/unit/auth.test.js index a028ed2..cd109c8 100644 --- a/test/unit/auth.test.js +++ b/test/unit/auth.test.js @@ -3,7 +3,7 @@ const expect = require('chai').expect, authRequests = require('../fixtures/auth_requests'), authorize = require('../../lib/auth'); -describe('Postman Request Authentication', function () { +describe('Postman Request Authorization', function () { describe('API KEY authentication', function () { it('should add api header to request when in type is header', function () { @@ -14,5 +14,41 @@ describe('Postman Request Authentication', function () { expect(request.headers.has('key')).to.be.true; expect(request.getHeaders('key').key).to.be.equal('value'); }); + + it('should add api headers to request when api value is not given', function () { + var request = authRequests.API_KEY.KEY_HEADER; + request = authorize(request); + expect(request).to.be.of.instanceOf(sdk.Request); + expect(request.headers.has('key')).to.be.true; + expect(request.getHeaders('key').key).to.be.equal(''); + }); + + it('should not add api headers to request when api key and value is not given', function () { + var request = authRequests.API_KEY.HEADER; + request = authorize(request); + expect(request).to.be.of.instanceOf(sdk.Request); + }); + + it('should add api key query param when api key value and in type is query', function () { + var request = authRequests.API_KEY.KEY_VAL_QUERY; + request = authorize(request); + expect(request).to.be.of.instanceOf(sdk.Request); + expect(request.url.query.has('key')).to.be.true; + expect(request.url.query.get('key')).to.be.equal('value'); + }); + + it('should add api key query param when api key and in type is query', function () { + var request = authRequests.API_KEY.KEY_QUERY; + request = authorize(request); + expect(request).to.be.of.instanceOf(sdk.Request); + expect(request.url.query.has('key')).to.be.true; + expect(request.url.query.get('key')).to.be.equal(''); + }); + + it('should add api key query param when api key value and in type is query', function () { + var request = authRequests.API_KEY.QUERY; + request = authorize(request); + expect(request).to.be.of.instanceOf(sdk.Request); + }); }); }); From e7d8c6f8a125980333681bb090f09bdabf13a311 Mon Sep 17 00:00:00 2001 From: Somesh Date: Sun, 5 Jul 2020 06:16:22 +0530 Subject: [PATCH 04/25] Adds basic and bearer token authorizer - adds method to authorize basic authorizatioin requests - adds method to authorize bearer token authrization request --- lib/auth.js | 60 +++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 60 insertions(+) create mode 100644 lib/auth.js diff --git a/lib/auth.js b/lib/auth.js new file mode 100644 index 0000000..03d5b8b --- /dev/null +++ b/lib/auth.js @@ -0,0 +1,60 @@ +const sdk = require('postman-collection'); +const authRequest = require('../test/fixtures/auth_requests'); + +/** + * Adds necessary headers / url params for api key authentication + * + * @param request - Postman Request instance + */ +function api_key (request = authRequest.API_KEY.KEY_VAL_HEADER) { + let config = {}, + auth = request.auth; + // TODO add desc for query param and new headers + // creating congif obj for easy access of elements + auth.apikey.each((element) => { + config[element.key] = element.value; + }); + + // if api key `key` is empty then dont add + if (config.key === '') { + return request; + } + + // if apikey auth in type is headers + if (config.in === 'header') { + // adding apikey auth headers to request + request.addHeader(new sdk.Header({ + key: config.key, + value: config.value + })); + } + // if apikey auth in type is query params + else if (config.in === 'query') { + // adding new query param to request for api key + request.url.addQueryParams(new sdk.QueryParam({ + key: config.key, + value: config.value + })); + } + else { + // TODO setup auth errors and return from here + // temp return + return 'error'; + } + return request; +} + +/** + * Authorizes a postman-request instance + * + * @param {PostmanRequest} request - Request instance which needs to be authorized + */ +function authorize (request) { + var authorizedRequest = null; + if (request.auth.type === 'apikey') { + authorizedRequest = api_key(request); + } + return authorizedRequest; +} + +module.exports = authorize; From 79883d04f3fcd1d3de5fe9c55ebe3780c5a70faa Mon Sep 17 00:00:00 2001 From: Somesh Date: Sun, 5 Jul 2020 06:17:47 +0530 Subject: [PATCH 05/25] Adds test for basic and bearer token auth - adds test for bearer token auth - when bearer token is provided - when beaere token is not provided - adds test for basic auth - when username pass are given - when username is given - when pass is given - when nothing is given - adds requests for auth for all abouve cases --- test/fixtures/auth_requests.js | 140 +++++++++++++++++++++++++++++++++ test/unit/auth.test.js | 57 ++++++++++++-- 2 files changed, 191 insertions(+), 6 deletions(-) diff --git a/test/fixtures/auth_requests.js b/test/fixtures/auth_requests.js index 06c6b26..b4d611b 100644 --- a/test/fixtures/auth_requests.js +++ b/test/fixtures/auth_requests.js @@ -194,5 +194,145 @@ module.exports = { }] } }) + }, + 'BEARER_TOKEN': { + 'TOKEN': new sdk.Request({ + 'url': { + 'protocol': 'https', + 'path': ['get'], + 'host': ['postman-echo', 'com'], + 'query': [{ + 'key': 'foo1', + 'value': 'bar1' + }, { + 'key': 'foo2', + 'value': 'aa' + }], + 'variable': [] + }, + 'method': 'GET', + 'auth': { + 'type': 'bearer', + 'bearer': [{ + 'type': 'any', + 'value': 'testbearertoken', + 'key': 'token' + }] + } + }) + }, + 'BASIC': { + 'USERNAME_PASSWORD': new sdk.Request({ + 'url': { + 'protocol': 'https', + 'path': ['get'], + 'host': ['postman-echo', 'com'], + 'query': [{ + 'key': 'foo1', + 'value': 'bar1' + }, { + 'key': 'foo2', + 'value': 'aa' + }], + 'variable': [] + }, + 'method': 'GET', + 'auth': { + 'type': 'basic', + 'basic': [{ + 'type': 'any', + 'value': 'test', + 'key': 'password' + }, { + 'type': 'any', + 'value': 'test', + 'key': 'username' + }] + } + }), + 'USERNAME': new sdk.Request({ + 'url': { + 'protocol': 'https', + 'path': ['get'], + 'host': ['postman-echo', 'com'], + 'query': [{ + 'key': 'foo1', + 'value': 'bar1' + }, { + 'key': 'foo2', + 'value': 'aa' + }], + 'variable': [] + }, + 'method': 'GET', + 'auth': { + 'type': 'basic', + 'basic': [{ + 'type': 'any', + 'value': '', + 'key': 'password' + }, { + 'type': 'any', + 'value': 'test', + 'key': 'username' + }] + } + }), + 'PASSWORD': new sdk.Request({ + 'url': { + 'protocol': 'https', + 'path': ['get'], + 'host': ['postman-echo', 'com'], + 'query': [{ + 'key': 'foo1', + 'value': 'bar1' + }, { + 'key': 'foo2', + 'value': 'aa' + }], + 'variable': [] + }, + 'method': 'GET', + 'auth': { + 'type': 'basic', + 'basic': [{ + 'type': 'any', + 'value': 'test', + 'key': 'password' + }, { + 'type': 'any', + 'value': '', + 'key': 'username' + }] + } + }), + 'NO_USERNAME_NO_PASS': new sdk.Request({ + 'url': { + 'protocol': 'https', + 'path': ['get'], + 'host': ['postman-echo', 'com'], + 'query': [{ + 'key': 'foo1', + 'value': 'bar1' + }, { + 'key': 'foo2', + 'value': 'aa' + }], + 'variable': [] + }, + 'method': 'GET', + 'auth': { + 'type': 'basic', + 'basic': [{ + 'type': 'any', + 'value': '', + 'key': 'password' + }, { + 'type': 'any', + 'value': '', + 'key': 'username' + }] + } + }) } }; diff --git a/test/unit/auth.test.js b/test/unit/auth.test.js index cd109c8..08311a3 100644 --- a/test/unit/auth.test.js +++ b/test/unit/auth.test.js @@ -7,7 +7,7 @@ describe('Postman Request Authorization', function () { describe('API KEY authentication', function () { it('should add api header to request when in type is header', function () { - var request = authRequests.API_KEY.KEY_VAL_HEADER; + let request = authRequests.API_KEY.KEY_VAL_HEADER; // TODO pass this request to authorize function request = authorize(request); expect(request).to.be.of.instanceOf(sdk.Request); @@ -16,7 +16,7 @@ describe('Postman Request Authorization', function () { }); it('should add api headers to request when api value is not given', function () { - var request = authRequests.API_KEY.KEY_HEADER; + let request = authRequests.API_KEY.KEY_HEADER; request = authorize(request); expect(request).to.be.of.instanceOf(sdk.Request); expect(request.headers.has('key')).to.be.true; @@ -24,13 +24,13 @@ describe('Postman Request Authorization', function () { }); it('should not add api headers to request when api key and value is not given', function () { - var request = authRequests.API_KEY.HEADER; + let request = authRequests.API_KEY.HEADER; request = authorize(request); expect(request).to.be.of.instanceOf(sdk.Request); }); it('should add api key query param when api key value and in type is query', function () { - var request = authRequests.API_KEY.KEY_VAL_QUERY; + let request = authRequests.API_KEY.KEY_VAL_QUERY; request = authorize(request); expect(request).to.be.of.instanceOf(sdk.Request); expect(request.url.query.has('key')).to.be.true; @@ -38,7 +38,7 @@ describe('Postman Request Authorization', function () { }); it('should add api key query param when api key and in type is query', function () { - var request = authRequests.API_KEY.KEY_QUERY; + let request = authRequests.API_KEY.KEY_QUERY; request = authorize(request); expect(request).to.be.of.instanceOf(sdk.Request); expect(request.url.query.has('key')).to.be.true; @@ -46,9 +46,54 @@ describe('Postman Request Authorization', function () { }); it('should add api key query param when api key value and in type is query', function () { - var request = authRequests.API_KEY.QUERY; + let request = authRequests.API_KEY.QUERY; request = authorize(request); expect(request).to.be.of.instanceOf(sdk.Request); }); }); + + describe('Bearer Token Authorization', function () { + + it('should add authorization header to request when bearer token it given', function () { + let request = authRequests.BEARER_TOKEN.TOKEN; + request = authorize(request); + expect(request).to.be.instanceOf(sdk.Request); + expect(request.headers.has('Authorization')).to.be.true; + expect(request.getHeaders('Authorization').Authorization).to.be.equal('Bearer testbearertoken'); + }); + }); + + describe('Basic Authorization', function () { + it('should add authorization header when basic authorization is used with username and password', function () { + var request = authRequests.BASIC.USERNAME_PASSWORD; + request = authorize(request); + expect(request).to.be.instanceOf(sdk.Request); + expect(request.headers.has('Authorization')).to.be.true; + expect(request.getHeaders('Authorization').Authorization).to.be.equal('Basic dGVzdDp0ZXN0'); + }); + + it('should add authorization header when basic authorization is used with username and no password', function () { + var request = authRequests.BASIC.USERNAME; + request = authorize(request); + expect(request).to.be.instanceOf(sdk.Request); + expect(request.headers.has('Authorization')).to.be.true; + expect(request.getHeaders('Authorization').Authorization).to.be.equal('Basic dGVzdDo='); + }); + + it('should add authorization header when basic authorization is used with just password', function () { + var request = authRequests.BASIC.PASSWORD; + request = authorize(request); + expect(request).to.be.instanceOf(sdk.Request); + expect(request.headers.has('Authorization')).to.be.true; + expect(request.getHeaders('Authorization').Authorization).to.be.equal('Basic OnRlc3Q='); + }); + + it('should add authorization header when basic authorization is used with no username/password', function () { + var request = authRequests.BASIC.NO_USERNAME_NO_PASS; + request = authorize(request); + expect(request).to.be.instanceOf(sdk.Request); + expect(request.headers.has('Authorization')).to.be.true; + expect(request.getHeaders('Authorization').Authorization).to.be.equal('Basic Og=='); + }); + }); }); From c278b867d777de311bf1039031c205759553f873 Mon Sep 17 00:00:00 2001 From: Somesh Date: Fri, 10 Jul 2020 05:21:09 +0530 Subject: [PATCH 06/25] Adds helper methods - md5 - sha256 - sha512 - tobase64 - objcopy --- lib/helpers.js | 68 ++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 68 insertions(+) create mode 100644 lib/helpers.js diff --git a/lib/helpers.js b/lib/helpers.js new file mode 100644 index 0000000..4a19a61 --- /dev/null +++ b/lib/helpers.js @@ -0,0 +1,68 @@ +'use strict'; + +var crypto = require('crypto'), + Buffer = require('safe-buffer').Buffer; + +/** + * Converts String to base64 equivalent + * + * @param {string} str - Input string + * @returns {string} - Base64 converted input string + */ +function toBase64 (str) { + return Buffer.from(str || '', 'utf8').toString('base64'); +} + +/** + * Creats a deep copy of an object; + * + * @param {object} object - Input Object + * @returns {object} - copy of input object + */ +function copy (object) { + var objectCopy = {}; + Object.keys(object).forEach(function (i) { + objectCopy[i] = object[i]; + }); + return objectCopy; +} + +/** + * Creates md5 hash for input string + * + * @param str - String whose md5 hash has to be calculated + * @returns {string} - md5 hash of input string + */ +function md5 (str) { + return crypto.createHash('md5').update(str).digest('hex'); +} + +/** + * Creates sha256 hash for input string + * + * @param str - String whose md5 hash has to be calculated + * @returns {string} - md5 hash of input string + */ +function sha256 (str) { + return crypto.createHash('sha256').update(str).digest('hex'); +} + +/** + * Creates sha512 hash for input string + * + * @param str - String whose md5 hash has to be calculated + * @returns {string} - md5 hash of input string + */ +function sha512 (str) { + return crypto.createHash('sha512').update(str).digest('hex'); +} + +module.exports = { + hash: { + md5, + sha256, + sha512 + }, + toBase64, + copy +}; From 1b3ff82ac2e1f83b6377a299b0dd07bbb8b01833 Mon Sep 17 00:00:00 2001 From: Somesh Date: Fri, 10 Jul 2020 05:23:21 +0530 Subject: [PATCH 07/25] Partialli adds digest auth and bearer token auth --- lib/auth.js | 167 +++++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 164 insertions(+), 3 deletions(-) diff --git a/lib/auth.js b/lib/auth.js index 03d5b8b..822ebb4 100644 --- a/lib/auth.js +++ b/lib/auth.js @@ -1,8 +1,15 @@ -const sdk = require('postman-collection'); -const authRequest = require('../test/fixtures/auth_requests'); +const sdk = require('postman-collection'), + uuid = require('uuid/v4'), + crypto = require('crypto'), + helpers = require('./helpers'); + +var toBase64 = helpers.toBase64, + hash = helpers.hash; /** * Adds necessary headers / url params for api key authentication + * In header -> key:value + * In query param -> url?key=value * * @param request - Postman Request instance */ @@ -41,6 +48,147 @@ function api_key (request = authRequest.API_KEY.KEY_VAL_HEADER) { // temp return return 'error'; } + + return request; +} + +/** + * Adds Authorization header for bearer authorization + * Authorization: Bearer bearer_token + * + * @param {PostmanRequest} request - Postman Request instance + */ +function bearerToken (request) { + let config = {}, + auth = request.auth; + // TODO add desc for header + // creating congif obj for easy access of elements + auth.bearer.each((element) => { + config[element.key] = element.value; + }); + + // if bearer token is empty then dont add + if (config.token === '') { + return request; + } + + // Adds Authorizaition: Bearer token_value to request + request.addHeader(new sdk.Header({ + key: 'Authorization', + value: 'Bearer ' + config.token + })); + + return request; +} + +/** + * Adds Authorization header for basic authorization + * Authorization: basic buagwdnasdw== + * + * @param {PostmanRequest} request - Postman Request instance + */ +function basic (request) { + let config = {}, + auth = request.auth; + // TODO add desc for header + // creating congif obj for easy access of elements + auth.basic.each((element) => { + config[element.key] = element.value; + }); + + request.addHeader(new sdk.Header({ + key: 'Authorization', + value: 'Basic ' + toBase64(`${config.username}:${config.password}`) + })); + + return request; +} + +/** + * Adds Authorization header for basic authorization + * RFC 7616: Extended version of RFC 2617 but is backward compatible with it. + * reference: https://tools.ietf.org/html/rfc7616 + * + * @param request - Postman Request instance + * @returns {PostmanRequest} Request Instance with added authorized headers/params + */ +function digest (request) { + let config = {}, + auth = request.auth, + username, password, realm, nonce, + algorithm, qop, ncount, cnonce, opaque, A1, A2, response, authHeaderValue; + + // TODO add desc for header + // creating congif obj for easy access of elements + auth.digest.each((element) => { + config[element.key] = element.value; + }); + + // if username or password is not given then dont add any auth headers + if (config.username === '' || config.password === '') { + return request; + } + + // elements of digest auth + username = config.username; + password = config.password; + realm = config.realm ? config.realm : 'testrealm@example.com'; + nonce = config.ncount ? config.ncount : toBase64(crypto.randomBytes(8)); + algorithm = config.algorithm.replace(/-/g, '') ? config.algorithm.toLowerCase() : 'md5'; + qop = config.qop ? config.qop : 'auth-init'; + ncount = config.ncount ? config.ncount : '00000001'; + cnonce = config.cnounce ? config.cnounce : uuid().replace(/-/g, ''); + + /** + * A1 calculation according to RFC 7616 + */ + if (algorithm.match(/[.]*-sess/g)) { + /** + * A1 calculation for algorithms with -sess (session) + * Algorithms: MD5-sess, SHA-256-sess, SHA-512-sess + * A1 = (username : realm : password) : ncount : cnonce + */ + A1 = hash[algorithm](username + ':' + realm + ':' + password) + ':' + ncount + ':' + cnonce; + } + else { + /** + * A1 calculation for algorithms with -sess (session) + * Algorithms: MD5, SHA-256, SHA-512 + * A1 = (username : realm : password) + */ + A1 = username + ':' + realm + ':' + password; + } + + /** + * A2 calculation according to RFC 7616 + */ + if (qop === 'auth') { + /** + * If qop = auth or undefined: + * A2 = method : request-uri + */ + A2 = request.method + ':' + request.url; + } + else if (qop === 'auth-init') { + /** + * if qop = auth-init: + * A2 = method : request-uri : (entity-body) + */ + // TODO find what is body-entity + } + + /** + * Calculation of response as per RFC 7616 + * response = ((A1) : nonce : ncount : cnonce : qop : (A2)) + * where () hashes input string with given algorithm + */ + response = hash[algorithm](hash[algorithm](A1) + ':' + + nonce + ':' + + ncount + ':' + + cnonce + ':' + + qop + ':' + + hash[algorithm](A2)); + // authHeaderValue = `Digest username$"{username}", ` return request; } @@ -52,7 +200,20 @@ function api_key (request = authRequest.API_KEY.KEY_VAL_HEADER) { function authorize (request) { var authorizedRequest = null; if (request.auth.type === 'apikey') { - authorizedRequest = api_key(request); + authorizedRequest = apiKey(request); + } + else if (request.auth.type === 'bearer') { + authorizedRequest = bearerToken(request); + } + else if (request.auth.type === 'basic') { + authorizedRequest = basic(request); + } + else if (request.auth.type === 'digest') { + authorizedRequest = digest(request); + } + else { + // TODO return legit err + console.log('return err'); } return authorizedRequest; } From e1939c6c91542df95af0a7d71cbe6662c9cb41a3 Mon Sep 17 00:00:00 2001 From: Somesh Date: Sat, 11 Jul 2020 02:07:37 +0530 Subject: [PATCH 08/25] Adds Digest Authorization - adds auth header to input request - follows RFC 7616 --- lib/auth.js | 37 +++++++++++++++++++++++++++++++++++-- 1 file changed, 35 insertions(+), 2 deletions(-) diff --git a/lib/auth.js b/lib/auth.js index 822ebb4..cba200b 100644 --- a/lib/auth.js +++ b/lib/auth.js @@ -96,6 +96,10 @@ function basic (request) { config[element.key] = element.value; }); + /** + * Authorization header: + * Basic base64(username:password) + */ request.addHeader(new sdk.Header({ key: 'Authorization', value: 'Basic ' + toBase64(`${config.username}:${config.password}`) @@ -115,7 +119,7 @@ function basic (request) { function digest (request) { let config = {}, auth = request.auth, - username, password, realm, nonce, + username, password, realm, nonce, uri, algorithm, qop, ncount, cnonce, opaque, A1, A2, response, authHeaderValue; // TODO add desc for header @@ -134,10 +138,14 @@ function digest (request) { password = config.password; realm = config.realm ? config.realm : 'testrealm@example.com'; nonce = config.ncount ? config.ncount : toBase64(crypto.randomBytes(8)); + // changing algorithm format by removing '-' and converting to lowercase + // for better access from helpers algorithm = config.algorithm.replace(/-/g, '') ? config.algorithm.toLowerCase() : 'md5'; qop = config.qop ? config.qop : 'auth-init'; ncount = config.ncount ? config.ncount : '00000001'; cnonce = config.cnounce ? config.cnounce : uuid().replace(/-/g, ''); + uri = '/' + request.url.path.join('/') + '?' + sdk.QueryParam.unparse(request.url.query.members); + opaque = config.opaque; /** * A1 calculation according to RFC 7616 @@ -188,7 +196,32 @@ function digest (request) { cnonce + ':' + qop + ':' + hash[algorithm](A2)); - // authHeaderValue = `Digest username$"{username}", ` + + /** + * Digest Auth header value + * Contains: + * - username + * - realm + * - nonce + * - uri + * - [algorithm] - default(MD5) + * -- Format "ALGONAME-VARIANT" + "-sess" (if session tag is required) + * - [qop] - default (auth) + * - nc + * - cnonce + * - opaque + */ + authHeaderValue = `Digest username="${username}", realm="${realm}", nonce="${nonce}", uri="${uri}",`; + authHeaderValue += ` algorithm="${config.algorithm}", qop="${qop}", nc="${ncount}",`; + authHeaderValue += ` cnonce="${cnonce}", response="${response}"`; + authHeaderValue += opaque ? ` opaque="${opaque}"` : ''; + + // Adding digest auth header to request + request.addHeader(new sdk.Header({ + key: 'Authentication', + value: authHeaderValue + })); + return request; } From b1922450e29a06b72cd44ac9d85d1b7982eef136 Mon Sep 17 00:00:00 2001 From: Somesh Date: Sat, 11 Jul 2020 02:09:06 +0530 Subject: [PATCH 09/25] Adds necessary comments to auth_requests --- test/fixtures/auth_requests.js | 27 +++++++++++++++++++++++++++ 1 file changed, 27 insertions(+) diff --git a/test/fixtures/auth_requests.js b/test/fixtures/auth_requests.js index b4d611b..2195545 100644 --- a/test/fixtures/auth_requests.js +++ b/test/fixtures/auth_requests.js @@ -1,7 +1,19 @@ const sdk = require('postman-collection'); +/** + * JSON containing request used for requestAuth testing + * Format: + * root { + * auth_method { + * PARAMETERS_CAPS_ON : PostmanRequest instance for this auth and config + * } + * } + */ module.exports = { + + // api key authorization 'API_KEY': { + // if api key & value are given with input type as header 'KEY_VAL_HEADER': new sdk.Request({ 'url': { 'protocol': 'https', @@ -34,6 +46,7 @@ module.exports = { }] } }), + // if only api key is given with input type as header 'KEY_HEADER': new sdk.Request({ 'url': { 'protocol': 'https', @@ -66,6 +79,7 @@ module.exports = { }] } }), + // if api key is not given and input type is header 'HEADER': new sdk.Request({ 'url': { 'protocol': 'https', @@ -98,6 +112,7 @@ module.exports = { }] } }), + // if api key and value are given with in type as query params 'KEY_VAL_QUERY': new sdk.Request({ 'url': { 'protocol': 'https', @@ -130,6 +145,7 @@ module.exports = { }] } }), + // if only api key is given with input type as query param 'KEY_QUERY': new sdk.Request({ 'url': { 'protocol': 'https', @@ -162,6 +178,7 @@ module.exports = { }] } }), + // if neither api key or value is given with in type as query param 'QUERY': new sdk.Request({ 'url': { 'protocol': 'https', @@ -195,7 +212,10 @@ module.exports = { } }) }, + + // bearer token authorization 'BEARER_TOKEN': { + // when bearer token is given 'TOKEN': new sdk.Request({ 'url': { 'protocol': 'https', @@ -221,7 +241,10 @@ module.exports = { } }) }, + + // basic authorization 'BASIC': { + // if username password are given 'USERNAME_PASSWORD': new sdk.Request({ 'url': { 'protocol': 'https', @@ -250,6 +273,7 @@ module.exports = { }] } }), + // if only username is given 'USERNAME': new sdk.Request({ 'url': { 'protocol': 'https', @@ -278,6 +302,7 @@ module.exports = { }] } }), + // if only password is given 'PASSWORD': new sdk.Request({ 'url': { 'protocol': 'https', @@ -306,6 +331,8 @@ module.exports = { }] } }), + + // if neither username nor password is given 'NO_USERNAME_NO_PASS': new sdk.Request({ 'url': { 'protocol': 'https', From 2a1f1f1b7c922359e30d12fba15e8a453224a24a Mon Sep 17 00:00:00 2001 From: Somesh Date: Sun, 12 Jul 2020 04:39:10 +0530 Subject: [PATCH 10/25] linting fix --- lib/helpers.js | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/lib/helpers.js b/lib/helpers.js index 4a19a61..5470f75 100644 --- a/lib/helpers.js +++ b/lib/helpers.js @@ -57,11 +57,23 @@ function sha512 (str) { return crypto.createHash('sha512').update(str).digest('hex'); } +/** + * Creates a HMAC hash with input secter key and algorithm + * + * @param secret - Secret key + * @param str - String whose hash has to be calculated + * @param algorithm - algorithm to be used + */ +function hmac (secret, str, algorithm) { + return crypto.createHmac(algorithm, secret).update(str).digest('hex'); +} + module.exports = { hash: { md5, sha256, - sha512 + sha512, + hmac }, toBase64, copy From 7eb61e483312a6012078017d759f10dfe4ede589 Mon Sep 17 00:00:00 2001 From: Somesh Date: Mon, 13 Jul 2020 02:15:12 +0530 Subject: [PATCH 11/25] Changes folder structure for auth - detached digest and hawk from auth.js - created new file for digest and hawk - path updates --- lib/auth/auth.js | 150 +++++++++++++++++++++++++++++++++++++++++++++ lib/auth/digest.js | 129 ++++++++++++++++++++++++++++++++++++++ lib/auth/hawk.js | 86 ++++++++++++++++++++++++++ 3 files changed, 365 insertions(+) create mode 100644 lib/auth/auth.js create mode 100644 lib/auth/digest.js create mode 100644 lib/auth/hawk.js diff --git a/lib/auth/auth.js b/lib/auth/auth.js new file mode 100644 index 0000000..bfa45b2 --- /dev/null +++ b/lib/auth/auth.js @@ -0,0 +1,150 @@ +/** + * Contains basic authentication methods: + * - Basic Auth + * - API key Auth + * - Bearer Token Auth + */ + +const sdk = require('postman-collection'), + digest = require('./digest'), + hawk = require('./hawk'), + { + toBase64 + } = require('../helpers'); + + +/** + * Adds necessary headers / url params for api key authentication + * In header -> key:value + * In query param -> url?key=value + * + * @param {PostmanRequest} request - Postman Request instance + */ +function apiKey (request) { + let config = {}, + auth = request.auth; + // TODO add desc for query param and new headers + // creating congif obj for easy access of elements + auth.apikey.each((element) => { + config[element.key] = element.value; + }); + + // if api key `key` is empty then dont add + if (config.key === '') { + return request; + } + + // if apikey auth in type is headers + if (config.in === 'header') { + // adding apikey auth headers to request + request.addHeader(new sdk.Header({ + key: config.key, + value: config.value + })); + } + // if apikey auth in type is query params + else if (config.in === 'query') { + // adding new query param to request for api key + request.url.addQueryParams(new sdk.QueryParam({ + key: config.key, + value: config.value + })); + } + else { + // TODO setup auth errors and return from here + // temp return + return 'error'; + } + + return request; +} + +/** + * Adds Authorization header for bearer authorization + * Authorization: Bearer bearer_token + * + * @param {PostmanRequest} request - Postman Request instance + */ +function bearerToken (request) { + let config = {}, + auth = request.auth; + // TODO add desc for header + // creating congif obj for easy access of elements + auth.bearer.each((element) => { + config[element.key] = element.value; + }); + + // if bearer token is empty then dont add + if (config.token === '') { + return request; + } + + // Adds Authorizaition: Bearer token_value to request + request.addHeader(new sdk.Header({ + key: 'Authorization', + value: 'Bearer ' + config.token + })); + + return request; +} + +/** + * Adds Authorization header for basic authorization + * Authorization: basic buagwdnasdw== + * + * @param {PostmanRequest} request - Postman Request instance + */ +function basic (request) { + let config = {}, + auth = request.auth; + // TODO add desc for header + // creating congif obj for easy access of elements + auth.basic.each((element) => { + config[element.key] = element.value; + }); + + /** + * Authorization header: + * Basic base64(username:password) + */ + request.addHeader(new sdk.Header({ + key: 'Authorization', + value: 'Basic ' + toBase64(`${config.username}:${config.password}`) + })); + + return request; +} + +/** + * Authorizes a postman-request instance + * + * @param {PostmanRequest} request - Request instance which needs to be authorized + */ +function authorize (request) { + if (!sdk.Request.isRequest(request)) { + throw new Error('Invalid Request Instance'); + } + var authorizedRequest = null; + if (request.auth.type === 'apikey') { + authorizedRequest = apiKey(request); + } + else if (request.auth.type === 'bearer') { + authorizedRequest = bearerToken(request); + } + else if (request.auth.type === 'basic') { + authorizedRequest = basic(request); + } + else if (request.auth.type === 'digest') { + authorizedRequest = digest(request); + } + else if (request.auth.type === 'hawk') { + authorizedRequest = hawk(request); + } + else { + // TODO return legit err + console.log('return err'); + } + return authorizedRequest; +} + +module.exports = authorize; diff --git a/lib/auth/digest.js b/lib/auth/digest.js new file mode 100644 index 0000000..d155d52 --- /dev/null +++ b/lib/auth/digest.js @@ -0,0 +1,129 @@ +const sdk = require('postman-collection'), + { + hash, + toBase64 + } = require('../helpers'); + +/** + * Adds Authorization header for Digest authorization + * RFC 7616: Extended version of RFC 2617 but is backward compatible with it. + * + * Reference: https://tools.ietf.org/html/rfc7616 + * + * @param request - Postman Request instance + * @returns {PostmanRequest} Request Instance with added authorized headers/params + * TODO add test for digest auth + * TODO add auth requests for digest auth + */ +function digest (request) { + let config = {}, + auth = request.auth, + username, password, realm, nonce, uri, + algorithm, qop, ncount, cnonce, opaque, A1, A2, response, authHeaderValue; + + // TODO add desc for header + // creating congif obj for easy access of elements + auth.digest.each((element) => { + config[element.key] = element.value; + }); + + // if username or password is not given then dont add any auth headers + if (config.username === '' || config.password === '') { + return request; + } + + // elements of digest auth + username = config.username; + password = config.password; + realm = config.realm || 'testrealm@example.com'; + nonce = config.ncount || toBase64(ramdomString(8)); + // changing algorithm format by removing '-' and converting to lowercase + // for better access from helpers + algorithm = config.algorithm.toLowerCase() || 'md5'; + qop = config.qop || 'auth-init'; + ncount = config.ncount || '00000001'; + cnonce = config.cnounce || toBase64(ramdomString(8)); + uri = '/' + request.url.path.join + sdk.QueryParam.unparse(request.url.query.members); + opaque = config.opaque; + + /** + * A1 calculation according to RFC 7616 + */ + if (algorithm.match(/[.]*-sess/g)) { + /** + * A1 calculation for algorithms with -sess (session) + * Algorithms: MD5-sess, SHA-256-sess, SHA-512-sess + * A1 = (username : realm : password) : ncount : cnonce + */ + A1 = hash[algorithm](username + ':' + realm + ':' + password) + ':' + ncount + ':' + cnonce; + } + else { + /** + * A1 calculation for algorithms with -sess (session) + * Algorithms: MD5, SHA-256, SHA-512 + * A1 = (username : realm : password) + */ + A1 = username + ':' + realm + ':' + password; + } + + /** + * A2 calculation according to RFC 7616 + */ + if (qop === 'auth') { + /** + * If qop = auth or undefined: + * A2 = method : request-uri + */ + A2 = request.method + ':' + request.url; + } + else if (qop === 'auth-init') { + /** + * if qop = auth-init: + * A2 = method : request-uri : (entity-body) + */ + // TODO find what is body-entity + } + + /** + * Calculation of response as per RFC 7616 + * response = ((A1) : nonce : ncount : cnonce : qop : (A2)) + * where () hashes input string with given algorithm + */ + response = hash[algorithm]([ + hash[algorithm](A1), + nonce, + ncount, + cnonce, + qop, + hash[algorithm](A2) + ].join(':')); + + /** + * Digest Auth header value + * Contains: + * - username + * - realm + * - nonce + * - uri + * - [algorithm] - default(MD5) + * -- Format "ALGONAME-VARIANT" + "-sess" (if session tag is required) + * - [qop] - default (auth) + * - nc + * - cnonce + * - opaque + */ + authHeaderValue = `Digest username="${username}", realm="${realm}", nonce="${nonce}", uri="${uri}",`; + authHeaderValue += ` algorithm="${config.algorithm}", qop="${qop}", nc="${ncount}",`; + authHeaderValue += ` cnonce="${cnonce}", response="${response}"`; + authHeaderValue += opaque ? ` opaque="${opaque}"` : ''; + + // Adding digest auth header to request + request.addHeader(new sdk.Header({ + key: 'Authentication', + value: authHeaderValue + })); + + return request; +} + +module.exports = digest; diff --git a/lib/auth/hawk.js b/lib/auth/hawk.js new file mode 100644 index 0000000..053d91c --- /dev/null +++ b/lib/auth/hawk.js @@ -0,0 +1,86 @@ +const sdk = require('postman-collection'), + { + randomString, + hash, + toBase64 + } = require('../helpers'); + +/** + * Adds necessary headers for Hawk Authorization + * Uses a hashed string called MAC (Message Authentication Code) to authorize requests + * + * @param {PostmanRequest} request - Postman Request instance + * @returns {PostmanRequest} Request Instance with added authorized headers/params + * TODO add tests for hawk auth + * TODO add auth requests for hawk auth + */ +function hawk (request) { + let config = {}, + auth = request.auth, + HAWK_HEADER = 'hawk.1.header', + nonce, hawkNormalizedString, + uri, host, method, port, + mac, hawkHeaderValue, ts; + // TODO add desc for header + // creating congif obj for easy access of elements + auth.hawk.each((element) => { + config[element.key] = element.value; + }); + + // if Authid and Authkey is not provided return without change + if (!config.authId || !config.authKey) { + return request; + } + + // Hawk Authorization elements + ts = config.timestamp || new Date().getTime(); + nonce = config.nonce || randomString(8); + method = request.method; + uri = '/' + sdk.QueryParam.unparse(request.url.query.members); + host = request.url.host.join('.'); + port = '443'; + + /** + * This is a normalised string containg the following elements + * - 'hawk.1.header' + * - time stamp + * - nonce + * - METHOD + * - path + * - host + * - 443 + * All these parameters are newline saperated + */ + hawkNormalizedString = [HAWK_HEADER, ts, nonce, method, uri, host, port].join('\n'); + + /** + * MAC calculation done with hmac with secret as authKey and input string as normalized string + */ + mac = toBase64(hash.hmac(config.authKey, hawkNormalizedString, config.algorithm)); + + /** + * Hawk header contains the following data unquoted saperated by comma and space + * Header Contains: + * - AuthId + * - ts(timestamp) + * - nonce + * - mac (calculation shown below) + * - [username] + * - [ext] - Extra app data + * - [app] - App id + * - [dlg] - delegated by + */ + hawkHeaderValue = `Hawk id=${config.authId}, ts=${ts}, nonce=${nonce}, mac=${mac}`; + hawkHeaderValue += config.extraData ? ` ext=${config.extraData}` : ''; + hawkHeaderValue += config.app ? ` app=${config.app}` : ''; + hawkHeaderValue += config.delegation ? ` dlg=${config.delegation}` : ''; + + // Adding hawk header to request + request.addHeader(new sdk.Header({ + key: 'Authorization', + value: hawkHeaderValue + })); + return request; +} + +module.exports = hawk; From 6459ce1b0f4c37b993c316eede8065c6ad399d8c Mon Sep 17 00:00:00 2001 From: Somesh Date: Wed, 15 Jul 2020 06:09:57 +0530 Subject: [PATCH 12/25] Adds auth parameter to request authorizer method - Adds auth param to all authorization methods - adds auth params to authorizer method - adds proper jsdocs for all methods related to auth --- lib/auth/auth.js | 46 ++++++++++++++++++++++++---------------------- lib/auth/digest.js | 8 ++++---- lib/auth/hawk.js | 8 ++++---- 3 files changed, 32 insertions(+), 30 deletions(-) diff --git a/lib/auth/auth.js b/lib/auth/auth.js index bfa45b2..75b28b1 100644 --- a/lib/auth/auth.js +++ b/lib/auth/auth.js @@ -18,12 +18,12 @@ const sdk = require('postman-collection'), * In header -> key:value * In query param -> url?key=value * - * @param {PostmanRequest} request - Postman Request instance + * @param {sdk.Request} request - Request Instance + * @param {sdk.RequestAuth} auth - Auth that needs to be applied to the given request + * @returns {sdk.Request} - Requst with applied auth */ -function apiKey (request) { - let config = {}, - auth = request.auth; - // TODO add desc for query param and new headers +function apiKey (request, auth) { + let config = {}; // creating congif obj for easy access of elements auth.apikey.each((element) => { config[element.key] = element.value; @@ -63,12 +63,12 @@ function apiKey (request) { * Adds Authorization header for bearer authorization * Authorization: Bearer bearer_token * - * @param {PostmanRequest} request - Postman Request instance + * @param {sdk.Request} request - Request Instance + * @param {sdk.RequestAuth} auth - Auth that needs to be applied to the given request + * @returns {sdk.Request} - Requst with applied auth */ -function bearerToken (request) { - let config = {}, - auth = request.auth; - // TODO add desc for header +function bearerToken (request, auth) { + let config = {}; // creating congif obj for easy access of elements auth.bearer.each((element) => { config[element.key] = element.value; @@ -92,12 +92,12 @@ function bearerToken (request) { * Adds Authorization header for basic authorization * Authorization: basic buagwdnasdw== * - * @param {PostmanRequest} request - Postman Request instance + * @param {sdk.Request} request - Request Instance + * @param {sdk.RequestAuth} auth - Auth that needs to be applied to the given request + * @returns {sdk.Request} - Requst with applied auth */ -function basic (request) { - let config = {}, - auth = request.auth; - // TODO add desc for header +function basic (request, auth) { + let config = {}; // creating congif obj for easy access of elements auth.basic.each((element) => { config[element.key] = element.value; @@ -118,27 +118,29 @@ function basic (request) { /** * Authorizes a postman-request instance * - * @param {PostmanRequest} request - Request instance which needs to be authorized + * @param {sdk.Request} request - Request Instance + * @param {sdk.RequestAuth} auth - Auth that needs to be applied to the given request + * @returns {sdk.Request} - Requst with applied auth */ -function authorize (request) { +function authorize (request, auth) { if (!sdk.Request.isRequest(request)) { throw new Error('Invalid Request Instance'); } var authorizedRequest = null; if (request.auth.type === 'apikey') { - authorizedRequest = apiKey(request); + authorizedRequest = apiKey(request, auth); } else if (request.auth.type === 'bearer') { - authorizedRequest = bearerToken(request); + authorizedRequest = bearerToken(request, auth); } else if (request.auth.type === 'basic') { - authorizedRequest = basic(request); + authorizedRequest = basic(request, auth); } else if (request.auth.type === 'digest') { - authorizedRequest = digest(request); + authorizedRequest = digest(request, auth); } else if (request.auth.type === 'hawk') { - authorizedRequest = hawk(request); + authorizedRequest = hawk(request, auth); } else { // TODO return legit err diff --git a/lib/auth/digest.js b/lib/auth/digest.js index d155d52..21eca47 100644 --- a/lib/auth/digest.js +++ b/lib/auth/digest.js @@ -10,14 +10,14 @@ const sdk = require('postman-collection'), * * Reference: https://tools.ietf.org/html/rfc7616 * - * @param request - Postman Request instance - * @returns {PostmanRequest} Request Instance with added authorized headers/params + * @param {sdk.Request} request - Request Instance + * @param {sdk.RequestAuth} auth - Auth that needs to be applied to the given request + * @returns {sdk.Request} - Requst with applied auth * TODO add test for digest auth * TODO add auth requests for digest auth */ -function digest (request) { +function digest (request, auth) { let config = {}, - auth = request.auth, username, password, realm, nonce, uri, algorithm, qop, ncount, cnonce, opaque, A1, A2, response, authHeaderValue; diff --git a/lib/auth/hawk.js b/lib/auth/hawk.js index 053d91c..1a8f70e 100644 --- a/lib/auth/hawk.js +++ b/lib/auth/hawk.js @@ -9,14 +9,14 @@ const sdk = require('postman-collection'), * Adds necessary headers for Hawk Authorization * Uses a hashed string called MAC (Message Authentication Code) to authorize requests * - * @param {PostmanRequest} request - Postman Request instance - * @returns {PostmanRequest} Request Instance with added authorized headers/params + * @param {sdk.Request} request - Request Instance + * @param {sdk.RequestAuth} auth - Auth that needs to be applied to the given request + * @returns {sdk.Request} - Requst with applied auth * TODO add tests for hawk auth * TODO add auth requests for hawk auth */ -function hawk (request) { +function hawk (request, auth) { let config = {}, - auth = request.auth, HAWK_HEADER = 'hawk.1.header', nonce, hawkNormalizedString, uri, host, method, port, From 7a82a097a360026167478e40e193e538ad29b6c7 Mon Sep 17 00:00:00 2001 From: Somesh Date: Thu, 16 Jul 2020 03:39:13 +0530 Subject: [PATCH 13/25] changes apikey auth insertion point - fixes authorize method's param parsing - fixes apikey auth insertion point error (default => header) --- lib/auth/auth.js | 29 ++++++++++++----------------- 1 file changed, 12 insertions(+), 17 deletions(-) diff --git a/lib/auth/auth.js b/lib/auth/auth.js index 75b28b1..15fedab 100644 --- a/lib/auth/auth.js +++ b/lib/auth/auth.js @@ -35,26 +35,21 @@ function apiKey (request, auth) { } // if apikey auth in type is headers - if (config.in === 'header') { - // adding apikey auth headers to request - request.addHeader(new sdk.Header({ + if (config.in === 'query') { + // adding new query param to request for api key + request.url.addQueryParams(new sdk.QueryParam({ key: config.key, value: config.value })); } // if apikey auth in type is query params - else if (config.in === 'query') { - // adding new query param to request for api key - request.url.addQueryParams(new sdk.QueryParam({ + else { + // adding apikey auth headers to request + request.addHeader(new sdk.Header({ key: config.key, value: config.value })); } - else { - // TODO setup auth errors and return from here - // temp return - return 'error'; - } return request; } @@ -126,20 +121,20 @@ function authorize (request, auth) { if (!sdk.Request.isRequest(request)) { throw new Error('Invalid Request Instance'); } - var authorizedRequest = null; - if (request.auth.type === 'apikey') { + let authorizedRequest = null; + if (auth.type === 'apikey') { authorizedRequest = apiKey(request, auth); } - else if (request.auth.type === 'bearer') { + else if (auth.type === 'bearer') { authorizedRequest = bearerToken(request, auth); } - else if (request.auth.type === 'basic') { + else if (auth.type === 'basic') { authorizedRequest = basic(request, auth); } - else if (request.auth.type === 'digest') { + else if (auth.type === 'digest') { authorizedRequest = digest(request, auth); } - else if (request.auth.type === 'hawk') { + else if (auth.type === 'hawk') { authorizedRequest = hawk(request, auth); } else { From c18c37e5a9e2acba6470858578f0ad66a6983526 Mon Sep 17 00:00:00 2001 From: Somesh Koli Date: Sat, 25 Jul 2020 01:33:19 +0530 Subject: [PATCH 14/25] Removes hawk auth from authorize method - syntax imporovements to authorizing request calls - adds unknows error to errors --- lib/assets/error.js | 13 +++ lib/auth.js | 254 -------------------------------------------- lib/auth/auth.js | 24 ++--- 3 files changed, 20 insertions(+), 271 deletions(-) create mode 100644 lib/assets/error.js delete mode 100644 lib/auth.js diff --git a/lib/assets/error.js b/lib/assets/error.js new file mode 100644 index 0000000..3e655cb --- /dev/null +++ b/lib/assets/error.js @@ -0,0 +1,13 @@ +/* eslint-disable max-len */ +module.exports = { + 'INVALID_COLLECTION_SOURCE': new Error('Invalid collection source. Requires json/PostmanCollection/file/url'), + 'INVALID_URL': new Error('Invalid url. Insert a proper url which returns json on get request.'), + 'UNABLE_TO_WRITE_FILE': new Error('Unable to write output to the file.'), + 'INVALID_LANGUAGE': new Error('SDK generator for this language does not exist.' + + 'Check getLanguageList to get list of available languages'), + 'INVALID_VAIRIANT': new Error('SDK generator for this language variant does not exist' + + 'Check getLanguageList method to get list of available sdk generators'), + 'INVALID_PARAMETER': new Error('Invalid parameter. Check docs and provide correct parameters'), + 'FILE_PATH_NOT_PROVIDED': new Error('File path not provided. The outpuFilePath option is required if outputType is set to File'), + 'INVALID_AUTH_TYPE': new Error('Unknown authorizaition method') +}; diff --git a/lib/auth.js b/lib/auth.js deleted file mode 100644 index cba200b..0000000 --- a/lib/auth.js +++ /dev/null @@ -1,254 +0,0 @@ -const sdk = require('postman-collection'), - uuid = require('uuid/v4'), - crypto = require('crypto'), - helpers = require('./helpers'); - -var toBase64 = helpers.toBase64, - hash = helpers.hash; - -/** - * Adds necessary headers / url params for api key authentication - * In header -> key:value - * In query param -> url?key=value - * - * @param request - Postman Request instance - */ -function api_key (request = authRequest.API_KEY.KEY_VAL_HEADER) { - let config = {}, - auth = request.auth; - // TODO add desc for query param and new headers - // creating congif obj for easy access of elements - auth.apikey.each((element) => { - config[element.key] = element.value; - }); - - // if api key `key` is empty then dont add - if (config.key === '') { - return request; - } - - // if apikey auth in type is headers - if (config.in === 'header') { - // adding apikey auth headers to request - request.addHeader(new sdk.Header({ - key: config.key, - value: config.value - })); - } - // if apikey auth in type is query params - else if (config.in === 'query') { - // adding new query param to request for api key - request.url.addQueryParams(new sdk.QueryParam({ - key: config.key, - value: config.value - })); - } - else { - // TODO setup auth errors and return from here - // temp return - return 'error'; - } - - return request; -} - -/** - * Adds Authorization header for bearer authorization - * Authorization: Bearer bearer_token - * - * @param {PostmanRequest} request - Postman Request instance - */ -function bearerToken (request) { - let config = {}, - auth = request.auth; - // TODO add desc for header - // creating congif obj for easy access of elements - auth.bearer.each((element) => { - config[element.key] = element.value; - }); - - // if bearer token is empty then dont add - if (config.token === '') { - return request; - } - - // Adds Authorizaition: Bearer token_value to request - request.addHeader(new sdk.Header({ - key: 'Authorization', - value: 'Bearer ' + config.token - })); - - return request; -} - -/** - * Adds Authorization header for basic authorization - * Authorization: basic buagwdnasdw== - * - * @param {PostmanRequest} request - Postman Request instance - */ -function basic (request) { - let config = {}, - auth = request.auth; - // TODO add desc for header - // creating congif obj for easy access of elements - auth.basic.each((element) => { - config[element.key] = element.value; - }); - - /** - * Authorization header: - * Basic base64(username:password) - */ - request.addHeader(new sdk.Header({ - key: 'Authorization', - value: 'Basic ' + toBase64(`${config.username}:${config.password}`) - })); - - return request; -} - -/** - * Adds Authorization header for basic authorization - * RFC 7616: Extended version of RFC 2617 but is backward compatible with it. - * reference: https://tools.ietf.org/html/rfc7616 - * - * @param request - Postman Request instance - * @returns {PostmanRequest} Request Instance with added authorized headers/params - */ -function digest (request) { - let config = {}, - auth = request.auth, - username, password, realm, nonce, uri, - algorithm, qop, ncount, cnonce, opaque, A1, A2, response, authHeaderValue; - - // TODO add desc for header - // creating congif obj for easy access of elements - auth.digest.each((element) => { - config[element.key] = element.value; - }); - - // if username or password is not given then dont add any auth headers - if (config.username === '' || config.password === '') { - return request; - } - - // elements of digest auth - username = config.username; - password = config.password; - realm = config.realm ? config.realm : 'testrealm@example.com'; - nonce = config.ncount ? config.ncount : toBase64(crypto.randomBytes(8)); - // changing algorithm format by removing '-' and converting to lowercase - // for better access from helpers - algorithm = config.algorithm.replace(/-/g, '') ? config.algorithm.toLowerCase() : 'md5'; - qop = config.qop ? config.qop : 'auth-init'; - ncount = config.ncount ? config.ncount : '00000001'; - cnonce = config.cnounce ? config.cnounce : uuid().replace(/-/g, ''); - uri = '/' + request.url.path.join('/') + '?' + sdk.QueryParam.unparse(request.url.query.members); - opaque = config.opaque; - - /** - * A1 calculation according to RFC 7616 - */ - if (algorithm.match(/[.]*-sess/g)) { - /** - * A1 calculation for algorithms with -sess (session) - * Algorithms: MD5-sess, SHA-256-sess, SHA-512-sess - * A1 = (username : realm : password) : ncount : cnonce - */ - A1 = hash[algorithm](username + ':' + realm + ':' + password) + ':' + ncount + ':' + cnonce; - } - else { - /** - * A1 calculation for algorithms with -sess (session) - * Algorithms: MD5, SHA-256, SHA-512 - * A1 = (username : realm : password) - */ - A1 = username + ':' + realm + ':' + password; - } - - /** - * A2 calculation according to RFC 7616 - */ - if (qop === 'auth') { - /** - * If qop = auth or undefined: - * A2 = method : request-uri - */ - A2 = request.method + ':' + request.url; - } - else if (qop === 'auth-init') { - /** - * if qop = auth-init: - * A2 = method : request-uri : (entity-body) - */ - // TODO find what is body-entity - } - - /** - * Calculation of response as per RFC 7616 - * response = ((A1) : nonce : ncount : cnonce : qop : (A2)) - * where () hashes input string with given algorithm - */ - response = hash[algorithm](hash[algorithm](A1) + ':' + - nonce + ':' + - ncount + ':' + - cnonce + ':' + - qop + ':' + - hash[algorithm](A2)); - - /** - * Digest Auth header value - * Contains: - * - username - * - realm - * - nonce - * - uri - * - [algorithm] - default(MD5) - * -- Format "ALGONAME-VARIANT" + "-sess" (if session tag is required) - * - [qop] - default (auth) - * - nc - * - cnonce - * - opaque - */ - authHeaderValue = `Digest username="${username}", realm="${realm}", nonce="${nonce}", uri="${uri}",`; - authHeaderValue += ` algorithm="${config.algorithm}", qop="${qop}", nc="${ncount}",`; - authHeaderValue += ` cnonce="${cnonce}", response="${response}"`; - authHeaderValue += opaque ? ` opaque="${opaque}"` : ''; - - // Adding digest auth header to request - request.addHeader(new sdk.Header({ - key: 'Authentication', - value: authHeaderValue - })); - - return request; -} - -/** - * Authorizes a postman-request instance - * - * @param {PostmanRequest} request - Request instance which needs to be authorized - */ -function authorize (request) { - var authorizedRequest = null; - if (request.auth.type === 'apikey') { - authorizedRequest = apiKey(request); - } - else if (request.auth.type === 'bearer') { - authorizedRequest = bearerToken(request); - } - else if (request.auth.type === 'basic') { - authorizedRequest = basic(request); - } - else if (request.auth.type === 'digest') { - authorizedRequest = digest(request); - } - else { - // TODO return legit err - console.log('return err'); - } - return authorizedRequest; -} - -module.exports = authorize; diff --git a/lib/auth/auth.js b/lib/auth/auth.js index 15fedab..1625522 100644 --- a/lib/auth/auth.js +++ b/lib/auth/auth.js @@ -6,8 +6,8 @@ */ const sdk = require('postman-collection'), + errors = require('../assets/error'), digest = require('./digest'), - hawk = require('./hawk'), { toBase64 } = require('../helpers'); @@ -118,30 +118,20 @@ function basic (request, auth) { * @returns {sdk.Request} - Requst with applied auth */ function authorize (request, auth) { - if (!sdk.Request.isRequest(request)) { - throw new Error('Invalid Request Instance'); - } - let authorizedRequest = null; if (auth.type === 'apikey') { - authorizedRequest = apiKey(request, auth); + return apiKey(request, auth); } else if (auth.type === 'bearer') { - authorizedRequest = bearerToken(request, auth); + return bearerToken(request, auth); } else if (auth.type === 'basic') { - authorizedRequest = basic(request, auth); + return basic(request, auth); } else if (auth.type === 'digest') { - authorizedRequest = digest(request, auth); - } - else if (auth.type === 'hawk') { - authorizedRequest = hawk(request, auth); + return digest(request, auth); } - else { - // TODO return legit err - console.log('return err'); - } - return authorizedRequest; + + throw errors.INVALID_AUTH_TYPE; } module.exports = authorize; From 06a650eebd9cef234e54c32f1154fdc29d62ce09 Mon Sep 17 00:00:00 2001 From: Somesh Koli Date: Sun, 26 Jul 2020 04:45:24 +0530 Subject: [PATCH 15/25] fixes auth test - changes authorize method call from authorize(request) > authrize(request, auth) - fixes authorizze method impor path --- test/unit/auth.test.js | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/test/unit/auth.test.js b/test/unit/auth.test.js index 08311a3..1cc918b 100644 --- a/test/unit/auth.test.js +++ b/test/unit/auth.test.js @@ -1,7 +1,7 @@ const expect = require('chai').expect, sdk = require('postman-collection'), authRequests = require('../fixtures/auth_requests'), - authorize = require('../../lib/auth'); + authorize = require('../../lib/auth/auth'); describe('Postman Request Authorization', function () { @@ -9,7 +9,7 @@ describe('Postman Request Authorization', function () { it('should add api header to request when in type is header', function () { let request = authRequests.API_KEY.KEY_VAL_HEADER; // TODO pass this request to authorize function - request = authorize(request); + request = authorize(request, request.auth); expect(request).to.be.of.instanceOf(sdk.Request); expect(request.headers.has('key')).to.be.true; expect(request.getHeaders('key').key).to.be.equal('value'); @@ -17,7 +17,7 @@ describe('Postman Request Authorization', function () { it('should add api headers to request when api value is not given', function () { let request = authRequests.API_KEY.KEY_HEADER; - request = authorize(request); + request = authorize(request, request.auth); expect(request).to.be.of.instanceOf(sdk.Request); expect(request.headers.has('key')).to.be.true; expect(request.getHeaders('key').key).to.be.equal(''); @@ -25,13 +25,13 @@ describe('Postman Request Authorization', function () { it('should not add api headers to request when api key and value is not given', function () { let request = authRequests.API_KEY.HEADER; - request = authorize(request); + request = authorize(request, request.auth); expect(request).to.be.of.instanceOf(sdk.Request); }); it('should add api key query param when api key value and in type is query', function () { let request = authRequests.API_KEY.KEY_VAL_QUERY; - request = authorize(request); + request = authorize(request, request.auth); expect(request).to.be.of.instanceOf(sdk.Request); expect(request.url.query.has('key')).to.be.true; expect(request.url.query.get('key')).to.be.equal('value'); @@ -39,7 +39,7 @@ describe('Postman Request Authorization', function () { it('should add api key query param when api key and in type is query', function () { let request = authRequests.API_KEY.KEY_QUERY; - request = authorize(request); + request = authorize(request, request.auth); expect(request).to.be.of.instanceOf(sdk.Request); expect(request.url.query.has('key')).to.be.true; expect(request.url.query.get('key')).to.be.equal(''); @@ -47,7 +47,7 @@ describe('Postman Request Authorization', function () { it('should add api key query param when api key value and in type is query', function () { let request = authRequests.API_KEY.QUERY; - request = authorize(request); + request = authorize(request, request.auth); expect(request).to.be.of.instanceOf(sdk.Request); }); }); @@ -56,7 +56,7 @@ describe('Postman Request Authorization', function () { it('should add authorization header to request when bearer token it given', function () { let request = authRequests.BEARER_TOKEN.TOKEN; - request = authorize(request); + request = authorize(request, request.auth); expect(request).to.be.instanceOf(sdk.Request); expect(request.headers.has('Authorization')).to.be.true; expect(request.getHeaders('Authorization').Authorization).to.be.equal('Bearer testbearertoken'); @@ -66,7 +66,7 @@ describe('Postman Request Authorization', function () { describe('Basic Authorization', function () { it('should add authorization header when basic authorization is used with username and password', function () { var request = authRequests.BASIC.USERNAME_PASSWORD; - request = authorize(request); + request = authorize(request, request.auth); expect(request).to.be.instanceOf(sdk.Request); expect(request.headers.has('Authorization')).to.be.true; expect(request.getHeaders('Authorization').Authorization).to.be.equal('Basic dGVzdDp0ZXN0'); @@ -74,7 +74,7 @@ describe('Postman Request Authorization', function () { it('should add authorization header when basic authorization is used with username and no password', function () { var request = authRequests.BASIC.USERNAME; - request = authorize(request); + request = authorize(request, request.auth); expect(request).to.be.instanceOf(sdk.Request); expect(request.headers.has('Authorization')).to.be.true; expect(request.getHeaders('Authorization').Authorization).to.be.equal('Basic dGVzdDo='); @@ -82,7 +82,7 @@ describe('Postman Request Authorization', function () { it('should add authorization header when basic authorization is used with just password', function () { var request = authRequests.BASIC.PASSWORD; - request = authorize(request); + request = authorize(request, request.auth); expect(request).to.be.instanceOf(sdk.Request); expect(request.headers.has('Authorization')).to.be.true; expect(request.getHeaders('Authorization').Authorization).to.be.equal('Basic OnRlc3Q='); @@ -90,7 +90,7 @@ describe('Postman Request Authorization', function () { it('should add authorization header when basic authorization is used with no username/password', function () { var request = authRequests.BASIC.NO_USERNAME_NO_PASS; - request = authorize(request); + request = authorize(request, request.auth); expect(request).to.be.instanceOf(sdk.Request); expect(request.headers.has('Authorization')).to.be.true; expect(request.getHeaders('Authorization').Authorization).to.be.equal('Basic Og=='); From 4fdeba78798c9ce0f1ee6229e9250d10dad5c344 Mon Sep 17 00:00:00 2001 From: Somesh Koli Date: Mon, 27 Jul 2020 00:23:23 +0530 Subject: [PATCH 16/25] Removes auth hawk from this branch - adds random string method to hellper - adds time stamp method to helper --- lib/auth/hawk.js | 86 ------------------------------------------------ lib/helpers.js | 33 +++++++++++++++---- 2 files changed, 27 insertions(+), 92 deletions(-) delete mode 100644 lib/auth/hawk.js diff --git a/lib/auth/hawk.js b/lib/auth/hawk.js deleted file mode 100644 index 1a8f70e..0000000 --- a/lib/auth/hawk.js +++ /dev/null @@ -1,86 +0,0 @@ -const sdk = require('postman-collection'), - { - randomString, - hash, - toBase64 - } = require('../helpers'); - -/** - * Adds necessary headers for Hawk Authorization - * Uses a hashed string called MAC (Message Authentication Code) to authorize requests - * - * @param {sdk.Request} request - Request Instance - * @param {sdk.RequestAuth} auth - Auth that needs to be applied to the given request - * @returns {sdk.Request} - Requst with applied auth - * TODO add tests for hawk auth - * TODO add auth requests for hawk auth - */ -function hawk (request, auth) { - let config = {}, - HAWK_HEADER = 'hawk.1.header', - nonce, hawkNormalizedString, - uri, host, method, port, - mac, hawkHeaderValue, ts; - // TODO add desc for header - // creating congif obj for easy access of elements - auth.hawk.each((element) => { - config[element.key] = element.value; - }); - - // if Authid and Authkey is not provided return without change - if (!config.authId || !config.authKey) { - return request; - } - - // Hawk Authorization elements - ts = config.timestamp || new Date().getTime(); - nonce = config.nonce || randomString(8); - method = request.method; - uri = '/' + sdk.QueryParam.unparse(request.url.query.members); - host = request.url.host.join('.'); - port = '443'; - - /** - * This is a normalised string containg the following elements - * - 'hawk.1.header' - * - time stamp - * - nonce - * - METHOD - * - path - * - host - * - 443 - * All these parameters are newline saperated - */ - hawkNormalizedString = [HAWK_HEADER, ts, nonce, method, uri, host, port].join('\n'); - - /** - * MAC calculation done with hmac with secret as authKey and input string as normalized string - */ - mac = toBase64(hash.hmac(config.authKey, hawkNormalizedString, config.algorithm)); - - /** - * Hawk header contains the following data unquoted saperated by comma and space - * Header Contains: - * - AuthId - * - ts(timestamp) - * - nonce - * - mac (calculation shown below) - * - [username] - * - [ext] - Extra app data - * - [app] - App id - * - [dlg] - delegated by - */ - hawkHeaderValue = `Hawk id=${config.authId}, ts=${ts}, nonce=${nonce}, mac=${mac}`; - hawkHeaderValue += config.extraData ? ` ext=${config.extraData}` : ''; - hawkHeaderValue += config.app ? ` app=${config.app}` : ''; - hawkHeaderValue += config.delegation ? ` dlg=${config.delegation}` : ''; - - // Adding hawk header to request - request.addHeader(new sdk.Header({ - key: 'Authorization', - value: hawkHeaderValue - })); - return request; -} - -module.exports = hawk; diff --git a/lib/helpers.js b/lib/helpers.js index 5470f75..c83a099 100644 --- a/lib/helpers.js +++ b/lib/helpers.js @@ -61,20 +61,41 @@ function sha512 (str) { * Creates a HMAC hash with input secter key and algorithm * * @param secret - Secret key - * @param str - String whose hash has to be calculated + * @param helper - String whose hash has to be calculated * @param algorithm - algorithm to be used */ -function hmac (secret, str, algorithm) { - return crypto.createHmac(algorithm, secret).update(str).digest('hex'); +function hmac (secret, helper, algorithm) { + return crypto.createHmac(algorithm, secret).update(helper); } +/** + * Method to return current time stamp + * + * @returns {number} timestamp + */ +function getTimeStamp () { + return Math.floor(Date.now() / 1000); +} + +/** + * Function to return random string of desired length + * + * @param length + * @returns {string} random string + */ +function randomString (length) { + return Math.random().toString(36).substring(length); + +} module.exports = { hash: { md5, sha256, - sha512, - hmac + sha512 }, + hmac, toBase64, - copy + copy, + getTimeStamp, + randomString }; From 2db78e812a0629512d88ec1a590f89a3c79e25de Mon Sep 17 00:00:00 2001 From: Somesh Koli Date: Mon, 27 Jul 2020 01:24:36 +0530 Subject: [PATCH 17/25] Removes digest auth from this branch - removes digest.js - removes digest auth check, call and import from auth.js --- lib/auth/auth.js | 4 -- lib/auth/digest.js | 129 --------------------------------------------- 2 files changed, 133 deletions(-) delete mode 100644 lib/auth/digest.js diff --git a/lib/auth/auth.js b/lib/auth/auth.js index 1625522..49294b0 100644 --- a/lib/auth/auth.js +++ b/lib/auth/auth.js @@ -7,7 +7,6 @@ const sdk = require('postman-collection'), errors = require('../assets/error'), - digest = require('./digest'), { toBase64 } = require('../helpers'); @@ -127,9 +126,6 @@ function authorize (request, auth) { else if (auth.type === 'basic') { return basic(request, auth); } - else if (auth.type === 'digest') { - return digest(request, auth); - } throw errors.INVALID_AUTH_TYPE; } diff --git a/lib/auth/digest.js b/lib/auth/digest.js deleted file mode 100644 index 21eca47..0000000 --- a/lib/auth/digest.js +++ /dev/null @@ -1,129 +0,0 @@ -const sdk = require('postman-collection'), - { - hash, - toBase64 - } = require('../helpers'); - -/** - * Adds Authorization header for Digest authorization - * RFC 7616: Extended version of RFC 2617 but is backward compatible with it. - * - * Reference: https://tools.ietf.org/html/rfc7616 - * - * @param {sdk.Request} request - Request Instance - * @param {sdk.RequestAuth} auth - Auth that needs to be applied to the given request - * @returns {sdk.Request} - Requst with applied auth - * TODO add test for digest auth - * TODO add auth requests for digest auth - */ -function digest (request, auth) { - let config = {}, - username, password, realm, nonce, uri, - algorithm, qop, ncount, cnonce, opaque, A1, A2, response, authHeaderValue; - - // TODO add desc for header - // creating congif obj for easy access of elements - auth.digest.each((element) => { - config[element.key] = element.value; - }); - - // if username or password is not given then dont add any auth headers - if (config.username === '' || config.password === '') { - return request; - } - - // elements of digest auth - username = config.username; - password = config.password; - realm = config.realm || 'testrealm@example.com'; - nonce = config.ncount || toBase64(ramdomString(8)); - // changing algorithm format by removing '-' and converting to lowercase - // for better access from helpers - algorithm = config.algorithm.toLowerCase() || 'md5'; - qop = config.qop || 'auth-init'; - ncount = config.ncount || '00000001'; - cnonce = config.cnounce || toBase64(ramdomString(8)); - uri = '/' + request.url.path.join + sdk.QueryParam.unparse(request.url.query.members); - opaque = config.opaque; - - /** - * A1 calculation according to RFC 7616 - */ - if (algorithm.match(/[.]*-sess/g)) { - /** - * A1 calculation for algorithms with -sess (session) - * Algorithms: MD5-sess, SHA-256-sess, SHA-512-sess - * A1 = (username : realm : password) : ncount : cnonce - */ - A1 = hash[algorithm](username + ':' + realm + ':' + password) + ':' + ncount + ':' + cnonce; - } - else { - /** - * A1 calculation for algorithms with -sess (session) - * Algorithms: MD5, SHA-256, SHA-512 - * A1 = (username : realm : password) - */ - A1 = username + ':' + realm + ':' + password; - } - - /** - * A2 calculation according to RFC 7616 - */ - if (qop === 'auth') { - /** - * If qop = auth or undefined: - * A2 = method : request-uri - */ - A2 = request.method + ':' + request.url; - } - else if (qop === 'auth-init') { - /** - * if qop = auth-init: - * A2 = method : request-uri : (entity-body) - */ - // TODO find what is body-entity - } - - /** - * Calculation of response as per RFC 7616 - * response = ((A1) : nonce : ncount : cnonce : qop : (A2)) - * where () hashes input string with given algorithm - */ - response = hash[algorithm]([ - hash[algorithm](A1), - nonce, - ncount, - cnonce, - qop, - hash[algorithm](A2) - ].join(':')); - - /** - * Digest Auth header value - * Contains: - * - username - * - realm - * - nonce - * - uri - * - [algorithm] - default(MD5) - * -- Format "ALGONAME-VARIANT" + "-sess" (if session tag is required) - * - [qop] - default (auth) - * - nc - * - cnonce - * - opaque - */ - authHeaderValue = `Digest username="${username}", realm="${realm}", nonce="${nonce}", uri="${uri}",`; - authHeaderValue += ` algorithm="${config.algorithm}", qop="${qop}", nc="${ncount}",`; - authHeaderValue += ` cnonce="${cnonce}", response="${response}"`; - authHeaderValue += opaque ? ` opaque="${opaque}"` : ''; - - // Adding digest auth header to request - request.addHeader(new sdk.Header({ - key: 'Authentication', - value: authHeaderValue - })); - - return request; -} - -module.exports = digest; From 70d119a6707652ad44671549f07b6730da9fb5dd Mon Sep 17 00:00:00 2001 From: Somesh Koli Date: Mon, 27 Jul 2020 05:28:58 +0530 Subject: [PATCH 18/25] reverts redundant errors --- lib/assets/error.js | 9 --------- 1 file changed, 9 deletions(-) diff --git a/lib/assets/error.js b/lib/assets/error.js index 3e655cb..8ec020e 100644 --- a/lib/assets/error.js +++ b/lib/assets/error.js @@ -1,13 +1,4 @@ /* eslint-disable max-len */ module.exports = { - 'INVALID_COLLECTION_SOURCE': new Error('Invalid collection source. Requires json/PostmanCollection/file/url'), - 'INVALID_URL': new Error('Invalid url. Insert a proper url which returns json on get request.'), - 'UNABLE_TO_WRITE_FILE': new Error('Unable to write output to the file.'), - 'INVALID_LANGUAGE': new Error('SDK generator for this language does not exist.' + - 'Check getLanguageList to get list of available languages'), - 'INVALID_VAIRIANT': new Error('SDK generator for this language variant does not exist' + - 'Check getLanguageList method to get list of available sdk generators'), - 'INVALID_PARAMETER': new Error('Invalid parameter. Check docs and provide correct parameters'), - 'FILE_PATH_NOT_PROVIDED': new Error('File path not provided. The outpuFilePath option is required if outputType is set to File'), 'INVALID_AUTH_TYPE': new Error('Unknown authorizaition method') }; From 85c31255f2976a4d8e09017d891587e92f955028 Mon Sep 17 00:00:00 2001 From: Somesh Koli Date: Mon, 27 Jul 2020 05:30:44 +0530 Subject: [PATCH 19/25] adds check for existing auth headers. If present then returns the same request --- lib/auth/auth.js | 3 +++ 1 file changed, 3 insertions(+) diff --git a/lib/auth/auth.js b/lib/auth/auth.js index 49294b0..e0dd6d6 100644 --- a/lib/auth/auth.js +++ b/lib/auth/auth.js @@ -117,6 +117,9 @@ function basic (request, auth) { * @returns {sdk.Request} - Requst with applied auth */ function authorize (request, auth) { + if (request.headers.has('authorization')) { + return request; + } if (auth.type === 'apikey') { return apiKey(request, auth); } From 7088de4c0eed0758735a75d3a27ce2a15d46a1ba Mon Sep 17 00:00:00 2001 From: Somesh Koli Date: Wed, 29 Jul 2020 06:07:35 +0530 Subject: [PATCH 20/25] Adds aws authorization method - uses aws4 package to generate signature - added required query param / headers to request - adds check,import & call for aws auth - adds aws to package - adds getAuthConfig option to auth utils --- lib/auth/auth.js | 5 ++++ lib/auth/aws.js | 69 +++++++++++++++++++++++++++++++++++++++++++++++ lib/auth/utils.js | 20 ++++++++++++++ package.json | 6 ++++- 4 files changed, 99 insertions(+), 1 deletion(-) create mode 100644 lib/auth/aws.js create mode 100644 lib/auth/utils.js diff --git a/lib/auth/auth.js b/lib/auth/auth.js index e0dd6d6..5959892 100644 --- a/lib/auth/auth.js +++ b/lib/auth/auth.js @@ -7,9 +7,11 @@ const sdk = require('postman-collection'), errors = require('../assets/error'), + aws = require('./aws'), { toBase64 } = require('../helpers'); +const awsAuth = require('./aws'); /** @@ -129,6 +131,9 @@ function authorize (request, auth) { else if (auth.type === 'basic') { return basic(request, auth); } + else if (auth.type === 'awsv4') { + return aws(request, auth); + } throw errors.INVALID_AUTH_TYPE; } diff --git a/lib/auth/aws.js b/lib/auth/aws.js new file mode 100644 index 0000000..7dc162a --- /dev/null +++ b/lib/auth/aws.js @@ -0,0 +1,69 @@ +const sdk = require('postman-collection'), + getConfig = require('./utils').getAuthOptions, + aws = require('aws4'), + { + hmac + } = require('../helpers'); + + +/** + * Adds necessary headers / url params for aws signature 4 authorization + * Reference: https://docs.aws.amazon.com/general/latest/gr/sigv4_signing.html + * + * @note Does not support requests with form data body type + * @param {sdk.Request} request - Request Instance + * @param {sdk.RequestAuth} auth - Auth that needs to be applied to the given request + * @returns {sdk.Request} - Requst with applied auth + */ +function awsAuth(request, auth) { + let config = getConfig(auth), + options, signedRequestParams, parsedUrl; + + /** + * options required for signing request + * - host + * - path along with query params + * - aws service, if null -> 'execute-api' + * - aws region, if null -. 'us-east-1' + * - headers (existing headers in the request) + * - stringified body (will return for form data and multipart body) + * - accessKeyId + * - secretAccessKey + * - signQuery (add auth data to query params) + * - sessionToken + */ + options = { + host: request.url.getHost(), + path: request.url.getPathWithQuery(), + headers: request.getHeaders(), + body: request.body ? request.body.toString() : undefined, + service: config.service || 'execute-api', + region: config.region || 'us-east-1', + asccessKeyId: config.accessKey, + secretAccessKey: config.secretKey, + sessionToken: config.sessionToken, + signQuery: config.addAuthDataToQuery + }; + + // signing request with above options + signedRequestParams = aws.sign(options); + + // unparsing path build by aws.sign + // if session token is provided in the options then it will be added to the path generated by aws.sign + parsedUrl = sdk.Url.parse(signedRequestParams.path); + + // repopuating request query params to eliminate unwanted duplication and to add session token if added + request.url.query.repopulate(parsedUrl.query) + + // adding auth data to headers if addAuthDataToQuery is false + if(!config.addAuthDataToQuery) { + request.addHeader({ + key: 'Authorization', + value: signedRequestParams.headers.Authorization + }); + } + + return request; +} + +module.exports = awsAuth; \ No newline at end of file diff --git a/lib/auth/utils.js b/lib/auth/utils.js new file mode 100644 index 0000000..d00232d --- /dev/null +++ b/lib/auth/utils.js @@ -0,0 +1,20 @@ +const sdk = require('postman-collection'); + +/** + * Pulls elements from auth instance and return a json object with all the params + * @param {sdk.RequestAuth} auth - auth instance + * @returns {object} - object containing all the auth parametes as key value pairs + */ +function getAuthOptions(auth) { + let config = {}; + // creating congif obj for easy access of elements + auth[auth.type].each((element) => { + config[element.key] = element.value; + }); + + return config; +}; + +module.exports = { + getAuthOptions, +} \ No newline at end of file diff --git a/package.json b/package.json index d3a2a04..998651d 100644 --- a/package.json +++ b/package.json @@ -20,5 +20,9 @@ "bugs": { "url": "https://github.com/umeshp7/postman-collection-code-generators/issues" }, - "homepage": "https://github.com/umeshp7/postman-collection-code-generators#readme" + "homepage": "https://github.com/umeshp7/postman-collection-code-generators#readme", + "dependencies": { + "aws4": "^1.10.0", + "postman-collection": "^3.6.4" + } } From 538a773ac3f6b98cb1dbaf0e2308d809f4c53fe2 Mon Sep 17 00:00:00 2001 From: Somesh Koli Date: Wed, 29 Jul 2020 06:59:37 +0530 Subject: [PATCH 21/25] Fixes issue with session token not being added to query param - adds sessionToken related query param to request in awsAuth method - adds basic test for aws auth - adds auth_request for aws auth --- lib/auth/aws.js | 26 +-- test/fixtures/auth_requests.js | 284 ++++++++++++++++++++++++++++++++- test/unit/auth.test.js | 48 ++++++ 3 files changed, 348 insertions(+), 10 deletions(-) diff --git a/lib/auth/aws.js b/lib/auth/aws.js index 7dc162a..34cc847 100644 --- a/lib/auth/aws.js +++ b/lib/auth/aws.js @@ -19,6 +19,14 @@ function awsAuth(request, auth) { let config = getConfig(auth), options, signedRequestParams, parsedUrl; + // adding X-Amz-Security-Token query param if session token is provided + if (config.sessionToken) { + request.url.addQueryParams({ + key: 'X-Amz-Security-Token', + value: config.sessionToken + }); + } + /** * options required for signing request * - host @@ -48,15 +56,15 @@ function awsAuth(request, auth) { // signing request with above options signedRequestParams = aws.sign(options); - // unparsing path build by aws.sign - // if session token is provided in the options then it will be added to the path generated by aws.sign - parsedUrl = sdk.Url.parse(signedRequestParams.path); - - // repopuating request query params to eliminate unwanted duplication and to add session token if added - request.url.query.repopulate(parsedUrl.query) - - // adding auth data to headers if addAuthDataToQuery is false - if(!config.addAuthDataToQuery) { + if(config.addAuthDataToQuery){ + // unparsing path build by aws.sign + // if session token is provided in the options then it will be added to the path + parsedUrl = sdk.Url.parse(signedRequestParams.path); + // repopuating request query params to eliminate unwanted duplication and to add auth query params + request.url.query.repopulate(parsedUrl.query) + // adding auth data to headers if addAuthDataToQuery is false + } + else { request.addHeader({ key: 'Authorization', value: signedRequestParams.headers.Authorization diff --git a/test/fixtures/auth_requests.js b/test/fixtures/auth_requests.js index 2195545..b3a626c 100644 --- a/test/fixtures/auth_requests.js +++ b/test/fixtures/auth_requests.js @@ -361,5 +361,287 @@ module.exports = { }] } }) + }, + 'AWSV4': { + 'NO_ACCESSKEY_NO_SECRET': new sdk.Request({ + "url": { + "protocol": "https", + "path": ["post"], + "host": ["postman-echo", "com"], + "query": [], + "variable": [] + }, + "header": [{ + "key": "Content-Length", + "value": "22" + }], + "method": "POST", + "body": { + "mode": "formdata", + "formdata": [{ + "key": "awdawd", + "value": "awdawd", + "type": "text" + }, { + "disabled": true, + "key": "", + "value": "", + "type": "file", + "src": "/home/wolf/Pictures/arch.png" + }] + }, + "auth": { + "type": "awsv4", + "awsv4": [{ + "type": "any", + "value": "", + "key": "accessKey" + }, { + "type": "any", + "value": false, + "key": "addAuthDataToQuery" + }, { + "type": "any", + "value": "", + "key": "region" + }, { + "type": "any", + "value": "", + "key": "secretKey" + }, { + "type": "any", + "value": "", + "key": "service" + }, { + "type": "any", + "value": "", + "key": "sessionToken" + }] + } + }), + 'ACCESSKEY_SECRET': new sdk.Request({ + "url": { + "protocol": "https", + "path": ["post"], + "host": ["postman-echo", "com"], + "query": [], + "variable": [] + }, + "header": [{ + "key": "Content-Length", + "value": "22" + }], + "method": "POST", + "body": { + "mode": "formdata", + "formdata": [{ + "key": "awdawd", + "value": "awdawd", + "type": "text" + }, { + "disabled": true, + "key": "", + "value": "", + "type": "file", + "src": "/home/wolf/Pictures/arch.png" + }] + }, + "auth": { + "type": "awsv4", + "awsv4": [{ + "type": "any", + "value": "KEY", + "key": "accessKey" + }, { + "type": "any", + "value": false, + "key": "addAuthDataToQuery" + }, { + "type": "any", + "value": "", + "key": "region" + }, { + "type": "any", + "value": "SECRET", + "key": "secretKey" + }, { + "type": "any", + "value": "", + "key": "service" + }, { + "type": "any", + "value": "", + "key": "sessionToken" + }] + } + }), + 'ACCESS_KEY_ADAVANCE_PARAM': new sdk.Request({ + "url": { + "protocol": "https", + "path": ["post"], + "host": ["postman-echo", "com"], + "query": [], + "variable": [] + }, + "header": [{ + "key": "Content-Length", + "value": "22" + }], + "method": "POST", + "body": { + "mode": "formdata", + "formdata": [{ + "key": "awdawd", + "value": "awdawd", + "type": "text" + }, { + "disabled": true, + "key": "", + "value": "", + "type": "file", + "src": "/home/wolf/Pictures/arch.png" + }] + }, + "auth": { + "type": "awsv4", + "awsv4": [{ + "type": "any", + "value": "KEY", + "key": "accessKey" + }, { + "type": "any", + "value": false, + "key": "addAuthDataToQuery" + }, { + "type": "any", + "value": "in-east-1", + "key": "region" + }, { + "type": "any", + "value": "SECRET", + "key": "secretKey" + }, { + "type": "any", + "value": "s4", + "key": "service" + }, { + "type": "any", + "value": "session", + "key": "sessionToken" + }] + } + }), + 'ACCESSKEY_SECRET_SESSION': new sdk.Request({ + "url": { + "protocol": "https", + "path": ["post"], + "host": ["postman-echo", "com"], + "query": [], + "variable": [] + }, + "header": [{ + "key": "Content-Length", + "value": "22" + }], + "method": "POST", + "body": { + "mode": "formdata", + "formdata": [{ + "key": "awdawd", + "value": "awdawd", + "type": "text" + }, { + "disabled": true, + "key": "", + "value": "", + "type": "file", + "src": "/home/wolf/Pictures/arch.png" + }] + }, + "auth": { + "type": "awsv4", + "awsv4": [{ + "type": "any", + "value": "KEY", + "key": "accessKey" + }, { + "type": "any", + "value": false, + "key": "addAuthDataToQuery" + }, { + "type": "any", + "value": "in-east-1", + "key": "region" + }, { + "type": "any", + "value": "SECRET", + "key": "secretKey" + }, { + "type": "any", + "value": "s4", + "key": "service" + }, { + "type": "any", + "value": "session", + "key": "sessionToken" + }] + } + }), + 'ALL_PARAM_IN_QUERY': new sdk.Request({ + "url": { + "protocol": "https", + "path": ["post"], + "host": ["postman-echo", "com"], + "query": [], + "variable": [] + }, + "header": [{ + "key": "Content-Length", + "value": "22" + }], + "method": "POST", + "body": { + "mode": "formdata", + "formdata": [{ + "key": "awdawd", + "value": "awdawd", + "type": "text" + }, { + "disabled": true, + "key": "", + "value": "", + "type": "file", + "src": "/home/wolf/Pictures/arch.png" + }] + }, + "auth": { + "type": "awsv4", + "awsv4": [{ + "type": "any", + "value": "KEY", + "key": "accessKey" + }, { + "type": "any", + "value": true, + "key": "addAuthDataToQuery" + }, { + "type": "any", + "value": "in-east-1", + "key": "region" + }, { + "type": "any", + "value": "SECRET", + "key": "secretKey" + }, { + "type": "any", + "value": "s4", + "key": "service" + }, { + "type": "any", + "value": "session", + "key": "sessionToken" + }] + } + }) } -}; +}; \ No newline at end of file diff --git a/test/unit/auth.test.js b/test/unit/auth.test.js index 1cc918b..5bcbb1d 100644 --- a/test/unit/auth.test.js +++ b/test/unit/auth.test.js @@ -96,4 +96,52 @@ describe('Postman Request Authorization', function () { expect(request.getHeaders('Authorization').Authorization).to.be.equal('Basic Og=='); }); }); + + describe('AWSv4 Authorization', function() { + it('should generate signature if acesskey and secret are not provided', function() { + var request = authRequests.AWSV4.NO_ACCESSKEY_NO_SECRET; + request = authorize(request, request.auth); + expect(request).to.be.instanceOf(sdk.Request); + expect(request.headers.has('Authorization')).to.be.true; + expect(request.getHeaders('Authorization').Authorization).to.include('AWS4-HMAC-SHA256'); + }) + + it('should generate signature with accessKey and secret provided', function() { + var request = authRequests.AWSV4.ACCESSKEY_SECRET; + request = authorize(request, request.auth); + expect(request).to.be.instanceOf(sdk.Request); + expect(request.headers.has('Authorization')).to.be.true; + expect(request.getHeaders('Authorization').Authorization).to.include('AWS4-HMAC-SHA256'); + }) + + it('should generate signature with optional parameters', function() { + var request = authRequests.AWSV4.ACCESS_KEY_ADAVANCE_PARAM; + request = authorize(request, request.auth); + expect(request).to.be.instanceOf(sdk.Request); + expect(request.headers.has('Authorization')).to.be.true; + expect(request.getHeaders('Authorization').Authorization).to.include('AWS4-HMAC-SHA256'); + expect(request.getHeaders('Authorization').Authorization).to.include('in-east-1'); + expect(request.getHeaders('Authorization').Authorization).to.include('s4'); + }) + + it('should generate signature with session token', function() { + var request = authRequests.AWSV4.ACCESSKEY_SECRET_SESSION; + request = authorize(request, request.auth); + expect(request).to.be.instanceOf(sdk.Request); + expect(request.headers.has('Authorization')).to.be.true; + expect(request.getHeaders('Authorization').Authorization).to.include('AWS4-HMAC-SHA256'); + expect(request.url.getPathWithQuery()).to.include('X-Amz-Security-Token'); + }) + + it('should generate signature with addToQuery set', function() { + var request = authRequests.AWSV4.ALL_PARAM_IN_QUERY; + request = authorize(request, request.auth); + expect(request).to.be.instanceOf(sdk.Request); + expect(request.url.getPathWithQuery()).to.include('X-Amz-Algorithm'); + expect(request.url.getPathWithQuery()).to.include('X-Amz-Credential'); + expect(request.url.getPathWithQuery()).to.include('X-Amz-Signature'); + expect(request.url.getPathWithQuery()).to.include('X-Amz-SignedHeaders'); + expect(request.url.getPathWithQuery()).to.include('X-Amz-Date'); + }) + }) }); From 1a2e4d9237b889d931317172fe2bcac6a4045128 Mon Sep 17 00:00:00 2001 From: Somesh Koli Date: Thu, 30 Jul 2020 04:26:12 +0530 Subject: [PATCH 22/25] adds check for existing authorization headers/query params and fixes typos --- lib/auth/auth.js | 24 +++++++++++++++++++----- 1 file changed, 19 insertions(+), 5 deletions(-) diff --git a/lib/auth/auth.js b/lib/auth/auth.js index e0dd6d6..833d8e1 100644 --- a/lib/auth/auth.js +++ b/lib/auth/auth.js @@ -3,6 +3,7 @@ * - Basic Auth * - API key Auth * - Bearer Token Auth + * Authorizer method which calls the above methods as per the input auth */ const sdk = require('postman-collection'), @@ -28,12 +29,17 @@ function apiKey (request, auth) { config[element.key] = element.value; }); + // return same request if apikey header or query param is already added + if (request.headers.has(config.key) || request.url.query.get(config.key)) { + return request; + } + // if api key `key` is empty then dont add if (config.key === '') { return request; } - // if apikey auth in type is headers + // if apikey auth intype is query params if (config.in === 'query') { // adding new query param to request for api key request.url.addQueryParams(new sdk.QueryParam({ @@ -41,7 +47,7 @@ function apiKey (request, auth) { value: config.value })); } - // if apikey auth in type is query params + // if apikey auth intype is headers else { // adding apikey auth headers to request request.addHeader(new sdk.Header({ @@ -62,6 +68,11 @@ function apiKey (request, auth) { * @returns {sdk.Request} - Requst with applied auth */ function bearerToken (request, auth) { + // return same request if auth headers are already added + if (request.headers.has('authorization')) { + return request; + } + let config = {}; // creating congif obj for easy access of elements auth.bearer.each((element) => { @@ -91,6 +102,11 @@ function bearerToken (request, auth) { * @returns {sdk.Request} - Requst with applied auth */ function basic (request, auth) { + // return same request if auth headers are already added + if (request.headers.has('authorization')) { + return request; + } + let config = {}; // creating congif obj for easy access of elements auth.basic.each((element) => { @@ -117,9 +133,7 @@ function basic (request, auth) { * @returns {sdk.Request} - Requst with applied auth */ function authorize (request, auth) { - if (request.headers.has('authorization')) { - return request; - } + // checking for auth type and invoking related method if (auth.type === 'apikey') { return apiKey(request, auth); } From e0232abfce81886f5e6207eb955d5a622c6933f7 Mon Sep 17 00:00:00 2001 From: Somesh Koli Date: Thu, 30 Jul 2020 04:54:37 +0530 Subject: [PATCH 23/25] updates package json --- package.json | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/package.json b/package.json index d3a2a04..905beeb 100644 --- a/package.json +++ b/package.json @@ -20,5 +20,9 @@ "bugs": { "url": "https://github.com/umeshp7/postman-collection-code-generators/issues" }, - "homepage": "https://github.com/umeshp7/postman-collection-code-generators#readme" + "homepage": "https://github.com/umeshp7/postman-collection-code-generators#readme", + "dependencies": { + "chai": "^4.2.0", + "postman-collection": "^3.6.4" + } } From e86eb54c128e0cbd9d1dc2856f5988b88e451c2e Mon Sep 17 00:00:00 2001 From: Somesh Koli Date: Thu, 30 Jul 2020 04:55:48 +0530 Subject: [PATCH 24/25] adds dependencies to package --- package.json | 1 + 1 file changed, 1 insertion(+) diff --git a/package.json b/package.json index 998651d..f8c57b2 100644 --- a/package.json +++ b/package.json @@ -23,6 +23,7 @@ "homepage": "https://github.com/umeshp7/postman-collection-code-generators#readme", "dependencies": { "aws4": "^1.10.0", + "chai": "^4.2.0", "postman-collection": "^3.6.4" } } From e57a411c61c4decd328d96426028d65f3d361d0c Mon Sep 17 00:00:00 2001 From: Somesh Koli Date: Thu, 30 Jul 2020 05:26:22 +0530 Subject: [PATCH 25/25] fixes npm test --- npm/test-unit.js | 1 + package.json | 14 +++++++++++++- 2 files changed, 14 insertions(+), 1 deletion(-) diff --git a/npm/test-unit.js b/npm/test-unit.js index 1bd787a..648d066 100644 --- a/npm/test-unit.js +++ b/npm/test-unit.js @@ -22,6 +22,7 @@ module.exports = function (exit) { test('-d', COV_REPORT_PATH) && rm('-rf', COV_REPORT_PATH); mkdir('-p', COV_REPORT_PATH); + mkdir('-p', COV_REPORT_PATH + '/processinfo'); var Mocha = require('mocha'), nyc = new NYC({ diff --git a/package.json b/package.json index 905beeb..f79ea79 100644 --- a/package.json +++ b/package.json @@ -22,7 +22,19 @@ }, "homepage": "https://github.com/umeshp7/postman-collection-code-generators#readme", "dependencies": { - "chai": "^4.2.0", + "async": "^3.2.0", "postman-collection": "^3.6.4" + }, + "devDependencies": { + "chai": "^4.2.0", + "eslint": "^7.5.0", + "eslint-plugin-jsdoc": "^30.0.3", + "eslint-plugin-lodash": "^7.1.0", + "eslint-plugin-security": "^1.4.0", + "mocha": "^8.0.1", + "nyc": "^15.1.0", + "pretty-ms": "^7.0.0", + "recursive-readdir": "^2.2.2", + "shelljs": "^0.8.4" } }