mirror of
https://github.com/element-hq/element-web
synced 2024-11-27 03:36:07 +03:00
Merge pull request #5173 from matrix-org/jaywink/jitsi-openidjwt-auth
Support creation of Jitsi widgets with "openidtoken-jwt" auth
This commit is contained in:
commit
75518254fb
7 changed files with 104 additions and 10 deletions
|
@ -94,6 +94,7 @@
|
|||
"react-focus-lock": "^2.4.1",
|
||||
"react-transition-group": "^4.4.1",
|
||||
"resize-observer-polyfill": "^1.5.1",
|
||||
"rfc4648": "^1.4.0",
|
||||
"sanitize-html": "^1.27.1",
|
||||
"tar-js": "^0.3.0",
|
||||
"text-encoding-utf-8": "^1.0.2",
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
/*
|
||||
Copyright 2015, 2016 OpenMarket Ltd
|
||||
Copyright 2017, 2018 New Vector Ltd
|
||||
Copyright 2019 The Matrix.org Foundation C.I.C.
|
||||
Copyright 2019, 2020 The Matrix.org Foundation C.I.C.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
|
@ -67,6 +67,7 @@ import {generateHumanReadableId} from "./utils/NamingUtils";
|
|||
import {Jitsi} from "./widgets/Jitsi";
|
||||
import {WidgetType} from "./widgets/WidgetType";
|
||||
import {SettingLevel} from "./settings/SettingLevel";
|
||||
import {base32} from "rfc4648";
|
||||
|
||||
global.mxCalls = {
|
||||
//room_id: MatrixCall
|
||||
|
@ -388,10 +389,21 @@ async function _startCallApp(roomId, type) {
|
|||
return;
|
||||
}
|
||||
|
||||
const confId = `JitsiConference${generateHumanReadableId()}`;
|
||||
const jitsiDomain = Jitsi.getInstance().preferredDomain;
|
||||
const jitsiAuth = await Jitsi.getInstance().getJitsiAuth();
|
||||
let confId;
|
||||
if (jitsiAuth === 'openidtoken-jwt') {
|
||||
// Create conference ID from room ID
|
||||
// For compatibility with Jitsi, use base32 without padding.
|
||||
// More details here:
|
||||
// https://github.com/matrix-org/prosody-mod-auth-matrix-user-verification
|
||||
confId = base32.stringify(Buffer.from(roomId), { pad: false });
|
||||
} else {
|
||||
// Create a random human readable conference ID
|
||||
confId = `JitsiConference${generateHumanReadableId()}`;
|
||||
}
|
||||
|
||||
let widgetUrl = WidgetUtils.getLocalJitsiWrapperUrl();
|
||||
let widgetUrl = WidgetUtils.getLocalJitsiWrapperUrl({auth: jitsiAuth});
|
||||
|
||||
// TODO: Remove URL hacks when the mobile clients eventually support v2 widgets
|
||||
const parsedUrl = new URL(widgetUrl);
|
||||
|
@ -403,6 +415,7 @@ async function _startCallApp(roomId, type) {
|
|||
conferenceId: confId,
|
||||
isAudioOnly: type === 'voice',
|
||||
domain: jitsiDomain,
|
||||
auth: jitsiAuth,
|
||||
};
|
||||
|
||||
const widgetId = (
|
||||
|
|
|
@ -626,7 +626,10 @@ export default class AppTile extends React.Component {
|
|||
|
||||
if (WidgetType.JITSI.matches(this.props.app.type)) {
|
||||
console.log("Replacing Jitsi widget URL with local wrapper");
|
||||
url = WidgetUtils.getLocalJitsiWrapperUrl({forLocalRender: true});
|
||||
url = WidgetUtils.getLocalJitsiWrapperUrl({
|
||||
forLocalRender: true,
|
||||
auth: this.props.app.data ? this.props.app.data.auth : null,
|
||||
});
|
||||
url = this._addWurlParams(url);
|
||||
} else {
|
||||
url = this._getSafeUrl(this.state.widgetUrl);
|
||||
|
@ -637,7 +640,10 @@ export default class AppTile extends React.Component {
|
|||
_getPopoutUrl() {
|
||||
if (WidgetType.JITSI.matches(this.props.app.type)) {
|
||||
return this._templatedUrl(
|
||||
WidgetUtils.getLocalJitsiWrapperUrl({forLocalRender: false}),
|
||||
WidgetUtils.getLocalJitsiWrapperUrl({
|
||||
forLocalRender: false,
|
||||
auth: this.props.app.data ? this.props.app.data.auth : null,
|
||||
}),
|
||||
this.props.app.type,
|
||||
);
|
||||
} else {
|
||||
|
|
|
@ -448,16 +448,21 @@ export default class WidgetUtils {
|
|||
return encodeURIComponent(`${widgetLocation}::${widgetUrl}`);
|
||||
}
|
||||
|
||||
static getLocalJitsiWrapperUrl(opts: {forLocalRender?: boolean}={}) {
|
||||
static getLocalJitsiWrapperUrl(opts: {forLocalRender?: boolean, auth?: string}={}) {
|
||||
// NB. we can't just encodeURIComponent all of these because the $ signs need to be there
|
||||
const queryString = [
|
||||
const queryStringParts = [
|
||||
'conferenceDomain=$domain',
|
||||
'conferenceId=$conferenceId',
|
||||
'isAudioOnly=$isAudioOnly',
|
||||
'displayName=$matrix_display_name',
|
||||
'avatarUrl=$matrix_avatar_url',
|
||||
'userId=$matrix_user_id',
|
||||
].join('&');
|
||||
'roomId=$matrix_room_id',
|
||||
];
|
||||
if (opts.auth) {
|
||||
queryStringParts.push(`auth=${opts.auth}`);
|
||||
}
|
||||
const queryString = queryStringParts.join('&');
|
||||
|
||||
let baseUrl = window.location;
|
||||
if (window.location.protocol !== "https:" && !opts.forLocalRender) {
|
||||
|
|
|
@ -34,6 +34,30 @@ export class Jitsi {
|
|||
return this.domain || 'jitsi.riot.im';
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks for auth needed by looking up a well-known file
|
||||
*
|
||||
* If the file does not exist, we assume no auth.
|
||||
*
|
||||
* See https://github.com/matrix-org/prosody-mod-auth-matrix-user-verification
|
||||
*/
|
||||
public async getJitsiAuth(): Promise<string|null> {
|
||||
if (!this.preferredDomain) {
|
||||
return null;
|
||||
}
|
||||
let data;
|
||||
try {
|
||||
const response = await fetch(`https://${this.preferredDomain}/.well-known/element/jitsi`);
|
||||
data = await response.json();
|
||||
} catch (error) {
|
||||
return null;
|
||||
}
|
||||
if (data.auth) {
|
||||
return data.auth;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public start() {
|
||||
const cli = MatrixClientPeg.get();
|
||||
cli.on("WellKnown.client", this.update);
|
||||
|
|
|
@ -34,6 +34,7 @@ export enum KnownWidgetActions {
|
|||
GetCapabilities = "capabilities",
|
||||
SendEvent = "send_event",
|
||||
UpdateVisibility = "visibility",
|
||||
GetOpenIDCredentials = "get_openid",
|
||||
ReceiveOpenIDCredentials = "openid_credentials",
|
||||
SetAlwaysOnScreen = "set_always_on_screen",
|
||||
ClientReady = "im.vector.ready",
|
||||
|
@ -64,6 +65,13 @@ export interface FromWidgetRequest extends WidgetRequest {
|
|||
response: any;
|
||||
}
|
||||
|
||||
export interface OpenIDCredentials {
|
||||
accessToken: string;
|
||||
tokenType: string;
|
||||
matrixServerName: string;
|
||||
expiresIn: number;
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles Element <--> Widget interactions for embedded/standalone widgets.
|
||||
*
|
||||
|
@ -73,10 +81,12 @@ export interface FromWidgetRequest extends WidgetRequest {
|
|||
* the given promise resolves.
|
||||
*/
|
||||
export class WidgetApi extends EventEmitter {
|
||||
private origin: string;
|
||||
private readonly origin: string;
|
||||
private inFlightRequests: { [requestId: string]: (reply: FromWidgetRequest) => void } = {};
|
||||
private readyPromise: Promise<any>;
|
||||
private readonly readyPromise: Promise<any>;
|
||||
private readyPromiseResolve: () => void;
|
||||
private openIDCredentialsCallback: () => void;
|
||||
public openIDCredentials: OpenIDCredentials;
|
||||
|
||||
/**
|
||||
* Set this to true if your widget is expecting a ready message from the client. False otherwise (default).
|
||||
|
@ -120,6 +130,10 @@ export class WidgetApi extends EventEmitter {
|
|||
// Acknowledge that we're shut down now
|
||||
this.replyToRequest(<ToWidgetRequest>payload, {});
|
||||
});
|
||||
} else if (payload.action === KnownWidgetActions.ReceiveOpenIDCredentials) {
|
||||
// Save OpenID credentials
|
||||
this.setOpenIDCredentials(<ToWidgetRequest>payload);
|
||||
this.replyToRequest(<ToWidgetRequest>payload, {});
|
||||
} else {
|
||||
console.warn(`[WidgetAPI] Got unexpected action: ${payload.action}`);
|
||||
}
|
||||
|
@ -134,6 +148,32 @@ export class WidgetApi extends EventEmitter {
|
|||
});
|
||||
}
|
||||
|
||||
public setOpenIDCredentials(value: WidgetRequest) {
|
||||
const data = value.data;
|
||||
if (data.state === 'allowed') {
|
||||
this.openIDCredentials = {
|
||||
accessToken: data.access_token,
|
||||
tokenType: data.token_type,
|
||||
matrixServerName: data.matrix_server_name,
|
||||
expiresIn: data.expires_in,
|
||||
}
|
||||
} else if (data.state === 'blocked') {
|
||||
this.openIDCredentials = null;
|
||||
}
|
||||
if (['allowed', 'blocked'].includes(data.state) && this.openIDCredentialsCallback) {
|
||||
this.openIDCredentialsCallback()
|
||||
}
|
||||
}
|
||||
|
||||
public requestOpenIDCredentials(credentialsResponseCallback: () => void) {
|
||||
this.openIDCredentialsCallback = credentialsResponseCallback;
|
||||
this.callAction(
|
||||
KnownWidgetActions.GetOpenIDCredentials,
|
||||
{},
|
||||
this.setOpenIDCredentials,
|
||||
);
|
||||
}
|
||||
|
||||
public waitReady(): Promise<any> {
|
||||
return this.readyPromise;
|
||||
}
|
||||
|
|
|
@ -7557,6 +7557,11 @@ retry@^0.10.0:
|
|||
resolved "https://registry.yarnpkg.com/retry/-/retry-0.10.1.tgz#e76388d217992c252750241d3d3956fed98d8ff4"
|
||||
integrity sha1-52OI0heZLCUnUCQdPTlW/tmNj/Q=
|
||||
|
||||
rfc4648@^1.4.0:
|
||||
version "1.4.0"
|
||||
resolved "https://registry.yarnpkg.com/rfc4648/-/rfc4648-1.4.0.tgz#c75b2856ad2e2d588b6ddb985d556f1f7f2a2abd"
|
||||
integrity sha512-3qIzGhHlMHA6PoT6+cdPKZ+ZqtxkIvg8DZGKA5z6PQ33/uuhoJ+Ws/D/J9rXW6gXodgH8QYlz2UCl+sdUDmNIg==
|
||||
|
||||
rimraf@2.6.3:
|
||||
version "2.6.3"
|
||||
resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-2.6.3.tgz#b2d104fe0d8fb27cf9e0a1cda8262dd3833c6cab"
|
||||
|
|
Loading…
Reference in a new issue