Skip to content
This repository was archived by the owner on Oct 22, 2024. It is now read-only.

Commit fe65702

Browse files
authored
Update to use non deprecated methods to decode recovery key (#54)
* Replace `MatrixClient.keyBackupKeyFromRecoveryKey` by `decodeRecoveryKey` * Replace `MatrixClient.isValidRecoveryKey` by local check with `decodeRecoveryKey` * Replace old `decodeRecoveryKey` import * Remove `matrix-js-sdk/src/crypto/recoverykey` import of eslint exception * Add tests for `RestoreKeyBackupDialog`
1 parent 490746e commit fe65702

File tree

7 files changed

+371
-13
lines changed

7 files changed

+371
-13
lines changed

.eslintrc.js

-1
Original file line numberDiff line numberDiff line change
@@ -122,7 +122,6 @@ module.exports = {
122122
"!matrix-js-sdk/src/crypto/aes",
123123
"!matrix-js-sdk/src/crypto/keybackup",
124124
"!matrix-js-sdk/src/crypto/deviceinfo",
125-
"!matrix-js-sdk/src/crypto/recoverykey",
126125
"!matrix-js-sdk/src/crypto/dehydration",
127126
"!matrix-js-sdk/src/oidc",
128127
"!matrix-js-sdk/src/oidc/discovery",

src/SecurityManager.ts

+1-2
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,7 @@ Please see LICENSE files in the repository root for full details.
77
*/
88

99
import { ICryptoCallbacks, SecretStorage } from "matrix-js-sdk/src/matrix";
10-
import { deriveRecoveryKeyFromPassphrase } from "matrix-js-sdk/src/crypto-api";
11-
import { decodeRecoveryKey } from "matrix-js-sdk/src/crypto/recoverykey";
10+
import { deriveRecoveryKeyFromPassphrase, decodeRecoveryKey } from "matrix-js-sdk/src/crypto-api";
1211
import { logger } from "matrix-js-sdk/src/logger";
1312

1413
import type CreateSecretStorageDialog from "./async-components/views/dialogs/security/CreateSecretStorageDialog";

src/components/views/dialogs/security/AccessSecretStorageDialog.tsx

+2-1
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import { debounce } from "lodash";
1010
import classNames from "classnames";
1111
import React, { ChangeEvent, FormEvent } from "react";
1212
import { logger } from "matrix-js-sdk/src/logger";
13+
import { decodeRecoveryKey } from "matrix-js-sdk/src/crypto-api";
1314
import { SecretStorage } from "matrix-js-sdk/src/matrix";
1415

1516
import { MatrixClientPeg } from "../../../../MatrixClientPeg";
@@ -100,7 +101,7 @@ export default class AccessSecretStorageDialog extends React.PureComponent<IProp
100101

101102
try {
102103
const cli = MatrixClientPeg.safeGet();
103-
const decodedKey = cli.keyBackupKeyFromRecoveryKey(this.state.recoveryKey);
104+
const decodedKey = decodeRecoveryKey(this.state.recoveryKey);
104105
const correct = await cli.checkSecretStorageKey(decodedKey, this.props.keyInfo);
105106
this.setState({
106107
recoveryKeyValid: true,

src/components/views/dialogs/security/RestoreKeyBackupDialog.tsx

+17-3
Original file line numberDiff line numberDiff line change
@@ -9,9 +9,9 @@ Please see LICENSE files in the repository root for full details.
99

1010
import React, { ChangeEvent } from "react";
1111
import { MatrixClient, MatrixError, SecretStorage } from "matrix-js-sdk/src/matrix";
12+
import { decodeRecoveryKey, KeyBackupInfo } from "matrix-js-sdk/src/crypto-api";
1213
import { IKeyBackupRestoreResult } from "matrix-js-sdk/src/crypto/keybackup";
1314
import { logger } from "matrix-js-sdk/src/logger";
14-
import { KeyBackupInfo } from "matrix-js-sdk/src/crypto-api";
1515

1616
import { MatrixClientPeg } from "../../../../MatrixClientPeg";
1717
import { _t } from "../../../../languageHandler";
@@ -118,10 +118,24 @@ export default class RestoreKeyBackupDialog extends React.PureComponent<IProps,
118118
accessSecretStorage(async (): Promise<void> => {}, /* forceReset = */ true);
119119
};
120120

121+
/**
122+
* Check if the recovery key is valid
123+
* @param recoveryKey
124+
* @private
125+
*/
126+
private isValidRecoveryKey(recoveryKey: string): boolean {
127+
try {
128+
decodeRecoveryKey(recoveryKey);
129+
return true;
130+
} catch (e) {
131+
return false;
132+
}
133+
}
134+
121135
private onRecoveryKeyChange = (e: ChangeEvent<HTMLInputElement>): void => {
122136
this.setState({
123137
recoveryKey: e.target.value,
124-
recoveryKeyValid: MatrixClientPeg.safeGet().isValidRecoveryKey(e.target.value),
138+
recoveryKeyValid: this.isValidRecoveryKey(e.target.value),
125139
});
126140
};
127141

@@ -184,7 +198,7 @@ export default class RestoreKeyBackupDialog extends React.PureComponent<IProps,
184198
{ progressCallback: this.progressCallback },
185199
);
186200
if (this.props.keyCallback) {
187-
const key = MatrixClientPeg.safeGet().keyBackupKeyFromRecoveryKey(this.state.recoveryKey);
201+
const key = decodeRecoveryKey(this.state.recoveryKey);
188202
this.props.keyCallback(key);
189203
}
190204
if (!this.props.showSummary) {

test/components/views/dialogs/AccessSecretStorageDialog-test.tsx

+2-6
Original file line numberDiff line numberDiff line change
@@ -58,15 +58,12 @@ describe("AccessSecretStorageDialog", () => {
5858

5959
beforeEach(() => {
6060
mockClient = getMockClientWithEventEmitter({
61-
keyBackupKeyFromRecoveryKey: jest.fn(),
6261
checkSecretStorageKey: jest.fn(),
63-
isValidRecoveryKey: jest.fn(),
6462
});
6563
});
6664

6765
it("Closes the dialog when the form is submitted with a valid key", async () => {
6866
mockClient.checkSecretStorageKey.mockResolvedValue(true);
69-
mockClient.isValidRecoveryKey.mockReturnValue(true);
7067

7168
const onFinished = jest.fn();
7269
const checkPrivateKey = jest.fn().mockResolvedValue(true);
@@ -88,8 +85,8 @@ describe("AccessSecretStorageDialog", () => {
8885
const checkPrivateKey = jest.fn().mockResolvedValue(true);
8986
renderComponent({ onFinished, checkPrivateKey });
9087

91-
mockClient.keyBackupKeyFromRecoveryKey.mockImplementation(() => {
92-
throw new Error("that's no key");
88+
mockClient.checkSecretStorageKey.mockImplementation(() => {
89+
throw new Error("invalid key");
9390
});
9491

9592
await enterSecurityKey();
@@ -115,7 +112,6 @@ describe("AccessSecretStorageDialog", () => {
115112
};
116113
const checkPrivateKey = jest.fn().mockResolvedValue(false);
117114
renderComponent({ checkPrivateKey, keyInfo });
118-
mockClient.isValidRecoveryKey.mockReturnValue(false);
119115

120116
await enterSecurityKey("Security Phrase");
121117
expect(screen.getByPlaceholderText("Security Phrase")).toHaveValue(securityKey);
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
/*
2+
* Copyright 2024 New Vector Ltd.
3+
*
4+
* SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only
5+
* Please see LICENSE files in the repository root for full details.
6+
*
7+
*/
8+
9+
import React from "react";
10+
import { screen, render, waitFor } from "@testing-library/react";
11+
import userEvent from "@testing-library/user-event";
12+
// Needed to be able to mock decodeRecoveryKey
13+
// eslint-disable-next-line no-restricted-imports
14+
import * as recoveryKeyModule from "matrix-js-sdk/src/crypto-api/recovery-key";
15+
16+
import RestoreKeyBackupDialog from "../../../../../src/components/views/dialogs/security/RestoreKeyBackupDialog.tsx";
17+
import { stubClient } from "../../../../test-utils";
18+
19+
describe("<RestoreKeyBackupDialog />", () => {
20+
beforeEach(() => {
21+
stubClient();
22+
jest.spyOn(recoveryKeyModule, "decodeRecoveryKey").mockReturnValue(new Uint8Array(32));
23+
});
24+
25+
it("should render", async () => {
26+
const { asFragment } = render(<RestoreKeyBackupDialog onFinished={jest.fn()} />);
27+
await waitFor(() => expect(screen.getByText("Enter Security Key")).toBeInTheDocument());
28+
expect(asFragment()).toMatchSnapshot();
29+
});
30+
31+
it("should display an error when recovery key is invalid", async () => {
32+
jest.spyOn(recoveryKeyModule, "decodeRecoveryKey").mockImplementation(() => {
33+
throw new Error("Invalid recovery key");
34+
});
35+
const { asFragment } = render(<RestoreKeyBackupDialog onFinished={jest.fn()} />);
36+
await waitFor(() => expect(screen.getByText("Enter Security Key")).toBeInTheDocument());
37+
38+
await userEvent.type(screen.getByRole("textbox"), "invalid key");
39+
await waitFor(() => expect(screen.getByText("👎 Not a valid Security Key")).toBeInTheDocument());
40+
expect(asFragment()).toMatchSnapshot();
41+
});
42+
43+
it("should not raise an error when recovery is valid", async () => {
44+
const { asFragment } = render(<RestoreKeyBackupDialog onFinished={jest.fn()} />);
45+
await waitFor(() => expect(screen.getByText("Enter Security Key")).toBeInTheDocument());
46+
47+
await userEvent.type(screen.getByRole("textbox"), "valid key");
48+
await waitFor(() => expect(screen.getByText("👍 This looks like a valid Security Key!")).toBeInTheDocument());
49+
expect(asFragment()).toMatchSnapshot();
50+
});
51+
});

0 commit comments

Comments
 (0)