From 7baf7cbe21da6bcd40fb0ed503e4aed26d707093 Mon Sep 17 00:00:00 2001 From: Ramon Henrique Ornelas Date: Thu, 28 Sep 2017 22:28:58 -0300 Subject: [PATCH 1/7] feat: header authorization uplink - define auth type basic or bearer; - assigns the header get process.env var NPM_TOKEN; - assigns the header get process.env by set name; - assigns the header raw token; --- src/lib/storage/up-storage.js | 40 +++++++++++++++++++++++++++++++++++ 1 file changed, 40 insertions(+) diff --git a/src/lib/storage/up-storage.js b/src/lib/storage/up-storage.js index 57d5b2180..f46fdd767 100644 --- a/src/lib/storage/up-storage.js +++ b/src/lib/storage/up-storage.js @@ -227,6 +227,46 @@ class ProxyStorage { headers[acceptEncoding] = headers[acceptEncoding] || 'gzip'; // registry.npmjs.org will only return search result if user-agent include string 'npm' headers[userAgent] = headers[userAgent] || `npm (${this.userAgent})`; + + // copy headers to normalize keys + let copyHeaders = {}; + Object.keys(headers).map((value) => { + copyHeaders[value.toLowerCase()] = headers[value]; + }); + + if (!copyHeaders['authorization']) { + let token = null; + // define authorization token to use in the Authorization Header + if (this.config.auth.token) { + token = this.config.auth.token; + } else if (this.config.auth.token_env) { + // get NPM_TOKEN http://blog.npmjs.org/post/118393368555/deploying-with-npm-private-modules + // or get other variable export in env + if (this.config.auth.token_env === true) { + token = process.env.NPM_TOKEN; + } else { + token = process.env[this.config.auth.token_env]; + } + } + + if (token) { + // define type Auth allow basic and bearer + const type = this.config.auth.type; + switch (type.toLowerCase()) { + case 'basic': + headers['Authorization'] = `Basic ${token}`; + break; + case 'bearer': + headers['Authorization'] = `Bearer ${token}`; + break; + default: + throw new Error(`Auth type '${type}' not allowed.`); + } + } + } + + copyHeaders = null; + return headers; } From 3f20290ee80edc36885774065beab2cf964ff848 Mon Sep 17 00:00:00 2001 From: Ramon Henrique Ornelas Date: Thu, 28 Sep 2017 23:02:38 -0300 Subject: [PATCH 2/7] docs: uplink headers authorization --- wiki/uplinks.md | 1 + 1 file changed, 1 insertion(+) diff --git a/wiki/uplinks.md b/wiki/uplinks.md index d9a190804..f1cc3e854 100644 --- a/wiki/uplinks.md +++ b/wiki/uplinks.md @@ -28,6 +28,7 @@ maxage | string | No |10m | all | limit maximun failure request | 2m fail_timeout | string | No |10m | all | defines max time when a request becomes a failure | 5m max_fails | number | No |2 | all | limit maximun failure request | 2 cache | boolean | No |[true,false] | >= 2.1 | avoid cache tarballs | true +auth | list | No | type: [bearer,basic], [token: "token",token_env: [true,\]] | >= 2.5 | assigns the header 'Authorization' see: http://blog.npmjs.org/post/118393368555/deploying-with-npm-private-modules | headers | list | No | authorization: "Basic YourBase64EncodedCredentials==" | all | list of custom headers for the uplink | From cc55c213036b8a5a842bd3fdd42703b92340b6b8 Mon Sep 17 00:00:00 2001 From: Ramon Henrique Ornelas Date: Fri, 29 Sep 2017 11:02:00 -0300 Subject: [PATCH 3/7] test: fix test and done some improvements in code --- src/lib/storage/up-storage.js | 67 ++++++++++++++++++++--------------- 1 file changed, 39 insertions(+), 28 deletions(-) diff --git a/src/lib/storage/up-storage.js b/src/lib/storage/up-storage.js index f46fdd767..5f95e6127 100644 --- a/src/lib/storage/up-storage.js +++ b/src/lib/storage/up-storage.js @@ -228,44 +228,55 @@ class ProxyStorage { // registry.npmjs.org will only return search result if user-agent include string 'npm' headers[userAgent] = headers[userAgent] || `npm (${this.userAgent})`; + if (!this.config.auth) { + return headers; + } + // copy headers to normalize keys let copyHeaders = {}; Object.keys(headers).map((value) => { copyHeaders[value.toLowerCase()] = headers[value]; }); - if (!copyHeaders['authorization']) { - let token = null; - // define authorization token to use in the Authorization Header - if (this.config.auth.token) { - token = this.config.auth.token; - } else if (this.config.auth.token_env) { - // get NPM_TOKEN http://blog.npmjs.org/post/118393368555/deploying-with-npm-private-modules - // or get other variable export in env - if (this.config.auth.token_env === true) { - token = process.env.NPM_TOKEN; - } else { - token = process.env[this.config.auth.token_env]; - } - } + // if header Authorization assigns this has precedence + if (copyHeaders['authorization']) { + return headers; + } - if (token) { - // define type Auth allow basic and bearer - const type = this.config.auth.type; - switch (type.toLowerCase()) { - case 'basic': - headers['Authorization'] = `Basic ${token}`; - break; - case 'bearer': - headers['Authorization'] = `Bearer ${token}`; - break; - default: - throw new Error(`Auth type '${type}' not allowed.`); - } + if (typeof this.config.auth !== 'object') { + throw new Error('Auth invalid'); + } + + let token = null; + // define authorization token to use in the Authorization Header + if (this.config.auth.token) { + token = this.config.auth.token; + } else if (this.config.auth.token_env) { + // get NPM_TOKEN http://blog.npmjs.org/post/118393368555/deploying-with-npm-private-modules + // or get other variable export in env + if (this.config.auth.token_env === true) { + token = process.env.NPM_TOKEN; + } else { + token = process.env[this.config.auth.token_env]; } } - copyHeaders = null; + if (!token) { + throw new Error('Token is required'); + } + + // define type Auth allow basic and bearer + const type = this.config.auth.type; + switch (type.toLowerCase()) { + case 'basic': + headers['Authorization'] = `Basic ${token}`; + break; + case 'bearer': + headers['Authorization'] = `Bearer ${token}`; + break; + default: + throw new Error(`Auth type '${type}' not allowed`); + } return headers; } From ab69b258ca15f32b76290a19e2b2175ab63bf1fd Mon Sep 17 00:00:00 2001 From: Ramon Henrique Ornelas Date: Fri, 29 Sep 2017 18:16:55 -0300 Subject: [PATCH 4/7] refactor: improvements auth uplink - find NPM_TOKEN by default; - remove parameter true to token_env; - remove duplicate code to assigns header authorization; - method created to validate rules of auth; --- src/lib/storage/up-storage.js | 62 +++++++++++++++++++---------------- 1 file changed, 34 insertions(+), 28 deletions(-) diff --git a/src/lib/storage/up-storage.js b/src/lib/storage/up-storage.js index 5f95e6127..ef8428d99 100644 --- a/src/lib/storage/up-storage.js +++ b/src/lib/storage/up-storage.js @@ -228,37 +228,37 @@ class ProxyStorage { // registry.npmjs.org will only return search result if user-agent include string 'npm' headers[userAgent] = headers[userAgent] || `npm (${this.userAgent})`; - if (!this.config.auth) { + return this._setAuth(headers); + } + + /** + * Validate configuration auth and assign Header authorization + * @param {object} headers + * @return {object} + * @private + */ + _setAuth(headers) { + + if (_.isUndefined(this.config.auth)) { return headers; } - // copy headers to normalize keys - let copyHeaders = {}; - Object.keys(headers).map((value) => { - copyHeaders[value.toLowerCase()] = headers[value]; - }); - // if header Authorization assigns this has precedence - if (copyHeaders['authorization']) { + if (headers['authorization']) { return headers; } - if (typeof this.config.auth !== 'object') { + if (!_.isObject(this.config.auth)) { throw new Error('Auth invalid'); } - let token = null; - // define authorization token to use in the Authorization Header + // get NPM_TOKEN http://blog.npmjs.org/post/118393368555/deploying-with-npm-private-modules + // or get other variable export in env + let token = process.env.NPM_TOKEN; if (this.config.auth.token) { token = this.config.auth.token; } else if (this.config.auth.token_env) { - // get NPM_TOKEN http://blog.npmjs.org/post/118393368555/deploying-with-npm-private-modules - // or get other variable export in env - if (this.config.auth.token_env === true) { - token = process.env.NPM_TOKEN; - } else { - token = process.env[this.config.auth.token_env]; - } + token = process.env[this.config.auth.token_env]; } if (!token) { @@ -267,18 +267,24 @@ class ProxyStorage { // define type Auth allow basic and bearer const type = this.config.auth.type; - switch (type.toLowerCase()) { - case 'basic': - headers['Authorization'] = `Basic ${token}`; - break; - case 'bearer': - headers['Authorization'] = `Bearer ${token}`; - break; - default: - throw new Error(`Auth type '${type}' not allowed`); + this._setHeaderAuthorization(headers, type, token); + return headers; + } + + /** + * Assign Header authorization with type authentication + * @param {object} headers + * @param {string} type + * @param {string} token + * @private + */ + _setHeaderAuthorization(headers, type, token) { + if (type !== 'bearer' && type !== 'basic') { + throw new Error(`Auth type '${type}' not allowed`); } - return headers; + type = _.upperFirst(type); + headers['authorization'] = `${type} ${token}`; } /** From f7ad05ec86e88fe122926b51aed47a23ebd0a9b9 Mon Sep 17 00:00:00 2001 From: Ramon Henrique Ornelas Date: Sat, 30 Sep 2017 00:06:42 -0300 Subject: [PATCH 5/7] test: add test uplink auth --- src/lib/storage/up-storage.js | 8 +- test/functional/index.js | 1 + test/functional/uplink.auth.spec.js | 136 ++++++++++++++++++++++++++++ 3 files changed, 141 insertions(+), 4 deletions(-) create mode 100644 test/functional/uplink.auth.spec.js diff --git a/src/lib/storage/up-storage.js b/src/lib/storage/up-storage.js index ef8428d99..5c6ae1def 100644 --- a/src/lib/storage/up-storage.js +++ b/src/lib/storage/up-storage.js @@ -233,8 +233,8 @@ class ProxyStorage { /** * Validate configuration auth and assign Header authorization - * @param {object} headers - * @return {object} + * @param {Object} headers + * @return {Object} * @private */ _setAuth(headers) { @@ -261,7 +261,7 @@ class ProxyStorage { token = process.env[this.config.auth.token_env]; } - if (!token) { + if (_.isUndefined(token) || _.isNil(token)) { throw new Error('Token is required'); } @@ -273,7 +273,7 @@ class ProxyStorage { /** * Assign Header authorization with type authentication - * @param {object} headers + * @param {Object} headers * @param {string} type * @param {string} token * @private diff --git a/test/functional/index.js b/test/functional/index.js index b450a040e..9958d9d57 100644 --- a/test/functional/index.js +++ b/test/functional/index.js @@ -77,6 +77,7 @@ describe('Create registry servers', function() { require('./notifications/notify')(); // requires packages published to server1/server2 require('./uplink.cache.spec')(); + require('./uplink.auth.spec')(); after(function(done) { const check = (server) => { diff --git a/test/functional/uplink.auth.spec.js b/test/functional/uplink.auth.spec.js new file mode 100644 index 000000000..f623136fe --- /dev/null +++ b/test/functional/uplink.auth.spec.js @@ -0,0 +1,136 @@ +'use strict'; + +const uplinkStorage = require('../../src/lib/storage/up-storage'); +const assert = require('assert'); + +function createUplink(config) { + const defaultConfig = { + url: 'https://registry.npmjs.org/' + }; + let mergeConfig = Object.assign({}, defaultConfig, config); + return new uplinkStorage(mergeConfig, {}); +} + +function setHeaders(config, headers) { + config = config || {}; + headers = headers || {}; + const uplink = createUplink(config); + return uplink._setHeaders({ + headers + }); +} + +module.exports = function () { + + describe('uplink auth test', function () { + + it('if set headers empty', function () { + const headers = setHeaders(); + + assert.equal(Object.keys(headers).length, 3); + }); + + it('invalid auth', function () { + const fnError = function () { + setHeaders({ + auth: '' + }); + }; + + assert.throws(fnError, 'Auth invalid'); + }); + + it('if set headers authorization', function () { + const headers = setHeaders({}, { + 'authorization': 'basic Zm9vX2Jhcg==' + }); + assert.equal(Object.keys(headers).length, 4); + assert.equal(headers['authorization'], 'basic Zm9vX2Jhcg=='); + }); + + it('if set headers authorization precendence token', function () { + const headers = setHeaders({ + auth: { + type: 'bearer', + token: 'tokenBearer' + } + }, { + 'authorization': 'basic tokenBasic' + }); + + assert.equal(headers['authorization'], 'basic tokenBasic'); + }); + + it('basic auth test', function () { + const headers = setHeaders({ + auth: { + type: 'basic', + token: 'Zm9vX2Jhcg==' + } + }); + assert.equal(Object.keys(headers).length, 4); + assert.equal(headers['authorization'], 'Basic Zm9vX2Jhcg=='); + }); + + it('basic auth test', function () { + const headers = setHeaders({ + auth: { + type: 'bearer', + token: 'Zm9vX2Jhcf===' + } + }); + assert.equal(Object.keys(headers).length, 4); + assert.equal(headers['authorization'], 'Bearer Zm9vX2Jhcf==='); + }); + + it('invalid auth type test', function () { + const fnError = function() { + setHeaders({ + auth: { + type: 'null', + token: 'Zm9vX2Jhcf===' + } + }) + }; + + assert.throws(fnError, `Auth type 'null' not allowed`); + }); + + it('get NPM_TOKEN process test', function () { + process.env.NPM_TOKEN = 'myToken'; + const headers = setHeaders({ + auth: { + type: 'bearer' + } + }); + assert.equal(headers['authorization'], 'Bearer myToken'); + delete process.env.NPM_TOKEN; + }); + + it('get name variable assigns process.env test', function () { + process.env.NPM_TOKEN_TEST = 'myTokenTest'; + const headers = setHeaders({ + auth: { + type: 'basic', + token_env: 'NPM_TOKEN_TEST' + } + }); + assert.equal(headers['authorization'], 'Basic myTokenTest'); + delete process.env.NPM_TOKEN_TEST; + }); + + + it('token is required', function () { + const fnError = function() { + setHeaders({ + auth: { + type: 'basic' + } + }); + }; + + assert.throws(fnError, 'Token is required'); + }); + }); +}; + From 231a4d7227bab5ddba237205b50b6b96bf0eb32e Mon Sep 17 00:00:00 2001 From: Ramon Henrique Ornelas Date: Sat, 30 Sep 2017 21:07:45 -0300 Subject: [PATCH 6/7] refactor: small changes in the code - describe message tests; - remove condition duplicate; - add logger in exceptions; --- src/lib/storage/up-storage.js | 25 +++++++++++++++---------- test/functional/uplink.auth.spec.js | 25 +++++++++++++++---------- 2 files changed, 30 insertions(+), 20 deletions(-) diff --git a/src/lib/storage/up-storage.js b/src/lib/storage/up-storage.js index 5c6ae1def..d0e53bc2d 100644 --- a/src/lib/storage/up-storage.js +++ b/src/lib/storage/up-storage.js @@ -239,17 +239,12 @@ class ProxyStorage { */ _setAuth(headers) { - if (_.isUndefined(this.config.auth)) { - return headers; - } - - // if header Authorization assigns this has precedence - if (headers['authorization']) { + if (_.isNil(this.config.auth) || headers['authorization']) { return headers; } if (!_.isObject(this.config.auth)) { - throw new Error('Auth invalid'); + this._throwErrorAuth('Auth invalid'); } // get NPM_TOKEN http://blog.npmjs.org/post/118393368555/deploying-with-npm-private-modules @@ -261,8 +256,8 @@ class ProxyStorage { token = process.env[this.config.auth.token_env]; } - if (_.isUndefined(token) || _.isNil(token)) { - throw new Error('Token is required'); + if (_.isNil(token)) { + this._throwErrorAuth('Token is required'); } // define type Auth allow basic and bearer @@ -271,6 +266,16 @@ class ProxyStorage { return headers; } + /** + * @param {string} message + * @throws {Error} + * @private + */ + _throwErrorAuth(message) { + this.logger.error(message); + throw new Error(message); + } + /** * Assign Header authorization with type authentication * @param {Object} headers @@ -280,7 +285,7 @@ class ProxyStorage { */ _setHeaderAuthorization(headers, type, token) { if (type !== 'bearer' && type !== 'basic') { - throw new Error(`Auth type '${type}' not allowed`); + this._throwErrorAuth(`Auth type '${type}' not allowed`); } type = _.upperFirst(type); diff --git a/test/functional/uplink.auth.spec.js b/test/functional/uplink.auth.spec.js index f623136fe..b8b3f7aa4 100644 --- a/test/functional/uplink.auth.spec.js +++ b/test/functional/uplink.auth.spec.js @@ -24,13 +24,13 @@ module.exports = function () { describe('uplink auth test', function () { - it('if set headers empty', function () { + it('if set headers empty should return default headers', function () { const headers = setHeaders(); assert.equal(Object.keys(headers).length, 3); }); - it('invalid auth', function () { + it('if assigns value invalid to attribute auth', function () { const fnError = function () { setHeaders({ auth: '' @@ -40,15 +40,16 @@ module.exports = function () { assert.throws(fnError, 'Auth invalid'); }); - it('if set headers authorization', function () { + it('if assigns the header authorization', function () { const headers = setHeaders({}, { 'authorization': 'basic Zm9vX2Jhcg==' }); + assert.equal(Object.keys(headers).length, 4); assert.equal(headers['authorization'], 'basic Zm9vX2Jhcg=='); }); - it('if set headers authorization precendence token', function () { + it('if assigns headers authorization and token the header precedes', function () { const headers = setHeaders({ auth: { type: 'bearer', @@ -61,29 +62,31 @@ module.exports = function () { assert.equal(headers['authorization'], 'basic tokenBasic'); }); - it('basic auth test', function () { + it('set type auth basic', function () { const headers = setHeaders({ auth: { type: 'basic', token: 'Zm9vX2Jhcg==' } }); + assert.equal(Object.keys(headers).length, 4); assert.equal(headers['authorization'], 'Basic Zm9vX2Jhcg=='); }); - it('basic auth test', function () { + it('set type auth bearer', function () { const headers = setHeaders({ auth: { type: 'bearer', token: 'Zm9vX2Jhcf===' } }); + assert.equal(Object.keys(headers).length, 4); assert.equal(headers['authorization'], 'Bearer Zm9vX2Jhcf==='); }); - it('invalid auth type test', function () { + it('set auth type invalid', function () { const fnError = function() { setHeaders({ auth: { @@ -96,18 +99,19 @@ module.exports = function () { assert.throws(fnError, `Auth type 'null' not allowed`); }); - it('get NPM_TOKEN process test', function () { + it('set auth with NPM_TOKEN', function () { process.env.NPM_TOKEN = 'myToken'; const headers = setHeaders({ auth: { type: 'bearer' } }); + assert.equal(headers['authorization'], 'Bearer myToken'); delete process.env.NPM_TOKEN; }); - it('get name variable assigns process.env test', function () { + it('set auth with token name and assigns in env', function () { process.env.NPM_TOKEN_TEST = 'myTokenTest'; const headers = setHeaders({ auth: { @@ -115,12 +119,13 @@ module.exports = function () { token_env: 'NPM_TOKEN_TEST' } }); + assert.equal(headers['authorization'], 'Basic myTokenTest'); delete process.env.NPM_TOKEN_TEST; }); - it('token is required', function () { + it('if token not set', function () { const fnError = function() { setHeaders({ auth: { From 3fcdc627363fefb29d9eaeb7f718a84b8834d718 Mon Sep 17 00:00:00 2001 From: Ramon Henrique Ornelas Date: Sat, 30 Sep 2017 21:29:13 -0300 Subject: [PATCH 7/7] test: add test return default headers --- test/functional/uplink.auth.spec.js | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/test/functional/uplink.auth.spec.js b/test/functional/uplink.auth.spec.js index b8b3f7aa4..0ce878e9c 100644 --- a/test/functional/uplink.auth.spec.js +++ b/test/functional/uplink.auth.spec.js @@ -26,8 +26,11 @@ module.exports = function () { it('if set headers empty should return default headers', function () { const headers = setHeaders(); + const keys = Object.keys(headers); + const keysExpected = ['Accept', 'Accept-Encoding', 'User-Agent']; - assert.equal(Object.keys(headers).length, 3); + assert.deepEqual(keys, keysExpected); + assert.equal(keys.length, 3); }); it('if assigns value invalid to attribute auth', function () {