Merge pull request #1270 from vector-im/SimonBrandner/feat/e2ee-pw
This commit is contained in:
@@ -60,7 +60,7 @@
|
||||
"i18next-http-backend": "^1.4.4",
|
||||
"livekit-client": "1.12.0",
|
||||
"lodash": "^4.17.21",
|
||||
"matrix-js-sdk": "github:matrix-org/matrix-js-sdk#d79d9ae69c3220c02406706d4a1ec52c22c44fbd",
|
||||
"matrix-js-sdk": "github:matrix-org/matrix-js-sdk#b698217445318f453e0b1086364a33113eaa85d9",
|
||||
"matrix-widget-api": "^1.3.1",
|
||||
"mermaid": "^8.13.8",
|
||||
"normalize.css": "^8.0.1",
|
||||
|
||||
@@ -38,6 +38,7 @@
|
||||
"Download debug logs": "Download debug logs",
|
||||
"Element Call Home": "Element Call Home",
|
||||
"Element Call is temporarily not end-to-end encrypted while we test scalability.": "Element Call is temporarily not end-to-end encrypted while we test scalability.",
|
||||
"Enable end-to-end encryption (password protected calls)": "Enable end-to-end encryption (password protected calls)",
|
||||
"Exit full screen": "Exit full screen",
|
||||
"Expose developer settings in the settings window.": "Expose developer settings in the settings window.",
|
||||
"Feedback": "Feedback",
|
||||
@@ -72,6 +73,7 @@
|
||||
"Not registered yet? <2>Create an account</2>": "Not registered yet? <2>Create an account</2>",
|
||||
"Other users are trying to join this call from incompatible versions. These users should ensure that they have refreshed their browsers:<1>{userLis}</1>": "Other users are trying to join this call from incompatible versions. These users should ensure that they have refreshed their browsers:<1>{userLis}</1>",
|
||||
"Password": "Password",
|
||||
"Password (if none, E2EE is disabled)": "Password (if none, E2EE is disabled)",
|
||||
"Passwords must match": "Passwords must match",
|
||||
"Profile": "Profile",
|
||||
"Recaptcha dismissed": "Recaptcha dismissed",
|
||||
|
||||
@@ -6,9 +6,7 @@ import {
|
||||
TrackPublishDefaults,
|
||||
VideoPreset,
|
||||
VideoPresets,
|
||||
ExternalE2EEKeyProvider,
|
||||
} from "livekit-client";
|
||||
import E2EEWorker from "livekit-client/e2ee-worker?worker";
|
||||
|
||||
const defaultLiveKitPublishOptions: TrackPublishDefaults = {
|
||||
audioPreset: AudioPresets.music,
|
||||
@@ -24,16 +22,7 @@ const defaultLiveKitPublishOptions: TrackPublishDefaults = {
|
||||
backupCodec: { codec: "vp8", encoding: VideoPresets.h720.encoding },
|
||||
} as const;
|
||||
|
||||
const e2eeWorker = new E2EEWorker();
|
||||
const e2eeKeyProvider = new ExternalE2EEKeyProvider();
|
||||
e2eeKeyProvider.setKey("not secret password");
|
||||
|
||||
export const defaultLiveKitOptions: RoomOptions = {
|
||||
e2ee: {
|
||||
keyProvider: e2eeKeyProvider,
|
||||
worker: e2eeWorker,
|
||||
},
|
||||
|
||||
// automatically manage subscribed video quality
|
||||
adaptiveStream: true,
|
||||
|
||||
|
||||
@@ -1,6 +1,12 @@
|
||||
import { Room, RoomOptions } from "livekit-client";
|
||||
import {
|
||||
E2EEOptions,
|
||||
ExternalE2EEKeyProvider,
|
||||
Room,
|
||||
RoomOptions,
|
||||
} from "livekit-client";
|
||||
import { useLiveKitRoom } from "@livekit/components-react";
|
||||
import { useMemo } from "react";
|
||||
import { useEffect, useMemo } from "react";
|
||||
import E2EEWorker from "livekit-client/e2ee-worker?worker";
|
||||
|
||||
import { defaultLiveKitOptions } from "./options";
|
||||
import { SFUConfig } from "./openIDSFU";
|
||||
@@ -15,10 +21,32 @@ export type DeviceChoices = {
|
||||
enabled: boolean;
|
||||
};
|
||||
|
||||
export type E2EEConfig = {
|
||||
sharedKey: string;
|
||||
};
|
||||
|
||||
export function useLiveKit(
|
||||
userChoices: UserChoices,
|
||||
sfuConfig?: SFUConfig
|
||||
sfuConfig?: SFUConfig,
|
||||
e2eeConfig?: E2EEConfig
|
||||
): Room | undefined {
|
||||
const e2eeOptions = useMemo(() => {
|
||||
if (!e2eeConfig?.sharedKey) return undefined;
|
||||
|
||||
return {
|
||||
keyProvider: new ExternalE2EEKeyProvider(),
|
||||
worker: new E2EEWorker(),
|
||||
} as E2EEOptions;
|
||||
}, [e2eeConfig]);
|
||||
|
||||
useEffect(() => {
|
||||
if (!e2eeConfig?.sharedKey || !e2eeOptions) return;
|
||||
|
||||
(e2eeOptions.keyProvider as ExternalE2EEKeyProvider).setKey(
|
||||
e2eeConfig?.sharedKey
|
||||
);
|
||||
}, [e2eeOptions, e2eeConfig?.sharedKey]);
|
||||
|
||||
const roomOptions = useMemo((): RoomOptions => {
|
||||
const options = defaultLiveKitOptions;
|
||||
options.videoCaptureDefaults = {
|
||||
@@ -29,8 +57,11 @@ export function useLiveKit(
|
||||
...options.audioCaptureDefaults,
|
||||
deviceId: userChoices.audio?.selectedId,
|
||||
};
|
||||
|
||||
options.e2ee = e2eeOptions;
|
||||
|
||||
return options;
|
||||
}, [userChoices.video, userChoices.audio]);
|
||||
}, [userChoices.video, userChoices.audio, e2eeOptions]);
|
||||
|
||||
const roomWithoutProps = useMemo(() => new Room(roomOptions), [roomOptions]);
|
||||
|
||||
|
||||
@@ -32,7 +32,7 @@ import { CallEndedView } from "./CallEndedView";
|
||||
import { useSentryGroupCallHandler } from "./useSentryGroupCallHandler";
|
||||
import { PosthogAnalytics } from "../analytics/PosthogAnalytics";
|
||||
import { useProfile } from "../profile/useProfile";
|
||||
import { UserChoices } from "../livekit/useLiveKit";
|
||||
import { E2EEConfig, UserChoices } from "../livekit/useLiveKit";
|
||||
import { findDeviceByName } from "../media-utils";
|
||||
import { OpenIDLoader } from "../livekit/OpenIDLoader";
|
||||
import { ActiveCall } from "./InCallView";
|
||||
@@ -218,10 +218,12 @@ export function GroupCallView({
|
||||
const [userChoices, setUserChoices] = useState<UserChoices | undefined>(
|
||||
undefined
|
||||
);
|
||||
const [e2eeConfig, setE2EEConfig] = useState<E2EEConfig | undefined>(
|
||||
undefined
|
||||
);
|
||||
|
||||
const livekitServiceURL =
|
||||
groupCall.foci[0]?.livekitServiceUrl ??
|
||||
Config.get().livekit?.livekit_service_url;
|
||||
groupCall.livekitServiceURL ?? Config.get().livekit?.livekit_service_url;
|
||||
if (!livekitServiceURL) {
|
||||
return <ErrorView error={new Error("No livekit_service_url defined")} />;
|
||||
}
|
||||
@@ -243,6 +245,7 @@ export function GroupCallView({
|
||||
unencryptedEventsFromUsers={unencryptedEventsFromUsers}
|
||||
hideHeader={hideHeader}
|
||||
userChoices={userChoices}
|
||||
e2eeConfig={e2eeConfig}
|
||||
otelGroupCallMembership={otelGroupCallMembership}
|
||||
/>
|
||||
</OpenIDLoader>
|
||||
@@ -283,8 +286,9 @@ export function GroupCallView({
|
||||
return (
|
||||
<LobbyView
|
||||
matrixInfo={matrixInfo}
|
||||
onEnter={(choices: UserChoices) => {
|
||||
onEnter={(choices: UserChoices, e2eeConfig?: E2EEConfig) => {
|
||||
setUserChoices(choices);
|
||||
setE2EEConfig(e2eeConfig);
|
||||
enter();
|
||||
}}
|
||||
isEmbedded={isEmbedded}
|
||||
|
||||
@@ -77,7 +77,7 @@ import { SettingsModal } from "../settings/SettingsModal";
|
||||
import { InviteModal } from "./InviteModal";
|
||||
import { useRageshakeRequestModal } from "../settings/submit-rageshake";
|
||||
import { RageshakeRequestModal } from "./RageshakeRequestModal";
|
||||
import { UserChoices, useLiveKit } from "../livekit/useLiveKit";
|
||||
import { E2EEConfig, UserChoices, useLiveKit } from "../livekit/useLiveKit";
|
||||
import { useMediaDevicesSwitcher } from "../livekit/useMediaDevicesSwitcher";
|
||||
import { useFullscreen } from "./useFullscreen";
|
||||
import { useLayoutStates } from "../video-grid/Layout";
|
||||
@@ -92,11 +92,16 @@ const isSafari = /^((?!chrome|android).)*safari/i.test(navigator.userAgent);
|
||||
|
||||
export interface ActiveCallProps extends Omit<InCallViewProps, "livekitRoom"> {
|
||||
userChoices: UserChoices;
|
||||
e2eeConfig?: E2EEConfig;
|
||||
}
|
||||
|
||||
export function ActiveCall(props: ActiveCallProps) {
|
||||
const sfuConfig = useSFUConfig();
|
||||
const livekitRoom = useLiveKit(props.userChoices, sfuConfig);
|
||||
const livekitRoom = useLiveKit(
|
||||
props.userChoices,
|
||||
sfuConfig,
|
||||
props.e2eeConfig
|
||||
);
|
||||
|
||||
if (!livekitRoom) {
|
||||
return null;
|
||||
|
||||
@@ -66,3 +66,9 @@ limitations under the License.
|
||||
.copyButton:last-child {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
.passwordField {
|
||||
width: 320px !important;
|
||||
margin-bottom: 20px;
|
||||
flex: 0;
|
||||
}
|
||||
|
||||
@@ -14,7 +14,7 @@ See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
import { useRef, useEffect, useState } from "react";
|
||||
import { useRef, useEffect, useState, useCallback, ChangeEvent } from "react";
|
||||
import { Trans, useTranslation } from "react-i18next";
|
||||
|
||||
import styles from "./LobbyView.module.css";
|
||||
@@ -25,12 +25,14 @@ import { UserMenuContainer } from "../UserMenuContainer";
|
||||
import { Body, Link } from "../typography/Typography";
|
||||
import { useLocationNavigation } from "../useLocationNavigation";
|
||||
import { MatrixInfo, VideoPreview } from "./VideoPreview";
|
||||
import { UserChoices } from "../livekit/useLiveKit";
|
||||
import { E2EEConfig, UserChoices } from "../livekit/useLiveKit";
|
||||
import { InputField } from "../input/Input";
|
||||
import { useEnableE2EE } from "../settings/useSetting";
|
||||
|
||||
interface Props {
|
||||
matrixInfo: MatrixInfo;
|
||||
|
||||
onEnter: (userChoices: UserChoices) => void;
|
||||
onEnter: (userChoices: UserChoices, e2eeConfig?: E2EEConfig) => void;
|
||||
isEmbedded: boolean;
|
||||
hideHeader: boolean;
|
||||
}
|
||||
@@ -39,6 +41,8 @@ export function LobbyView(props: Props) {
|
||||
const { t } = useTranslation();
|
||||
useLocationNavigation();
|
||||
|
||||
const [enableE2EE] = useEnableE2EE();
|
||||
|
||||
const joinCallButtonRef = useRef<HTMLButtonElement>(null);
|
||||
useEffect(() => {
|
||||
if (joinCallButtonRef.current) {
|
||||
@@ -49,6 +53,17 @@ export function LobbyView(props: Props) {
|
||||
const [userChoices, setUserChoices] = useState<UserChoices | undefined>(
|
||||
undefined
|
||||
);
|
||||
const [e2eeSharedKey, setE2EESharedKey] = useState<string | undefined>(
|
||||
undefined
|
||||
);
|
||||
|
||||
const onE2EESharedKeyChanged = useCallback(
|
||||
(event: ChangeEvent<HTMLInputElement>) => {
|
||||
const value = event.target.value;
|
||||
setE2EESharedKey(value === "" ? undefined : value);
|
||||
},
|
||||
[setE2EESharedKey]
|
||||
);
|
||||
|
||||
return (
|
||||
<div className={styles.room}>
|
||||
@@ -68,12 +83,26 @@ export function LobbyView(props: Props) {
|
||||
matrixInfo={props.matrixInfo}
|
||||
onUserChoicesChanged={setUserChoices}
|
||||
/>
|
||||
{enableE2EE && (
|
||||
<InputField
|
||||
className={styles.passwordField}
|
||||
label={t("Password (if none, E2EE is disabled)")}
|
||||
type="text"
|
||||
onChange={onE2EESharedKeyChanged}
|
||||
value={e2eeSharedKey}
|
||||
/>
|
||||
)}
|
||||
<Trans>
|
||||
<Button
|
||||
ref={joinCallButtonRef}
|
||||
className={styles.copyButton}
|
||||
size="lg"
|
||||
onPress={() => props.onEnter(userChoices!)}
|
||||
onPress={() =>
|
||||
props.onEnter(
|
||||
userChoices!,
|
||||
e2eeSharedKey ? { sharedKey: e2eeSharedKey } : undefined
|
||||
)
|
||||
}
|
||||
data-testid="lobby_joinCall"
|
||||
>
|
||||
Join call now
|
||||
|
||||
@@ -34,6 +34,7 @@ import {
|
||||
useOptInAnalytics,
|
||||
useDeveloperSettingsTab,
|
||||
useShowConnectionStats,
|
||||
useEnableE2EE,
|
||||
} from "./useSetting";
|
||||
import { FieldRow, InputField } from "../input/Input";
|
||||
import { Button } from "../button";
|
||||
@@ -68,6 +69,7 @@ export const SettingsModal = (props: Props) => {
|
||||
useDeveloperSettingsTab();
|
||||
const [showConnectionStats, setShowConnectionStats] =
|
||||
useShowConnectionStats();
|
||||
const [enableE2EE, setEnableE2EE] = useEnableE2EE();
|
||||
|
||||
const downloadDebugLog = useDownloadDebugLog();
|
||||
|
||||
@@ -249,6 +251,18 @@ export const SettingsModal = (props: Props) => {
|
||||
}
|
||||
/>
|
||||
</FieldRow>
|
||||
<FieldRow>
|
||||
<InputField
|
||||
id="enableE2EE"
|
||||
name="end-to-end-encryption"
|
||||
label={t("Enable end-to-end encryption (password protected calls)")}
|
||||
type="checkbox"
|
||||
checked={enableE2EE}
|
||||
onChange={(e: ChangeEvent<HTMLInputElement>) =>
|
||||
setEnableE2EE(e.target.checked)
|
||||
}
|
||||
/>
|
||||
</FieldRow>
|
||||
<FieldRow>
|
||||
<Button onPress={downloadDebugLog}>{t("Download debug logs")}</Button>
|
||||
</FieldRow>
|
||||
|
||||
@@ -104,6 +104,9 @@ export const useDeveloperSettingsTab = () =>
|
||||
export const useShowConnectionStats = () =>
|
||||
useSetting("show-connection-stats", false);
|
||||
|
||||
export const useEnableE2EE = () =>
|
||||
useSetting("enable-end-to-end-encryption", false);
|
||||
|
||||
export const useDefaultDevices = () =>
|
||||
useSetting("defaultDevices", {
|
||||
audioinput: "",
|
||||
|
||||
@@ -164,7 +164,7 @@ export const widget: WidgetHelpers | null = (() => {
|
||||
// message so that we can use the widget API in less racy mode, but we need to change
|
||||
// element-web to use waitForIFrameLoad=false. Once that change has rolled out,
|
||||
// we can just start the client after we've fetched the config.
|
||||
foci: [],
|
||||
livekitServiceURL: undefined,
|
||||
}
|
||||
);
|
||||
|
||||
@@ -178,9 +178,7 @@ export const widget: WidgetHelpers | null = (() => {
|
||||
// Now we've fetched the config, be evil and use the getter to inject the focus
|
||||
// into the client (see above XXX).
|
||||
if (focus) {
|
||||
client.getFoci().push({
|
||||
livekitServiceUrl: livekit.livekit_service_url,
|
||||
});
|
||||
client.setLivekitServiceURL(livekit.livekit_service_url);
|
||||
}
|
||||
await client.startClient();
|
||||
resolve(client);
|
||||
|
||||
@@ -11012,9 +11012,9 @@ matrix-events-sdk@0.0.1:
|
||||
resolved "https://registry.yarnpkg.com/matrix-events-sdk/-/matrix-events-sdk-0.0.1.tgz#c8c38911e2cb29023b0bbac8d6f32e0de2c957dd"
|
||||
integrity sha512-1QEOsXO+bhyCroIe2/A5OwaxHvBm7EsSQ46DEDn8RBIfQwN5HWBpFvyWWR4QY0KHPPnnJdI99wgRiAl7Ad5qaA==
|
||||
|
||||
"matrix-js-sdk@github:matrix-org/matrix-js-sdk#d79d9ae69c3220c02406706d4a1ec52c22c44fbd":
|
||||
"matrix-js-sdk@github:matrix-org/matrix-js-sdk#b698217445318f453e0b1086364a33113eaa85d9":
|
||||
version "26.2.0"
|
||||
resolved "https://codeload.github.com/matrix-org/matrix-js-sdk/tar.gz/d79d9ae69c3220c02406706d4a1ec52c22c44fbd"
|
||||
resolved "https://codeload.github.com/matrix-org/matrix-js-sdk/tar.gz/b698217445318f453e0b1086364a33113eaa85d9"
|
||||
dependencies:
|
||||
"@babel/runtime" "^7.12.5"
|
||||
"@matrix-org/matrix-sdk-crypto-js" "^0.1.1"
|
||||
|
||||
Reference in New Issue
Block a user