mirror of
https://github.com/SchildiChat/element-web.git
synced 2024-10-01 01:26:12 -04:00
Merge pull request #15114 from vector-im/jaywink/jitsi-openidjwt-auth
Support usage of Jitsi widgets with "openidtoken-jwt" auth
This commit is contained in:
commit
da0afeda9b
@ -59,6 +59,7 @@
|
|||||||
"browser-request": "^0.3.3",
|
"browser-request": "^0.3.3",
|
||||||
"gfm.css": "^1.1.2",
|
"gfm.css": "^1.1.2",
|
||||||
"highlight.js": "^9.13.1",
|
"highlight.js": "^9.13.1",
|
||||||
|
"jsrsasign": "^9.1.5",
|
||||||
"matrix-js-sdk": "github:matrix-org/matrix-js-sdk#develop",
|
"matrix-js-sdk": "github:matrix-org/matrix-js-sdk#develop",
|
||||||
"matrix-react-sdk": "github:matrix-org/matrix-react-sdk#develop",
|
"matrix-react-sdk": "github:matrix-org/matrix-react-sdk#develop",
|
||||||
"olm": "https://packages.matrix.org/npm/olm/olm-3.1.4.tgz",
|
"olm": "https://packages.matrix.org/npm/olm/olm-3.1.4.tgz",
|
||||||
|
@ -11,7 +11,9 @@
|
|||||||
<div class="joinConferencePrompt">
|
<div class="joinConferencePrompt">
|
||||||
<!-- TODO: i18n -->
|
<!-- TODO: i18n -->
|
||||||
<h2>Jitsi Video Conference</h2>
|
<h2>Jitsi Video Conference</h2>
|
||||||
<button type="button" id="joinButton">Join Conference</button>
|
<div id="widgetActionContainer">
|
||||||
|
<button type="button" id="joinButton">Join Conference</button>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -18,7 +18,10 @@ limitations under the License.
|
|||||||
require("./index.scss");
|
require("./index.scss");
|
||||||
|
|
||||||
import * as qs from 'querystring';
|
import * as qs from 'querystring';
|
||||||
import { Capability, WidgetApi } from "matrix-react-sdk/src/widgets/WidgetApi";
|
import {Capability, WidgetApi} from 'matrix-react-sdk/src/widgets/WidgetApi';
|
||||||
|
import {KJUR} from 'jsrsasign';
|
||||||
|
|
||||||
|
const JITSI_OPENIDTOKEN_JWT_AUTH = 'openidtoken-jwt';
|
||||||
|
|
||||||
// Dev note: we use raw JS without many dependencies to reduce bundle size.
|
// Dev note: we use raw JS without many dependencies to reduce bundle size.
|
||||||
// We do not need all of React to render a Jitsi conference.
|
// We do not need all of React to render a Jitsi conference.
|
||||||
@ -33,6 +36,8 @@ let conferenceId: string;
|
|||||||
let displayName: string;
|
let displayName: string;
|
||||||
let avatarUrl: string;
|
let avatarUrl: string;
|
||||||
let userId: string;
|
let userId: string;
|
||||||
|
let jitsiAuth: string;
|
||||||
|
let roomId: string;
|
||||||
|
|
||||||
let widgetApi: WidgetApi;
|
let widgetApi: WidgetApi;
|
||||||
|
|
||||||
@ -69,40 +74,118 @@ let widgetApi: WidgetApi;
|
|||||||
displayName = qsParam('displayName', true);
|
displayName = qsParam('displayName', true);
|
||||||
avatarUrl = qsParam('avatarUrl', true); // http not mxc
|
avatarUrl = qsParam('avatarUrl', true); // http not mxc
|
||||||
userId = qsParam('userId');
|
userId = qsParam('userId');
|
||||||
|
jitsiAuth = qsParam('auth', true);
|
||||||
|
roomId = qsParam('roomId', true);
|
||||||
|
|
||||||
if (widgetApi) {
|
if (widgetApi) {
|
||||||
await widgetApi.waitReady();
|
await widgetApi.waitReady();
|
||||||
await widgetApi.setAlwaysOnScreen(false); // start off as detachable from the screen
|
await widgetApi.setAlwaysOnScreen(false); // start off as detachable from the screen
|
||||||
|
|
||||||
|
// See https://github.com/matrix-org/prosody-mod-auth-matrix-user-verification
|
||||||
|
if (jitsiAuth === JITSI_OPENIDTOKEN_JWT_AUTH) {
|
||||||
|
// Request credentials, give callback to continue when received
|
||||||
|
widgetApi.requestOpenIDCredentials(credentialsResponseCallback);
|
||||||
|
} else {
|
||||||
|
enableJoinButton();
|
||||||
|
}
|
||||||
|
// TODO: register widgetApi listeners for PTT controls (https://github.com/vector-im/riot-web/issues/12795)
|
||||||
|
} else {
|
||||||
|
enableJoinButton();
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: register widgetApi listeners for PTT controls (https://github.com/vector-im/riot-web/issues/12795)
|
|
||||||
|
|
||||||
document.getElementById("joinButton").onclick = () => joinConference();
|
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.error("Error setting up Jitsi widget", e);
|
console.error("Error setting up Jitsi widget", e);
|
||||||
document.getElementById("jitsiContainer").innerText = "Failed to load Jitsi widget";
|
document.getElementById("widgetActionContainer").innerText = "Failed to load Jitsi widget";
|
||||||
switchVisibleContainers();
|
|
||||||
}
|
}
|
||||||
})();
|
})();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Enable or show error depending on what the credentials response is.
|
||||||
|
*/
|
||||||
|
function credentialsResponseCallback() {
|
||||||
|
if (widgetApi.openIDCredentials) {
|
||||||
|
console.info('Successfully got OpenID credentials.');
|
||||||
|
enableJoinButton();
|
||||||
|
} else {
|
||||||
|
console.warn('OpenID credentials request was blocked by user.');
|
||||||
|
document.getElementById("widgetActionContainer").innerText = "Failed to load Jitsi widget";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function enableJoinButton() {
|
||||||
|
document.getElementById("joinButton").onclick = () => joinConference();
|
||||||
|
}
|
||||||
|
|
||||||
function switchVisibleContainers() {
|
function switchVisibleContainers() {
|
||||||
inConference = !inConference;
|
inConference = !inConference;
|
||||||
document.getElementById("jitsiContainer").style.visibility = inConference ? 'unset' : 'hidden';
|
document.getElementById("jitsiContainer").style.visibility = inConference ? 'unset' : 'hidden';
|
||||||
document.getElementById("joinButtonContainer").style.visibility = inConference ? 'hidden' : 'unset';
|
document.getElementById("joinButtonContainer").style.visibility = inConference ? 'hidden' : 'unset';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a JWT token fot jitsi openidtoken-jwt auth
|
||||||
|
*
|
||||||
|
* See https://github.com/matrix-org/prosody-mod-auth-matrix-user-verification
|
||||||
|
*/
|
||||||
|
function createJWTToken() {
|
||||||
|
// Header
|
||||||
|
const header = {alg: 'HS256', typ: 'JWT'};
|
||||||
|
// Payload
|
||||||
|
const payload = {
|
||||||
|
// As per Jitsi token auth, `iss` needs to be set to something agreed between
|
||||||
|
// JWT generating side and Prosody config. Since we have no configuration for
|
||||||
|
// the widgets, we can't set one anywhere. Using the Jitsi domain here probably makes sense.
|
||||||
|
iss: jitsiDomain,
|
||||||
|
sub: jitsiDomain,
|
||||||
|
aud: `https://${jitsiDomain}`,
|
||||||
|
room: "*",
|
||||||
|
context: {
|
||||||
|
matrix: {
|
||||||
|
token: widgetApi.openIDCredentials.accessToken,
|
||||||
|
room_id: roomId,
|
||||||
|
},
|
||||||
|
user: {
|
||||||
|
avatar: avatarUrl,
|
||||||
|
name: displayName,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
// Sign JWT
|
||||||
|
// The secret string here is irrelevant, we're only using the JWT
|
||||||
|
// to transport data to Prosody in the Jitsi stack.
|
||||||
|
return KJUR.jws.JWS.sign(
|
||||||
|
'HS256',
|
||||||
|
JSON.stringify(header),
|
||||||
|
JSON.stringify(payload),
|
||||||
|
'notused',
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
function joinConference() { // event handler bound in HTML
|
function joinConference() { // event handler bound in HTML
|
||||||
|
let jwt;
|
||||||
|
if (jitsiAuth === JITSI_OPENIDTOKEN_JWT_AUTH) {
|
||||||
|
if (!widgetApi.openIDCredentials || !widgetApi.openIDCredentials.accessToken) {
|
||||||
|
// We've failing to get a token, don't try to init conference
|
||||||
|
console.warn('Expected to have an OpenID credential, cannot initialize widget.');
|
||||||
|
document.getElementById("widgetActionContainer").innerText = "Failed to load Jitsi widget";
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
jwt = createJWTToken();
|
||||||
|
}
|
||||||
|
|
||||||
switchVisibleContainers();
|
switchVisibleContainers();
|
||||||
|
|
||||||
// noinspection JSIgnoredPromiseFromCall
|
if (widgetApi) {
|
||||||
if (widgetApi) widgetApi.setAlwaysOnScreen(true); // ignored promise because we don't care if it works
|
// ignored promise because we don't care if it works
|
||||||
|
// noinspection JSIgnoredPromiseFromCall
|
||||||
|
widgetApi.setAlwaysOnScreen(true);
|
||||||
|
}
|
||||||
|
|
||||||
console.warn(
|
console.warn(
|
||||||
"[Jitsi Widget] The next few errors about failing to parse URL parameters are fine if " +
|
"[Jitsi Widget] The next few errors about failing to parse URL parameters are fine if " +
|
||||||
"they mention 'external_api' or 'jitsi' in the stack. They're just Jitsi Meet trying to parse " +
|
"they mention 'external_api' or 'jitsi' in the stack. They're just Jitsi Meet trying to parse " +
|
||||||
"our fragment values and not recognizing the options.",
|
"our fragment values and not recognizing the options.",
|
||||||
);
|
);
|
||||||
const meetApi = new JitsiMeetExternalAPI(jitsiDomain, {
|
const options = {
|
||||||
width: "100%",
|
width: "100%",
|
||||||
height: "100%",
|
height: "100%",
|
||||||
parentNode: document.querySelector("#jitsiContainer"),
|
parentNode: document.querySelector("#jitsiContainer"),
|
||||||
@ -113,7 +196,10 @@ function joinConference() { // event handler bound in HTML
|
|||||||
MAIN_TOOLBAR_BUTTONS: [],
|
MAIN_TOOLBAR_BUTTONS: [],
|
||||||
VIDEO_LAYOUT_FIT: "height",
|
VIDEO_LAYOUT_FIT: "height",
|
||||||
},
|
},
|
||||||
});
|
jwt: jwt,
|
||||||
|
};
|
||||||
|
|
||||||
|
const meetApi = new JitsiMeetExternalAPI(jitsiDomain, options);
|
||||||
if (displayName) meetApi.executeCommand("displayName", displayName);
|
if (displayName) meetApi.executeCommand("displayName", displayName);
|
||||||
if (avatarUrl) meetApi.executeCommand("avatarUrl", avatarUrl);
|
if (avatarUrl) meetApi.executeCommand("avatarUrl", avatarUrl);
|
||||||
if (userId) meetApi.executeCommand("email", userId);
|
if (userId) meetApi.executeCommand("email", userId);
|
||||||
@ -121,8 +207,11 @@ function joinConference() { // event handler bound in HTML
|
|||||||
meetApi.on("readyToClose", () => {
|
meetApi.on("readyToClose", () => {
|
||||||
switchVisibleContainers();
|
switchVisibleContainers();
|
||||||
|
|
||||||
// noinspection JSIgnoredPromiseFromCall
|
if (widgetApi) {
|
||||||
if (widgetApi) widgetApi.setAlwaysOnScreen(false); // ignored promise because we don't care if it works
|
// ignored promise because we don't care if it works
|
||||||
|
// noinspection JSIgnoredPromiseFromCall
|
||||||
|
widgetApi.setAlwaysOnScreen(false);
|
||||||
|
}
|
||||||
|
|
||||||
document.getElementById("jitsiContainer").innerHTML = "";
|
document.getElementById("jitsiContainer").innerHTML = "";
|
||||||
});
|
});
|
||||||
|
@ -6914,6 +6914,11 @@ jsprim@^1.2.2:
|
|||||||
json-schema "0.2.3"
|
json-schema "0.2.3"
|
||||||
verror "1.10.0"
|
verror "1.10.0"
|
||||||
|
|
||||||
|
jsrsasign@^9.1.5:
|
||||||
|
version "9.1.5"
|
||||||
|
resolved "https://registry.yarnpkg.com/jsrsasign/-/jsrsasign-9.1.5.tgz#fe286425d2c05b2d0865d24ded53e34b12abd2ca"
|
||||||
|
integrity sha512-iJLF8FvZHlwyQudrRtQomHj1HdPAcM8QSRTt0FJo8a6iFgaGCpKUrE7lWyELpAjrFs8jUC/Azc0vfhlj3yqHPQ==
|
||||||
|
|
||||||
jsx-ast-utils@^2.2.3:
|
jsx-ast-utils@^2.2.3:
|
||||||
version "2.3.0"
|
version "2.3.0"
|
||||||
resolved "https://registry.yarnpkg.com/jsx-ast-utils/-/jsx-ast-utils-2.3.0.tgz#edd727794ea284d7fda575015ed1b0cde0289ab6"
|
resolved "https://registry.yarnpkg.com/jsx-ast-utils/-/jsx-ast-utils-2.3.0.tgz#edd727794ea284d7fda575015ed1b0cde0289ab6"
|
||||||
|
Loading…
Reference in New Issue
Block a user