CreateSecretStorageDialog: stop using deprecated APIs (#11635)

* Add some tests for `CreateSecretStorageDialog`

* CreateSecretStorageDialog: stop using deprecated APIs
This commit is contained in:
Richard van der Hoff 2023-09-22 13:03:05 +02:00 committed by GitHub
parent 11f258e62e
commit 6fd46f3bc8
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
5 changed files with 830 additions and 28 deletions

View file

@ -1,6 +1,6 @@
/*
Copyright 2018, 2019 New Vector Ltd
Copyright 2019, 2020 The Matrix.org Foundation C.I.C.
Copyright 2019, 2020, 2023 The Matrix.org Foundation C.I.C.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
@ -18,12 +18,11 @@ limitations under the License.
import React, { createRef } from "react";
import FileSaver from "file-saver";
import { logger } from "matrix-js-sdk/src/logger";
import { IKeyBackupInfo } from "matrix-js-sdk/src/crypto/keybackup";
import { TrustInfo } from "matrix-js-sdk/src/crypto/backup";
import { CrossSigningKeys, IAuthDict, MatrixError, UIAFlow, UIAResponse } from "matrix-js-sdk/src/matrix";
import { AuthDict, CrossSigningKeys, MatrixError, UIAFlow, UIAResponse } from "matrix-js-sdk/src/matrix";
import { IRecoveryKey } from "matrix-js-sdk/src/crypto/api";
import { CryptoEvent } from "matrix-js-sdk/src/crypto";
import classNames from "classnames";
import { BackupTrustInfo, KeyBackupInfo } from "matrix-js-sdk/src/crypto-api";
import { MatrixClientPeg } from "../../../../MatrixClientPeg";
import { _t, _td } from "../../../../languageHandler";
@ -81,8 +80,25 @@ interface IState {
copied: boolean;
downloaded: boolean;
setPassphrase: boolean;
backupInfo: IKeyBackupInfo | null;
backupSigStatus: TrustInfo | null;
/** Information on the current key backup version, as returned by the server.
*
* `null` could mean any of:
* * we haven't yet requested the data from the server.
* * we were unable to reach the server.
* * the server returned key backup version data we didn't understand or was malformed.
* * there is actually no backup on the server.
*/
backupInfo: KeyBackupInfo | null;
/**
* Information on whether the backup in `backupInfo` is correctly signed, and whether we have the right key to
* decrypt it.
*
* `undefined` if `backupInfo` is null, or if crypto is not enabled in the client.
*/
backupTrustInfo: BackupTrustInfo | undefined;
// does the server offer a UI auth flow with just m.login.password
// for /keys/device_signing/upload?
canUploadKeysWithPasswordOnly: boolean | null;
@ -144,7 +160,7 @@ export default class CreateSecretStorageDialog extends React.PureComponent<IProp
downloaded: false,
setPassphrase: false,
backupInfo: null,
backupSigStatus: null,
backupTrustInfo: undefined,
// does the server offer a UI auth flow with just m.login.password
// for /keys/device_signing/upload?
accountPasswordCorrect: null,
@ -177,13 +193,21 @@ export default class CreateSecretStorageDialog extends React.PureComponent<IProp
this.fetchBackupInfo();
}
private async fetchBackupInfo(): Promise<{ backupInfo?: IKeyBackupInfo; backupSigStatus?: TrustInfo }> {
/**
* Attempt to get information on the current backup from the server, and update the state.
*
* Updates {@link IState.backupInfo} and {@link IState.backupTrustInfo}, and picks an appropriate phase for
* {@link IState.phase}.
*
* @returns If the backup data was retrieved successfully, the trust info for the backup. Otherwise, undefined.
*/
private async fetchBackupInfo(): Promise<BackupTrustInfo | undefined> {
try {
const cli = MatrixClientPeg.safeGet();
const backupInfo = await cli.getKeyBackupVersion();
const backupSigStatus =
const backupTrustInfo =
// we may not have started crypto yet, in which case we definitely don't trust the backup
backupInfo && cli.isCryptoEnabled() ? await cli.isKeyBackupTrusted(backupInfo) : null;
backupInfo ? await cli.getCrypto()?.isKeyBackupTrusted(backupInfo) : undefined;
const { forceReset } = this.props;
const phase = backupInfo && !forceReset ? Phase.Migrate : Phase.ChooseKeyPassphrase;
@ -191,16 +215,14 @@ export default class CreateSecretStorageDialog extends React.PureComponent<IProp
this.setState({
phase,
backupInfo,
backupSigStatus,
backupTrustInfo,
});
return {
backupInfo: backupInfo ?? undefined,
backupSigStatus: backupSigStatus ?? undefined,
};
return backupTrustInfo;
} catch (e) {
console.error("Error fetching backup data from server", e);
this.setState({ phase: Phase.LoadError });
return {};
return undefined;
}
}
@ -237,7 +259,7 @@ export default class CreateSecretStorageDialog extends React.PureComponent<IProp
private onChooseKeyPassphraseFormSubmit = async (): Promise<void> => {
if (this.state.passPhraseKeySelected === SecureBackupSetupMethod.Key) {
this.recoveryKey = await MatrixClientPeg.safeGet().createRecoveryKeyFromPassphrase();
this.recoveryKey = await MatrixClientPeg.safeGet().getCrypto()!.createRecoveryKeyFromPassphrase();
this.setState({
copied: false,
downloaded: false,
@ -255,7 +277,7 @@ export default class CreateSecretStorageDialog extends React.PureComponent<IProp
private onMigrateFormSubmit = (e: React.FormEvent): void => {
e.preventDefault();
if (this.state.backupSigStatus?.usable) {
if (this.state.backupTrustInfo?.trusted) {
this.bootstrapSecretStorage();
} else {
this.restoreBackup();
@ -284,7 +306,7 @@ export default class CreateSecretStorageDialog extends React.PureComponent<IProp
};
private doBootstrapUIAuth = async (
makeRequest: (authData: IAuthDict) => Promise<UIAResponse<void>>,
makeRequest: (authData: AuthDict) => Promise<UIAResponse<void>>,
): Promise<void> => {
if (this.state.canUploadKeysWithPasswordOnly && this.state.accountPassword) {
await makeRequest({
@ -337,13 +359,14 @@ export default class CreateSecretStorageDialog extends React.PureComponent<IProp
});
const cli = MatrixClientPeg.safeGet();
const crypto = cli.getCrypto()!;
const { forceReset } = this.props;
try {
if (forceReset) {
logger.log("Forcing secret storage reset");
await cli.bootstrapSecretStorage({
await crypto.bootstrapSecretStorage({
createSecretStorageKey: async () => this.recoveryKey!,
setupNewKeyBackup: true,
setupNewSecretStorage: true,
@ -356,10 +379,10 @@ export default class CreateSecretStorageDialog extends React.PureComponent<IProp
// * SSO authentication users which require interactive auth to upload
// keys (and also happen to skip all post-authentication flows at the
// moment via token login)
await cli.bootstrapCrossSigning({
await crypto.bootstrapCrossSigning({
authUploadDeviceSigningKeys: this.doBootstrapUIAuth,
});
await cli.bootstrapSecretStorage({
await crypto.bootstrapSecretStorage({
createSecretStorageKey: async () => this.recoveryKey!,
keyBackupInfo: this.state.backupInfo!,
setupNewKeyBackup: !this.state.backupInfo,
@ -420,8 +443,8 @@ export default class CreateSecretStorageDialog extends React.PureComponent<IProp
);
await finished;
const { backupSigStatus } = await this.fetchBackupInfo();
if (backupSigStatus?.usable && this.state.canUploadKeysWithPasswordOnly && this.state.accountPassword) {
const backupTrustInfo = await this.fetchBackupInfo();
if (backupTrustInfo?.trusted && this.state.canUploadKeysWithPasswordOnly && this.state.accountPassword) {
this.bootstrapSecretStorage();
}
};
@ -462,7 +485,9 @@ export default class CreateSecretStorageDialog extends React.PureComponent<IProp
if (this.state.passPhrase !== this.state.passPhraseConfirm) return;
this.recoveryKey = await MatrixClientPeg.safeGet().createRecoveryKeyFromPassphrase(this.state.passPhrase);
this.recoveryKey = await MatrixClientPeg.safeGet()
.getCrypto()!
.createRecoveryKeyFromPassphrase(this.state.passPhrase);
this.setState({
copied: false,
downloaded: false,
@ -585,6 +610,7 @@ export default class CreateSecretStorageDialog extends React.PureComponent<IProp
<div>{_t("Enter your account password to confirm the upgrade:")}</div>
<div>
<Field
id="mx_CreateSecretStorageDialog_password"
type="password"
label={_t("common|password")}
value={this.state.accountPassword}
@ -595,7 +621,7 @@ export default class CreateSecretStorageDialog extends React.PureComponent<IProp
</div>
</div>
);
} else if (!this.state.backupSigStatus?.usable) {
} else if (!this.state.backupTrustInfo?.trusted) {
authPrompt = (
<div>
<div>{_t("Restore your key backup to upgrade your encryption")}</div>