Skip to content

Commit 87f1bc3

Browse files
committed
Get keybackup when bootstraping the secret storage.
1 parent 63a2ef1 commit 87f1bc3

2 files changed

Lines changed: 42 additions & 69 deletions

File tree

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

Lines changed: 31 additions & 53 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 { 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

@@ -70,16 +70,6 @@ interface IState {
7070
downloaded: boolean;
7171
setPassphrase: boolean;
7272

73-
/** Information on the current key backup version, as returned by the server.
74-
*
75-
* `null` could mean any of:
76-
* * we haven't yet requested the data from the server.
77-
* * we were unable to reach the server.
78-
* * the server returned key backup version data we didn't understand or was malformed.
79-
* * there is actually no backup on the server.
80-
*/
81-
backupInfo: KeyBackupInfo | null;
82-
8373
// does the server offer a UI auth flow with just m.login.password
8474
// for /keys/device_signing/upload?
8575
canUploadKeysWithPasswordOnly: boolean | null;
@@ -131,15 +121,17 @@ export default class CreateSecretStorageDialog extends React.PureComponent<IProp
131121
this.queryKeyUploadAuth();
132122
}
133123

124+
const keyFromCustomisations = ModuleRunner.instance.extensions.cryptoSetup.createSecretStorageKey();
125+
const phase = keyFromCustomisations ? Phase.Loading : Phase.ChooseKeyPassphrase;
126+
134127
this.state = {
135-
phase: Phase.Loading,
128+
phase,
136129
passPhrase: "",
137130
passPhraseValid: false,
138131
passPhraseConfirm: "",
139132
copied: false,
140133
downloaded: false,
141134
setPassphrase: false,
142-
backupInfo: null,
143135
// does the server offer a UI auth flow with just m.login.password
144136
// for /keys/device_signing/upload?
145137
accountPasswordCorrect: null,
@@ -149,40 +141,15 @@ export default class CreateSecretStorageDialog extends React.PureComponent<IProp
149141
accountPassword,
150142
};
151143

152-
this.getInitialPhase();
153-
}
154-
155-
private getInitialPhase(): void {
156-
const keyFromCustomisations = ModuleRunner.instance.extensions.cryptoSetup.createSecretStorageKey();
157-
if (keyFromCustomisations) {
158-
logger.log("CryptoSetupExtension: Created key via extension, jumping to bootstrap step");
159-
this.recoveryKey = {
160-
privateKey: keyFromCustomisations,
161-
};
162-
this.bootstrapSecretStorage();
163-
return;
164-
}
165-
166-
this.fetchBackupInfo();
144+
if (keyFromCustomisations) this.initExtension(keyFromCustomisations);
167145
}
168146

169-
/**
170-
* Attempt to get information on the current backup from the server, and update the state.
171-
*
172-
* Updates {@link IState.backupInfo} and set the phase to {@link Phase.ChooseKeyPassphrase} if successful.
173-
*/
174-
private async fetchBackupInfo(): Promise<void> {
175-
try {
176-
const cli = MatrixClientPeg.safeGet();
177-
const backupInfo = await cli.getKeyBackupVersion();
178-
this.setState({
179-
phase: Phase.ChooseKeyPassphrase,
180-
backupInfo,
181-
});
182-
} catch (e) {
183-
console.error("Error fetching backup data from server", e);
184-
this.setState({ phase: Phase.LoadError });
185-
}
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();
186153
}
187154

188155
private async queryKeyUploadAuth(): Promise<void> {
@@ -296,16 +263,28 @@ export default class CreateSecretStorageDialog extends React.PureComponent<IProp
296263
};
297264

298265
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, we try to get the keybackup info
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+
299283
this.setState({
300284
phase: Phase.Storing,
301285
error: undefined,
302286
});
303287

