diff --git a/config.js b/config.js index b844027f0683..1aff9ff3bfd6 100644 --- a/config.js +++ b/config.js @@ -1441,6 +1441,31 @@ var config = { // dialInConfCodeUrl is the conference mapper converting a meeting id to a PIN used for dial-in // or the other way around (more info in resources/cloud-api.swagger) + // You can use external service for authentication that will redirect back passing a jwt token + // You can use tokenAuthUrl config to point to a URL of such service. + // The URL for the service supports few params which will be filled in by the code. + // tokenAuthUrl: + // 'https://myservice.com/auth/{room}?code_challenge_method=S256&code_challenge={code_challenge}&state={state}' + // Supported parameters in tokenAuthUrl: + // {room} - will be replaced with the room name + // {code_challenge} - (A web only). A oauth 2.0 code challenge that will be sent to the service. See: + // https://datatracker.ietf.org/doc/html/rfc7636. The code verifier will be saved in the sessionStorage + // under key: 'code_verifier'. + // {state} - A json with the current state before redirecting. Keys that are included in the state: + // - room (The current room name as shown in the address bar) + // - roomSafe (the backend safe room name to use (lowercase), that is passed to the backend) + // - tenant (The tenant if any) + // - config.xxx (all config overrides) + // - interfaceConfig.xxx (all interfaceConfig overrides) + // - ios=true (in case ios mobile app is used) + // - android=true (in case android mobile app is used) + // - electron=true (when web is loaded in electron app) + // If there is a logout service you can specify its URL with: + // tokenLogoutUrl: 'https://myservice.com/logout' + // You can enable tokenAuthUrlAutoRedirect which will detect that you have logged in successfully before + // and will automatically redirect to the token service to get the token for the meeting. + // tokenAuthUrlAutoRedirect: false + // List of undocumented settings used in jitsi-meet /** _immediateReloadThreshold @@ -1460,9 +1485,6 @@ var config = { peopleSearchQueryTypes peopleSearchUrl requireDisplayName - tokenAuthUrl - tokenAuthUrlAutoRedirect - tokenLogoutUrl */ /** diff --git a/package-lock.json b/package-lock.json index fe73fb0b8d61..3f5792783258 100644 --- a/package-lock.json +++ b/package-lock.json @@ -42,7 +42,7 @@ "@vladmandic/human-models": "2.5.9", "@xmldom/xmldom": "0.8.7", "amplitude-js": "8.2.1", - "base64-js": "1.3.1", + "base64-js": "1.5.1", "bc-css-flags": "3.0.0", "clipboard-copy": "4.0.1", "clsx": "1.1.1", @@ -7446,9 +7446,23 @@ } }, "node_modules/base64-js": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.3.1.tgz", - "integrity": "sha512-mLQ4i2QO1ytvGWFWmcngKO//JXAQueZvwEKtjgQFM4jIK0kU+ytMfplL8j+n5mspOfjHwoAg+9yhb7BwAHm36g==" + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", + "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ] }, "node_modules/batch": { "version": "0.6.1", @@ -12883,6 +12897,11 @@ "url": "https://github.com/chalk/ansi-styles?sponsor=1" } }, + "node_modules/lib-jitsi-meet/node_modules/base64-js": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.3.1.tgz", + "integrity": "sha512-mLQ4i2QO1ytvGWFWmcngKO//JXAQueZvwEKtjgQFM4jIK0kU+ytMfplL8j+n5mspOfjHwoAg+9yhb7BwAHm36g==" + }, "node_modules/lib-jitsi-meet/node_modules/chalk": { "version": "4.1.2", "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", @@ -15133,25 +15152,6 @@ "node": ">=6" } }, - "node_modules/plist/node_modules/base64-js": { - "version": "1.5.1", - "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", - "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ] - }, "node_modules/portfinder": { "version": "1.0.28", "resolved": "https://registry.npmjs.org/portfinder/-/portfinder-1.0.28.tgz", @@ -16617,25 +16617,6 @@ "react-native": ">=0.60.0" } }, - "node_modules/react-native-webrtc/node_modules/base64-js": { - "version": "1.5.1", - "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", - "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ] - }, "node_modules/react-native-webrtc/node_modules/event-target-shim": { "version": "6.0.2", "resolved": "https://registry.npmjs.org/event-target-shim/-/event-target-shim-6.0.2.tgz", @@ -25853,9 +25834,9 @@ "integrity": "sha1-eAqZyE59YAJgNhURxId2E78k9rs=" }, "base64-js": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.3.1.tgz", - "integrity": "sha512-mLQ4i2QO1ytvGWFWmcngKO//JXAQueZvwEKtjgQFM4jIK0kU+ytMfplL8j+n5mspOfjHwoAg+9yhb7BwAHm36g==" + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", + "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==" }, "batch": { "version": "0.6.1", @@ -29955,6 +29936,11 @@ "color-convert": "^2.0.1" } }, + "base64-js": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.3.1.tgz", + "integrity": "sha512-mLQ4i2QO1ytvGWFWmcngKO//JXAQueZvwEKtjgQFM4jIK0kU+ytMfplL8j+n5mspOfjHwoAg+9yhb7BwAHm36g==" + }, "chalk": { "version": "4.1.2", "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", @@ -31667,13 +31653,6 @@ "requires": { "base64-js": "^1.5.1", "xmlbuilder": "^15.1.1" - }, - "dependencies": { - "base64-js": { - "version": "1.5.1", - "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", - "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==" - } } }, "portfinder": { @@ -32688,11 +32667,6 @@ "event-target-shim": "6.0.2" }, "dependencies": { - "base64-js": { - "version": "1.5.1", - "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", - "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==" - }, "event-target-shim": { "version": "6.0.2", "resolved": "https://registry.npmjs.org/event-target-shim/-/event-target-shim-6.0.2.tgz", diff --git a/package.json b/package.json index 22339884be25..ca6084369673 100644 --- a/package.json +++ b/package.json @@ -48,7 +48,7 @@ "@vladmandic/human-models": "2.5.9", "@xmldom/xmldom": "0.8.7", "amplitude-js": "8.2.1", - "base64-js": "1.3.1", + "base64-js": "1.5.1", "bc-css-flags": "3.0.0", "clipboard-copy": "4.0.1", "clsx": "1.1.1", diff --git a/react/features/app/getRouteToRender.web.ts b/react/features/app/getRouteToRender.web.ts index e2028b7ee74c..75cf0f06dcc6 100644 --- a/react/features/app/getRouteToRender.web.ts +++ b/react/features/app/getRouteToRender.web.ts @@ -1,12 +1,13 @@ // @ts-expect-error import { generateRoomWithoutSeparator } from '@jitsi/js-utils/random'; -import { getTokenAuthUrl } from '../authentication/functions'; +import { getTokenAuthUrl } from '../authentication/functions.web'; import { IStateful } from '../base/app/types'; import { isRoomValid } from '../base/conference/functions'; import { isSupportedBrowser } from '../base/environment/environment'; import { browser } from '../base/lib-jitsi-meet'; import { toState } from '../base/redux/functions'; +import { parseURIString } from '../base/util/uri'; import Conference from '../conference/components/web/Conference'; import { getDeepLinkingPage } from '../deep-linking/functions'; import UnsupportedDesktopBrowser from '../unsupported-browser/components/UnsupportedDesktopBrowser'; @@ -52,9 +53,16 @@ function _getWebConferenceRoute(state: IReduxState) { if (!browser.isElectron() && config.tokenAuthUrl && config.tokenAuthUrlAutoRedirect && state['features/authentication'].tokenAuthUrlSuccessful && !state['features/base/jwt'].jwt && room) { - route.href = getTokenAuthUrl(config, room); + const { locationURL = { href: '' } } = state['features/base/connection']; + const { tenant } = parseURIString(locationURL.href) || {}; - return Promise.resolve(route); + return getTokenAuthUrl(config, room, tenant) + .then((url: string | undefined) => { + route.href = url; + + return route; + }) + .catch(() => Promise.resolve(route)); } // Update the location if it doesn't match. This happens when a room is diff --git a/react/features/authentication/actions.native.ts b/react/features/authentication/actions.native.ts index 20962f1a2ee7..226098591a79 100644 --- a/react/features/authentication/actions.native.ts +++ b/react/features/authentication/actions.native.ts @@ -1,11 +1,10 @@ -import { Linking, Platform } from 'react-native'; +import { Linking } from 'react-native'; import { appNavigate } from '../app/actions.native'; import { IStore } from '../app/types'; import { conferenceLeft } from '../base/conference/actions'; import { connectionFailed } from '../base/connection/actions.native'; import { set } from '../base/redux/functions'; -import { appendURLHashParam } from '../base/util/uri'; import { CANCEL_LOGIN } from './actionTypes'; import { stopWaitForOwner } from './actions.any'; @@ -85,12 +84,7 @@ export function redirectToDefaultLocation() { * @returns {Function} */ export function openTokenAuthUrl(tokenAuthServiceUrl: string) { - let url = appendURLHashParam(tokenAuthServiceUrl, 'skipPrejoin', 'true'); - - // Append ios=true or android=true to the token URL. - url = appendURLHashParam(url, Platform.OS, 'true'); - return () => { - Linking.openURL(url); + Linking.openURL(tokenAuthServiceUrl); }; } diff --git a/react/features/authentication/actions.web.ts b/react/features/authentication/actions.web.ts index 8d3d71de8dfc..69e682a2614b 100644 --- a/react/features/authentication/actions.web.ts +++ b/react/features/authentication/actions.web.ts @@ -2,7 +2,6 @@ import { maybeRedirectToWelcomePage } from '../app/actions.web'; import { IStore } from '../app/types'; import { openDialog } from '../base/dialog/actions'; import { browser } from '../base/lib-jitsi-meet'; -import { appendURLHashParam } from '../base/util/uri'; import { CANCEL_LOGIN } from './actionTypes'; import LoginQuestionDialog from './components/web/LoginQuestionDialog'; @@ -57,14 +56,10 @@ export function redirectToDefaultLocation() { export function openTokenAuthUrl(tokenAuthServiceUrl: string): any { return (dispatch: IStore['dispatch'], getState: IStore['getState']) => { const redirect = () => { - // We have already shown the prejoin screen, no need to show it again after obtaining the token. - let url = appendURLHashParam(tokenAuthServiceUrl, 'skipPrejoin', 'true'); - if (browser.isElectron()) { - url = appendURLHashParam(url, 'electron', 'true'); - window.open(url, '_blank'); + window.open(tokenAuthServiceUrl, '_blank'); } else { - window.location.href = url; + window.location.href = tokenAuthServiceUrl; } }; diff --git a/react/features/authentication/functions.any.ts b/react/features/authentication/functions.any.ts new file mode 100644 index 000000000000..163b78f3fee9 --- /dev/null +++ b/react/features/authentication/functions.any.ts @@ -0,0 +1,48 @@ +import { IConfig } from '../base/config/configType'; +import { getBackendSafeRoomName } from '../base/util/uri'; + +/** + * Checks if the token for authentication is available. + * + * @param {Object} config - Configuration state object from store. + * @returns {boolean} + */ +export const isTokenAuthEnabled = (config: IConfig): boolean => + typeof config.tokenAuthUrl === 'string' && config.tokenAuthUrl.length > 0; + +/** + * Returns the state that we can add as a parameter to the tokenAuthUrl. + * + * @param {string?} roomName - The room name. + * @param {string?} tenant - The tenant name if any. + * @param {boolean} skipPrejoin - Whether to skip pre-join page. + * @returns {Object} The state object. + */ +export const _getTokenAuthState = ( + roomName: string | undefined, + tenant: string | undefined, + skipPrejoin: boolean | undefined = false): object => { + const state = { + room: roomName, + roomSafe: getBackendSafeRoomName(roomName), + tenant + }; + + if (skipPrejoin) { + // We have already shown the prejoin screen, no need to show it again after obtaining the token. + // @ts-ignore + state['config.prejoinConfig.enabled'] = false; + } + + const params = new URLSearchParams(window.location.search); + + for (const [ key, value ] of params) { + // we allow only config and interfaceConfig overrides in the state + if (key.startsWith('config.') || key.startsWith('interfaceConfig.')) { + // @ts-ignore + state[key] = value; + } + } + + return state; +}; diff --git a/react/features/authentication/functions.native.ts b/react/features/authentication/functions.native.ts new file mode 100644 index 000000000000..18d02dd3e199 --- /dev/null +++ b/react/features/authentication/functions.native.ts @@ -0,0 +1,48 @@ +import { Platform } from 'react-native'; + +import { IConfig } from '../base/config/configType'; + +import { _getTokenAuthState } from './functions.any'; + +export * from './functions.any'; + +/** + * Creates the URL pointing to JWT token authentication service. It is + * formatted from the 'urlPattern' argument which can contain the following + * constants: + * '{room}' - name of the conference room passed as roomName + * argument to this method. + * + * @param {Object} config - Configuration state object from store. A URL pattern pointing to the login service. + * @param {string} roomName - The name of the conference room for which the user will be authenticated. + * @param {string} tenant - The name of the conference tenant. + * @param {string} skipPrejoin - The name of the conference room for which the user will be authenticated. + * + * @returns {Promise} - The URL pointing to JWT login service or + * undefined if the pattern stored in config is not a string and the URL can not be + * constructed. + */ +export const getTokenAuthUrl = ( + config: IConfig, + roomName: string | undefined, + tenant: string | undefined, + skipPrejoin: boolean | undefined = false): Promise => { + + let url = config.tokenAuthUrl; + + if (!url || !roomName) { + return Promise.resolve(undefined); + } + + if (url.indexOf('{state}')) { + const state = _getTokenAuthState(roomName, tenant, skipPrejoin); + + // Append ios=true or android=true to the token URL. + // @ts-ignore + state[Platform.OS] = true; + + url = url.replace('{state}', encodeURIComponent(JSON.stringify(state))); + } + + return Promise.resolve(url.replace('{room}', roomName)); +}; diff --git a/react/features/authentication/functions.ts b/react/features/authentication/functions.ts deleted file mode 100644 index cc9bf15180f2..000000000000 --- a/react/features/authentication/functions.ts +++ /dev/null @@ -1,39 +0,0 @@ -import { IConfig } from '../base/config/configType'; - -/** - * Checks if the token for authentication is available. - * - * @param {Object} config - Configuration state object from store. - * @returns {boolean} - */ -export const isTokenAuthEnabled = (config: IConfig) => - typeof config.tokenAuthUrl === 'string' - && config.tokenAuthUrl.length; - -/** - * Creates the URL pointing to JWT token authentication service. It is - * formatted from the 'urlPattern' argument which can contain the following - * constants: - * '{room}' - name of the conference room passed as roomName - * argument to this method. - * '{roleUpgrade}' - will contain 'true' if the URL will be used for - * the role upgrade scenario, where user connects from anonymous domain and - * then gets upgraded to the moderator by logging-in from the popup window. - * - * @param {Object} config - Configuration state object from store. A URL pattern pointing to the login service. - * @param {string} roomName - The name of the conference room for which the user will be authenticated. - * - * @returns {string|undefined} - The URL pointing to JWT login service or - * undefined if the pattern stored in config is not a string and the URL can not be - * constructed. - */ -export const getTokenAuthUrl = (config: IConfig, roomName: string | undefined) => { - - const url = config.tokenAuthUrl; - - if (typeof url !== 'string' || !roomName) { - return undefined; - } - - return url.replace('{room}', roomName); -}; diff --git a/react/features/authentication/functions.web.ts b/react/features/authentication/functions.web.ts new file mode 100644 index 000000000000..ca06905b4402 --- /dev/null +++ b/react/features/authentication/functions.web.ts @@ -0,0 +1,92 @@ +import base64js from 'base64-js'; + +import { IConfig } from '../base/config/configType'; +import { browser } from '../base/lib-jitsi-meet'; + +import { _getTokenAuthState } from './functions.any'; + +export * from './functions.any'; + +/** + * Based on rfc7636 we need a random string for a code verifier. + */ +const POSSIBLE_CHARS = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789'; + +/** + * Crypto random, alternative of Math.random. + * + * @returns {float} A random value. + */ +function _cryptoRandom() { + const typedArray = new Uint8Array(1); + const randomValue = crypto.getRandomValues(typedArray)[0]; + + return randomValue / Math.pow(2, 8); +} + +/** + * Creates the URL pointing to JWT token authentication service. It is + * formatted from the 'urlPattern' argument which can contain the following + * constants: + * '{room}' - name of the conference room passed as roomName + * argument to this method. + * + * @param {Object} config - Configuration state object from store. A URL pattern pointing to the login service. + * @param {string} roomName - The name of the conference room for which the user will be authenticated. + * @param {string} tenant - The name of the conference tenant. + * @param {string} skipPrejoin - The name of the conference room for which the user will be authenticated. + * + * @returns {Promise} - The URL pointing to JWT login service or + * undefined if the pattern stored in config is not a string and the URL can not be + * constructed. + */ +export const getTokenAuthUrl = ( + config: IConfig, + roomName: string | undefined, + tenant: string | undefined, + skipPrejoin: boolean | undefined = false): Promise => { + + let url = config.tokenAuthUrl; + + if (!url || !roomName) { + return Promise.resolve(undefined); + } + + if (url.indexOf('{state}')) { + const state = _getTokenAuthState(roomName, tenant, skipPrejoin); + + if (browser.isElectron()) { + // @ts-ignore + state.electron = true; + } + + url = url.replace('{state}', encodeURIComponent(JSON.stringify(state))); + } + + url = url.replace('{room}', roomName); + + if (url.indexOf('{code_challenge}')) { + let codeVerifier = ''; + + // random string + for (let i = 0; i < 64; i++) { + codeVerifier += POSSIBLE_CHARS.charAt(Math.floor(_cryptoRandom() * POSSIBLE_CHARS.length)); + } + + window.sessionStorage.setItem('code_verifier', codeVerifier); + + return window.crypto.subtle.digest('SHA-256', new TextEncoder().encode(codeVerifier)) + .then(digest => { + // prepare code challenge - base64 encoding without padding as described in: + // https://datatracker.ietf.org/doc/html/rfc7636#appendix-A + const codeChallenge = base64js.fromByteArray(new Uint8Array(digest)) + .replace(/=/g, '') + .replace(/\+/g, '-') + .replace(/\//g, '_'); + + return url ? url.replace('{code_challenge}', codeChallenge) : undefined; + }); + } + + return Promise.resolve(url); +}; diff --git a/react/features/authentication/middleware.ts b/react/features/authentication/middleware.ts index f5af3138a22c..d093a713adfb 100644 --- a/react/features/authentication/middleware.ts +++ b/react/features/authentication/middleware.ts @@ -14,7 +14,7 @@ import { JitsiConnectionErrors } from '../base/lib-jitsi-meet'; import MiddlewareRegistry from '../base/redux/MiddlewareRegistry'; -import { getBackendSafeRoomName } from '../base/util/uri'; +import { parseURIString } from '../base/util/uri'; import { openLogoutDialog } from '../settings/actions'; import { @@ -254,7 +254,9 @@ function _isWaitingForOwner({ getState }: IStore) { function _handleLogin({ dispatch, getState }: IStore) { const state = getState(); const config = state['features/base/config']; - const room = getBackendSafeRoomName(state['features/base/conference'].room); + const room = state['features/base/conference'].room; + const { locationURL = { href: '' } } = state['features/base/connection']; + const { tenant } = parseURIString(locationURL.href) || {}; if (!room) { logger.warn('Cannot handle login, room is undefined!'); @@ -268,16 +270,16 @@ function _handleLogin({ dispatch, getState }: IStore) { return; } - // FIXME: This method will not preserve the other URL params that were originally passed. - const tokenAuthServiceUrl = getTokenAuthUrl(config, room); + getTokenAuthUrl(config, room, tenant, true) + .then((tokenAuthServiceUrl: string | undefined) => { + if (!tokenAuthServiceUrl) { + logger.warn('Cannot handle login, token service URL is not set'); - if (!tokenAuthServiceUrl) { - logger.warn('Cannot handle login, token service URL is not set'); - - return; - } + return; + } - dispatch(openTokenAuthUrl(tokenAuthServiceUrl)); + return dispatch(openTokenAuthUrl(tokenAuthServiceUrl)); + }); } /** diff --git a/react/features/calendar-sync/web/microsoftCalendar.ts b/react/features/calendar-sync/web/microsoftCalendar.ts index cf974418dfe4..769ae2288fc2 100644 --- a/react/features/calendar-sync/web/microsoftCalendar.ts +++ b/react/features/calendar-sync/web/microsoftCalendar.ts @@ -1,6 +1,5 @@ import { Client } from '@microsoft/microsoft-graph-client'; // eslint-disable-next-line lines-around-comment -// @ts-expect-error import base64js from 'base64-js'; import { v4 as uuidV4 } from 'uuid'; import { findWindows } from 'windows-iana'; diff --git a/react/features/deep-linking/components/DeepLinkingMobilePage.web.tsx b/react/features/deep-linking/components/DeepLinkingMobilePage.web.tsx index 3855642d047b..6b713a8ab418 100644 --- a/react/features/deep-linking/components/DeepLinkingMobilePage.web.tsx +++ b/react/features/deep-linking/components/DeepLinkingMobilePage.web.tsx @@ -228,6 +228,7 @@ const DeepLinkingMobilePage: React.FC = ({ t }) => { diff --git a/react/features/invite/components/dial-in-info-page/DialInInfoApp.web.tsx b/react/features/invite/components/dial-in-info-page/DialInInfoApp.web.tsx index 21851f714ead..413a4a956c6b 100644 --- a/react/features/invite/components/dial-in-info-page/DialInInfoApp.web.tsx +++ b/react/features/invite/components/dial-in-info-page/DialInInfoApp.web.tsx @@ -10,6 +10,9 @@ import DialInSummary from '../dial-in-summary/web/DialInSummary'; import NoRoomError from './NoRoomError.web'; +/** + * TODO: This seems unused, so we can drop it. + */ document.addEventListener('DOMContentLoaded', () => { // @ts-ignore const { room } = parseURLParams(window.location, true, 'search'); diff --git a/react/features/invite/components/dial-in-summary/web/DialInSummary.tsx b/react/features/invite/components/dial-in-summary/web/DialInSummary.tsx index 2101aa24317d..c35488f0d574 100644 --- a/react/features/invite/components/dial-in-summary/web/DialInSummary.tsx +++ b/react/features/invite/components/dial-in-summary/web/DialInSummary.tsx @@ -31,6 +31,11 @@ interface IProps extends WithTranslation { */ clickableNumbers: boolean; + /** + * Whether to hide the error. + */ + hideError?: boolean; + /** * The name of the conference to show a conferenceID for. */ @@ -167,14 +172,16 @@ class DialInSummary extends Component { let contents; const { conferenceID, error, loading, numbersEnabled } = this.state; - const { classes, showTitle, room, clickableNumbers, scrollable, t } = this.props; + const { classes, hideError, showTitle, room, clickableNumbers, scrollable, t } = this.props; if (loading) { contents = ''; } else if (numbersEnabled === false) { contents = t('info.dialInNotSupported'); } else if (error) { - contents = error; + if (!hideError) { + contents = error; + } } else { className = clsx(classes.hasNumbers, scrollable && classes.scrollable); contents = [ diff --git a/resources/prosody-plugins/mod_muc_meeting_id.lua b/resources/prosody-plugins/mod_muc_meeting_id.lua index 4dddf1cfc7d1..d8e5ca8132c6 100644 --- a/resources/prosody-plugins/mod_muc_meeting_id.lua +++ b/resources/prosody-plugins/mod_muc_meeting_id.lua @@ -81,7 +81,7 @@ module:hook('muc-occupant-pre-join', function (event) local room, stanza = event.room, event.stanza; -- we skip processing only if jicofo_lock is set to false - if room._data.jicofo_lock == false or is_healthcheck_room(stanza.attr.from) then + if room._data.jicofo_lock == false or is_healthcheck_room(room.jid) then return; end diff --git a/resources/prosody-plugins/mod_muc_wait_for_host.lua b/resources/prosody-plugins/mod_muc_wait_for_host.lua index d1e632fe8706..a4ca1ca05347 100644 --- a/resources/prosody-plugins/mod_muc_wait_for_host.lua +++ b/resources/prosody-plugins/mod_muc_wait_for_host.lua @@ -47,7 +47,7 @@ module:hook('muc-occupant-pre-join', function (event) -- we ignore jicofo as we want it to join the room or if the room has already seen its -- authenticated host - if is_admin(occupant.bare_jid) or room.has_host then + if is_admin(occupant.bare_jid) or is_healthcheck_room(room.jid) or room.has_host then return; end @@ -70,6 +70,7 @@ module:hook('muc-occupant-pre-join', function (event) module:log('info', 'Host %s arrived in %s.', occupant.bare_jid, room.jid); audit_logger('room_jid:%s created_by:%s', room.jid, session.jitsi_meet_context_user and session.jitsi_meet_context_user.id or 'nil'); + module:fire_event('room_host_arrived', room.jid, session); lobby_host:fire_event('destroy-lobby-room', { room = room, newjid = room.jid, diff --git a/resources/prosody-plugins/mod_persistent_lobby.lua b/resources/prosody-plugins/mod_persistent_lobby.lua index 585790bdd5e5..2ebfaa48b275 100644 --- a/resources/prosody-plugins/mod_persistent_lobby.lua +++ b/resources/prosody-plugins/mod_persistent_lobby.lua @@ -144,7 +144,7 @@ run_when_component_loaded(lobby_muc_component_host, function(host_module, host_n local lobby_room = event.room; local main_room = lobby_room.main_room; - if is_healthcheck_room(main_room.jid) or not has_persistent_lobby(main_room) then + if not main_room or is_healthcheck_room(main_room.jid) or not has_persistent_lobby(main_room) then return; end