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