From bba6855872b915eab70ffc3226418bc83b8b5d0d Mon Sep 17 00:00:00 2001 From: KAZUHIRO ITO Date: Wed, 26 Feb 2020 12:59:40 +0900 Subject: [PATCH 1/4] Add admin api authentication function --- .../@node-red/editor-api/lib/auth/index.js | 3 +- .../editor-api/lib/auth/strategies.js | 24 ++++++++++- .../@node-red/editor-api/lib/auth/users.js | 14 +++++- .../editor-api/lib/auth/strategies_spec.js | 32 ++++++++++++++ .../editor-api/lib/auth/users_spec.js | 43 +++++++++++++++++++ 5 files changed, 112 insertions(+), 4 deletions(-) diff --git a/packages/node_modules/@node-red/editor-api/lib/auth/index.js b/packages/node_modules/@node-red/editor-api/lib/auth/index.js index a7ee2e7f3..c3948cd27 100644 --- a/packages/node_modules/@node-red/editor-api/lib/auth/index.js +++ b/packages/node_modules/@node-red/editor-api/lib/auth/index.js @@ -36,6 +36,7 @@ var log = require("@node-red/util").log; // TODO: separate module passport.use(strategies.bearerStrategy.BearerStrategy); passport.use(strategies.clientPasswordStrategy.ClientPasswordStrategy); passport.use(strategies.anonymousStrategy); +passport.use(strategies.tokensStrategy); var server = oauth2orize.createServer(); @@ -60,7 +61,7 @@ function init(_settings,storage) { function needsPermission(permission) { return function(req,res,next) { if (settings && settings.adminAuth) { - return passport.authenticate(['bearer','anon'],{ session: false })(req,res,function() { + return passport.authenticate(['bearer','anon','tokens'],{ session: false })(req,res,function() { if (!req.user) { return next(); } diff --git a/packages/node_modules/@node-red/editor-api/lib/auth/strategies.js b/packages/node_modules/@node-red/editor-api/lib/auth/strategies.js index b17bf1473..9705ddd28 100644 --- a/packages/node_modules/@node-red/editor-api/lib/auth/strategies.js +++ b/packages/node_modules/@node-red/editor-api/lib/auth/strategies.js @@ -123,9 +123,31 @@ AnonymousStrategy.prototype.authenticate = function(req) { }); } +function TokensStrategy() { + passport.Strategy.call(this); + this.name = 'tokens'; +} +util.inherits(TokensStrategy, passport.Strategy); +TokensStrategy.prototype.authenticate = function(req) { + var self = this; + var token = req.headers[Users.tokenHeader()]; + if (token) { + Users.tokens(token).then(function(admin) { + if (admin) { + self.success(admin,{scope:admin.permissions}); + } else { + self.fail(401); + } + }); + } else { + self.fail(401); + } +} + module.exports = { bearerStrategy: bearerStrategy, clientPasswordStrategy: clientPasswordStrategy, passwordTokenExchange: passwordTokenExchange, - anonymousStrategy: new AnonymousStrategy() + anonymousStrategy: new AnonymousStrategy(), + tokensStrategy: new TokensStrategy() } diff --git a/packages/node_modules/@node-red/editor-api/lib/auth/users.js b/packages/node_modules/@node-red/editor-api/lib/auth/users.js index 24a762958..f032332db 100644 --- a/packages/node_modules/@node-red/editor-api/lib/auth/users.js +++ b/packages/node_modules/@node-red/editor-api/lib/auth/users.js @@ -59,7 +59,9 @@ function getDefaultUser() { var api = { get: get, authenticate: authenticate, - default: getDefaultUser + default: getDefaultUser, + tokens: getDefaultUser, + tokenHeader: "authorization" } function init(config) { @@ -105,6 +107,12 @@ function init(config) { } else { api.default = getDefaultUser; } + if (config.tokens && typeof config.tokens === "function") { + api.tokens = config.tokens; + if (config.tokenHeader && typeof config.tokenHeader === "string") { + api.tokenHeader = config.tokenHeader.toLowerCase(); + } + } } function cleanUser(user) { if (user && user.hasOwnProperty('password')) { @@ -118,5 +126,7 @@ module.exports = { init: init, get: function(username) { return api.get(username).then(cleanUser)}, authenticate: function() { return api.authenticate.apply(null, arguments) }, - default: function() { return api.default(); } + default: function() { return api.default(); }, + tokens: function(token) { return api.tokens(token); }, + tokenHeader: function() { return api.tokenHeader } }; diff --git a/test/unit/@node-red/editor-api/lib/auth/strategies_spec.js b/test/unit/@node-red/editor-api/lib/auth/strategies_spec.js index d30f6198a..13573f22a 100644 --- a/test/unit/@node-red/editor-api/lib/auth/strategies_spec.js +++ b/test/unit/@node-red/editor-api/lib/auth/strategies_spec.js @@ -129,6 +129,38 @@ describe("api/auth/strategies", function() { }) }); + describe("Tokens Strategy", function() { + it('Succeeds if tokens user enabled',function(done) { + var userDefault = sinon.stub(Users,"tokens",function(token) { + return when.resolve("tokens-"+token); + }); + strategies.tokensStrategy._success = strategies.tokensStrategy.success; + strategies.tokensStrategy.success = function(user) { + user.should.equal("tokens-1234"); + strategies.tokensStrategy.success = strategies.tokensStrategy._success; + delete strategies.tokensStrategy._success; + done(); + }; + strategies.tokensStrategy.authenticate({headers:{"authorization":"1234"}}); + }); + it('Fails if tokens user not enabled',function(done) { + var userDefault = sinon.stub(Users,"tokens",function() { + return when.resolve(null); + }); + strategies.tokensStrategy._fail = strategies.tokensStrategy.fail; + strategies.tokensStrategy.fail = function(err) { + err.should.equal(401); + strategies.tokensStrategy.fail = strategies.tokensStrategy._fail; + delete strategies.tokensStrategy._fail; + done(); + }; + strategies.tokensStrategy.authenticate({headers:{"authorization":"1234"}}); + }); + afterEach(function() { + Users.tokens.restore(); + }) + }); + describe("Bearer Strategy", function() { it('Rejects invalid token',function(done) { var getToken = sinon.stub(Tokens,"get",function(token) { diff --git a/test/unit/@node-red/editor-api/lib/auth/users_spec.js b/test/unit/@node-red/editor-api/lib/auth/users_spec.js index 515d23034..228163684 100644 --- a/test/unit/@node-red/editor-api/lib/auth/users_spec.js +++ b/test/unit/@node-red/editor-api/lib/auth/users_spec.js @@ -227,4 +227,47 @@ describe("api/auth/users", function() { }); }); }); + + describe('Initialised with tokens set as function',function() { + before(function() { + Users.init({ + type:"strategy", + tokens: function(token) { return("Done-"+token); } + }); + }); + after(function() { + Users.init({}); + }); + describe('#tokens',function() { + it('handles api.tokens being a function',function(done) { + Users.should.have.property('tokens').which.is.a.Function(); + (Users.tokens("1234")).should.equal("Done-1234"); + (Users.tokenHeader()).should.equal("authorization"); + done(); + }); + }); + }); + + describe('Initialised with tokens set as function and tokenHeader set as token header name',function() { + before(function() { + Users.init({ + type:"strategy", + tokens: function(token) { return("Done-"+token); }, + tokenHeader: "X-TEST-TOKEN" + }); + }); + after(function() { + Users.init({}); + }); + describe('#tokens',function() { + it('handles api.tokens being a function and api.tokenHeader being a header name',function(done) { + Users.should.have.property('tokens').which.is.a.Function(); + (Users.tokens("1234")).should.equal("Done-1234"); + Users.should.have.property('tokenHeader').which.is.a.Function(); + (Users.tokenHeader()).should.equal("x-test-token"); + done(); + }); + }); + }); + }); From 95982ad46493650a725627142872303cc5e0ea3f Mon Sep 17 00:00:00 2001 From: KAZUHIRO ITO Date: Thu, 27 Feb 2020 16:01:57 +0900 Subject: [PATCH 2/4] Update adminAuth tokensStrategy test spec --- .../editor-api/lib/auth/strategies_spec.js | 33 ++++++++++++++++--- 1 file changed, 28 insertions(+), 5 deletions(-) diff --git a/test/unit/@node-red/editor-api/lib/auth/strategies_spec.js b/test/unit/@node-red/editor-api/lib/auth/strategies_spec.js index 13573f22a..848aaf99d 100644 --- a/test/unit/@node-red/editor-api/lib/auth/strategies_spec.js +++ b/test/unit/@node-red/editor-api/lib/auth/strategies_spec.js @@ -130,10 +130,13 @@ describe("api/auth/strategies", function() { }); describe("Tokens Strategy", function() { - it('Succeeds if tokens user enabled',function(done) { - var userDefault = sinon.stub(Users,"tokens",function(token) { + it('Succeeds if tokens user enabled custom header',function(done) { + var userTokens = sinon.stub(Users,"tokens",function(token) { return when.resolve("tokens-"+token); }); + var userTokenHeader = sinon.stub(Users,"tokenHeader",function(token) { + return "x-test-token"; + }); strategies.tokensStrategy._success = strategies.tokensStrategy.success; strategies.tokensStrategy.success = function(user) { user.should.equal("tokens-1234"); @@ -141,12 +144,31 @@ describe("api/auth/strategies", function() { delete strategies.tokensStrategy._success; done(); }; - strategies.tokensStrategy.authenticate({headers:{"authorization":"1234"}}); + strategies.tokensStrategy.authenticate({headers:{"x-test-token":"1234"}}); + }); + it('Succeeds if tokens user enabled default header',function(done) { + var userTokens = sinon.stub(Users,"tokens",function(token) { + return when.resolve("tokens-"+token); + }); + var userTokenHeader = sinon.stub(Users,"tokenHeader",function(token) { + return "authorization"; + }); + strategies.tokensStrategy._success = strategies.tokensStrategy.success; + strategies.tokensStrategy.success = function(user) { + user.should.equal("tokens-1234"); + strategies.tokensStrategy.success = strategies.tokensStrategy._success; + delete strategies.tokensStrategy._success; + done(); + }; + strategies.tokensStrategy.authenticate({headers:{"authorization":"Bearer 1234"}}); }); it('Fails if tokens user not enabled',function(done) { - var userDefault = sinon.stub(Users,"tokens",function() { + var userTokens = sinon.stub(Users,"tokens",function() { return when.resolve(null); }); + var userTokenHeader = sinon.stub(Users,"tokenHeader",function(token) { + return "authorization"; + }); strategies.tokensStrategy._fail = strategies.tokensStrategy.fail; strategies.tokensStrategy.fail = function(err) { err.should.equal(401); @@ -154,10 +176,11 @@ describe("api/auth/strategies", function() { delete strategies.tokensStrategy._fail; done(); }; - strategies.tokensStrategy.authenticate({headers:{"authorization":"1234"}}); + strategies.tokensStrategy.authenticate({headers:{"authorization":"Bearer 1234"}}); }); afterEach(function() { Users.tokens.restore(); + Users.tokenHeader.restore(); }) }); From 458d794f52492a311316add9486a3986c2b64904 Mon Sep 17 00:00:00 2001 From: KAZUHIRO ITO Date: Thu, 27 Feb 2020 19:41:59 +0900 Subject: [PATCH 3/4] Fix tokensStrategy order --- packages/node_modules/@node-red/editor-api/lib/auth/index.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/node_modules/@node-red/editor-api/lib/auth/index.js b/packages/node_modules/@node-red/editor-api/lib/auth/index.js index c3948cd27..88e924e53 100644 --- a/packages/node_modules/@node-red/editor-api/lib/auth/index.js +++ b/packages/node_modules/@node-red/editor-api/lib/auth/index.js @@ -61,7 +61,7 @@ function init(_settings,storage) { function needsPermission(permission) { return function(req,res,next) { if (settings && settings.adminAuth) { - return passport.authenticate(['bearer','anon','tokens'],{ session: false })(req,res,function() { + return passport.authenticate(['bearer','tokens','anon'],{ session: false })(req,res,function() { if (!req.user) { return next(); } From 83942c255151aee0e92b2045b93a927576483f54 Mon Sep 17 00:00:00 2001 From: KAZUHIRO ITO Date: Thu, 27 Feb 2020 19:55:21 +0900 Subject: [PATCH 4/4] Fix plugin only receives the actual token --- .../@node-red/editor-api/lib/auth/strategies.js | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/packages/node_modules/@node-red/editor-api/lib/auth/strategies.js b/packages/node_modules/@node-red/editor-api/lib/auth/strategies.js index 9705ddd28..87023a487 100644 --- a/packages/node_modules/@node-red/editor-api/lib/auth/strategies.js +++ b/packages/node_modules/@node-red/editor-api/lib/auth/strategies.js @@ -130,7 +130,14 @@ function TokensStrategy() { util.inherits(TokensStrategy, passport.Strategy); TokensStrategy.prototype.authenticate = function(req) { var self = this; - var token = req.headers[Users.tokenHeader()]; + var token = null; + if (Users.tokenHeader() === 'authorization') { + if (req.headers.authorization && req.headers.authorization.split(' ')[0] === 'Bearer') { + token = req.headers.authorization.split(' ')[1]; + } + } else { + token = req.headers[Users.tokenHeader()]; + } if (token) { Users.tokens(token).then(function(admin) { if (admin) {