Skip to content

Commit 386b782

Browse files
Remove "Upgrade your encryption" flow in CreateSecretStorageDialog (#28290)
* Remove "Upgrade your encryption" flow * Rename and remove tests * Remove `BackupTrustInfo` * Get keybackup when bootstraping the secret storage. * Update src/async-components/views/dialogs/security/CreateSecretStorageDialog.tsx Co-authored-by: Richard van der Hoff <[email protected]> --------- Co-authored-by: Richard van der Hoff <[email protected]>
1 parent c23c9df commit 386b782

File tree

5 files changed

+115
-663
lines changed

5 files changed

+115
-663
lines changed

src/async-components/views/dialogs/security/CreateSecretStorageDialog.tsx

Lines changed: 32 additions & 190 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ import React, { createRef } from "react";
1111
import FileSaver from "file-saver";
1212
import { logger } from "matrix-js-sdk/src/logger";
1313
import { AuthDict, CrossSigningKeys, MatrixError, UIAFlow, UIAResponse } from "matrix-js-sdk/src/matrix";
14-
import { CryptoEvent, BackupTrustInfo, GeneratedSecretStorageKey, KeyBackupInfo } from "matrix-js-sdk/src/crypto-api";
14+
import { GeneratedSecretStorageKey } from "matrix-js-sdk/src/crypto-api";
1515
import classNames from "classnames";
1616
import CheckmarkIcon from "@vector-im/compound-design-tokens/assets/web/icons/check";
1717

@@ -25,7 +25,6 @@ import StyledRadioButton from "../../../../components/views/elements/StyledRadio
2525
import AccessibleButton from "../../../../components/views/elements/AccessibleButton";
2626
import DialogButtons from "../../../../components/views/elements/DialogButtons";
2727
import InlineSpinner from "../../../../components/views/elements/InlineSpinner";
28-
import RestoreKeyBackupDialog from "../../../../components/views/dialogs/security/RestoreKeyBackupDialog";
2928
import {
3029
getSecureBackupSetupMethods,
3130
isSecureBackupRequired,
@@ -45,7 +44,6 @@ enum Phase {
4544
Loading = "loading",
4645
LoadError = "load_error",
4746
ChooseKeyPassphrase = "choose_key_passphrase",
48-
Migrate = "migrate",
4947
Passphrase = "passphrase",
5048
PassphraseConfirm = "passphrase_confirm",
5149
ShowKey = "show_key",
@@ -72,24 +70,6 @@ interface IState {
7270
downloaded: boolean;
7371
setPassphrase: boolean;
7472

75-
/** Information on the current key backup version, as returned by the server.
76-
*
77-
* `null` could mean any of:
78-
* * we haven't yet requested the data from the server.
79-
* * we were unable to reach the server.
80-
* * the server returned key backup version data we didn't understand or was malformed.
81-
* * there is actually no backup on the server.
82-
*/
83-
backupInfo: KeyBackupInfo | null;
84-
85-
/**
86-
* Information on whether the backup in `backupInfo` is correctly signed, and whether we have the right key to
87-
* decrypt it.
88-
*
89-
* `undefined` if `backupInfo` is null, or if crypto is not enabled in the client.
90-
*/
91-
backupTrustInfo: BackupTrustInfo | undefined;
92-
9373
// does the server offer a UI auth flow with just m.login.password
9474
// for /keys/device_signing/upload?
9575
canUploadKeysWithPasswordOnly: boolean | null;
@@ -141,16 +121,17 @@ export default class CreateSecretStorageDialog extends React.PureComponent<IProp
141121
this.queryKeyUploadAuth();
142122
}
143123

124+
const keyFromCustomisations = ModuleRunner.instance.extensions.cryptoSetup.createSecretStorageKey();
125+
const phase = keyFromCustomisations ? Phase.Loading : Phase.ChooseKeyPassphrase;
126+
144127
this.state = {
145-
phase: Phase.Loading,
128+
phase,
146129
passPhrase: "",
147130
passPhraseValid: false,
148131
passPhraseConfirm: "",
149132
copied: false,
150133
downloaded: false,
151134
setPassphrase: false,
152-
backupInfo: null,
153-
backupTrustInfo: undefined,
154135
// does the server offer a UI auth flow with just m.login.password
155136
// for /keys/device_signing/upload?
156137
accountPasswordCorrect: null,
@@ -160,60 +141,15 @@ export default class CreateSecretStorageDialog extends React.PureComponent<IProp
160141
accountPassword,
161142
};
162143

163-
cli.on(CryptoEvent.KeyBackupStatus, this.onKeyBackupStatusChange);
164-
165-
this.getInitialPhase();
166-
}
167-
168-
public componentWillUnmount(): void {
169-
MatrixClientPeg.get()?.removeListener(CryptoEvent.KeyBackupStatus, this.onKeyBackupStatusChange);
170-
}
171-
172-
private getInitialPhase(): void {
173-
const keyFromCustomisations = ModuleRunner.instance.extensions.cryptoSetup.createSecretStorageKey();
174-
if (keyFromCustomisations) {
175-
logger.log("CryptoSetupExtension: Created key via extension, jumping to bootstrap step");
176-
this.recoveryKey = {
177-
privateKey: keyFromCustomisations,
178-
};
179-
this.bootstrapSecretStorage();
180-
return;
181-
}
182-
183-
this.fetchBackupInfo();
144+
if (keyFromCustomisations) this.initExtension(keyFromCustomisations);
184145
}
185146

186-
/**
187-
* Attempt to get information on the current backup from the server, and update the state.
188-
*
189-
* Updates {@link IState.backupInfo} and {@link IState.backupTrustInfo}, and picks an appropriate phase for
190-
* {@link IState.phase}.
191-
*
192-
* @returns If the backup data was retrieved successfully, the trust info for the backup. Otherwise, undefined.
193-
*/
194-
private async fetchBackupInfo(): Promise<BackupTrustInfo | undefined> {
195-
try {
196-
const cli = MatrixClientPeg.safeGet();
197-
const backupInfo = await cli.getKeyBackupVersion();
198-
const backupTrustInfo =
199-
// we may not have started crypto yet, in which case we definitely don't trust the backup
200-
backupInfo ? await cli.getCrypto()?.isKeyBackupTrusted(backupInfo) : undefined;
201-
202-
const { forceReset } = this.props;
203-
const phase = backupInfo && !forceReset ? Phase.Migrate : Phase.ChooseKeyPassphrase;
204-
205-
this.setState({
206-
phase,
207-
backupInfo,
208-
backupTrustInfo,
209-
});
210-
211-
return backupTrustInfo;
212-
} catch (e) {
213-
console.error("Error fetching backup data from server", e);
214-
this.setState({ phase: Phase.LoadError });
215-
return undefined;
216-
}
147+
private initExtension(keyFromCustomisations: Uint8Array): void {
148+
logger.log("CryptoSetupExtension: Created key via extension, jumping to bootstrap step");
149+
this.recoveryKey = {
150+
privateKey: keyFromCustomisations,
151+
};
152+
this.bootstrapSecretStorage();
217153
}
218154

219155
private async queryKeyUploadAuth(): Promise<void> {
@@ -237,10 +173,6 @@ export default class CreateSecretStorageDialog extends React.PureComponent<IProp
237173
}
238174
}
239175

240-
private onKeyBackupStatusChange = (): void => {
241-
if (this.state.phase === Phase.Migrate) this.fetchBackupInfo();
242-
};
243-
244176
private onKeyPassphraseChange = (e: React.ChangeEvent<HTMLInputElement>): void => {
245177
this.setState({
246178
passPhraseKeySelected: e.target.value,
@@ -265,15 +197,6 @@ export default class CreateSecretStorageDialog extends React.PureComponent<IProp
265197
}
266198
};
267199

268-
private onMigrateFormSubmit = (e: React.FormEvent): void => {
269-
e.preventDefault();
270-
if (this.state.backupTrustInfo?.trusted) {
271-
this.bootstrapSecretStorage();
272-
} else {
273-
this.restoreBackup();
274-
}
275-
};
276-
277200
private onCopyClick = (): void => {
278201
const successful = copyNode(this.recoveryKeyNode.current);
279202
if (successful) {
@@ -340,16 +263,28 @@ export default class CreateSecretStorageDialog extends React.PureComponent<IProp
340263
};
341264

342265
private bootstrapSecretStorage = async (): Promise<void> => {
266+
const cli = MatrixClientPeg.safeGet();
267+
const crypto = cli.getCrypto()!;
268+
const { forceReset } = this.props;
269+
270+
let backupInfo;
271+
// First, unless we know we want to do a reset, we see if there is an existing key backup
272+
if (!forceReset) {
273+
try {
274+
this.setState({ phase: Phase.Loading });
275+
backupInfo = await cli.getKeyBackupVersion();
276+
} catch (e) {
277+
logger.error("Error fetching backup data from server", e);
278+
this.setState({ phase: Phase.LoadError });
279+
return;
280+
}
281+
}
282+
343283
this.setState({
344284
phase: Phase.Storing,
345285
error: undefined,
346286
});
347287

348-
const cli = MatrixClientPeg.safeGet();
349-
const crypto = cli.getCrypto()!;
350-
351-
const { forceReset } = this.props;
352-
353288
try {
354289
if (forceReset) {
355290
logger.log("Forcing secret storage reset");
@@ -371,8 +306,7 @@ export default class CreateSecretStorageDialog extends React.PureComponent<IProp
371306
});
372307
await crypto.bootstrapSecretStorage({
373308
createSecretStorageKey: async () => this.recoveryKey!,
374-
keyBackupInfo: this.state.backupInfo!,
375-
setupNewKeyBackup: !this.state.backupInfo,
309+
setupNewKeyBackup: !backupInfo,
376310
});
377311
}
378312
await initialiseDehydration(true);
@@ -381,20 +315,7 @@ export default class CreateSecretStorageDialog extends React.PureComponent<IProp
381315
phase: Phase.Stored,
382316
});
383317
} catch (e) {
384-
if (
385-
this.state.canUploadKeysWithPasswordOnly &&
386-
e instanceof MatrixError &&
387-
e.httpStatus === 401 &&
388-
e.data.flows
389-
) {
390-
this.setState({
391-
accountPassword: "",
392-
accountPasswordCorrect: false,
393-
phase: Phase.Migrate,
394-
});
395-
} else {
396-
this.setState({ error: true });
397-
}
318+
this.setState({ error: true });
398319
logger.error("Error bootstrapping secret storage", e);
399320
}
400321
};
@@ -403,27 +324,8 @@ export default class CreateSecretStorageDialog extends React.PureComponent<IProp
403324
this.props.onFinished(false);
404325
};
405326

