diff --git a/src/lib/storage/up-storage.js b/src/lib/storage/up-storage.js index 57d5b2180..d0e53bc2d 100644 --- a/src/lib/storage/up-storage.js +++ b/src/lib/storage/up-storage.js @@ -227,9 +227,71 @@ 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})`; + + return this._setAuth(headers); + } + + /** + * Validate configuration auth and assign Header authorization + * @param {Object} headers + * @return {Object} + * @private + */ + _setAuth(headers) { + + if (_.isNil(this.config.auth) || headers['authorization']) { + return headers; + } + + if (!_.isObject(this.config.auth)) { + this._throwErrorAuth('Auth invalid'); + } + + // 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) { + token = process.env[this.config.auth.token_env]; + } + + if (_.isNil(token)) { + this._throwErrorAuth('Token is required'); + } + + // define type Auth allow basic and bearer + const type = this.config.auth.type; + this._setHeaderAuthorization(headers, type, token); 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 + * @param {string} type + * @param {string} token + * @private + */ + _setHeaderAuthorization(headers, type, token) { + if (type !== 'bearer' && type !== 'basic') { + this._throwErrorAuth(`Auth type '${type}' not allowed`); + } + + type = _.upperFirst(type); + headers['authorization'] = `${type} ${token}`; + } + /** * It will add or override specified headers from config file. * 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..0ce878e9c --- /dev/null +++ b/test/functional/uplink.auth.spec.js @@ -0,0 +1,144 @@ +'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 should return default headers', function () { + const headers = setHeaders(); + const keys = Object.keys(headers); + const keysExpected = ['Accept', 'Accept-Encoding', 'User-Agent']; + + assert.deepEqual(keys, keysExpected); + assert.equal(keys.length, 3); + }); + + it('if assigns value invalid to attribute auth', function () { + const fnError = function () { + setHeaders({ + auth: '' + }); + }; + + assert.throws(fnError, 'Auth invalid'); + }); + + 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 assigns headers authorization and token the header precedes', function () { + const headers = setHeaders({ + auth: { + type: 'bearer', + token: 'tokenBearer' + } + }, { + 'authorization': 'basic tokenBasic' + }); + + assert.equal(headers['authorization'], 'basic tokenBasic'); + }); + + 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('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('set auth type invalid', function () { + const fnError = function() { + setHeaders({ + auth: { + type: 'null', + token: 'Zm9vX2Jhcf===' + } + }) + }; + + assert.throws(fnError, `Auth type 'null' not allowed`); + }); + + 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('set auth with token name and assigns in env', 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('if token not set', function () { + const fnError = function() { + setHeaders({ + auth: { + type: 'basic' + } + }); + }; + + assert.throws(fnError, 'Token is required'); + }); + }); +}; + 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 |