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 87023a487..bae4df5c3 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,38 +123,57 @@ AnonymousStrategy.prototype.authenticate = function(req) { }); } + +function authenticateUserToken(req) { + return new Promise( (resolve,reject) => { + var token = null; + var tokenHeader = Users.tokenHeader(); + if (Users.tokenHeader() === null) { + // No custom user token provided. Fail the request + reject(); + return; + } else 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(user) { + if (user) { + resolve(user); + } else { + reject(); + } + }); + } else { + reject(); + } + }); +} + + function TokensStrategy() { passport.Strategy.call(this); this.name = 'tokens'; } util.inherits(TokensStrategy, passport.Strategy); TokensStrategy.prototype.authenticate = function(req) { - var self = this; - 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) { - self.success(admin,{scope:admin.permissions}); - } else { - self.fail(401); - } - }); - } else { - self.fail(401); - } + authenticateUserToken(req).then(user => { + this.success(user,{scope:user.permissions}); + }).catch(err => { + this.fail(401); + }); } + + module.exports = { bearerStrategy: bearerStrategy, clientPasswordStrategy: clientPasswordStrategy, passwordTokenExchange: passwordTokenExchange, anonymousStrategy: new AnonymousStrategy(), - tokensStrategy: new TokensStrategy() + tokensStrategy: new TokensStrategy(), + authenticateUserToken: authenticateUserToken } 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 f032332db..b5754bee9 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 @@ -61,7 +61,7 @@ var api = { authenticate: authenticate, default: getDefaultUser, tokens: getDefaultUser, - tokenHeader: "authorization" + tokenHeader: null } function init(config) { @@ -111,6 +111,8 @@ function init(config) { api.tokens = config.tokens; if (config.tokenHeader && typeof config.tokenHeader === "string") { api.tokenHeader = config.tokenHeader.toLowerCase(); + } else { + api.tokenHeader = "authorization"; } } } diff --git a/packages/node_modules/@node-red/editor-api/lib/editor/comms.js b/packages/node_modules/@node-red/editor-api/lib/editor/comms.js index 2c46f87e8..84da62a69 100644 --- a/packages/node_modules/@node-red/editor-api/lib/editor/comms.js +++ b/packages/node_modules/@node-red/editor-api/lib/editor/comms.js @@ -21,6 +21,7 @@ var log = require("@node-red/util").log; // TODO: separate module var Tokens; var Users; var Permissions; +var Strategies; var server; var settings; @@ -44,6 +45,7 @@ function init(_server,_settings,_runtimeAPI) { Tokens.onSessionExpiry(handleSessionExpiry); Users = require("../auth/users"); Permissions = require("../auth/permissions"); + Strategies = require("../auth/strategies"); } function handleSessionExpiry(session) { @@ -63,17 +65,18 @@ function generateSession(length) { return token.join(""); } -function CommsConnection(ws) { +function CommsConnection(ws, user) { this.session = generateSession(32); this.ws = ws; this.stack = []; - this.user = null; + this.user = user; this.lastSentTime = 0; var self = this; log.audit({event: "comms.open"}); log.trace("comms.open "+self.session); - var pendingAuth = (settings.adminAuth != null); + var preAuthed = !!user; + var pendingAuth = !this.user && (settings.adminAuth != null); if (!pendingAuth) { addActiveConnection(self); @@ -199,8 +202,8 @@ function start() { var commsPath = settings.httpAdminRoot || "/"; commsPath = (commsPath.slice(0,1) != "/" ? "/":"") + commsPath + (commsPath.slice(-1) == "/" ? "":"/") + "comms"; wsServer = new ws.Server({ noServer: true }); - wsServer.on('connection',function(ws) { - var commsConnection = new CommsConnection(ws); + wsServer.on('connection',function(ws, request, user) { + var commsConnection = new CommsConnection(ws, user); }); wsServer.on('error', function(err) { log.warn(log._("comms.error-server",{message:err.toString()})); @@ -209,8 +212,26 @@ function start() { server.on('upgrade', function upgrade(request, socket, head) { const pathname = url.parse(request.url).pathname; if (pathname === commsPath) { + if (Users.tokenHeader() !== null && request.headers[Users.tokenHeader()]) { + // The user has provided custom token handling. For the websocket, + // the token could be provided in two ways: + // - as an http header (only possible with a reverse proxy setup) + // - passed over the connected websock in an auth packet + // If the header is present, verify the token. If not, use the auth + // packet over the connected socket + // + Strategies.authenticateUserToken(request).then(user => { + wsServer.handleUpgrade(request, socket, head, function done(ws) { + wsServer.emit('connection', ws, request, user); + }); + }).catch(err => { + log.audit({event: "comms.auth.fail"}); + socket.destroy(); + }) + return + } wsServer.handleUpgrade(request, socket, head, function done(ws) { - wsServer.emit('connection', ws, request); + wsServer.emit('connection', ws, request, null); }); } // Don't destroy the socket as other listeners may want to handle the 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 228163684..18a179ac7 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 @@ -155,6 +155,10 @@ describe("api/auth/users", function() { }); describe('#get',function() { + it("returns null for tokenHeader", function() { + should.not.exist(Users.tokenHeader()); + }); + it('delegates get user',function(done) { Users.get('dave').then(function(user) { try {