406-
private restoreBackup = async (): Promise<void> => {
407-
const { finished } = Modal.createDialog(
408-
RestoreKeyBackupDialog,
409-
{
410-
showSummary: false,
411-
},
412-
undefined,
413-
/* priority = */ false,
414-
/* static = */ false,
415-
);
416-
417-
await finished;
418-
const backupTrustInfo = await this.fetchBackupInfo();
419-
if (backupTrustInfo?.trusted && this.state.canUploadKeysWithPasswordOnly && this.state.accountPassword) {
420-
this.bootstrapSecretStorage();
421-
}
422-
};
423-
424327
private onLoadRetryClick = (): void => {
425-
this.setState({ phase: Phase.Loading });
426-
this.fetchBackupInfo();
328+
this.bootstrapSecretStorage();
427329
};
428330

429331
private onShowKeyContinueClick = (): void => {
@@ -495,12 +397,6 @@ export default class CreateSecretStorageDialog extends React.PureComponent<IProp
495397
});
496398
};
497399

498-
private onAccountPasswordChange = (e: React.ChangeEvent<HTMLInputElement>): void => {
499-
this.setState({
500-
accountPassword: e.target.value,
501-
});
502-
};
503-
504400
private renderOptionKey(): JSX.Element {
505401
return (
506402
<StyledRadioButton
@@ -565,55 +461,6 @@ export default class CreateSecretStorageDialog extends React.PureComponent<IProp
565461
);
566462
}
567463

