Add E2EE password prompt

Signed-off-by: Šimon Brandner <simon.bra.ag@gmail.com>
This commit is contained in:
Šimon Brandner
2023-07-17 16:53:58 +02:00
parent 8946af8f4e
commit 4193629c2c
6 changed files with 84 additions and 25 deletions

View File

@@ -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,

View File

@@ -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]);

View File

@@ -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}

View File

@@ -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;

View File

@@ -66,3 +66,9 @@ limitations under the License.
.copyButton:last-child {
margin-bottom: 0;
}
.passwordField {
width: 320px !important;
margin-bottom: 20px;
flex: 0;
}

View File

@@ -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,13 @@ 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";
interface Props {
matrixInfo: MatrixInfo;
onEnter: (userChoices: UserChoices) => void;
onEnter: (userChoices: UserChoices, e2eeConfig?: E2EEConfig) => void;
isEmbedded: boolean;
hideHeader: boolean;
}
@@ -49,6 +50,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 +80,24 @@ export function LobbyView(props: Props) {
matrixInfo={props.matrixInfo}
onUserChoicesChanged={setUserChoices}
/>
<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