From bddb9b6a9c9faa1e0770e5f08d138a82ab7f0848 Mon Sep 17 00:00:00 2001 From: Ike The Coder Date: Wed, 6 Nov 2019 10:06:02 -0800 Subject: [PATCH] Changes for supporting websocket and webhook --- microservices/requestApi/app.js | 3 + microservices/requestApi/auth/webhook_auth.js | 22 +++++ .../requestApi/config/default.json.example | 3 + .../requestApi/config/test.json.example | 1 + microservices/requestApi/package-lock.json | 63 +++++++------ microservices/requestApi/package.json | 2 + .../requestApi/routes/v1/auth/webhook_auth.js | 1 + .../requestApi/routes/v1/messages/messages.js | 48 ++++++++++ .../requestApi/routes/v1/routes/webhook.js | 26 +++++ microservices/requestApi/routes/v1/v1.js | 3 + microservices/requestApi/test/v1/webhook.js | 24 +++++ microservices/requestApi/websocket.js | 94 +++++++++++++++++++ 12 files changed, 260 insertions(+), 30 deletions(-) create mode 100644 microservices/requestApi/auth/webhook_auth.js create mode 100644 microservices/requestApi/routes/v1/auth/webhook_auth.js create mode 100644 microservices/requestApi/routes/v1/messages/messages.js create mode 100644 microservices/requestApi/routes/v1/routes/webhook.js create mode 100644 microservices/requestApi/test/v1/webhook.js create mode 100644 microservices/requestApi/websocket.js diff --git a/microservices/requestApi/app.js b/microservices/requestApi/app.js index 96db36b8f..2229d9634 100644 --- a/microservices/requestApi/app.js +++ b/microservices/requestApi/app.js @@ -27,6 +27,9 @@ app.get("/version", function(req, res){ }) }); +var websockets = require('./websocket'); +var wss = websockets.init(); + log.level = config.get('logLevel'); log.addLevel('debug', 2900, { fg: 'green' }); diff --git a/microservices/requestApi/auth/webhook_auth.js b/microservices/requestApi/auth/webhook_auth.js new file mode 100644 index 000000000..c82a21638 --- /dev/null +++ b/microservices/requestApi/auth/webhook_auth.js @@ -0,0 +1,22 @@ +const passport = require('passport'); +const passJwt = require('passport-jwt'); +const passApiKey = require('passport-headerapikey'); +const HeaderAPIKeyStrategy = passApiKey.HeaderAPIKeyStrategy; +const config = require('config'); +const logger = require('npmlog'); + +passport.use(new HeaderAPIKeyStrategy( + { header: 'Authorization', prefix: 'Api-Key ' }, + false, + function(apiKey, cb) { + if (config.get("webhookSecret") == apiKey) { + const user = { + } + return cb(null, user); + } else { + return cb(null, false); + } + } +)); + +module.exports = passport; diff --git a/microservices/requestApi/config/default.json.example b/microservices/requestApi/config/default.json.example index b1ca1cc94..9b8624c63 100644 --- a/microservices/requestApi/config/default.json.example +++ b/microservices/requestApi/config/default.json.example @@ -1,5 +1,6 @@ { "apiPort": 3002, + "wsPort": 2998, "logLevel": "info", "morganLogType": "dev", @@ -14,6 +15,8 @@ "validationApi": "http://localhost:3003", "validationApiSecret": "MySecret", + "webhookSecret": "s3cr3t", + "storageApi": { "uri": "localhost", "port": 9000, diff --git a/microservices/requestApi/config/test.json.example b/microservices/requestApi/config/test.json.example index b65fc8609..15f82533b 100644 --- a/microservices/requestApi/config/test.json.example +++ b/microservices/requestApi/config/test.json.example @@ -4,6 +4,7 @@ "testJWT": "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJpc3MiOiJPbmxpbmUgSldUIEJ1aWxkZXIiLCJpYXQiOjE1NDA5MTUzMzAsImV4cCI6MTU3MjQ1MTMzMCwiYXVkIjoid3d3LmV4YW1wbGUuY29tIiwic3ViIjoianJvY2tldEBleGFtcGxlLmNvbSIsIkdpdmVuTmFtZSI6IkpvaG5ueSIsIlN1cm5hbWUiOiJSb2NrZXQiLCJFbWFpbCI6ImphbmVEb2VAZXhhbXBsZS5jb20iLCJHcm91cHMiOlsiZXhwb3J0ZXIiLCJwcm9qZWN0X2EiLCJvYyIsInJlcG9ydHMiXSwiem9uZSI6ImludGVybmFsIn0.TB80qVLtfhlOLm8RmavHUomjBUED8s7A_kPdPxJFx8A", "testSupervisorExternalJWT": "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJpc3MiOiJPbmxpbmUgSldUIEJ1aWxkZXIiLCJpYXQiOjE1NDA5MTUzMzAsImV4cCI6MTU3MjQ1MTMzMCwiYXVkIjoid3d3LmV4YW1wbGUuY29tIiwic3ViIjoianJvY2tldEBleGFtcGxlLmNvbSIsIkdpdmVuTmFtZSI6IkpvaG5ueSIsIlN1cm5hbWUiOiJSb2NrZXQiLCJFbWFpbCI6ImphbmVEb2VAZXhhbXBsZS5jb20iLCJHcm91cHMiOlsicmVwb3J0cyJdLCJ6b25lIjoiZXh0ZXJuYWwifQ.swxroVSE_k7tvs9LPtCSov_PPiPJOI6K9bco9B7TKbg", "testSupervisorInternalJWT": "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJpc3MiOiJPbmxpbmUgSldUIEJ1aWxkZXIiLCJpYXQiOjE1NDA5MTUzMzAsImV4cCI6MTU3MjQ1MTMzMCwiYXVkIjoid3d3LmV4YW1wbGUuY29tIiwic3ViIjoianJvY2tldEBleGFtcGxlLmNvbSIsIkdpdmVuTmFtZSI6IkpvaG5ueSIsIlN1cm5hbWUiOiJSb2NrZXQiLCJFbWFpbCI6ImphbmVEb2VAZXhhbXBsZS5jb20iLCJHcm91cHMiOlsicmVwb3J0cyJdLCJ6b25lIjoiaW50ZXJuYWwifQ.SVSwyj6N32zixRmCYekm0P6moYXcLi0ksc9Ih5mqx8U", + "testWebsocketJWT": "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJpc3MiOiJPbmxpbmUgSldUIEJ1aWxkZXIiLCJpYXQiOjE1NDA5MTUzMzAsImV4cCI6MTY3NDQ2MTMzMCwiYXVkIjoid3d3LmV4YW1wbGUuY29tIiwic3ViIjoianJvY2tldEBleGFtcGxlLmNvbSIsInByZWZlcnJlZF91c2VybmFtZSI6Impyb2NrZXQiLCJnaXZlbl9uYW1lIjoiSm9obm55IiwiZmFtaWx5X25hbWUiOiJSb2NrZXQiLCJlbWFpbCI6ImphbmVEb2VAZXhhbXBsZS5jb20iLCJncm91cHMiOlsiL2V4cG9ydGVyIiwiL3Byb2plY3RfMSIsIi9wcm9qZWN0X2EiLCIvb2MiXSwiem9uZSI6ImludGVybmFsIn0.HTEyzY9__W2yyJBgczhFItx6hZ8DTZeOSXcnqK_IlNU", "database": { "host": "localhost", "username": "forumUser", diff --git a/microservices/requestApi/package-lock.json b/microservices/requestApi/package-lock.json index 0f2a64ddc..a19d03344 100644 --- a/microservices/requestApi/package-lock.json +++ b/microservices/requestApi/package-lock.json @@ -366,6 +366,11 @@ "integrity": "sha512-z/WhQ5FPySLdvREByI2vZiTWwCnF0moMJ1hK9YQwDTHKh6I7/uSckMetoRGb5UBZPC1z0jlw+n/XCgjeH7y1AQ==", "dev": true }, + "async-limiter": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/async-limiter/-/async-limiter-1.0.1.tgz", + "integrity": "sha512-csOlWGAcRFJaI6m+F2WKdnMKr4HhdhFVBk0H/QbJFMCr+uO2kwohwXQPxw/9OCxp05r5ghVBFSyioixx3gfkNQ==" + }, "asynckit": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", @@ -1739,8 +1744,7 @@ "version": "2.1.1", "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz", "integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8=", - "dev": true, - "optional": true + "dev": true }, "aproba": { "version": "1.2.0", @@ -1764,15 +1768,13 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz", "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=", - "dev": true, - "optional": true + "dev": true }, "brace-expansion": { "version": "1.1.11", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", "dev": true, - "optional": true, "requires": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" @@ -1789,22 +1791,19 @@ "version": "1.1.0", "resolved": "https://registry.npmjs.org/code-point-at/-/code-point-at-1.1.0.tgz", "integrity": "sha1-DQcLTQQ6W+ozovGkDi7bPZpMz3c=", - "dev": true, - "optional": true + "dev": true }, "concat-map": { "version": "0.0.1", "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=", - "dev": true, - "optional": true + "dev": true }, "console-control-strings": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/console-control-strings/-/console-control-strings-1.1.0.tgz", "integrity": "sha1-PXz0Rk22RG6mRL9LOVB/mFEAjo4=", - "dev": true, - "optional": true + "dev": true }, "core-util-is": { "version": "1.0.2", @@ -1935,8 +1934,7 @@ "version": "2.0.3", "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=", - "dev": true, - "optional": true + "dev": true }, "ini": { "version": "1.3.5", @@ -1950,7 +1948,6 @@ "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-1.0.0.tgz", "integrity": "sha1-754xOG8DGn8NZDr4L95QxFfvAMs=", "dev": true, - "optional": true, "requires": { "number-is-nan": "^1.0.0" } @@ -1967,7 +1964,6 @@ "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", "dev": true, - "optional": true, "requires": { "brace-expansion": "^1.1.7" } @@ -1976,15 +1972,13 @@ "version": "0.0.8", "resolved": "https://registry.npmjs.org/minimist/-/minimist-0.0.8.tgz", "integrity": "sha1-hX/Kv8M5fSYluCKCYuhqp6ARsF0=", - "dev": true, - "optional": true + "dev": true }, "minipass": { "version": "2.3.5", "resolved": "https://registry.npmjs.org/minipass/-/minipass-2.3.5.tgz", "integrity": "sha512-Gi1W4k059gyRbyVUZQ4mEqLm0YIUiGYfvxhF6SIlk3ui1WVxMTGfGdQ2SInh3PDrRTVvPKgULkpJtT4RH10+VA==", "dev": true, - "optional": true, "requires": { "safe-buffer": "^5.1.2", "yallist": "^3.0.0" @@ -2005,7 +1999,6 @@ "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.1.tgz", "integrity": "sha1-MAV0OOrGz3+MR2fzhkjWaX11yQM=", "dev": true, - "optional": true, "requires": { "minimist": "0.0.8" } @@ -2094,8 +2087,7 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/number-is-nan/-/number-is-nan-1.0.1.tgz", "integrity": "sha1-CXtgK1NCKlIsGvuHkDGDNpQaAR0=", - "dev": true, - "optional": true + "dev": true }, "object-assign": { "version": "4.1.1", @@ -2109,7 +2101,6 @@ "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", "dev": true, - "optional": true, "requires": { "wrappy": "1" } @@ -2205,8 +2196,7 @@ "version": "5.1.2", "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", - "dev": true, - "optional": true + "dev": true }, "safer-buffer": { "version": "2.1.2", @@ -2248,7 +2238,6 @@ "resolved": "https://registry.npmjs.org/string-width/-/string-width-1.0.2.tgz", "integrity": "sha1-EYvfW4zcUaKn5w0hHgfisLmxB9M=", "dev": true, - "optional": true, "requires": { "code-point-at": "^1.0.0", "is-fullwidth-code-point": "^1.0.0", @@ -2270,7 +2259,6 @@ "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=", "dev": true, - "optional": true, "requires": { "ansi-regex": "^2.0.0" } @@ -2319,15 +2307,13 @@ "version": "1.0.2", "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=", - "dev": true, - "optional": true + "dev": true }, "yallist": { "version": "3.0.3", "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.0.3.tgz", "integrity": "sha512-S+Zk8DEWE6oKpV+vI3qWkaK+jSbIK86pCwe2IF/xwIpQ8jEuxpw9NyaGjmp9+BoJv5FV2piqCDcoCtStppiq2A==", - "dev": true, - "optional": true + "dev": true } } }, @@ -4122,6 +4108,15 @@ "pause": "0.0.1" } }, + "passport-headerapikey": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/passport-headerapikey/-/passport-headerapikey-1.2.0.tgz", + "integrity": "sha512-MThURwa/zjEhD8qn60lYYQYqJL5RtJl3vfGFvIw0zoZNam4Sfy/lUsz/4eB/ustoNy3+2FnDfM7aW0oGoRNX6Q==", + "requires": { + "lodash": "^4.17.15", + "passport-strategy": "^1.0.0" + } + }, "passport-jwt": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/passport-jwt/-/passport-jwt-4.0.0.tgz", @@ -5519,6 +5514,14 @@ "signal-exit": "^3.0.2" } }, + "ws": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/ws/-/ws-7.2.0.tgz", + "integrity": "sha512-+SqNqFbwTm/0DC18KYzIsMTnEWpLwJsiasW/O17la4iDRRIO9uaHbvKiAS3AHgTiuuWerK/brj4O6MYZkei9xg==", + "requires": { + "async-limiter": "^1.0.0" + } + }, "xdg-basedir": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/xdg-basedir/-/xdg-basedir-3.0.0.tgz", diff --git a/microservices/requestApi/package.json b/microservices/requestApi/package.json index 07de9ca38..122ec254e 100644 --- a/microservices/requestApi/package.json +++ b/microservices/requestApi/package.json @@ -22,7 +22,9 @@ "nodemailer": "^6.3.0", "npmlog": "^4.1.2", "passport": "^0.4.0", + "passport-headerapikey": "^1.1.0", "passport-jwt": "^4.0.0", + "ws": "^7.1.1", "request": "^2.88.0" }, "devDependencies": { diff --git a/microservices/requestApi/routes/v1/auth/webhook_auth.js b/microservices/requestApi/routes/v1/auth/webhook_auth.js new file mode 100644 index 000000000..94c5a7d1f --- /dev/null +++ b/microservices/requestApi/routes/v1/auth/webhook_auth.js @@ -0,0 +1 @@ +module.exports = require('../../../auth/webhook_auth'); \ No newline at end of file diff --git a/microservices/requestApi/routes/v1/messages/messages.js b/microservices/requestApi/routes/v1/messages/messages.js new file mode 100644 index 000000000..71f565e3a --- /dev/null +++ b/microservices/requestApi/routes/v1/messages/messages.js @@ -0,0 +1,48 @@ +var messages = {}; +var logger = require('npmlog'); + +function checkRequestPermissions(user, requestId, fileId, sock, cb){ + + var db = require('../db/db'); + + logger.debug('Checking request permission', requestId, fileId); + + db.Request.getAll({_id: requestId}, 1, 1, user, function(findErr, findRes){ + if (findErr || !findRes || findRes.length === 0){ + logger.debug('Request not found.', requestId, findErr); + cb(false, sock); + return; + } + // Verify that the fileId is in the request files list + if (!(fileId in findRes.files)) { + logger.debug('File not found in request.', requestId); + cb(false, sock); + return; + } + cb(true, sock); + }); +} + +function sendFileStatusMessage(fileStatus){ + var websockets = require('../../../websocket'); + var conns = websockets.getConnections(); + var keys = Object.keys(conns); + for (var i=0; i ws.terminate(), 20000); \ No newline at end of file diff --git a/microservices/requestApi/websocket.js b/microservices/requestApi/websocket.js new file mode 100644 index 000000000..e23d0af9d --- /dev/null +++ b/microservices/requestApi/websocket.js @@ -0,0 +1,94 @@ +var websocket = {}; +var config = require('config'); +var auth = require('./auth/auth'); +var jwt = require('jsonwebtoken'); +const WebSocket = require('ws'); +var logger = require('npmlog'); + +websocket.connections = {}; +websocket.server = null; + +websocket.init = function(){ + var self = this; + + this.server = new WebSocket.Server({ + port: config.get("wsPort"), + perMessageDeflate: false, + verifyClient: function(info, cb){ + logger.debug("Websocket verifying"); + + var token = info.req.headers['sec-websocket-token']; + if (!token){ + cb(false, 401, "Unauthorized") + }else{ + logger.debug("Websocket verifying validate JWT"); + jwt.verify(token, config.get("jwtSecret"), function(err, decoded){ + if (err){ + logger.error(err); + cb(false, 401, "Unauthorized"); + }else{ + var userConf = config.get('user') + var user = { + jwt: token, + email: decoded[userConf.emailField], + firstName: decoded[userConf.givenNameField], + lastName: decoded[userConf.surNameField], + name: decoded[userConf.givenNameField] + " " + decoded[userConf.surNameField], + groups: decoded[userConf.groupField], + id: decoded[userConf.idField], + zone: (decoded.zone) ? decoded.zone : "external", + EXTERNAL_ZONE: 'external', + INTERNAL_ZONE: 'internal' + }; + info.req.user = user; + cb(true); + } + }) + } + } + }); + + function heartbeat() { + this.isAlive = true; + } + + this.server.on('connection', function connection(ws, req) { + var logger = require('npmlog'); + + logger.debug("Websocket connection opened for " + req.user.id); + logger.debug("Connection: " + req.url); + + // URL: /{requestId} + + ws.isAlive = true; + ws.on('pong', heartbeat); + ws.user = req.user; + ws.requestId = req.url.substring(1); + self.connections[req.user.id] = ws; + }); + + +//terminate stale websockets + const interval = setInterval(function ping() { + self.server.clients.forEach(function each(ws) { + if (ws.isAlive === false) return ws.terminate(); + + ws.isAlive = false; + ws.ping(function(){}); + }); + }, 30000); +}; + +websocket.getConnections = function(){ + return this.connections; +}; + +websocket.updateClient = function(message, id){ + this.connections[id].send(message); +}; + +websocket.isOpen = function (client) { + return (client.readyState === WebSocket.OPEN); +} + +module.exports = websocket; \ No newline at end of file