Skip to content

Commit

Permalink
refactor(encryption tab): simplify the RecoveryPanel without having…
Browse files Browse the repository at this point in the history
… to handle the missing secrets
  • Loading branch information
florianduros committed Jan 29, 2025
1 parent 4450809 commit 37c9fb8
Showing 1 changed file with 15 additions and 62 deletions.
77 changes: 15 additions & 62 deletions src/components/views/settings/encryption/RecoveryPanel.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
* Please see LICENSE files in the repository root for full details.
*/

import React, { JSX, useCallback, useEffect, useState } from "react";
import React, { JSX } from "react";
import { Button, InlineSpinner } from "@vector-im/compound-web";
import KeyIcon from "@vector-im/compound-design-tokens/assets/web/icons/key";

Expand All @@ -15,16 +15,15 @@ import { useMatrixClientContext } from "../../../../contexts/MatrixClientContext
import { SettingsHeader } from "../SettingsHeader";
import { accessSecretStorage } from "../../../../SecurityManager";
import { SettingsSubheader } from "../SettingsSubheader";
import { useAsyncMemo } from "../../../../hooks/useAsyncMemo";

/**
* The possible states of the recovery panel.
* - `loading`: We are checking the recovery key and the secrets.
* - `missing_recovery_key`: The user has no recovery key.
* - `secrets_not_cached`: The user has a recovery key but the secrets are not cached.
* This can happen if we verified another device and secret-gossiping failed, or the other device itself lacked the secrets.
* - `good`: The user has a recovery key and the secrets are cached.
*/
type State = "loading" | "missing_recovery_key" | "secrets_not_cached" | "good";
type State = "loading" | "missing_recovery_key" | "good";

interface RecoveryPanelProps {
/**
Expand All @@ -40,29 +39,18 @@ interface RecoveryPanelProps {
* This component allows the user to set up or change their recovery key.
*/
export function RecoveryPanel({ onChangeRecoveryKeyClick }: RecoveryPanelProps): JSX.Element {
const [state, setState] = useState<State>("loading");
const isMissingRecoveryKey = state === "missing_recovery_key";

const matrixClient = useMatrixClientContext();

const checkEncryption = useCallback(async () => {
const crypto = matrixClient.getCrypto()!;

// Check if the user has a recovery key
const hasRecoveryKey = Boolean(await matrixClient.secretStorage.getDefaultKeyId());
if (!hasRecoveryKey) return setState("missing_recovery_key");

// Check if the secrets are cached
const cachedSecrets = (await crypto.getCrossSigningStatus()).privateKeysCachedLocally;
const secretsOk = cachedSecrets.masterKey && cachedSecrets.selfSigningKey && cachedSecrets.userSigningKey;
if (!secretsOk) return setState("secrets_not_cached");

setState("good");
}, [matrixClient]);

useEffect(() => {
checkEncryption();
}, [checkEncryption]);
const state = useAsyncMemo<State>(
async () => {
// Check if the user has a recovery key
const hasRecoveryKey = Boolean(await matrixClient.secretStorage.getDefaultKeyId());
if (hasRecoveryKey) return "good";
else return "missing_recovery_key";
},
[matrixClient],
"loading",
);
const isMissingRecoveryKey = state === "missing_recovery_key";

let content: JSX.Element;
switch (state) {
Expand All @@ -76,18 +64,6 @@ export function RecoveryPanel({ onChangeRecoveryKeyClick }: RecoveryPanelProps):
</Button>
);
break;
case "secrets_not_cached":
content = (
<Button
size="sm"
kind="primary"
Icon={KeyIcon}
onClick={async () => await accessSecretStorage(checkEncryption)}
>
{_t("settings|encryption|recovery|enter_recovery_key")}
</Button>
);
break;
case "good":
content = (
<Button size="sm" kind="secondary" Icon={KeyIcon} onClick={() => onChangeRecoveryKeyClick(false)}>
Expand All @@ -105,37 +81,14 @@ export function RecoveryPanel({ onChangeRecoveryKeyClick }: RecoveryPanelProps):
label={_t("settings|encryption|recovery|title")}
/>
}
subHeading={<Subheader state={state} />}
subHeading={_t("settings|encryption|recovery|description")}
data-testid="recoveryPanel"
>
{content}
</SettingsSection>
);
}

interface SubheaderProps {
/**
* The state of the recovery panel.
*/
state: State;
}

/**
* The subheader for the recovery panel.
*/
function Subheader({ state }: SubheaderProps): JSX.Element {
// If the secrets are not cached, we display a warning message.
if (state !== "secrets_not_cached") return <>{_t("settings|encryption|recovery|description")}</>;

return (
<SettingsSubheader
label={_t("settings|encryption|recovery|description")}
state="error"
stateMessage={_t("settings|encryption|recovery|key_storage_warning")}
/>
);
}

interface RecoveryPanelOutOfSyncProps {
/**
* Callback for when the user has finished entering their recovery key.
Expand Down

0 comments on commit 37c9fb8

Please sign in to comment.