SecureBackupPanel
: stop using deprecated APIs, and other fixes (#11644)
* SecureBackupPanel: replace `isKeyBackupTrusted` `MatrixClient.isKeyBackupTrusted` -> `CryptoApi.isKeyBackupTrusted` * SecureBackupPanel: replace `getKeyBackupEnabled` `MatrixClient.getKeyBackupEnabled` -> `CryptoApi.getActiveSessionBackupVersion` * SecureBackupPanel: replace `deleteKeyBackupVersion` `MatrixClient.deleteKeyBackupVersion` -> `CryptoApi.deleteKeyBackupVersion` * Do not show session count if we have no info We shouldn't say "zero sessions to back up" if we don't know. * SecureBackupPanel: distinguish between server and active backup
This commit is contained in:
parent
4f7d9da140
commit
11f258e62e
4 changed files with 81 additions and 39 deletions
|
@ -16,10 +16,9 @@ limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import React, { ReactNode } from "react";
|
import React, { ReactNode } from "react";
|
||||||
import { IKeyBackupInfo } from "matrix-js-sdk/src/crypto/keybackup";
|
|
||||||
import { TrustInfo } from "matrix-js-sdk/src/crypto/backup";
|
|
||||||
import { CryptoEvent } from "matrix-js-sdk/src/crypto";
|
import { CryptoEvent } from "matrix-js-sdk/src/crypto";
|
||||||
import { logger } from "matrix-js-sdk/src/logger";
|
import { logger } from "matrix-js-sdk/src/logger";
|
||||||
|
import { BackupTrustInfo, KeyBackupInfo } from "matrix-js-sdk/src/crypto-api";
|
||||||
|
|
||||||
import type CreateKeyBackupDialog from "../../../async-components/views/dialogs/security/CreateKeyBackupDialog";
|
import type CreateKeyBackupDialog from "../../../async-components/views/dialogs/security/CreateKeyBackupDialog";
|
||||||
import { MatrixClientPeg } from "../../../MatrixClientPeg";
|
import { MatrixClientPeg } from "../../../MatrixClientPeg";
|
||||||
|
@ -41,9 +40,34 @@ interface IState {
|
||||||
backupKeyWellFormed: boolean | null;
|
backupKeyWellFormed: boolean | null;
|
||||||
secretStorageKeyInAccount: boolean | null;
|
secretStorageKeyInAccount: boolean | null;
|
||||||
secretStorageReady: boolean | null;
|
secretStorageReady: boolean | null;
|
||||||
backupInfo: IKeyBackupInfo | null;
|
|
||||||
backupSigStatus: TrustInfo | null;
|
/** Information on the current key backup version, as returned by the server.
|
||||||
sessionsRemaining: number;
|
*
|
||||||
|
* `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;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* If key backup is currently enabled, the backup version we are backing up to.
|
||||||
|
*/
|
||||||
|
activeBackupVersion: string | null;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Number of sessions remaining to be backed up. `null` if we have no information on this.
|
||||||
|
*/
|
||||||
|
sessionsRemaining: number | null;
|
||||||
}
|
}
|
||||||
|
|
||||||
export default class SecureBackupPanel extends React.PureComponent<{}, IState> {
|
export default class SecureBackupPanel extends React.PureComponent<{}, IState> {
|
||||||
|
@ -61,8 +85,9 @@ export default class SecureBackupPanel extends React.PureComponent<{}, IState> {
|
||||||
secretStorageKeyInAccount: null,
|
secretStorageKeyInAccount: null,
|
||||||
secretStorageReady: null,
|
secretStorageReady: null,
|
||||||
backupInfo: null,
|
backupInfo: null,
|
||||||
backupSigStatus: null,
|
backupTrustInfo: undefined,
|
||||||
sessionsRemaining: 0,
|
activeBackupVersion: null,
|
||||||
|
sessionsRemaining: null,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -101,14 +126,19 @@ export default class SecureBackupPanel extends React.PureComponent<{}, IState> {
|
||||||
this.setState({ loading: true });
|
this.setState({ loading: true });
|
||||||
this.getUpdatedDiagnostics();
|
this.getUpdatedDiagnostics();
|
||||||
try {
|
try {
|
||||||
const backupInfo = await MatrixClientPeg.safeGet().getKeyBackupVersion();
|
const cli = MatrixClientPeg.safeGet();
|
||||||
const backupSigStatus = backupInfo ? await MatrixClientPeg.safeGet().isKeyBackupTrusted(backupInfo) : null;
|
const backupInfo = await cli.getKeyBackupVersion();
|
||||||
|
const backupTrustInfo = backupInfo ? await cli.getCrypto()?.isKeyBackupTrusted(backupInfo) : undefined;
|
||||||
|
|
||||||
|
const activeBackupVersion = (await cli.getCrypto()?.getActiveSessionBackupVersion()) ?? null;
|
||||||
|
|
||||||
if (this.unmounted) return;
|
if (this.unmounted) return;
|
||||||
this.setState({
|
this.setState({
|
||||||
loading: false,
|
loading: false,
|
||||||
error: false,
|
error: false,
|
||||||
backupInfo,
|
backupInfo,
|
||||||
backupSigStatus,
|
backupTrustInfo,
|
||||||
|
activeBackupVersion,
|
||||||
});
|
});
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
logger.log("Unable to fetch key backup status", e);
|
logger.log("Unable to fetch key backup status", e);
|
||||||
|
@ -117,7 +147,8 @@ export default class SecureBackupPanel extends React.PureComponent<{}, IState> {
|
||||||
loading: false,
|
loading: false,
|
||||||
error: true,
|
error: true,
|
||||||
backupInfo: null,
|
backupInfo: null,
|
||||||
backupSigStatus: null,
|
backupTrustInfo: undefined,
|
||||||
|
activeBackupVersion: null,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -173,8 +204,10 @@ export default class SecureBackupPanel extends React.PureComponent<{}, IState> {
|
||||||
onFinished: (proceed) => {
|
onFinished: (proceed) => {
|
||||||
if (!proceed) return;
|
if (!proceed) return;
|
||||||
this.setState({ loading: true });
|
this.setState({ loading: true });
|
||||||
|
const versionToDelete = this.state.backupInfo!.version!;
|
||||||
MatrixClientPeg.safeGet()
|
MatrixClientPeg.safeGet()
|
||||||
.deleteKeyBackupVersion(this.state.backupInfo!.version!)
|
.getCrypto()
|
||||||
|
?.deleteKeyBackupVersion(versionToDelete)
|
||||||
.then(() => {
|
.then(() => {
|
||||||
this.loadBackupStatus();
|
this.loadBackupStatus();
|
||||||
});
|
});
|
||||||
|
@ -209,7 +242,7 @@ export default class SecureBackupPanel extends React.PureComponent<{}, IState> {
|
||||||
secretStorageKeyInAccount,
|
secretStorageKeyInAccount,
|
||||||
secretStorageReady,
|
secretStorageReady,
|
||||||
backupInfo,
|
backupInfo,
|
||||||
backupSigStatus,
|
backupTrustInfo,
|
||||||
sessionsRemaining,
|
sessionsRemaining,
|
||||||
} = this.state;
|
} = this.state;
|
||||||
|
|
||||||
|
@ -228,7 +261,7 @@ export default class SecureBackupPanel extends React.PureComponent<{}, IState> {
|
||||||
} else if (backupInfo) {
|
} else if (backupInfo) {
|
||||||
let restoreButtonCaption = _t("Restore from Backup");
|
let restoreButtonCaption = _t("Restore from Backup");
|
||||||
|
|
||||||
if (MatrixClientPeg.safeGet().getKeyBackupEnabled()) {
|
if (this.state.activeBackupVersion !== null) {
|
||||||
statusDescription = (
|
statusDescription = (
|
||||||
<SettingsSubsectionText>✅ {_t("This session is backing up your keys.")}</SettingsSubsectionText>
|
<SettingsSubsectionText>✅ {_t("This session is backing up your keys.")}</SettingsSubsectionText>
|
||||||
);
|
);
|
||||||
|
@ -253,7 +286,7 @@ export default class SecureBackupPanel extends React.PureComponent<{}, IState> {
|
||||||
}
|
}
|
||||||
|
|
||||||
let uploadStatus: ReactNode;
|
let uploadStatus: ReactNode;
|
||||||
if (!MatrixClientPeg.safeGet().getKeyBackupEnabled()) {
|
if (sessionsRemaining === null) {
|
||||||
// No upload status to show when backup disabled.
|
// No upload status to show when backup disabled.
|
||||||
uploadStatus = "";
|
uploadStatus = "";
|
||||||
} else if (sessionsRemaining > 0) {
|
} else if (sessionsRemaining > 0) {
|
||||||
|
@ -271,19 +304,21 @@ export default class SecureBackupPanel extends React.PureComponent<{}, IState> {
|
||||||
}
|
}
|
||||||
|
|
||||||
let trustedLocally: string | undefined;
|
let trustedLocally: string | undefined;
|
||||||
if (backupSigStatus?.trusted_locally) {
|
if (backupTrustInfo?.matchesDecryptionKey) {
|
||||||
trustedLocally = _t("This backup is trusted because it has been restored on this session");
|
trustedLocally = _t("This backup can be restored on this session");
|
||||||
}
|
}
|
||||||
|
|
||||||
extraDetailsTableRows = (
|
extraDetailsTableRows = (
|
||||||
<>
|
<>
|
||||||
<tr>
|
<tr>
|
||||||
<th scope="row">{_t("Backup version:")}</th>
|
<th scope="row">{_t("Latest backup version on server:")}</th>
|
||||||
<td>{backupInfo.version}</td>
|
<td>
|
||||||
|
{backupInfo.version} ({_t("Algorithm:")} <code>{backupInfo.algorithm}</code>)
|
||||||
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<th scope="row">{_t("Algorithm:")}</th>
|
<th scope="row">{_t("Active backup version:")}</th>
|
||||||
<td>{backupInfo.algorithm}</td>
|
<td>{this.state.activeBackupVersion === null ? _t("None") : this.state.activeBackupVersion}</td>
|
||||||
</tr>
|
</tr>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
|
|
|
@ -2138,9 +2138,10 @@
|
||||||
"Connect this session to Key Backup": "Connect this session to Key Backup",
|
"Connect this session to Key Backup": "Connect this session to Key Backup",
|
||||||
"Backing up %(sessionsRemaining)s keys…": "Backing up %(sessionsRemaining)s keys…",
|
"Backing up %(sessionsRemaining)s keys…": "Backing up %(sessionsRemaining)s keys…",
|
||||||
"All keys backed up": "All keys backed up",
|
"All keys backed up": "All keys backed up",
|
||||||
"This backup is trusted because it has been restored on this session": "This backup is trusted because it has been restored on this session",
|
"This backup can be restored on this session": "This backup can be restored on this session",
|
||||||
"Backup version:": "Backup version:",
|
"Latest backup version on server:": "Latest backup version on server:",
|
||||||
"Algorithm:": "Algorithm:",
|
"Algorithm:": "Algorithm:",
|
||||||
|
"Active backup version:": "Active backup version:",
|
||||||
"Your keys are <b>not being backed up from this session</b>.": "Your keys are <b>not being backed up from this session</b>.",
|
"Your keys are <b>not being backed up from this session</b>.": "Your keys are <b>not being backed up from this session</b>.",
|
||||||
"Back up your keys before signing out to avoid losing them.": "Back up your keys before signing out to avoid losing them.",
|
"Back up your keys before signing out to avoid losing them.": "Back up your keys before signing out to avoid losing them.",
|
||||||
"Set up": "Set up",
|
"Set up": "Set up",
|
||||||
|
|
|
@ -36,11 +36,8 @@ describe("<SecureBackupPanel />", () => {
|
||||||
const client = getMockClientWithEventEmitter({
|
const client = getMockClientWithEventEmitter({
|
||||||
...mockClientMethodsUser(userId),
|
...mockClientMethodsUser(userId),
|
||||||
...mockClientMethodsCrypto(),
|
...mockClientMethodsCrypto(),
|
||||||
getKeyBackupEnabled: jest.fn(),
|
|
||||||
getKeyBackupVersion: jest.fn().mockReturnValue("1"),
|
getKeyBackupVersion: jest.fn().mockReturnValue("1"),
|
||||||
isKeyBackupTrusted: jest.fn().mockResolvedValue(true),
|
|
||||||
getClientWellKnown: jest.fn(),
|
getClientWellKnown: jest.fn(),
|
||||||
deleteKeyBackupVersion: jest.fn(),
|
|
||||||
});
|
});
|
||||||
|
|
||||||
const getComponent = () => render(<SecureBackupPanel />);
|
const getComponent = () => render(<SecureBackupPanel />);
|
||||||
|
@ -53,15 +50,17 @@ describe("<SecureBackupPanel />", () => {
|
||||||
public_key: "1234",
|
public_key: "1234",
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
client.isKeyBackupTrusted.mockResolvedValue({
|
Object.assign(client.getCrypto()!, {
|
||||||
usable: false,
|
isKeyBackupTrusted: jest.fn().mockResolvedValue({
|
||||||
sigs: [],
|
trusted: false,
|
||||||
|
matchesDecryptionKey: false,
|
||||||
|
}),
|
||||||
|
getActiveSessionBackupVersion: jest.fn().mockResolvedValue(null),
|
||||||
|
deleteKeyBackupVersion: jest.fn().mockResolvedValue(undefined),
|
||||||
});
|
});
|
||||||
|
|
||||||
mocked(client.secretStorage.hasKey).mockClear().mockResolvedValue(false);
|
mocked(client.secretStorage.hasKey).mockClear().mockResolvedValue(false);
|
||||||
client.deleteKeyBackupVersion.mockClear().mockResolvedValue();
|
|
||||||
client.getKeyBackupVersion.mockClear();
|
client.getKeyBackupVersion.mockClear();
|
||||||
client.isKeyBackupTrusted.mockClear();
|
|
||||||
|
|
||||||
mocked(accessSecretStorage).mockClear().mockResolvedValue();
|
mocked(accessSecretStorage).mockClear().mockResolvedValue();
|
||||||
});
|
});
|
||||||
|
@ -100,7 +99,7 @@ describe("<SecureBackupPanel />", () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
it("displays when session is connected to key backup", async () => {
|
it("displays when session is connected to key backup", async () => {
|
||||||
client.getKeyBackupEnabled.mockReturnValue(true);
|
mocked(client.getCrypto()!).getActiveSessionBackupVersion.mockResolvedValue("1");
|
||||||
getComponent();
|
getComponent();
|
||||||
// flush checkKeyBackup promise
|
// flush checkKeyBackup promise
|
||||||
await flushPromises();
|
await flushPromises();
|
||||||
|
@ -125,7 +124,7 @@ describe("<SecureBackupPanel />", () => {
|
||||||
|
|
||||||
fireEvent.click(within(dialog).getByText("Cancel"));
|
fireEvent.click(within(dialog).getByText("Cancel"));
|
||||||
|
|
||||||
expect(client.deleteKeyBackupVersion).not.toHaveBeenCalled();
|
expect(client.getCrypto()!.deleteKeyBackupVersion).not.toHaveBeenCalled();
|
||||||
});
|
});
|
||||||
|
|
||||||
it("deletes backup after confirmation", async () => {
|
it("deletes backup after confirmation", async () => {
|
||||||
|
@ -154,7 +153,7 @@ describe("<SecureBackupPanel />", () => {
|
||||||
|
|
||||||
fireEvent.click(within(dialog).getByTestId("dialog-primary-button"));
|
fireEvent.click(within(dialog).getByTestId("dialog-primary-button"));
|
||||||
|
|
||||||
expect(client.deleteKeyBackupVersion).toHaveBeenCalledWith("1");
|
expect(client.getCrypto()!.deleteKeyBackupVersion).toHaveBeenCalledWith("1");
|
||||||
|
|
||||||
// delete request
|
// delete request
|
||||||
await flushPromises();
|
await flushPromises();
|
||||||
|
@ -169,7 +168,7 @@ describe("<SecureBackupPanel />", () => {
|
||||||
await flushPromises();
|
await flushPromises();
|
||||||
|
|
||||||
client.getKeyBackupVersion.mockClear();
|
client.getKeyBackupVersion.mockClear();
|
||||||
client.isKeyBackupTrusted.mockClear();
|
mocked(client.getCrypto()!).isKeyBackupTrusted.mockClear();
|
||||||
|
|
||||||
fireEvent.click(screen.getByText("Reset"));
|
fireEvent.click(screen.getByText("Reset"));
|
||||||
|
|
||||||
|
@ -179,6 +178,6 @@ describe("<SecureBackupPanel />", () => {
|
||||||
|
|
||||||
// backup status refreshed
|
// backup status refreshed
|
||||||
expect(client.getKeyBackupVersion).toHaveBeenCalled();
|
expect(client.getKeyBackupVersion).toHaveBeenCalled();
|
||||||
expect(client.isKeyBackupTrusted).toHaveBeenCalled();
|
expect(client.getCrypto()!.isKeyBackupTrusted).toHaveBeenCalled();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
|
@ -140,20 +140,27 @@ exports[`<SecureBackupPanel /> suggests connecting session to key backup when ba
|
||||||
<th
|
<th
|
||||||
scope="row"
|
scope="row"
|
||||||
>
|
>
|
||||||
Backup version:
|
Latest backup version on server:
|
||||||
</th>
|
</th>
|
||||||
<td>
|
<td>
|
||||||
1
|
1
|
||||||
|
(
|
||||||
|
Algorithm:
|
||||||
|
|
||||||
|
<code>
|
||||||
|
test
|
||||||
|
</code>
|
||||||
|
)
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<th
|
<th
|
||||||
scope="row"
|
scope="row"
|
||||||
>
|
>
|
||||||
Algorithm:
|
Active backup version:
|
||||||
</th>
|
</th>
|
||||||
<td>
|
<td>
|
||||||
test
|
None
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
</table>
|
</table>
|
||||||
|
|
Loading…
Reference in a new issue