From 6cb13ae1fb160e9db93e1d69c996bd332708ec1f Mon Sep 17 00:00:00 2001 From: Martin Strigl Date: Tue, 12 Feb 2019 17:32:31 +0100 Subject: [PATCH] * clean integration and rebase of https://github.com/RocketChat/Rocket.Chat/pull/12259 --- package.json | 1 + packages/rocketchat-i18n/i18n/de.i18n.json | 5 +- packages/rocketchat-i18n/i18n/en.i18n.json | 3 + packages/rocketchat-i18n/i18n/ru.i18n.json | 5 +- .../client/public/external_api.js | 6 +- .../client/views/videoFlexTab.js | 34 +++++++++-- packages/rocketchat-videobridge/package.js | 6 ++ .../server/methods/jitsiGenerateToken.js | 56 +++++++++++++++++++ .../rocketchat-videobridge/server/settings.js | 30 ++++++++++ 9 files changed, 139 insertions(+), 7 deletions(-) create mode 100644 packages/rocketchat-videobridge/server/methods/jitsiGenerateToken.js diff --git a/package.json b/package.json index 8e9054f709a32..5f34447428bcb 100644 --- a/package.json +++ b/package.json @@ -161,6 +161,7 @@ "jquery": "^3.3.1", "jschardet": "^1.6.0", "juice": "^5.0.1", + "jsrsasign": "^8.0.12", "ldapjs": "^1.0.2", "less": "https://github.com/meteor/less.js/tarball/8130849eb3d7f0ecf0ca8d0af7c4207b0442e3f6", "less-plugin-autoprefix": "^2.0.0", diff --git a/packages/rocketchat-i18n/i18n/de.i18n.json b/packages/rocketchat-i18n/i18n/de.i18n.json index 68b4dad593225..00d3798105718 100644 --- a/packages/rocketchat-i18n/i18n/de.i18n.json +++ b/packages/rocketchat-i18n/i18n/de.i18n.json @@ -1555,6 +1555,9 @@ "italic": "Kursiv", "italics": "kursiv", "Jitsi_Chrome_Extension": "Chrome Extension ID", + "Jitsi_Enabled_TokenAuth": "Einschalten JWT genehmigung", + "Jitsi_Application_ID": "Anwendungs ID (iss)", + "Jitsi_Application_Secret": "Anwendungsgeheimnis", "Jitsi_Enable_Channels": "In Kanälen aktivieren", "Job_Title": "Berufsbezeichnung", "join": "Beitreten", @@ -3030,4 +3033,4 @@ "Your_push_was_sent_to_s_devices": "Eine Push-Nachricht wurde an %s Geräte gesendet.", "Your_server_link": "Ihre Serververbindung", "Your_workspace_is_ready": "Ihr Arbeitsbereich ist einsatzbereit 🎉" -} \ No newline at end of file +} diff --git a/packages/rocketchat-i18n/i18n/en.i18n.json b/packages/rocketchat-i18n/i18n/en.i18n.json index 60ab3a5282e13..2741854217722 100644 --- a/packages/rocketchat-i18n/i18n/en.i18n.json +++ b/packages/rocketchat-i18n/i18n/en.i18n.json @@ -1617,6 +1617,9 @@ "italic": "Italic", "italics": "italics", "Jitsi_Chrome_Extension": "Chrome Extension Id", + "Jitsi_Enabled_TokenAuth": "Enable JWT auth", + "Jitsi_Application_ID": "Application ID (iss)", + "Jitsi_Application_Secret": "Application Secret", "Jitsi_Enable_Channels": "Enable in Channels", "Job_Title": "Job Title", "join": "Join", diff --git a/packages/rocketchat-i18n/i18n/ru.i18n.json b/packages/rocketchat-i18n/i18n/ru.i18n.json index a00db2c690a3f..14bd85be08be6 100644 --- a/packages/rocketchat-i18n/i18n/ru.i18n.json +++ b/packages/rocketchat-i18n/i18n/ru.i18n.json @@ -1567,6 +1567,9 @@ "italic": "Курсивный", "italics": "курсив", "Jitsi_Chrome_Extension": "Идентификатор расширения для Chrome ", + "Jitsi_Enabled_TokenAuth": "Включить JWT авторизацию", + "Jitsi_Application_ID": "Идентификатор приложения (iss)", + "Jitsi_Application_Secret": "Секретный ключ", "Jitsi_Enable_Channels": "Включить на канале", "Job_Title": "Должность", "join": "Присоединиться", @@ -3037,4 +3040,4 @@ "Your_push_was_sent_to_s_devices": "Оповещение было отправлено на %s устройств.", "Your_server_link": "Ссылка на ваш сервер", "Your_workspace_is_ready": "Ваше рабочее пространство готово к работе 🎉" -} \ No newline at end of file +} diff --git a/packages/rocketchat-videobridge/client/public/external_api.js b/packages/rocketchat-videobridge/client/public/external_api.js index 50825e32b2cc9..fc5e3d7af595d 100644 --- a/packages/rocketchat-videobridge/client/public/external_api.js +++ b/packages/rocketchat-videobridge/client/public/external_api.js @@ -93,9 +93,10 @@ var JitsiMeetExternalAPI; * @param filmStripOnly if the value is true only the small videos will be * visible. * @param noSsl if the value is true https won't be used + * @param token if you need token authentication, then pass the token * @constructor */ -function JitsiMeetExternalAPI(domain, room_name, width, height, parentNode, configOverwrite, interfaceConfigOverwrite, noSsl) { +function JitsiMeetExternalAPI(domain, room_name, width, height, parentNode, configOverwrite, interfaceConfigOverwrite, noSsl, token) { if (!width || width < MIN_WIDTH) width = MIN_WIDTH; if (!height || height < MIN_HEIGHT) height = MIN_HEIGHT; @@ -114,6 +115,9 @@ function JitsiMeetExternalAPI(domain, room_name, width, height, parentNode, conf this.frameName = "jitsiConferenceFrame" + id; this.url = (noSsl ? "http" : "https") + "://" + domain + "/"; if (room_name) this.url += room_name; + if (token) { + this.url += "?jwt=" + token; + } this.url += "#jitsi_meet_external_api_id=" + id; var key; diff --git a/packages/rocketchat-videobridge/client/views/videoFlexTab.js b/packages/rocketchat-videobridge/client/views/videoFlexTab.js index b0ce866336fd7..e7ffacb570ca3 100644 --- a/packages/rocketchat-videobridge/client/views/videoFlexTab.js +++ b/packages/rocketchat-videobridge/client/views/videoFlexTab.js @@ -53,14 +53,22 @@ Template.videoFlexTab.onRendered(function() { return closePanel(); } this.timeout = null; - this.autorun(() => { + this.autorun(async() => { if (RocketChat.settings.get('Jitsi_Enabled')) { if (this.tabBar.getState() === 'opened') { const roomId = Session.get('openedRoom'); const domain = RocketChat.settings.get('Jitsi_Domain'); - const jitsiRoom = RocketChat.settings.get('Jitsi_URL_Room_Prefix') + RocketChat.settings.get('uniqueID') + roomId; + const uniqueID = RocketChat.settings.get('uniqueID'); const noSsl = !RocketChat.settings.get('Jitsi_SSL'); + const isEnabledTokenAuth = RocketChat.settings.get('Jitsi_Enabled_TokenAuth'); + + let jitsiRoom = ''; + if (typeof uniqueID !== 'undefined') { + jitsiRoom = RocketChat.settings.get('Jitsi_URL_Room_Prefix') + uniqueID + roomId; + } else { + jitsiRoom = RocketChat.settings.get('Jitsi_URL_Room_Prefix') + roomId; + } if (jitsiRoomActive !== null && jitsiRoomActive !== jitsiRoom) { jitsiRoomActive = null; @@ -73,6 +81,19 @@ Template.videoFlexTab.onRendered(function() { clearInterval(timeOut); } } else { + + let accessToken = null; + if (isEnabledTokenAuth) { + accessToken = await new Promise((resolve, reject) => + Meteor.call('jitsi:generateAccessToken', (error, result) => { + if (error) { + return reject(error); + } + resolve(result); + }) + ); + } + jitsiRoomActive = jitsiRoom; RocketChat.TabBar.updateButton('video', { class: 'red' }); @@ -81,7 +102,11 @@ Template.videoFlexTab.onRendered(function() { Meteor.call('jitsi:updateTimeout', roomId); timeOut = Meteor.setInterval(() => Meteor.call('jitsi:updateTimeout', roomId), 10 * 1000); - const newWindow = window.open(`${ (noSsl ? 'http://' : 'https://') + domain }/${ jitsiRoom }`, jitsiRoom); + let queryString = ''; + if (accessToken) { + queryString = `?jwt=${ accessToken }`; + } + const newWindow = window.open(`${ (noSsl ? 'http://' : 'https://') + domain }/${ jitsiRoom }${ queryString }`, jitsiRoom); const closeInterval = setInterval(() => { if (newWindow.closed !== false) { closePanel(); @@ -99,7 +124,8 @@ Template.videoFlexTab.onRendered(function() { // Keep it from showing duplicates when re-evaluated on variable change. if (!$('[id^=jitsiConference]').length) { - this.api = new JitsiMeetExternalAPI(domain, jitsiRoom, width, height, this.$('.video-container').get(0), configOverwrite, interfaceConfigOverwrite, noSsl); + + this.api = new JitsiMeetExternalAPI(domain, jitsiRoom, width, height, this.$('.video-container').get(0), configOverwrite, interfaceConfigOverwrite, noSsl, accessToken); /* * Hack to send after frame is loaded. diff --git a/packages/rocketchat-videobridge/package.js b/packages/rocketchat-videobridge/package.js index a455f5f047ab5..c10c8209aeb18 100644 --- a/packages/rocketchat-videobridge/package.js +++ b/packages/rocketchat-videobridge/package.js @@ -16,6 +16,12 @@ Package.onUse(function(api) { ]); api.addAssets('client/public/external_api.js', 'client'); api.addFiles('client/stylesheets/video.less', 'client'); + api.addFiles('server/settings.js', 'server'); + api.addFiles('server/models/Rooms.js', 'server'); + api.addFiles('server/methods/jitsiSetTimeout.js', 'server'); + api.addFiles('server/methods/jitsiGenerateToken.js', 'server'); + api.addFiles('server/methods/bbb.js', 'server'); + api.addFiles('server/actionLink.js', 'server'); api.mainModule('client/index.js', 'client'); api.mainModule('server/index.js', 'server'); diff --git a/packages/rocketchat-videobridge/server/methods/jitsiGenerateToken.js b/packages/rocketchat-videobridge/server/methods/jitsiGenerateToken.js new file mode 100644 index 0000000000000..37ef9cbe0a095 --- /dev/null +++ b/packages/rocketchat-videobridge/server/methods/jitsiGenerateToken.js @@ -0,0 +1,56 @@ +// we need only jws functionality +import { jws } from 'jsrsasign'; + +Meteor.methods({ + 'jitsi:generateAccessToken': () => { + + if (!Meteor.userId()) { + throw new Meteor.Error('error-invalid-user', 'Invalid user', { method: 'jitsi:generateToken' }); + } + + const jitsiDomain = RocketChat.settings.get('Jitsi_Domain'); + const jitsiApplicationId = RocketChat.settings.get('Jitsi_Application_ID'); + const jitsiApplicationSecret = RocketChat.settings.get('Jitsi_Application_Secret'); + + function addUserContextToPayload(payload) { + const user = Meteor.user(); + payload.context = { + user: { + name: user.name, + email: user.emails[0].address, + avatar: Meteor.absoluteUrl(`avatar/${ user.username }`), + id: user._id, + }, + }; + return payload; + } + + const JITSI_OPTIONS = { + jitsi_domain: jitsiDomain, + jitsi_lifetime_token: '1hour', // only 1 hour (for security reasons) + jitsi_application_id: jitsiApplicationId, + jitsi_application_secret: jitsiApplicationSecret, + }; + + const HEADER = { + typ: 'JWT', + alg: 'HS256', + }; + + const commonPayload = { + iss: JITSI_OPTIONS.jitsi_application_id, + sub: JITSI_OPTIONS.jitsi_domain, + iat: jws.IntDate.get('now'), + nbf: jws.IntDate.get('now'), + exp: jws.IntDate.get(`now + ${ JITSI_OPTIONS.jitsi_lifetime_token }`), + aud: 'RocketChat', + room: '*', + context: '', // first empty + }; + + const header = JSON.stringify(HEADER); + const payload = JSON.stringify(addUserContextToPayload(commonPayload)); + + return jws.JWS.sign(HEADER.alg, header, payload, { rstr: JITSI_OPTIONS.jitsi_application_secret }); + }, +}); diff --git a/packages/rocketchat-videobridge/server/settings.js b/packages/rocketchat-videobridge/server/settings.js index 0285b387c413a..038bd703403d2 100644 --- a/packages/rocketchat-videobridge/server/settings.js +++ b/packages/rocketchat-videobridge/server/settings.js @@ -130,6 +130,36 @@ Meteor.startup(function() { i18nLabel: 'Jitsi_Chrome_Extension', public: true, }); + + this.add('Jitsi_Enabled_TokenAuth', false, { + type: 'boolean', + enableQuery: { + _id: 'Jitsi_Enabled', + value: true, + }, + i18nLabel: 'Jitsi_Enabled_TokenAuth', + public: true, + }); + + this.add('Jitsi_Application_ID', '', { + type: 'string', + enableQuery: [ + { _id: 'Jitsi_Enabled', value: true }, + { _id: 'Jitsi_Enabled_TokenAuth', value: true }, + ], + i18nLabel: 'Jitsi_Application_ID', + public: true, + }); + + this.add('Jitsi_Application_Secret', '', { + type: 'string', + enableQuery: [ + { _id: 'Jitsi_Enabled', value: true }, + { _id: 'Jitsi_Enabled_TokenAuth', value: true }, + ], + i18nLabel: 'Jitsi_Application_Secret', + public: true, + }); }); }); });