Skip to content

Commit 94a0663

Browse files
committed
initial work on identity reset dialog redesign
1 parent c24a1ba commit 94a0663

File tree

5 files changed

+220
-92
lines changed

5 files changed

+220
-92
lines changed

src/components/structures/auth/SetupEncryptionBody.tsx

Lines changed: 15 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ import { SetupEncryptionStore, Phase } from "../../../stores/SetupEncryptionStor
1919
import EncryptionPanel from "../../views/right_panel/EncryptionPanel";
2020
import AccessibleButton, { type ButtonEvent } from "../../views/elements/AccessibleButton";
2121
import Spinner from "../../views/elements/Spinner";
22+
import { ResetIdentityDialog } from "../../views/settings/encryption/ResetIdentityDialog";
2223

2324
function keyHasPassphrase(keyInfo: SecretStorageKeyDescription): boolean {
2425
return Boolean(keyInfo.passphrase && keyInfo.passphrase.salt && keyInfo.passphrase.iterations);
@@ -114,12 +115,18 @@ export default class SetupEncryptionBody extends React.Component<IProps, IState>
114115
ev.preventDefault();
115116
const store = SetupEncryptionStore.sharedInstance();
116117
store.reset();
117-
};
118-
119-
private onResetConfirmClick = (): void => {
120-
this.props.onFinished();
121-
const store = SetupEncryptionStore.sharedInstance();
122-
store.resetConfirm();
118+
Modal.createDialog(ResetIdentityDialog, {
119+
onResetFinished: () => {
120+
close();
121+
this.onDoneClick();
122+
},
123+
onCancelClick: () => {
124+
close();
125+
this.onResetBackClick();
126+
},
127+
variant: "forgot",
128+
title: _t("encryption|verification|reset_confirm_title"),
129+
});
123130
};
124131

