diff --git a/src/auth.js b/src/auth.js index bfdb0eb20..23800ca48 100644 --- a/src/auth.js +++ b/src/auth.js @@ -2,7 +2,9 @@ exports = module.exports = { initialize: initialize, - uninitialize: uninitialize + uninitialize: uninitialize, + + accessTokenAuth: accessTokenAuth }; var assert = require('assert'), @@ -23,22 +25,22 @@ var assert = require('assert'), function initialize(callback) { assert.strictEqual(typeof callback, 'function'); - + passport.serializeUser(function (user, callback) { callback(null, user.id); }); - + passport.deserializeUser(function(userId, callback) { user.get(userId, function (error, result) { if (error) return callback(error); - + var md5 = crypto.createHash('md5').update(result.alternateEmail || result.email).digest('hex'); result.gravatar = 'https://www.gravatar.com/avatar/' + md5 + '.jpg?s=24&d=mm'; - + callback(null, result); }); }); - + passport.use(new LocalStrategy(function (username, password, callback) { if (username.indexOf('@') === -1) { user.verifyWithUsername(username, password, function (error, result) { @@ -58,7 +60,7 @@ function initialize(callback) { }); } })); - + passport.use(new BasicStrategy(function (username, password, callback) { if (username.indexOf('cid-') === 0) { debug('BasicStrategy: detected client id %s instead of username:password', username); @@ -80,7 +82,7 @@ function initialize(callback) { }); } })); - + passport.use(new ClientPasswordStrategy(function (clientId, clientSecret, callback) { clients.get(clientId, function(error, client) { if (error && error.reason === ClientsError.NOT_FOUND) return callback(null, false); @@ -89,30 +91,35 @@ function initialize(callback) { return callback(null, client); }); })); - - passport.use(new BearerStrategy(function (accessToken, callback) { - tokendb.get(accessToken, function (error, token) { - if (error && error.reason === DatabaseError.NOT_FOUND) return callback(null, false); - if (error) return callback(error); - - // scopes here can define what capabilities that token carries - // passport put the 'info' object into req.authInfo, where we can further validate the scopes - var info = { scope: token.scope }; - - user.get(token.identifier, function (error, user) { - if (error && error.reason === UserError.NOT_FOUND) return callback(null, false); - if (error) return callback(error); - - callback(null, user, info); - }); - }); - })); - + + passport.use(new BearerStrategy(accessTokenAuth)); + callback(null); } function uninitialize(callback) { assert.strictEqual(typeof callback, 'function'); - + callback(null); } + +function accessTokenAuth(accessToken, callback) { + assert.strictEqual(typeof accessToken, 'string'); + assert.strictEqual(typeof callback, 'function'); + + tokendb.get(accessToken, function (error, token) { + if (error && error.reason === DatabaseError.NOT_FOUND) return callback(null, false); + if (error) return callback(error); + + // scopes here can define what capabilities that token carries + // passport put the 'info' object into req.authInfo, where we can further validate the scopes + var info = { scope: token.scope }; + + user.get(token.identifier, function (error, user) { + if (error && error.reason === UserError.NOT_FOUND) return callback(null, false); + if (error) return callback(error); + + callback(null, user, info); + }); + }); +} diff --git a/src/routes/oauth2.js b/src/routes/oauth2.js index 882f14f89..15da3e46d 100644 --- a/src/routes/oauth2.js +++ b/src/routes/oauth2.js @@ -3,6 +3,7 @@ var appdb = require('../appdb'), apps = require('../apps'), assert = require('assert'), + auth = require('../auth.js'), authcodedb = require('../authcodedb'), clients = require('../clients'), ClientsError = clients.ClientsError, @@ -533,6 +534,20 @@ function scope(requestedScope) { ]; } +function websocketAuth(ws, req, next) { + if (typeof req.query.access_token !== 'string') return next(new HttpError(401, 'Unauthorized')); + + auth.accessTokenAuth(req.query.access_token, function (error, user, info) { + if (error) return next(new HttpError(500, error.message)); + if (!user) return next(new HttpError(401, 'Unauthorized')); + + req.user = user; + req.authInfo = info; + + next(); + }); +} + // Cross-site request forgery protection middleware for login form var csrf = [ middleware.csrf(), @@ -559,5 +574,6 @@ exports = module.exports = { token: token, validateRequestedScopes: validateRequestedScopes, scope: scope, + websocketAuth: websocketAuth, csrf: csrf }; diff --git a/src/server.js b/src/server.js index 2e474a638..3b5c20f15 100644 --- a/src/server.js +++ b/src/server.js @@ -192,7 +192,7 @@ function initializeExpressSync() { router.get ('/api/v1/apps/:id/logstream', appsScope, routes.user.requireAdmin, routes.apps.getLogStream); router.get ('/api/v1/apps/:id/logs', appsScope, routes.user.requireAdmin, routes.apps.getLogs); router.get ('/api/v1/apps/:id/exec', routes.developer.enabled, appsScope, routes.user.requireAdmin, routes.apps.exec); - router.ws ('/api/v1/apps/:id/execws', routes.apps.execWebSocket); + router.ws ('/api/v1/apps/:id/execws', routes.oauth2.websocketAuth, routes.apps.execWebSocket); router.post('/api/v1/apps/:id/clone', appsScope, routes.user.requireAdmin, routes.apps.cloneApp); // settings routes (these are for the settings tab - avatar & name have public routes for normal users. see above) diff --git a/webadmin/src/js/client.js b/webadmin/src/js/client.js index dc512a635..69e638471 100644 --- a/webadmin/src/js/client.js +++ b/webadmin/src/js/client.js @@ -242,6 +242,10 @@ angular.module('Application').service('Client', ['$http', 'md5', 'Notification', token = accessToken; }; + Client.prototype.getToken = function () { + return token; + }; + /* * Rest API wrappers */ diff --git a/webadmin/src/views/debug.js b/webadmin/src/views/debug.js index 37de47472..882eb23a7 100644 --- a/webadmin/src/views/debug.js +++ b/webadmin/src/views/debug.js @@ -58,6 +58,8 @@ angular.module('Application').controller('DebugController', ['$scope', '$locatio reset(); + if (!$scope.selected) return; + var func = $scope.selected.type === 'platform' ? Client.getPlatformLogs : Client.getAppLogs; func($scope.selected.value, true, $scope.lines, function handleLogs(error, result) { if (error) return console.error(error); @@ -91,6 +93,8 @@ angular.module('Application').controller('DebugController', ['$scope', '$locatio reset(); + if (!$scope.selected) return; + // we can only connect to apps here if ($scope.selected.type !== 'app') { var tmp = $('.logs-and-term-container'); @@ -104,7 +108,7 @@ angular.module('Application').controller('DebugController', ['$scope', '$locatio try { // websocket cannot use relative urls - var url = Client.apiOrigin.replace('https', 'wss') + '/api/v1/apps/' + $scope.selected.value + '/execws?tty=true'; + var url = Client.apiOrigin.replace('https', 'wss') + '/api/v1/apps/' + $scope.selected.value + '/execws?tty=true&access_token=' + Client.getToken(); $scope.terminalSocket = new WebSocket(url); $scope.terminal.attach($scope.terminalSocket);