304-
const cli = MatrixClientPeg.safeGet();
305-
const crypto = cli.getCrypto()!;
306-
307-
const { forceReset } = this.props;
308-
309288
try {
310289
if (forceReset) {
311290
logger.log("Forcing secret storage reset");
@@ -327,7 +306,7 @@ export default class CreateSecretStorageDialog extends React.PureComponent<IProp
327306
});
328307
await crypto.bootstrapSecretStorage({
329308
createSecretStorageKey: async () => this.recoveryKey!,
330-
setupNewKeyBackup: !this.state.backupInfo,
309+
setupNewKeyBackup: !backupInfo,
331310
});
332311
}
333312
await initialiseDehydration(true);
@@ -346,8 +325,7 @@ export default class CreateSecretStorageDialog extends React.PureComponent<IProp
346325
};
347326

348327
private onLoadRetryClick = (): void => {
349-
this.setState({ phase: Phase.Loading });
350-
this.fetchBackupInfo();
328+
this.bootstrapSecretStorage();
351329
};
352330

353331
private onShowKeyContinueClick = (): void => {

test/unit-tests/components/views/dialogs/security/CreateSecretStorageDialog-test.tsx

Lines changed: 11 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -12,9 +12,8 @@ import React from "react";
1212
import { mocked, MockedObject } from "jest-mock";
1313
import { MatrixClient, MatrixError } from "matrix-js-sdk/src/matrix";
1414
import { sleep } from "matrix-js-sdk/src/utils";
15-
import { waitFor } from "@testing-library/dom";
1615

17-
import { filterConsole, flushPromises, stubClient } from "../../../../../test-utils";
16+
import { filterConsole, stubClient } from "../../../../../test-utils";
1817
import CreateSecretStorageDialog from "../../../../../../src/async-components/views/dialogs/security/CreateSecretStorageDialog";
1918

2019
describe("CreateSecretStorageDialog", () => {
@@ -41,13 +40,6 @@ describe("CreateSecretStorageDialog", () => {
4140
return render(<CreateSecretStorageDialog onFinished={onFinished} {...props} />);
4241
}
4342

44-
it("shows a loading spinner initially", async () => {
45-
const { container } = renderComponent();
46-
expect(screen.getByTestId("spinner")).toBeDefined();
47-
expect(container).toMatchSnapshot();
48-
await flushPromises();
49-
});
50-
5143
it("handles the happy path", async () => {
5244
const result = renderComponent();
5345
await result.findByText(
@@ -81,13 +73,6 @@ describe("CreateSecretStorageDialog", () => {
8173
await screen.findByText("Unable to set up secret storage");
8274
});
8375

84-
it("when there is an error fetching the backup version handles the error sensibly", async () => {
85-
mockClient.getKeyBackupVersion.mockRejectedValue(new Error("error"));
86-
renderComponent();
87-
88-
await waitFor(() => expect(screen.queryByText("Unable to query secret storage status")).not.toBeNull());
89-
});
90-
9176
describe("when there is an error fetching the backup version", () => {
9277
filterConsole("Error fetching backup data from server");
9378

@@ -97,9 +82,19 @@ describe("CreateSecretStorageDialog", () => {
9782
});
9883

9984
const result = renderComponent();
85+
// We go though the dialog until we have to get the key backup
86+
await userEvent.click(result.getByRole("button", { name: "Continue" }));
87+
await userEvent.click(screen.getByRole("button", { name: "Copy" }));
88+
await userEvent.click(screen.getByRole("button", { name: "Continue" }));
89+
10090
// XXX the error message is... misleading.
10191
await screen.findByText("Unable to query secret storage status");
10292
expect(result.container).toMatchSnapshot();
93+
94+
// Now we can get the backup and we retry
95+
mockClient.getKeyBackupVersion.mockRestore();
96+
await userEvent.click(screen.getByRole("button", { name: "Retry" }));
97+
await screen.findByText("Your keys are now being backed up from this device.");
10398
});
10499
});
105100
});

0 commit comments

Comments
 (0)