Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Authentication updates #13852

Merged
merged 6 commits into from
Sep 21, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
28 changes: 25 additions & 3 deletions config.js
Original file line number Diff line number Diff line change
Expand Up @@ -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:
saghul marked this conversation as resolved.
Show resolved Hide resolved
// '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
Expand All @@ -1460,9 +1485,6 @@ var config = {
peopleSearchQueryTypes
peopleSearchUrl
requireDisplayName
tokenAuthUrl
tokenAuthUrlAutoRedirect
tokenLogoutUrl
*/

/**
Expand Down
88 changes: 31 additions & 57 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
14 changes: 11 additions & 3 deletions react/features/app/getRouteToRender.web.ts
Original file line number Diff line number Diff line change
@@ -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';
Expand Down Expand Up @@ -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)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What if this fails? What state is the user left in?

.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
Expand Down
10 changes: 2 additions & 8 deletions react/features/authentication/actions.native.ts
Original file line number Diff line number Diff line change
@@ -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';
Expand Down Expand Up @@ -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);
};
}
9 changes: 2 additions & 7 deletions react/features/authentication/actions.web.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand Down Expand Up @@ -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;
}
};

Expand Down
48 changes: 48 additions & 0 deletions react/features/authentication/functions.any.ts
Original file line number Diff line number Diff line change
@@ -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);
saghul marked this conversation as resolved.
Show resolved Hide resolved

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;
};
48 changes: 48 additions & 0 deletions react/features/authentication/functions.native.ts
Original file line number Diff line number Diff line change
@@ -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 <tt>roomName</tt>
* 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<string|undefined>} - The URL pointing to JWT login service or
* <tt>undefined</tt> 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<string | undefined> => {

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));
};
Loading