568-
private renderPhaseMigrate(): JSX.Element {
569-
let authPrompt;
570-
let nextCaption = _t("action|next");
571-
if (this.state.canUploadKeysWithPasswordOnly) {
572-
authPrompt = (
573-
<div>
574-
<div>{_t("settings|key_backup|setup_secure_backup|requires_password_confirmation")}</div>
575-
<div>
576-
<Field
577-
id="mx_CreateSecretStorageDialog_password"
578-
type="password"
579-
label={_t("common|password")}
580-
value={this.state.accountPassword}
581-
onChange={this.onAccountPasswordChange}
582-
forceValidity={this.state.accountPasswordCorrect === false ? false : undefined}
583-
autoFocus={true}
584-
/>
585-
</div>
586-
</div>
587-
);
588-
} else if (!this.state.backupTrustInfo?.trusted) {
589-
authPrompt = (
590-
<div>
591-
<div>{_t("settings|key_backup|setup_secure_backup|requires_key_restore")}</div>
592-
</div>
593-
);
594-
nextCaption = _t("action|restore");
595-
} else {
596-
authPrompt = <p>{_t("settings|key_backup|setup_secure_backup|requires_server_authentication")}</p>;
597-
}
598-
599-
return (
600-
<form onSubmit={this.onMigrateFormSubmit}>
601-
<p>{_t("settings|key_backup|setup_secure_backup|session_upgrade_description")}</p>
602-
<div>{authPrompt}</div>
603-
<DialogButtons
604-
primaryButton={nextCaption}
605-
onPrimaryButtonClick={this.onMigrateFormSubmit}
606-
hasCancel={false}
607-
primaryDisabled={!!this.state.canUploadKeysWithPasswordOnly && !this.state.accountPassword}
608-
>
609-
<button type="button" className="danger" onClick={this.onCancelClick}>
610-
{_t("action|skip")}
611-
</button>
612-
</DialogButtons>
613-
</form>
614-
);
615-
}
616-
617464
private renderPhasePassPhrase(): JSX.Element {
618465
return (
619466
<form onSubmit={this.onPassPhraseNextClick}>
@@ -829,8 +676,6 @@ export default class CreateSecretStorageDialog extends React.PureComponent<IProp
829676
switch (phase) {
830677
case Phase.ChooseKeyPassphrase:
831678
return _t("encryption|set_up_toast_title");
832-
case Phase.Migrate:
833-
return _t("settings|key_backup|setup_secure_backup|title_upgrade_encryption");
834679
case Phase.Passphrase:
835680
return _t("settings|key_backup|setup_secure_backup|title_set_phrase");
836681
case Phase.PassphraseConfirm:
@@ -889,9 +734,6 @@ export default class CreateSecretStorageDialog extends React.PureComponent<IProp
889734
case Phase.ChooseKeyPassphrase:
890735
content = this.renderPhaseChooseKeyPassphrase();
891736
break;
892-
case Phase.Migrate:
893-
content = this.renderPhaseMigrate();
894-
break;
895737
case Phase.Passphrase:
896738
content = this.renderPhasePassPhrase();
897739
break;

src/i18n/strings/en_EN.json

Lines changed: 0 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -103,7 +103,6 @@
103103
"report_content": "Report Content",
104104
"resend": "Resend",
105105
"reset": "Reset",
106-
"restore": "Restore",
107106
"resume": "Resume",
108107
"retry": "Retry",
109108
"review": "Review",
@@ -2587,18 +2586,13 @@
25872586
"pass_phrase_match_failed": "That doesn't match.",
25882587
"pass_phrase_match_success": "That matches!",
25892588
"phrase_strong_enough": "Great! This Security Phrase looks strong enough.",
2590-
"requires_key_restore": "Restore your key backup to upgrade your encryption",
2591-
"requires_password_confirmation": "Enter your account password to confirm the upgrade:",
2592-
"requires_server_authentication": "You'll need to authenticate with the server to confirm the upgrade.",
25932589
"secret_storage_query_failure": "Unable to query secret storage status",
25942590
"security_key_safety_reminder": "Store your Security Key somewhere safe, like a password manager or a safe, as it's used to safeguard your encrypted data.",
2595-
"session_upgrade_description": "Upgrade this session to allow it to verify other sessions, granting them access to encrypted messages and marking them as trusted for other users.",
25962591
"set_phrase_again": "Go back to set it again.",
25972592
"settings_reminder": "You can also set up Secure Backup & manage your keys in Settings.",
25982593
"title_confirm_phrase": "Confirm Security Phrase",
25992594
"title_save_key": "Save your Security Key",
26002595
"title_set_phrase": "Set a Security Phrase",
2601-
"title_upgrade_encryption": "Upgrade your encryption",
26022596
"unable_to_setup": "Unable to set up secret storage",
26032597
"use_different_passphrase": "Use a different passphrase?",
26042598
"use_phrase_only_you_know": "Use a secret phrase only you know, and optionally save a Security Key to use for backup."

test/test-utils/test-utils.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -127,6 +127,10 @@ export function createTestClient(): MatrixClient {
127127
prepareToEncrypt: jest.fn(),
128128
bootstrapCrossSigning: jest.fn(),
129129
getActiveSessionBackupVersion: jest.fn().mockResolvedValue(null),
130+
isKeyBackupTrusted: jest.fn().mockResolvedValue({}),
131+
createRecoveryKeyFromPassphrase: jest.fn().mockResolvedValue({}),
132+
bootstrapSecretStorage: jest.fn(),
133+
isDehydrationSupported: jest.fn().mockResolvedValue(false),
130134
}),
131135

132136
getPushActionsForEvent: jest.fn(),
@@ -270,6 +274,7 @@ export function createTestClient(): MatrixClient {
270274
getOrCreateFilter: jest.fn(),
271275
sendStickerMessage: jest.fn(),
272276
getLocalAliases: jest.fn().mockReturnValue([]),
277+
uploadDeviceSigningKeys: jest.fn(),
273278
} as unknown as MatrixClient;
274279

275280
client.reEmitter = new ReEmitter(client);

0 commit comments

Comments
 (0)