125132
private onResetBackClick = (): void => {
@@ -157,7 +164,7 @@ export default class SetupEncryptionBody extends React.Component<IProps, IState>
157164
<p>{_t("encryption|verification|no_key_or_device")}</p>
158165

159166
<div className="mx_CompleteSecurity_actionRow">
160-
<AccessibleButton kind="primary" onClick={this.onResetConfirmClick}>
167+
<AccessibleButton kind="primary" onClick={this.onResetClick}>
161168
{_t("encryption|verification|reset_proceed_prompt")}
162169
</AccessibleButton>
163170
</div>
@@ -246,23 +253,7 @@ export default class SetupEncryptionBody extends React.Component<IProps, IState>
246253
</div>
247254
</div>
248255
);
249-
} else if (phase === Phase.ConfirmReset) {
250-
return (
251-
<div>
252-
<p>{_t("encryption|verification|verify_reset_warning_1")}</p>
253-
<p>{_t("encryption|verification|verify_reset_warning_2")}</p>
254-
255-
<div className="mx_CompleteSecurity_actionRow">
256-
<AccessibleButton kind="danger_outline" onClick={this.onResetConfirmClick}>
257-
{_t("encryption|verification|reset_proceed_prompt")}
258-
</AccessibleButton>
259-
<AccessibleButton kind="primary" onClick={this.onResetBackClick}>
260-
{_t("action|go_back")}
261-
</AccessibleButton>
262-
</div>
263-
</div>
264-
);
265-
} else if (phase === Phase.Busy || phase === Phase.Loading) {
256+
} else if (phase === Phase.ConfirmReset || phase === Phase.Busy || phase === Phase.Loading) {
266257
return <Spinner />;
267258
} else {
268259
logger.log(`SetupEncryptionBody: Unknown phase ${phase}`);
Lines changed: 116 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,116 @@
1+
/*
2+
* Copyright 2024-2025 New Vector Ltd.
3+
*
4+
* SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Commercial
5+
* Please see LICENSE files in the repository root for full details.
6+
*/
7+
8+
import { type MatrixClient } from "matrix-js-sdk/src/matrix";
9+
import { Button, InlineSpinner, VisualList, VisualListItem } from "@vector-im/compound-web";
10+
import CheckIcon from "@vector-im/compound-design-tokens/assets/web/icons/check";
11+
import InfoIcon from "@vector-im/compound-design-tokens/assets/web/icons/info";
12+
import ErrorIcon from "@vector-im/compound-design-tokens/assets/web/icons/error-solid";
13+
import React, { type JSX, useState, type MouseEventHandler } from "react";
14+
15+
import { _t } from "../../../../languageHandler";
16+
import { EncryptionCard } from "./EncryptionCard";
17+
import { uiAuthCallback } from "../../../../CreateCrossSigning";
18+
import { EncryptionCardButtons } from "./EncryptionCardButtons";
19+
import { EncryptionCardEmphasisedContent } from "./EncryptionCardEmphasisedContent";
20+
21+
/**
22+
* Reset the user's cryptographic identity. This component should not be used
23+
* directly, but should be used via a wrapper such as ResetIdentityPanel or
24+
* ...? .
25+
*/
26+
27+
interface ResetIdentityBodyProps {
28+
/**
29+
* Called when the identity is reset.
30+
*/
31+
onFinish: MouseEventHandler<HTMLButtonElement>;
32+
/**
33+
* Called when the cancel button is clicked.
34+
*/
35+
onCancelClick: () => void;
36+
37+
/**
38+
* The title to display for the panel.
39+
*/
40+
title: string;
41+
42+
/**
43+
* The variant of the panel to show. We show more warnings in the 'compromised' variant (no use in showing a user this
44+
* warning if they have to reset because they no longer have their key)
45+
* "compromised" is shown when the user chooses 'reset' explicitly in settings, usually because they believe their
46+
* identity has been compromised.
47+
* "forgot" is shown when the user has just forgotten their passphrase.
48+
*/
49+
variant: "compromised" | "forgot";
50+
51+
client: MatrixClient;
52+
}
53+
54+
/**
55+
* The panel for resetting the identity of the current user.
56+
*/
57+
export function ResetIdentityBody({
58+
onCancelClick,
59+
onFinish,
60+
variant,
61+
client,
62+
title,
63+
}: ResetIdentityBodyProps): JSX.Element {
64+
// After the user clicks "Continue", we disable the button so it can't be
65+
// clicked again, and warn the user not to close the window.
66+
const [inProgress, setInProgress] = useState(false);
67+
68+
return (
69+
<EncryptionCard Icon={ErrorIcon} destructive={true} title={title}>
70+
<EncryptionCardEmphasisedContent>
71+
<VisualList>
72+
<VisualListItem Icon={CheckIcon} success={true}>
73+
{_t("settings|encryption|advanced|breadcrumb_first_description")}
74+
</VisualListItem>
75+
<VisualListItem Icon={InfoIcon}>
76+
{_t("settings|encryption|advanced|breadcrumb_second_description")}
77+
</VisualListItem>
78+
<VisualListItem Icon={InfoIcon}>
79+
{_t("settings|encryption|advanced|breadcrumb_third_description")}
80+
</VisualListItem>
81+
</VisualList>
82+
{variant === "compromised" && <span>{_t("settings|encryption|advanced|breadcrumb_warning")}</span>}
83+
</EncryptionCardEmphasisedContent>
84+
<EncryptionCardButtons>
85+
<Button
86+
destructive={true}
87+
disabled={inProgress}
88+
onClick={async (evt) => {
89+
setInProgress(true);
90+
await client.getCrypto()?.resetEncryption((makeRequest) => uiAuthCallback(client, makeRequest));
91+
onFinish(evt);
92+
}}
93+
>
94+
{inProgress ? (
95+
<>
96+
<InlineSpinner /> {_t("settings|encryption|advanced|reset_in_progress")}
97+
</>
98+
) : (
99+
_t("action|continue")
100+
)}
101+
</Button>
102+
{inProgress ? (
103+
<EncryptionCardEmphasisedContent>
104+
<span className="mx_ResetIdentityPanel_warning">
105+
{_t("settings|encryption|advanced|do_not_close_warning")}
106+
</span>
107+
</EncryptionCardEmphasisedContent>
108+
) : (
109+
<Button kind="tertiary" onClick={onCancelClick}>
110+
{_t("action|cancel")}
111+
</Button>
112+
)}
113+
</EncryptionCardButtons>
114+
</EncryptionCard>
115+
);
116+
}
Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
/*
2+
* Copyright 2025 New Vector Ltd.
3+
*
4+
* SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Commercial
5+
* Please see LICENSE files in the repository root for full details.
6+
*/
7+
8+
import React, { type MouseEventHandler } from "react";
9+
10+
import { MatrixClientPeg } from "../../../../MatrixClientPeg";
11+
import { ResetIdentityBody } from "./ResetIdentityBody";
12+
13+
interface ResetIdentityDialogProps {
14+
/**
15+
* Called when the dialog closes.
16+
*/
17+
onFinished: () => void;
18+
/**
19+
* Called when the identity is reset.
20+
*/
21+
onResetFinished: MouseEventHandler<HTMLButtonElement>;
22+
/**
23+
* Called when the cancel button is clicked.
24+
*/
25+
onCancelClick: () => void;
26+
27+
/**
28+
* The title to display for the panel.
29+
*/
30+
title: string;
31+
32+
/**
33+
* The variant of the panel to show. We show more warnings in the 'compromised' variant (no use in showing a user this
34+
* warning if they have to reset because they no longer have their key)
35+
* "compromised" is shown when the user chooses 'reset' explicitly in settings, usually because they believe their
36+
* identity has been compromised.
37+
* "forgot" is shown when the user has just forgotten their passphrase.
38+
*/
39+
variant: "compromised" | "forgot";
40+
}
41+
42+
/**
43+
* The dialog for resetting the identity of the current user.
44+
*/
45+
export function ResetIdentityDialog({
46+
onFinished,
47+
onCancelClick,
48+
onResetFinished,
49+
title,
50+
variant,
51+
}: ResetIdentityDialogProps): JSX.Element {
52+
const client = MatrixClientPeg.safeGet();
53+
54+
// wrappers for ResetIdentityBody's callbacks so that onFinish gets called
55+
// whenever the reset is done, whether by completing successfully, or by
56+
// being cancelled
57+
const onResetWrapper: MouseEventHandler<HTMLButtonElement> = (...args) => {
58+
onFinished();
59+
onResetFinished(...args);
60+
};
61+
const onCancelWrapper: () => void = () => {
62+
onFinished();
63+
onCancelClick();
64+
};
65+
return (
66+
<ResetIdentityBody
67+
onFinish={onResetWrapper}
68+
onCancelClick={onCancelWrapper}
69+
variant={variant}
70+
title={title}
71+
client={client}
72+
/>
73+
);
74+
}

src/components/views/settings/encryption/ResetIdentityPanel.tsx

Lines changed: 14 additions & 66 deletions
Original file line numberDiff line numberDiff line change
@@ -5,18 +5,16 @@
55
* Please see LICENSE files in the repository root for full details.
66
*/
77

8-
import { Breadcrumb, Button, InlineSpinner, VisualList, VisualListItem } from "@vector-im/compound-web";
9-
import CheckIcon from "@vector-im/compound-design-tokens/assets/web/icons/check";
10-
import InfoIcon from "@vector-im/compound-design-tokens/assets/web/icons/info";
11-
import ErrorIcon from "@vector-im/compound-design-tokens/assets/web/icons/error-solid";
12-
import React, { type JSX, useState, type MouseEventHandler } from "react";
8+
import { Breadcrumb } from "@vector-im/compound-web";
9+
import React, { type JSX, type MouseEventHandler } from "react";
1310

14-
import { _t } from "../../../../languageHandler";
15-
import { EncryptionCard } from "./EncryptionCard";
1611
import { useMatrixClientContext } from "../../../../contexts/MatrixClientContext";
17-
import { uiAuthCallback } from "../../../../CreateCrossSigning";
18-
import { EncryptionCardButtons } from "./EncryptionCardButtons";
19-
import { EncryptionCardEmphasisedContent } from "./EncryptionCardEmphasisedContent";
12+
import { _t } from "../../../../languageHandler";
13+
import { ResetIdentityBody } from "./ResetIdentityBody";
14+
15+
/**
16+
* Wraps ResetIdentityBody to work in the settings page by adding a breadcrumb.
17+
*/
2018

2119
interface ResetIdentityPanelProps {
2220
/**
@@ -45,11 +43,6 @@ interface ResetIdentityPanelProps {
4543
*/
4644
export function ResetIdentityPanel({ onCancelClick, onFinish, variant }: ResetIdentityPanelProps): JSX.Element {
4745
const matrixClient = useMatrixClientContext();
48-
49-
// After the user clicks "Continue", we disable the button so it can't be
50-
// clicked again, and warn the user not to close the window.
51-
const [inProgress, setInProgress] = useState(false);
52-
5346
return (
5447
<>
5548
<Breadcrumb
@@ -58,62 +51,17 @@ export function ResetIdentityPanel({ onCancelClick, onFinish, variant }: ResetId
5851
pages={[_t("settings|encryption|title"), _t("settings|encryption|advanced|breadcrumb_page")]}
5952
onPageClick={onCancelClick}
6053
/>
61-
<EncryptionCard
62-
Icon={ErrorIcon}
63-
destructive={true}
54+
<ResetIdentityBody
55+
onFinish={onFinish}
56+
onCancelClick={onCancelClick}
57+
variant={variant}
6458
title={
6559
variant === "forgot"
6660
? _t("settings|encryption|advanced|breadcrumb_title_forgot")
6761
: _t("settings|encryption|advanced|breadcrumb_title")
6862
}
69-
>
70-
<EncryptionCardEmphasisedContent>
71-
<VisualList>
72-
<VisualListItem Icon={CheckIcon} success={true}>
73-
{_t("settings|encryption|advanced|breadcrumb_first_description")}
74-
</VisualListItem>
75-
<VisualListItem Icon={InfoIcon}>
76-
{_t("settings|encryption|advanced|breadcrumb_second_description")}
77-
</VisualListItem>
78-
<VisualListItem Icon={InfoIcon}>
79-
{_t("settings|encryption|advanced|breadcrumb_third_description")}
80-
</VisualListItem>
81-
</VisualList>
82-
{variant === "compromised" && <span>{_t("settings|encryption|advanced|breadcrumb_warning")}</span>}
83-
</EncryptionCardEmphasisedContent>
84-
<EncryptionCardButtons>
85-
<Button
86-
destructive={true}
87-
disabled={inProgress}
88-
onClick={async (evt) => {
89-
setInProgress(true);
90-
await matrixClient
91-
.getCrypto()
92-
?.resetEncryption((makeRequest) => uiAuthCallback(matrixClient, makeRequest));
93-
onFinish(evt);
94-
}}
95-
>
96-
{inProgress ? (
97-
<>
98-
<InlineSpinner /> {_t("settings|encryption|advanced|reset_in_progress")}
99-
</>
100-
) : (
101-
_t("action|continue")
102-
)}
103-
</Button>
104-
{inProgress ? (
105-
<EncryptionCardEmphasisedContent>
106-
<span className="mx_ResetIdentityPanel_warning">
107-
{_t("settings|encryption|advanced|do_not_close_warning")}
108-
</span>
109-
</EncryptionCardEmphasisedContent>
110-
) : (
111-
<Button kind="tertiary" onClick={onCancelClick}>
112-
{_t("action|cancel")}
113-
</Button>
114-
)}
115-
</EncryptionCardButtons>
116-
</EncryptionCard>
63+
client={matrixClient}
64+
/>
11765
</>
11866
);
11967
}

src/i18n/strings/en_EN.json

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1035,6 +1035,7 @@
10351035
"request_toast_accept_user": "Verify User",
10361036
"request_toast_decline_counter": "Ignore (%(counter)s)",
10371037
"request_toast_detail": "%(deviceId)s from %(ip)s",
1038+
"reset_confirm_title": "Are you sure you want to reset your identity?",
10381039
"reset_proceed_prompt": "Proceed with reset",
10391040
"sas_caption_self": "Verify this device by confirming the following number appears on its screen.",
10401041
"sas_caption_user": "Verify this user by confirming the following number appears on their screen.",
@@ -1068,8 +1069,6 @@
10681069
"verify_emoji_prompt": "Verify by comparing unique emoji.",
10691070
"verify_emoji_prompt_qr": "If you can't scan the code above, verify by comparing unique emoji.",
10701071
"verify_later": "I'll verify later",
1071-
"verify_reset_warning_1": "Resetting your verification keys cannot be undone. After resetting, you won't have access to old encrypted messages, and any friends who have previously verified you will see security warnings until you re-verify with them.",
1072-
"verify_reset_warning_2": "Please only proceed if you're sure you've lost all of your other devices and your Recovery Key.",
10731072
"verify_using_device": "Verify with another device",
10741073
"verify_using_key": "Verify with Recovery Key",
10751074
"verify_using_key_or_phrase": "Verify with Recovery Key or Phrase",

0 commit comments

Comments
 (0)