From 662a708bc53761e515febf3f4e54c0e67335bf1a Mon Sep 17 00:00:00 2001 From: damencho Date: Mon, 11 Sep 2023 14:09:38 -0500 Subject: [PATCH 1/6] feat: Adds an event for host arrived. --- resources/prosody-plugins/mod_muc_wait_for_host.lua | 1 + 1 file changed, 1 insertion(+) diff --git a/resources/prosody-plugins/mod_muc_wait_for_host.lua b/resources/prosody-plugins/mod_muc_wait_for_host.lua index d1e632fe8706..df405a90f2fa 100644 --- a/resources/prosody-plugins/mod_muc_wait_for_host.lua +++ b/resources/prosody-plugins/mod_muc_wait_for_host.lua @@ -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, From eca2a3f1f3ce5e2ec843a015a76e0c82eff61f3b Mon Sep 17 00:00:00 2001 From: damencho Date: Wed, 13 Sep 2023 07:58:32 -0500 Subject: [PATCH 2/6] feat: Hides any error from the UI for the DialIn info app. If the conference mapper return an error we show it on deeplinking page. In case the conf mapper receives non authenticated request it may return an error and this is normal so hide it from that page. --- .../components/DeepLinkingMobilePage.web.tsx | 1 + .../dial-in-info-page/DialInInfoApp.web.tsx | 3 +++ .../components/dial-in-summary/web/DialInSummary.tsx | 11 +++++++++-- 3 files changed, 13 insertions(+), 2 deletions(-) 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 = [ From 6eb26a83e1ea40186aeb29b8a03b7db6661b9389 Mon Sep 17 00:00:00 2001 From: damencho Date: Wed, 20 Sep 2023 16:05:40 -0500 Subject: [PATCH 3/6] fix: Fixes check for health check room. --- resources/prosody-plugins/mod_muc_meeting_id.lua | 2 +- resources/prosody-plugins/mod_muc_wait_for_host.lua | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) 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 df405a90f2fa..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 From cd1e70b8bd626cef8b43b3746056232d6ed9682d Mon Sep 17 00:00:00 2001 From: damencho Date: Wed, 20 Sep 2023 16:06:53 -0500 Subject: [PATCH 4/6] feat: Updates base64-js dependency. --- package-lock.json | 88 +++++++------------ package.json | 2 +- .../calendar-sync/web/microsoftCalendar.ts | 1 - 3 files changed, 32 insertions(+), 59 deletions(-) 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/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'; From f90ef61328a9de3b67df12eb7f3227394bf7aab4 Mon Sep 17 00:00:00 2001 From: damencho Date: Wed, 20 Sep 2023 16:11:29 -0500 Subject: [PATCH 5/6] feat: Introduces passing state to the token authUrl. Fixes jitsi/jitsi-meet-electron#902. --- config.js | 28 +++++- react/features/app/getRouteToRender.web.ts | 14 ++- .../features/authentication/actions.native.ts | 10 +- react/features/authentication/actions.web.ts | 9 +- .../features/authentication/functions.any.ts | 48 ++++++++++ .../authentication/functions.native.ts | 48 ++++++++++ react/features/authentication/functions.ts | 39 -------- .../features/authentication/functions.web.ts | 92 +++++++++++++++++++ react/features/authentication/middleware.ts | 22 +++-- 9 files changed, 240 insertions(+), 70 deletions(-) create mode 100644 react/features/authentication/functions.any.ts create mode 100644 react/features/authentication/functions.native.ts delete mode 100644 react/features/authentication/functions.ts create mode 100644 react/features/authentication/functions.web.ts 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/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)); + }); } /** From 139b7cd07cba0d720d3136bcb9511a45763022bf Mon Sep 17 00:00:00 2001 From: damencho Date: Thu, 21 Sep 2023 09:53:35 -0500 Subject: [PATCH 6/6] fix: Adds check for missing main_room. --- resources/prosody-plugins/mod_persistent_lobby.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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