diff --git a/npm-shrinkwrap.json b/npm-shrinkwrap.json index aa6b99cfc..3ce80ff83 100644 --- a/npm-shrinkwrap.json +++ b/npm-shrinkwrap.json @@ -757,7 +757,7 @@ }, "supports-color": { "version": "3.2.3", - "from": "supports-color@^3.2.3", + "from": "supports-color@>=3.2.3 <4.0.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-3.2.3.tgz", "dev": true } @@ -867,7 +867,7 @@ "dependencies": { "debug": { "version": "2.6.8", - "from": "debug@2.X", + "from": "debug@>=2.0.0 <3.0.0", "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.8.tgz", "dev": true }, @@ -1258,11 +1258,6 @@ } } }, - "express-ws": { - "version": "3.0.0", - "from": "express-ws@latest", - "resolved": "https://registry.npmjs.org/express-ws/-/express-ws-3.0.0.tgz" - }, "extend": { "version": "3.0.1", "from": "extend@>=3.0.0 <3.1.0", @@ -1430,7 +1425,7 @@ "mkdirp": { "version": "0.3.5", "from": "mkdirp@>=0.3.0 <0.4.0", - "resolved": "http://registry.npmjs.org/mkdirp/-/mkdirp-0.3.5.tgz", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.3.5.tgz", "dev": true }, "ncp": { @@ -1556,7 +1551,7 @@ }, "through2": { "version": "0.6.5", - "from": "through2@^0.6.1", + "from": "through2@>=0.6.1 <0.7.0", "resolved": "https://registry.npmjs.org/through2/-/through2-0.6.5.tgz", "dev": true } @@ -1606,7 +1601,7 @@ }, "object-assign": { "version": "3.0.0", - "from": "object-assign@^3.0.0", + "from": "object-assign@>=3.0.0 <4.0.0", "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-3.0.0.tgz", "dev": true } @@ -1686,7 +1681,7 @@ "dependencies": { "minimist": { "version": "1.2.0", - "from": "minimist@^1.1.0", + "from": "minimist@>=1.1.0 <2.0.0", "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz", "dev": true } @@ -1724,13 +1719,13 @@ }, "string_decoder": { "version": "0.10.31", - "from": "string_decoder@~0.10.x", + "from": "string_decoder@>=0.10.0 <0.11.0", "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-0.10.31.tgz", "dev": true }, "through2": { "version": "0.6.5", - "from": "through2@^0.6.2", + "from": "through2@>=0.6.2 <0.7.0", "resolved": "https://registry.npmjs.org/through2/-/through2-0.6.5.tgz", "dev": true }, @@ -1806,13 +1801,13 @@ }, "minimist": { "version": "1.2.0", - "from": "minimist@^1.1.0", + "from": "minimist@>=1.1.0 <2.0.0", "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz", "dev": true }, "object-assign": { "version": "3.0.0", - "from": "object-assign@^3.0.0", + "from": "object-assign@>=3.0.0 <4.0.0", "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-3.0.0.tgz", "dev": true }, @@ -1824,7 +1819,7 @@ }, "string_decoder": { "version": "0.10.31", - "from": "string_decoder@~0.10.x", + "from": "string_decoder@>=0.10.0 <0.11.0", "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-0.10.31.tgz", "dev": true }, @@ -1920,7 +1915,7 @@ "dependencies": { "async": { "version": "1.5.2", - "from": "async@^1.4.0", + "from": "async@>=1.4.0 <2.0.0", "resolved": "https://registry.npmjs.org/async/-/async-1.5.2.tgz", "dev": true } @@ -2293,13 +2288,13 @@ "dependencies": { "async": { "version": "1.5.2", - "from": "async@1.x", + "from": "async@>=1.0.0 <2.0.0", "resolved": "https://registry.npmjs.org/async/-/async-1.5.2.tgz", "dev": true }, "glob": { "version": "5.0.15", - "from": "glob@^5.0.15", + "from": "glob@>=5.0.15 <6.0.0", "resolved": "https://registry.npmjs.org/glob/-/glob-5.0.15.tgz", "dev": true }, @@ -2311,7 +2306,7 @@ }, "supports-color": { "version": "3.2.3", - "from": "supports-color@^3.1.0", + "from": "supports-color@>=3.1.0 <4.0.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-3.2.3.tgz", "dev": true }, @@ -2712,7 +2707,7 @@ "dependencies": { "minimist": { "version": "1.2.0", - "from": "minimist@^1.1.3", + "from": "minimist@>=1.1.3 <2.0.0", "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz" } } @@ -2907,7 +2902,7 @@ "dependencies": { "debug": { "version": "2.6.8", - "from": "debug@^2.2.0", + "from": "debug@>=2.2.0 <3.0.0", "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.8.tgz", "dev": true }, @@ -2937,7 +2932,7 @@ "dependencies": { "semver": { "version": "5.3.0", - "from": "semver@~5.3.0", + "from": "semver@>=5.3.0 <5.4.0", "resolved": "https://registry.npmjs.org/semver/-/semver-5.3.0.tgz" } } @@ -3385,7 +3380,7 @@ }, "supports-color": { "version": "3.2.3", - "from": "supports-color@^3.2.3", + "from": "supports-color@>=3.2.3 <4.0.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-3.2.3.tgz", "dev": true } @@ -3411,7 +3406,7 @@ }, "supports-color": { "version": "3.2.3", - "from": "supports-color@^3.2.3", + "from": "supports-color@>=3.2.3 <4.0.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-3.2.3.tgz", "dev": true } @@ -3437,7 +3432,7 @@ }, "supports-color": { "version": "3.2.3", - "from": "supports-color@^3.2.3", + "from": "supports-color@>=3.2.3 <4.0.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-3.2.3.tgz", "dev": true } @@ -3463,7 +3458,7 @@ }, "supports-color": { "version": "3.2.3", - "from": "supports-color@^3.2.3", + "from": "supports-color@>=3.2.3 <4.0.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-3.2.3.tgz", "dev": true } @@ -3489,7 +3484,7 @@ }, "supports-color": { "version": "3.2.3", - "from": "supports-color@^3.2.3", + "from": "supports-color@>=3.2.3 <4.0.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-3.2.3.tgz", "dev": true } @@ -3515,7 +3510,7 @@ }, "supports-color": { "version": "3.2.3", - "from": "supports-color@^3.2.3", + "from": "supports-color@>=3.2.3 <4.0.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-3.2.3.tgz", "dev": true } @@ -3541,7 +3536,7 @@ }, "supports-color": { "version": "3.2.3", - "from": "supports-color@^3.2.3", + "from": "supports-color@>=3.2.3 <4.0.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-3.2.3.tgz", "dev": true } @@ -3567,7 +3562,7 @@ }, "supports-color": { "version": "3.2.3", - "from": "supports-color@^3.2.3", + "from": "supports-color@>=3.2.3 <4.0.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-3.2.3.tgz", "dev": true } @@ -3593,7 +3588,7 @@ }, "supports-color": { "version": "3.2.3", - "from": "supports-color@^3.2.3", + "from": "supports-color@>=3.2.3 <4.0.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-3.2.3.tgz", "dev": true } @@ -3619,7 +3614,7 @@ }, "supports-color": { "version": "3.2.3", - "from": "supports-color@^3.2.3", + "from": "supports-color@>=3.2.3 <4.0.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-3.2.3.tgz", "dev": true } @@ -3645,7 +3640,7 @@ }, "supports-color": { "version": "3.2.3", - "from": "supports-color@^3.2.3", + "from": "supports-color@>=3.2.3 <4.0.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-3.2.3.tgz", "dev": true } @@ -3677,7 +3672,7 @@ }, "supports-color": { "version": "3.2.3", - "from": "supports-color@^3.2.3", + "from": "supports-color@>=3.2.3 <4.0.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-3.2.3.tgz", "dev": true } @@ -3709,7 +3704,7 @@ }, "supports-color": { "version": "3.2.3", - "from": "supports-color@^3.2.3", + "from": "supports-color@>=3.2.3 <4.0.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-3.2.3.tgz", "dev": true } @@ -3735,7 +3730,7 @@ }, "supports-color": { "version": "3.2.3", - "from": "supports-color@^3.2.3", + "from": "supports-color@>=3.2.3 <4.0.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-3.2.3.tgz", "dev": true } @@ -3761,7 +3756,7 @@ }, "supports-color": { "version": "3.2.3", - "from": "supports-color@^3.2.3", + "from": "supports-color@>=3.2.3 <4.0.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-3.2.3.tgz", "dev": true } @@ -3787,7 +3782,7 @@ }, "supports-color": { "version": "3.2.3", - "from": "supports-color@^3.2.3", + "from": "supports-color@>=3.2.3 <4.0.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-3.2.3.tgz", "dev": true } @@ -3813,7 +3808,7 @@ }, "supports-color": { "version": "3.2.3", - "from": "supports-color@^3.2.3", + "from": "supports-color@>=3.2.3 <4.0.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-3.2.3.tgz", "dev": true } @@ -3839,7 +3834,7 @@ }, "supports-color": { "version": "3.2.3", - "from": "supports-color@^3.2.3", + "from": "supports-color@>=3.2.3 <4.0.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-3.2.3.tgz", "dev": true } @@ -3865,7 +3860,7 @@ }, "supports-color": { "version": "3.2.3", - "from": "supports-color@^3.2.3", + "from": "supports-color@>=3.2.3 <4.0.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-3.2.3.tgz", "dev": true } @@ -3891,7 +3886,7 @@ }, "supports-color": { "version": "3.2.3", - "from": "supports-color@^3.2.3", + "from": "supports-color@>=3.2.3 <4.0.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-3.2.3.tgz", "dev": true } @@ -3917,7 +3912,7 @@ }, "supports-color": { "version": "3.2.3", - "from": "supports-color@^3.2.3", + "from": "supports-color@>=3.2.3 <4.0.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-3.2.3.tgz", "dev": true } @@ -3943,7 +3938,7 @@ }, "supports-color": { "version": "3.2.3", - "from": "supports-color@^3.2.3", + "from": "supports-color@>=3.2.3 <4.0.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-3.2.3.tgz", "dev": true } @@ -3975,7 +3970,7 @@ }, "supports-color": { "version": "3.2.3", - "from": "supports-color@^3.2.3", + "from": "supports-color@>=3.2.3 <4.0.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-3.2.3.tgz", "dev": true } @@ -4001,7 +3996,7 @@ }, "supports-color": { "version": "3.2.3", - "from": "supports-color@^3.2.3", + "from": "supports-color@>=3.2.3 <4.0.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-3.2.3.tgz", "dev": true } @@ -4033,7 +4028,7 @@ }, "supports-color": { "version": "3.2.3", - "from": "supports-color@^3.2.3", + "from": "supports-color@>=3.2.3 <4.0.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-3.2.3.tgz", "dev": true } @@ -5152,7 +5147,7 @@ }, "string_decoder": { "version": "0.10.31", - "from": "string_decoder@~0.10.x", + "from": "string_decoder@>=0.10.0 <0.11.0", "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-0.10.31.tgz", "dev": true }, @@ -5253,7 +5248,7 @@ }, "ws": { "version": "2.3.1", - "from": "ws@>=2.0.0 <3.0.0", + "from": "https://registry.npmjs.org/ws/-/ws-2.3.1.tgz", "resolved": "https://registry.npmjs.org/ws/-/ws-2.3.1.tgz", "dependencies": { "safe-buffer": { diff --git a/package.json b/package.json index 4024dbbf9..e74d7b363 100644 --- a/package.json +++ b/package.json @@ -33,7 +33,6 @@ "ejs-cli": "^2.0.0", "express": "^4.15.4", "express-session": "^1.15.5", - "express-ws": "^3.0.0", "gulp-sass": "^3.0.0", "hat": "0.0.3", "hock": "https://registry.npmjs.org/hock/-/hock-1.3.2.tgz", @@ -69,7 +68,8 @@ "underscore": "^1.7.0", "uuid": "^3.1.0", "valid-url": "^1.0.9", - "validator": "^4.9.0" + "validator": "^4.9.0", + "ws": "https://registry.npmjs.org/ws/-/ws-2.3.1.tgz" }, "devDependencies": { "bootstrap-sass": "^3.3.3", diff --git a/src/routes/apps.js b/src/routes/apps.js index c4669e2b9..787e08352 100644 --- a/src/routes/apps.js +++ b/src/routes/apps.js @@ -461,7 +461,7 @@ function exec(req, res, next) { }); } -function execWebSocket(ws, req, next) { +function execWebSocket(req, res, next) { assert.strictEqual(typeof req.params.id, 'string'); debug('Execing websocket into app id:%s and cmd:%s', req.params.id, req.query.cmd); @@ -487,24 +487,28 @@ function execWebSocket(ws, req, next) { console.log('Connected to terminal'); - duplexStream.on('end', function () { ws.close(); }); - duplexStream.on('close', function () { ws.close(); }); - duplexStream.on('error', function (error) { - console.error('duplexStream error:', error); - }); - duplexStream.on('data', function (data) { - if (ws.readyState !== WebSocket.OPEN) return; - ws.send(data.toString()); - }); + req.clearTimeout(); - ws.on('error', function (error) { - console.error('websocket error:', error); - }); - ws.on('message', function (msg) { - duplexStream.write(msg); - }); - ws.on('close', function () { - // Clean things up, if any? + res.handleUpgrade(function (ws) { + duplexStream.on('end', function () { ws.close(); }); + duplexStream.on('close', function () { ws.close(); }); + duplexStream.on('error', function (error) { + console.error('duplexStream error:', error); + }); + duplexStream.on('data', function (data) { + if (ws.readyState !== WebSocket.OPEN) return; + ws.send(data.toString()); + }); + + ws.on('error', function (error) { + console.error('websocket error:', error); + }); + ws.on('message', function (msg) { + duplexStream.write(msg); + }); + ws.on('close', function () { + // Clean things up, if any? + }); }); }); } diff --git a/src/routes/oauth2.js b/src/routes/oauth2.js index 15da3e46d..ce92c73dc 100644 --- a/src/routes/oauth2.js +++ b/src/routes/oauth2.js @@ -534,7 +534,9 @@ function scope(requestedScope) { ]; } -function websocketAuth(ws, req, next) { +function websocketAuth(requestedScopes, req, res, next) { + assert(Array.isArray(requestedScopes)); + if (typeof req.query.access_token !== 'string') return next(new HttpError(401, 'Unauthorized')); auth.accessTokenAuth(req.query.access_token, function (error, user, info) { @@ -544,6 +546,9 @@ function websocketAuth(ws, req, next) { req.user = user; req.authInfo = info; + var error = validateRequestedScopes(req, requestedScopes); + if (error) return next(new HttpError(401, error.message)); + next(); }); } diff --git a/src/server.js b/src/server.js index aa47c4dec..738ca71dd 100644 --- a/src/server.js +++ b/src/server.js @@ -19,7 +19,8 @@ var assert = require('assert'), middleware = require('./middleware'), passport = require('passport'), path = require('path'), - routes = require('./routes/index.js'); + routes = require('./routes/index.js'), + ws = require('ws'); var gHttpServer = null; var gSysadminHttpServer = null; @@ -28,8 +29,7 @@ function initializeExpressSync() { var app = express(); var httpServer = http.createServer(app); - // enabled websocket handling - require('express-ws')(app, httpServer); + const wsServer = new ws.Server({ noServer: true }); // in noServer mode, we have to handle 'upgrade' and call handleUpgrade var QUERY_LIMIT = '1mb', // max size for json and urlencoded queries (see also client_max_body_size in nginx) FIELD_LIMIT = 2 * 1024 * 1024; // max fields that can appear in multipart @@ -192,7 +192,8 @@ 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.oauth2.websocketAuth, routes.apps.execWebSocket); + // websocket cannot do bearer authentication + router.get ('/api/v1/apps/:id/execws', routes.oauth2.websocketAuth.bind(null, [ clients.SCOPE_APPS ]), routes.user.requireAdmin, 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) @@ -238,13 +239,15 @@ function initializeExpressSync() { // upgrade handler httpServer.on('upgrade', function (req, socket, head) { + // create a node response object for express + var res = new http.ServerResponse({}); + res.assignSocket(socket); + if (req.headers.upgrade === 'websocket') { - // websocket connections should never time out - req.clearTimeout(); + res.handleUpgrade = function (callback) { + wsServer.handleUpgrade(req, socket, head, callback); + }; } else { - // create a node response object for express - var res = new http.ServerResponse({}); - res.assignSocket(socket); res.sendUpgradeHandshake = function () { // could extend express.response as well console.log('----- now send the upgrade handshake') socket.write('HTTP/1.1 101 TCP Handshake\r\n' + @@ -252,11 +255,11 @@ function initializeExpressSync() { 'Connection: Upgrade\r\n' + '\r\n'); }; - - // route through express middleware. if we provide no callback, express will provide a 'finalhandler' - // TODO: it's not clear if socket needs to be destroyed - app(req, res); } + + // route through express middleware. if we provide no callback, express will provide a 'finalhandler' + // TODO: it's not clear if socket needs to be destroyed + app(req, res); }); return httpServer;