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

Commit

Permalink
Update to use non deprecated methods to decode recovery key (#54)
Browse files Browse the repository at this point in the history
* 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`
  • Loading branch information
florianduros authored Sep 19, 2024
1 parent 490746e commit fe65702
Show file tree
Hide file tree
Showing 7 changed files with 371 additions and 13 deletions.
1 change: 0 additions & 1 deletion .eslintrc.js
Original file line number Diff line number Diff line change
Expand Up @@ -122,7 +122,6 @@ module.exports = {
"!matrix-js-sdk/src/crypto/aes",
"!matrix-js-sdk/src/crypto/keybackup",
"!matrix-js-sdk/src/crypto/deviceinfo",
"!matrix-js-sdk/src/crypto/recoverykey",
"!matrix-js-sdk/src/crypto/dehydration",
"!matrix-js-sdk/src/oidc",
"!matrix-js-sdk/src/oidc/discovery",
Expand Down
3 changes: 1 addition & 2 deletions src/SecurityManager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,7 @@ Please see LICENSE files in the repository root for full details.
*/

import { ICryptoCallbacks, SecretStorage } from "matrix-js-sdk/src/matrix";
import { deriveRecoveryKeyFromPassphrase } from "matrix-js-sdk/src/crypto-api";
import { decodeRecoveryKey } from "matrix-js-sdk/src/crypto/recoverykey";
import { deriveRecoveryKeyFromPassphrase, decodeRecoveryKey } from "matrix-js-sdk/src/crypto-api";
import { logger } from "matrix-js-sdk/src/logger";

import type CreateSecretStorageDialog from "./async-components/views/dialogs/security/CreateSecretStorageDialog";
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import { debounce } from "lodash";
import classNames from "classnames";
import React, { ChangeEvent, FormEvent } from "react";
import { logger } from "matrix-js-sdk/src/logger";
import { decodeRecoveryKey } from "matrix-js-sdk/src/crypto-api";
import { SecretStorage } from "matrix-js-sdk/src/matrix";

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

try {
const cli = MatrixClientPeg.safeGet();
const decodedKey = cli.keyBackupKeyFromRecoveryKey(this.state.recoveryKey);
const decodedKey = decodeRecoveryKey(this.state.recoveryKey);
const correct = await cli.checkSecretStorageKey(decodedKey, this.props.keyInfo);
this.setState({
recoveryKeyValid: true,
Expand Down
20 changes: 17 additions & 3 deletions src/components/views/dialogs/security/RestoreKeyBackupDialog.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,9 @@ Please see LICENSE files in the repository root for full details.

import React, { ChangeEvent } from "react";
import { MatrixClient, MatrixError, SecretStorage } from "matrix-js-sdk/src/matrix";
import { decodeRecoveryKey, KeyBackupInfo } from "matrix-js-sdk/src/crypto-api";
import { IKeyBackupRestoreResult } from "matrix-js-sdk/src/crypto/keybackup";
import { logger } from "matrix-js-sdk/src/logger";
import { KeyBackupInfo } from "matrix-js-sdk/src/crypto-api";

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

/**
* Check if the recovery key is valid
* @param recoveryKey
* @private
*/
private isValidRecoveryKey(recoveryKey: string): boolean {
try {
decodeRecoveryKey(recoveryKey);
return true;
} catch (e) {
return false;
}
}

private onRecoveryKeyChange = (e: ChangeEvent<HTMLInputElement>): void => {
this.setState({
recoveryKey: e.target.value,
recoveryKeyValid: MatrixClientPeg.safeGet().isValidRecoveryKey(e.target.value),
recoveryKeyValid: this.isValidRecoveryKey(e.target.value),
});
};

Expand Down Expand Up @@ -184,7 +198,7 @@ export default class RestoreKeyBackupDialog extends React.PureComponent<IProps,
{ progressCallback: this.progressCallback },
);
if (this.props.keyCallback) {
const key = MatrixClientPeg.safeGet().keyBackupKeyFromRecoveryKey(this.state.recoveryKey);
const key = decodeRecoveryKey(this.state.recoveryKey);
this.props.keyCallback(key);
}
if (!this.props.showSummary) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -58,15 +58,12 @@ describe("AccessSecretStorageDialog", () => {

beforeEach(() => {
mockClient = getMockClientWithEventEmitter({
keyBackupKeyFromRecoveryKey: jest.fn(),
checkSecretStorageKey: jest.fn(),
isValidRecoveryKey: jest.fn(),
});
});

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

const onFinished = jest.fn();
const checkPrivateKey = jest.fn().mockResolvedValue(true);
Expand All @@ -88,8 +85,8 @@ describe("AccessSecretStorageDialog", () => {
const checkPrivateKey = jest.fn().mockResolvedValue(true);
renderComponent({ onFinished, checkPrivateKey });

mockClient.keyBackupKeyFromRecoveryKey.mockImplementation(() => {
throw new Error("that's no key");
mockClient.checkSecretStorageKey.mockImplementation(() => {
throw new Error("invalid key");
});

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

await enterSecurityKey("Security Phrase");
expect(screen.getByPlaceholderText("Security Phrase")).toHaveValue(securityKey);
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
/*
* Copyright 2024 New Vector Ltd.
*
* SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only
* Please see LICENSE files in the repository root for full details.
*
*/

import React from "react";
import { screen, render, waitFor } from "@testing-library/react";
import userEvent from "@testing-library/user-event";
// Needed to be able to mock decodeRecoveryKey
// eslint-disable-next-line no-restricted-imports
import * as recoveryKeyModule from "matrix-js-sdk/src/crypto-api/recovery-key";

import RestoreKeyBackupDialog from "../../../../../src/components/views/dialogs/security/RestoreKeyBackupDialog.tsx";
import { stubClient } from "../../../../test-utils";

describe("<RestoreKeyBackupDialog />", () => {
beforeEach(() => {
stubClient();
jest.spyOn(recoveryKeyModule, "decodeRecoveryKey").mockReturnValue(new Uint8Array(32));
});

it("should render", async () => {
const { asFragment } = render(<RestoreKeyBackupDialog onFinished={jest.fn()} />);
await waitFor(() => expect(screen.getByText("Enter Security Key")).toBeInTheDocument());
expect(asFragment()).toMatchSnapshot();
});

it("should display an error when recovery key is invalid", async () => {
jest.spyOn(recoveryKeyModule, "decodeRecoveryKey").mockImplementation(() => {
throw new Error("Invalid recovery key");
});
const { asFragment } = render(<RestoreKeyBackupDialog onFinished={jest.fn()} />);
await waitFor(() => expect(screen.getByText("Enter Security Key")).toBeInTheDocument());

await userEvent.type(screen.getByRole("textbox"), "invalid key");
await waitFor(() => expect(screen.getByText("👎 Not a valid Security Key")).toBeInTheDocument());
expect(asFragment()).toMatchSnapshot();
});

it("should not raise an error when recovery is valid", async () => {
const { asFragment } = render(<RestoreKeyBackupDialog onFinished={jest.fn()} />);
await waitFor(() => expect(screen.getByText("Enter Security Key")).toBeInTheDocument());

await userEvent.type(screen.getByRole("textbox"), "valid key");
await waitFor(() => expect(screen.getByText("👍 This looks like a valid Security Key!")).toBeInTheDocument());
expect(asFragment()).toMatchSnapshot();
});
});
Loading

0 comments on commit fe65702

Please sign in to comment.