/* Copyright 2023, 2024 New Vector Ltd. SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial Please see LICENSE in the repository root for full details. */ import { useEffect, useMemo } from "react"; import { setLocalStorageItem, useLocalStorage } from "../useLocalStorage"; import { type UrlParams, getUrlParams, useUrlParams } from "../UrlParams"; import { E2eeType } from "./e2eeType"; import { useClient } from "../ClientContext"; export function saveKeyForRoom(roomId: string, password: string): void { setLocalStorageItem(getRoomSharedKeyLocalStorageKey(roomId), password); } const getRoomSharedKeyLocalStorageKey = (roomId: string): string => `room-shared-key-${roomId}`; const useInternalRoomSharedKey = (roomId: string): string | null => { const key = getRoomSharedKeyLocalStorageKey(roomId); const [roomSharedKey] = useLocalStorage(key); return roomSharedKey; }; export function getKeyForRoom(roomId: string): string | null { saveKeyFromUrlParams(getUrlParams()); const key = getRoomSharedKeyLocalStorageKey(roomId); return localStorage.getItem(key); } function saveKeyFromUrlParams(urlParams: UrlParams): void { if (!urlParams.password || !urlParams.roomId) return; // Take the key from the URL and save it. // It's important to always use the room ID specified in the URL // when saving keys rather than whatever the current room ID might be, // in case we've moved to a different room but the URL hasn't changed. saveKeyForRoom(urlParams.roomId, urlParams.password); } /** * Extracts the room password from the URL if one is present, saving it in localstorage * and returning it in a tuple with the corresponding room ID from the URL. * @returns A tuple of the roomId and password from the URL if the URL has both, * otherwise [undefined, undefined] */ const useKeyFromUrl = (): [string, string] | [undefined, undefined] => { const urlParams = useUrlParams(); useEffect(() => saveKeyFromUrlParams(urlParams), [urlParams]); return urlParams.roomId && urlParams.password ? [urlParams.roomId, urlParams.password] : [undefined, undefined]; }; export type Unencrypted = { kind: E2eeType.NONE }; export type SharedSecret = { kind: E2eeType.SHARED_KEY; secret: string }; export type PerParticipantE2EE = { kind: E2eeType.PER_PARTICIPANT }; export type EncryptionSystem = Unencrypted | SharedSecret | PerParticipantE2EE; export function useRoomEncryptionSystem(roomId: string): EncryptionSystem { const { client } = useClient(); // make sure we've extracted the key from the URL first // (and we still need to take the value it returns because // the effect won't run in time for it to save to localstorage in // time for us to read it out again). const [urlRoomId, passwordFromUrl] = useKeyFromUrl(); const storedPassword = useInternalRoomSharedKey(roomId); const room = client?.getRoom(roomId); const e2eeSystem = useMemo(() => { if (!room) return { kind: E2eeType.NONE }; if (storedPassword) return { kind: E2eeType.SHARED_KEY, secret: storedPassword, }; if (urlRoomId === roomId) return { kind: E2eeType.SHARED_KEY, secret: passwordFromUrl, }; if (room.hasEncryptionStateEvent()) { return { kind: E2eeType.PER_PARTICIPANT }; } return { kind: E2eeType.NONE }; }, [passwordFromUrl, room, roomId, storedPassword, urlRoomId]); return e2eeSystem; }