Delegate to new ResetIdentityDialog from SetupEncryptionBody (#29701)

This commit is contained in:
Andy Balaam
2025-04-30 10:08:38 +00:00
committed by GitHub
parent 4f4f391959
commit 23597e959b
16 changed files with 453 additions and 218 deletions
@@ -78,9 +78,6 @@ export default class CompleteSecurity extends React.Component<IProps, IState> {
} else if (phase === Phase.Busy) {
icon = <span className="mx_CompleteSecurity_headerIcon mx_E2EIcon_warning" />;
title = _t("encryption|verification|after_new_login|verify_this_device");
} else if (phase === Phase.ConfirmReset) {
icon = <span className="mx_CompleteSecurity_headerIcon mx_E2EIcon_warning" />;
title = _t("encryption|verification|after_new_login|reset_confirmation");
} else if (phase === Phase.Finished) {
// SetupEncryptionBody will take care of calling onFinished, we don't need to do anything
} else {
@@ -90,7 +87,7 @@ export default class CompleteSecurity extends React.Component<IProps, IState> {
const forceVerification = SdkConfig.get("force_verification");
let skipButton;
if (!forceVerification && (phase === Phase.Intro || phase === Phase.ConfirmReset)) {
if (!forceVerification && phase === Phase.Intro) {
skipButton = (
<AccessibleButton
onClick={this.onSkipClick}
@@ -1,5 +1,5 @@
/*
Copyright 2024 New Vector Ltd.
Copyright 2024, 2025 New Vector Ltd.
Copyright 2020, 2021 The Matrix.org Foundation C.I.C.
SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Commercial
@@ -19,6 +19,7 @@ import { SetupEncryptionStore, Phase } from "../../../stores/SetupEncryptionStor
import EncryptionPanel from "../../views/right_panel/EncryptionPanel";
import AccessibleButton, { type ButtonEvent } from "../../views/elements/AccessibleButton";
import Spinner from "../../views/elements/Spinner";
import { ResetIdentityDialog } from "../../views/dialogs/ResetIdentityDialog";
function keyHasPassphrase(keyInfo: SecretStorageKeyDescription): boolean {
return Boolean(keyInfo.passphrase && keyInfo.passphrase.salt && keyInfo.passphrase.iterations);
@@ -112,19 +113,15 @@ export default class SetupEncryptionBody extends React.Component<IProps, IState>
private onResetClick = (ev: ButtonEvent): void => {
ev.preventDefault();
const store = SetupEncryptionStore.sharedInstance();
store.reset();
};
private onResetConfirmClick = (): void => {
this.props.onFinished();
const store = SetupEncryptionStore.sharedInstance();
store.resetConfirm();
};
private onResetBackClick = (): void => {
const store = SetupEncryptionStore.sharedInstance();
store.returnAfterReset();
Modal.createDialog(ResetIdentityDialog, {
onReset: () => {
// The user completed the reset process - close this dialog
this.props.onFinished();
const store = SetupEncryptionStore.sharedInstance();
store.done();
},
variant: "confirm",
});
};
private onDoneClick = (): void => {
@@ -157,7 +154,7 @@ export default class SetupEncryptionBody extends React.Component<IProps, IState>
<p>{_t("encryption|verification|no_key_or_device")}</p>
<div className="mx_CompleteSecurity_actionRow">
<AccessibleButton kind="primary" onClick={this.onResetConfirmClick}>
<AccessibleButton kind="primary" onClick={this.onResetClick}>
{_t("encryption|verification|reset_proceed_prompt")}
</AccessibleButton>
</div>
@@ -246,22 +243,6 @@ export default class SetupEncryptionBody extends React.Component<IProps, IState>
</div>
</div>
);
} else if (phase === Phase.ConfirmReset) {
return (
<div>
<p>{_t("encryption|verification|verify_reset_warning_1")}</p>
<p>{_t("encryption|verification|verify_reset_warning_2")}</p>
<div className="mx_CompleteSecurity_actionRow">
<AccessibleButton kind="danger_outline" onClick={this.onResetConfirmClick}>
{_t("encryption|verification|reset_proceed_prompt")}
</AccessibleButton>
<AccessibleButton kind="primary" onClick={this.onResetBackClick}>
{_t("action|go_back")}
</AccessibleButton>
</div>
</div>
);
} else if (phase === Phase.Busy || phase === Phase.Loading) {
return <Spinner />;
} else {
@@ -0,0 +1,49 @@
/*
* Copyright 2025 New Vector Ltd.
*
* SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Commercial
* Please see LICENSE files in the repository root for full details.
*/
import React, { type JSX } from "react";
import { MatrixClientPeg } from "../../../MatrixClientPeg";
import MatrixClientContext from "../../../contexts/MatrixClientContext";
import { ResetIdentityBody, type ResetIdentityBodyVariant } from "../settings/encryption/ResetIdentityBody";
interface ResetIdentityDialogProps {
/**
* Called when the dialog is complete.
*
* `ResetIdentityDialog` expects this to be provided by `Modal.createDialog`, and that it will close the dialog.
*/
onFinished: () => void;
/**
* Called when the identity is reset (before onFinished is called).
*/
onReset: () => void;
/**
* Which variant of this dialog to show.
*/
variant: ResetIdentityBodyVariant;
}
/**
* The dialog for resetting the identity of the current user.
*/
export function ResetIdentityDialog({ onFinished, onReset, variant }: ResetIdentityDialogProps): JSX.Element {
const matrixClient = MatrixClientPeg.safeGet();
const onResetWrapper: () => void = () => {
onReset();
// Close the dialog
onFinished();
};
return (
<MatrixClientContext.Provider value={matrixClient}>
<ResetIdentityBody onReset={onResetWrapper} onCancelClick={onFinished} variant={variant} />
</MatrixClientContext.Provider>
);
}
@@ -9,7 +9,7 @@ import { Button, InlineSpinner, VisualList, VisualListItem } from "@vector-im/co
import CheckIcon from "@vector-im/compound-design-tokens/assets/web/icons/check";
import InfoIcon from "@vector-im/compound-design-tokens/assets/web/icons/info";
import ErrorIcon from "@vector-im/compound-design-tokens/assets/web/icons/error-solid";
import React, { type JSX, useState, type MouseEventHandler } from "react";
import React, { type JSX, useState } from "react";
import { _t } from "../../../../languageHandler";
import { EncryptionCard } from "./EncryptionCard";
@@ -22,7 +22,8 @@ interface ResetIdentityBodyProps {
/**
* Called when the identity is reset.
*/
onFinish: MouseEventHandler<HTMLButtonElement>;
onReset: () => void;
/**
* Called when the cancel button is clicked.
*/
@@ -36,22 +37,26 @@ interface ResetIdentityBodyProps {
}
/**
* "compromised" is shown when the user chooses 'reset' explicitly in settings, usually because they believe their
* identity has been compromised.
* The variant of the panel to show. This affects the message displayed to the user.
*
* "compromised" is shown when the user chose 'Reset cryptographic identity' explicitly in settings, usually because
* they believe their identity has been compromised.
*
* "sync_failed" is shown when the user tried to recover their identity but the process failed, probably because
* the required information is missing from recovery.
*
* "forgot" is shown when the user has just forgotten their passphrase.
* "forgot" is shown when the user chose 'Forgot recovery key?' during `SetupEncryptionToast`.
*
* "confirm" is shown when the user chose 'Reset all' during `SetupEncryptionBody`.
*/
export type ResetIdentityBodyVariant = "compromised" | "forgot" | "sync_failed";
export type ResetIdentityBodyVariant = "compromised" | "forgot" | "sync_failed" | "confirm";
/**
* User interface component allowing the user to reset their cryptographic identity.
*
* Used by {@link ResetIdentityPanel}.
*/
export function ResetIdentityBody({ onCancelClick, onFinish, variant }: ResetIdentityBodyProps): JSX.Element {
export function ResetIdentityBody({ onCancelClick, onReset, variant }: ResetIdentityBodyProps): JSX.Element {
const matrixClient = useMatrixClientContext();
// After the user clicks "Continue", we disable the button so it can't be
@@ -78,12 +83,12 @@ export function ResetIdentityBody({ onCancelClick, onFinish, variant }: ResetIde
<Button
destructive={true}
disabled={inProgress}
onClick={async (evt) => {
onClick={async () => {
setInProgress(true);
await matrixClient
.getCrypto()
?.resetEncryption((makeRequest) => uiAuthCallback(matrixClient, makeRequest));
onFinish(evt);
onReset();
}}
>
{inProgress ? (
@@ -113,11 +118,10 @@ export function ResetIdentityBody({ onCancelClick, onFinish, variant }: ResetIde
function titleForVariant(variant: ResetIdentityBodyVariant): string {
switch (variant) {
case "compromised":
case "confirm":
return _t("settings|encryption|advanced|breadcrumb_title");
case "sync_failed":
return _t("settings|encryption|advanced|breadcrumb_title_sync_failed");
default:
case "forgot":
return _t("settings|encryption|advanced|breadcrumb_title_forgot");
}
@@ -6,7 +6,7 @@
*/
import { Breadcrumb } from "@vector-im/compound-web";
import React, { type JSX, type MouseEventHandler } from "react";
import React, { type JSX } from "react";
import { _t } from "../../../../languageHandler";
import { ResetIdentityBody, type ResetIdentityBodyVariant } from "./ResetIdentityBody";
@@ -15,7 +15,8 @@ interface ResetIdentityPanelProps {
/**
* Called when the identity is reset.
*/
onFinish: MouseEventHandler<HTMLButtonElement>;
onReset: () => void;
/**
* Called when the cancel button is clicked or when we go back in the breadcrumbs.
*/
@@ -32,7 +33,7 @@ interface ResetIdentityPanelProps {
*
* A thin wrapper around {@link ResetIdentityBody}, just adding breadcrumbs.
*/
export function ResetIdentityPanel({ onCancelClick, onFinish, variant }: ResetIdentityPanelProps): JSX.Element {
export function ResetIdentityPanel({ onCancelClick, onReset, variant }: ResetIdentityPanelProps): JSX.Element {
return (
<>
<Breadcrumb
@@ -41,7 +42,7 @@ export function ResetIdentityPanel({ onCancelClick, onFinish, variant }: ResetId
pages={[_t("settings|encryption|title"), _t("settings|encryption|advanced|breadcrumb_page")]}
onPageClick={onCancelClick}
/>
<ResetIdentityBody onFinish={onFinish} onCancelClick={onCancelClick} variant={variant} />
<ResetIdentityBody onReset={onReset} onCancelClick={onCancelClick} variant={variant} />
</>
);
}
@@ -128,7 +128,7 @@ export function EncryptionUserSettingsTab({ initialState = "loading" }: Props):
<ResetIdentityPanel
variant={findResetVariant(state)}
onCancelClick={checkEncryptionState}
onFinish={checkEncryptionState}
onReset={checkEncryptionState}
/>
);
break;