[NEW] Jitsi meet room access via a token (#12259)

* closes #7611 - append token auth in jitsi videoconference

* Ádd missing imports

* Fix imports and jws dep

* Add option to limit the token validity to the room that it was generated for

* use canAccessRoom for jitsi:generateAccessToken to prevent abuse

* fix diff in jitsi external api

* Make settings not public
pull/15158/head^2
Roman Zharikov 6 years ago committed by Aaron Ogle
parent fcfa6cd62d
commit 2423511599
  1. 24
      app/videobridge/client/views/videoFlexTab.js
  2. 1
      app/videobridge/server/index.js
  3. 69
      app/videobridge/server/methods/jitsiGenerateToken.js
  4. 38
      app/videobridge/server/settings.js
  5. 5
      package-lock.json
  6. 1
      package.json
  7. 3
      packages/rocketchat-i18n/i18n/de.i18n.json
  8. 4
      packages/rocketchat-i18n/i18n/en.i18n.json
  9. 3
      packages/rocketchat-i18n/i18n/ru.i18n.json
  10. 6
      public/packages/rocketchat_videobridge/client/public/external_api.js

@ -86,7 +86,7 @@ Template.videoFlexTab.onRendered(function() {
return closePanel();
}
this.intervalHandler = null;
this.autorun(() => {
this.autorun(async () => {
if (!settings.get('Jitsi_Enabled')) {
return closePanel();
}
@ -99,6 +99,7 @@ Template.videoFlexTab.onRendered(function() {
const domain = settings.get('Jitsi_Domain');
const jitsiRoom = settings.get('Jitsi_URL_Room_Prefix') + settings.get('uniqueID') + rid;
const noSsl = !settings.get('Jitsi_SSL');
const isEnabledTokenAuth = settings.get('Jitsi_Enabled_TokenAuth');
if (jitsiRoomActive !== null && jitsiRoomActive !== jitsiRoom) {
jitsiRoomActive = null;
@ -108,11 +109,28 @@ Template.videoFlexTab.onRendered(function() {
return stop();
}
let accessToken = null;
if (isEnabledTokenAuth) {
accessToken = await new Promise((resolve, reject) => {
Meteor.call('jitsi:generateAccessToken', rid, (error, result) => {
if (error) {
return reject(error);
}
resolve(result);
});
});
}
jitsiRoomActive = jitsiRoom;
if (settings.get('Jitsi_Open_New_Window')) {
start();
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);
if (newWindow) {
const closeInterval = setInterval(() => {
if (newWindow.closed === false) {
@ -130,7 +148,7 @@ Template.videoFlexTab.onRendered(function() {
// Keep it from showing duplicates when re-evaluated on variable change.
const name = Users.findOne(Meteor.userId(), { fields: { name: 1 } });
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.

@ -1,5 +1,6 @@
import '../lib/messageType';
import './settings';
import './methods/jitsiSetTimeout';
import './methods/jitsiGenerateToken';
import './methods/bbb';
import './actionLink';

@ -0,0 +1,69 @@
import { Meteor } from 'meteor/meteor';
import { jws } from 'jsrsasign';
import { Rooms } from '../../../models';
import { settings } from '../../../settings';
import { canAccessRoom } from '../../../authorization/server/functions/canAccessRoom';
Meteor.methods({
'jitsi:generateAccessToken': (rid) => {
if (!Meteor.userId()) {
throw new Meteor.Error('error-invalid-user', 'Invalid user', { method: 'jitsi:generateToken' });
}
const room = Rooms.findOneById(rid);
if (!canAccessRoom(room, Meteor.user())) {
throw new Meteor.Error('error-not-allowed', 'not allowed', { method: 'jitsi:generateToken' });
}
const jitsiRoom = settings.get('Jitsi_URL_Room_Prefix') + settings.get('uniqueID') + rid;
const jitsiDomain = settings.get('Jitsi_Domain');
const jitsiApplicationId = settings.get('Jitsi_Application_ID');
const jitsiApplicationSecret = settings.get('Jitsi_Application_Secret');
const jitsiLimitTokenToRoom = settings.get('Jitsi_Limit_Token_To_Room');
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: jitsiLimitTokenToRoom ? jitsiRoom : '*',
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 });
},
});

@ -128,6 +128,44 @@ 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',
});
this.add('Jitsi_Application_Secret', '', {
type: 'string',
enableQuery: [
{ _id: 'Jitsi_Enabled', value: true },
{ _id: 'Jitsi_Enabled_TokenAuth', value: true },
],
i18nLabel: 'Jitsi_Application_Secret',
});
this.add('Jitsi_Limit_Token_To_Room', true, {
type: 'boolean',
enableQuery: [
{ _id: 'Jitsi_Enabled', value: true },
{ _id: 'Jitsi_Enabled_TokenAuth', value: true },
],
i18nLabel: 'Jitsi_Limit_Token_To_Room',
public: true,
});
});
});
});

5
package-lock.json generated

@ -10824,6 +10824,11 @@
"verror": "1.10.0"
}
},
"jsrsasign": {
"version": "8.0.12",
"resolved": "https://registry.npmjs.org/jsrsasign/-/jsrsasign-8.0.12.tgz",
"integrity": "sha1-Iqu5ZW00owuVMENnIINeicLlwxY="
},
"juice": {
"version": "5.2.0",
"resolved": "https://registry.npmjs.org/juice/-/juice-5.2.0.tgz",

@ -182,6 +182,7 @@
"ip-range-check": "^0.0.2",
"jquery": "^3.3.1",
"jschardet": "^1.6.0",
"jsrsasign": "^8.0.12",
"juice": "^5.2.0",
"katex": "^0.9.0",
"ldap-escape": "^2.0.1",

@ -1696,6 +1696,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",

@ -1723,7 +1723,11 @@
"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",
"Jitsi_Limit_Token_To_Room": "Limit token to Jitsi Room",
"Job_Title": "Job Title",
"join": "Join",
"join-without-join-code": "Join Without Join Code",

@ -1723,6 +1723,9 @@
"italic": "Курсивный",
"italics": "курсив",
"Jitsi_Chrome_Extension": "Идентификатор расширения для Chrome ",
"Jitsi_Enabled_TokenAuth": "Включить JWT авторизацию",
"Jitsi_Application_ID": "Идентификатор приложения (iss)",
"Jitsi_Application_Secret": "Секретный ключ",
"Jitsi_Enable_Channels": "Включить на канале",
"Job_Title": "Должность",
"join": "Присоединиться",

@ -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;

Loading…
Cancel
Save