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

Update to use non deprecated methods to decode recovery key #54

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
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
Loading