Use new CrytoApi.restoreKeyBackup
& CrytoApi.restoreKeyBackupFromPassphrase
api (#28385)
* Use new `CrytoApi.restoreKeyBackup` & `CrytoApi.restoreKeyBackupFromPassprhase` api * Use new `CrytoApi.restoreKeyBackup` api in `SetupEncryptionStore` * Add tests to `RestoreKeyBackupDialog`
This commit is contained in:
parent
7b1e303328
commit
c67e67af4e
5 changed files with 383 additions and 44 deletions
|
@ -8,9 +8,8 @@ Please see LICENSE files in the repository root for full details.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import React, { ChangeEvent } from "react";
|
import React, { ChangeEvent } from "react";
|
||||||
import { MatrixClient, MatrixError, SecretStorage } from "matrix-js-sdk/src/matrix";
|
import { MatrixClient, MatrixError } from "matrix-js-sdk/src/matrix";
|
||||||
import { decodeRecoveryKey, KeyBackupInfo } from "matrix-js-sdk/src/crypto-api";
|
import { decodeRecoveryKey, KeyBackupInfo, KeyBackupRestoreResult } 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 { logger } from "matrix-js-sdk/src/logger";
|
||||||
|
|
||||||
import { MatrixClientPeg } from "../../../../MatrixClientPeg";
|
import { MatrixClientPeg } from "../../../../MatrixClientPeg";
|
||||||
|
@ -42,12 +41,11 @@ interface IProps {
|
||||||
|
|
||||||
interface IState {
|
interface IState {
|
||||||
backupInfo: KeyBackupInfo | null;
|
backupInfo: KeyBackupInfo | null;
|
||||||
backupKeyStored: Record<string, SecretStorage.SecretStorageKeyDescription> | null;
|
|
||||||
loading: boolean;
|
loading: boolean;
|
||||||
loadError: boolean | null;
|
loadError: boolean | null;
|
||||||
restoreError: unknown | null;
|
restoreError: unknown | null;
|
||||||
recoveryKey: string;
|
recoveryKey: string;
|
||||||
recoverInfo: IKeyBackupRestoreResult | null;
|
recoverInfo: KeyBackupRestoreResult | null;
|
||||||
recoveryKeyValid: boolean;
|
recoveryKeyValid: boolean;
|
||||||
forceRecoveryKey: boolean;
|
forceRecoveryKey: boolean;
|
||||||
passPhrase: string;
|
passPhrase: string;
|
||||||
|
@ -72,7 +70,6 @@ export default class RestoreKeyBackupDialog extends React.PureComponent<IProps,
|
||||||
super(props);
|
super(props);
|
||||||
this.state = {
|
this.state = {
|
||||||
backupInfo: null,
|
backupInfo: null,
|
||||||
backupKeyStored: null,
|
|
||||||
loading: false,
|
loading: false,
|
||||||
loadError: null,
|
loadError: null,
|
||||||
restoreError: null,
|
restoreError: null,
|
||||||
|
@ -137,7 +134,8 @@ export default class RestoreKeyBackupDialog extends React.PureComponent<IProps,
|
||||||
};
|
};
|
||||||
|
|
||||||
private onPassPhraseNext = async (): Promise<void> => {
|
private onPassPhraseNext = async (): Promise<void> => {
|
||||||
if (!this.state.backupInfo) return;
|
const crypto = MatrixClientPeg.safeGet().getCrypto();
|
||||||
|
if (!crypto) return;
|
||||||
this.setState({
|
this.setState({
|
||||||
loading: true,
|
loading: true,
|
||||||
restoreError: null,
|
restoreError: null,
|
||||||
|
@ -146,13 +144,9 @@ export default class RestoreKeyBackupDialog extends React.PureComponent<IProps,
|
||||||
try {
|
try {
|
||||||
// We do still restore the key backup: we must ensure that the key backup key
|
// We do still restore the key backup: we must ensure that the key backup key
|
||||||
// is the right one and restoring it is currently the only way we can do this.
|
// is the right one and restoring it is currently the only way we can do this.
|
||||||
const recoverInfo = await MatrixClientPeg.safeGet().restoreKeyBackupWithPassword(
|
const recoverInfo = await crypto.restoreKeyBackupWithPassphrase(this.state.passPhrase, {
|
||||||
this.state.passPhrase,
|
progressCallback: this.progressCallback,
|
||||||
undefined,
|
});
|
||||||
undefined,
|
|
||||||
this.state.backupInfo,
|
|
||||||
{ progressCallback: this.progressCallback },
|
|
||||||
);
|
|
||||||
|
|
||||||
if (!this.props.showSummary) {
|
if (!this.props.showSummary) {
|
||||||
this.props.onFinished(true);
|
this.props.onFinished(true);
|
||||||
|
@ -172,7 +166,8 @@ export default class RestoreKeyBackupDialog extends React.PureComponent<IProps,
|
||||||
};
|
};
|
||||||
|
|
||||||
private onRecoveryKeyNext = async (): Promise<void> => {
|
private onRecoveryKeyNext = async (): Promise<void> => {
|
||||||
if (!this.state.recoveryKeyValid || !this.state.backupInfo) return;
|
const crypto = MatrixClientPeg.safeGet().getCrypto();
|
||||||
|
if (!this.state.recoveryKeyValid || !this.state.backupInfo?.version || !crypto) return;
|
||||||
|
|
||||||
this.setState({
|
this.setState({
|
||||||
loading: true,
|
loading: true,
|
||||||
|
@ -180,13 +175,14 @@ export default class RestoreKeyBackupDialog extends React.PureComponent<IProps,
|
||||||
restoreType: RestoreType.RecoveryKey,
|
restoreType: RestoreType.RecoveryKey,
|
||||||
});
|
});
|
||||||
try {
|
try {
|
||||||
const recoverInfo = await MatrixClientPeg.safeGet().restoreKeyBackupWithRecoveryKey(
|
await crypto.storeSessionBackupPrivateKey(
|
||||||
this.state.recoveryKey,
|
decodeRecoveryKey(this.state.recoveryKey),
|
||||||
undefined,
|
this.state.backupInfo.version,
|
||||||
undefined,
|
|
||||||
this.state.backupInfo,
|
|
||||||
{ progressCallback: this.progressCallback },
|
|
||||||
);
|
);
|
||||||
|
const recoverInfo = await crypto.restoreKeyBackup({
|
||||||
|
progressCallback: this.progressCallback,
|
||||||
|
});
|
||||||
|
|
||||||
if (!this.props.showSummary) {
|
if (!this.props.showSummary) {
|
||||||
this.props.onFinished(true);
|
this.props.onFinished(true);
|
||||||
return;
|
return;
|
||||||
|
@ -210,44 +206,41 @@ export default class RestoreKeyBackupDialog extends React.PureComponent<IProps,
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
private async restoreWithSecretStorage(): Promise<void> {
|
private async restoreWithSecretStorage(): Promise<boolean> {
|
||||||
|
const crypto = MatrixClientPeg.safeGet().getCrypto();
|
||||||
|
if (!crypto) return false;
|
||||||
|
|
||||||
this.setState({
|
this.setState({
|
||||||
loading: true,
|
|
||||||
restoreError: null,
|
restoreError: null,
|
||||||
restoreType: RestoreType.SecretStorage,
|
restoreType: RestoreType.SecretStorage,
|
||||||
});
|
});
|
||||||
try {
|
try {
|
||||||
|
let recoverInfo: KeyBackupRestoreResult | null = null;
|
||||||
// `accessSecretStorage` may prompt for storage access as needed.
|
// `accessSecretStorage` may prompt for storage access as needed.
|
||||||
await accessSecretStorage(async (): Promise<void> => {
|
await accessSecretStorage(async (): Promise<void> => {
|
||||||
if (!this.state.backupInfo) return;
|
await crypto.loadSessionBackupPrivateKeyFromSecretStorage();
|
||||||
await MatrixClientPeg.safeGet().restoreKeyBackupWithSecretStorage(
|
recoverInfo = await crypto.restoreKeyBackup({ progressCallback: this.progressCallback });
|
||||||
this.state.backupInfo,
|
|
||||||
undefined,
|
|
||||||
undefined,
|
|
||||||
{ progressCallback: this.progressCallback },
|
|
||||||
);
|
|
||||||
});
|
});
|
||||||
this.setState({
|
this.setState({
|
||||||
loading: false,
|
loading: false,
|
||||||
|
recoverInfo,
|
||||||
});
|
});
|
||||||
|
return true;
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
logger.log("Error restoring backup", e);
|
logger.log("restoreWithSecretStorage failed:", e);
|
||||||
this.setState({
|
this.setState({
|
||||||
restoreError: e,
|
restoreError: e,
|
||||||
loading: false,
|
loading: false,
|
||||||
});
|
});
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private async restoreWithCachedKey(backupInfo: KeyBackupInfo | null): Promise<boolean> {
|
private async restoreWithCachedKey(backupInfo: KeyBackupInfo | null): Promise<boolean> {
|
||||||
if (!backupInfo) return false;
|
const crypto = MatrixClientPeg.safeGet().getCrypto();
|
||||||
|
if (!crypto) return false;
|
||||||
try {
|
try {
|
||||||
const recoverInfo = await MatrixClientPeg.safeGet().restoreKeyBackupWithCache(
|
const recoverInfo = await crypto.restoreKeyBackup({ progressCallback: this.progressCallback });
|
||||||
undefined /* targetRoomId */,
|
|
||||||
undefined /* targetSessionId */,
|
|
||||||
backupInfo,
|
|
||||||
{ progressCallback: this.progressCallback },
|
|
||||||
);
|
|
||||||
this.setState({
|
this.setState({
|
||||||
recoverInfo,
|
recoverInfo,
|
||||||
});
|
});
|
||||||
|
@ -270,7 +263,6 @@ export default class RestoreKeyBackupDialog extends React.PureComponent<IProps,
|
||||||
const backupKeyStored = has4S ? await cli.isKeyBackupKeyStored() : null;
|
const backupKeyStored = has4S ? await cli.isKeyBackupKeyStored() : null;
|
||||||
this.setState({
|
this.setState({
|
||||||
backupInfo,
|
backupInfo,
|
||||||
backupKeyStored,
|
|
||||||
});
|
});
|
||||||
|
|
||||||
const gotCache = await this.restoreWithCachedKey(backupInfo);
|
const gotCache = await this.restoreWithCachedKey(backupInfo);
|
||||||
|
@ -282,9 +274,13 @@ export default class RestoreKeyBackupDialog extends React.PureComponent<IProps,
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// If the backup key is stored, we can proceed directly to restore.
|
const hasBackupFromSS = backupKeyStored && (await this.restoreWithSecretStorage());
|
||||||
if (backupKeyStored) {
|
if (hasBackupFromSS) {
|
||||||
return this.restoreWithSecretStorage();
|
logger.log("RestoreKeyBackupDialog: found backup key in secret storage");
|
||||||
|
this.setState({
|
||||||
|
loading: false,
|
||||||
|
});
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
this.setState({
|
this.setState({
|
||||||
|
@ -398,6 +394,7 @@ export default class RestoreKeyBackupDialog extends React.PureComponent<IProps,
|
||||||
|
|
||||||
<form className="mx_RestoreKeyBackupDialog_primaryContainer">
|
<form className="mx_RestoreKeyBackupDialog_primaryContainer">
|
||||||
<input
|
<input
|
||||||
|
data-testid="passphraseInput"
|
||||||
type="password"
|
type="password"
|
||||||
className="mx_RestoreKeyBackupDialog_passPhraseInput"
|
className="mx_RestoreKeyBackupDialog_passPhraseInput"
|
||||||
onChange={this.onPassPhraseChange}
|
onChange={this.onPassPhraseChange}
|
||||||
|
|
|
@ -151,7 +151,8 @@ export class SetupEncryptionStore extends EventEmitter {
|
||||||
await initialiseDehydration();
|
await initialiseDehydration();
|
||||||
|
|
||||||
if (backupInfo) {
|
if (backupInfo) {
|
||||||
await cli.restoreKeyBackupWithSecretStorage(backupInfo);
|
await cli.getCrypto()?.loadSessionBackupPrivateKeyFromSecretStorage();
|
||||||
|
await cli.getCrypto()?.restoreKeyBackup();
|
||||||
}
|
}
|
||||||
}).catch(reject);
|
}).catch(reject);
|
||||||
});
|
});
|
||||||
|
|
|
@ -131,6 +131,10 @@ export function createTestClient(): MatrixClient {
|
||||||
createRecoveryKeyFromPassphrase: jest.fn().mockResolvedValue({}),
|
createRecoveryKeyFromPassphrase: jest.fn().mockResolvedValue({}),
|
||||||
bootstrapSecretStorage: jest.fn(),
|
bootstrapSecretStorage: jest.fn(),
|
||||||
isDehydrationSupported: jest.fn().mockResolvedValue(false),
|
isDehydrationSupported: jest.fn().mockResolvedValue(false),
|
||||||
|
restoreKeyBackup: jest.fn(),
|
||||||
|
restoreKeyBackupWithPassphrase: jest.fn(),
|
||||||
|
loadSessionBackupPrivateKeyFromSecretStorage: jest.fn(),
|
||||||
|
storeSessionBackupPrivateKey: jest.fn(),
|
||||||
}),
|
}),
|
||||||
|
|
||||||
getPushActionsForEvent: jest.fn(),
|
getPushActionsForEvent: jest.fn(),
|
||||||
|
@ -275,6 +279,7 @@ export function createTestClient(): MatrixClient {
|
||||||
sendStickerMessage: jest.fn(),
|
sendStickerMessage: jest.fn(),
|
||||||
getLocalAliases: jest.fn().mockReturnValue([]),
|
getLocalAliases: jest.fn().mockReturnValue([]),
|
||||||
uploadDeviceSigningKeys: jest.fn(),
|
uploadDeviceSigningKeys: jest.fn(),
|
||||||
|
isKeyBackupKeyStored: jest.fn().mockResolvedValue(null),
|
||||||
} as unknown as MatrixClient;
|
} as unknown as MatrixClient;
|
||||||
|
|
||||||
client.reEmitter = new ReEmitter(client);
|
client.reEmitter = new ReEmitter(client);
|
||||||
|
|
|
@ -9,6 +9,8 @@
|
||||||
import React from "react";
|
import React from "react";
|
||||||
import { screen, render, waitFor } from "jest-matrix-react";
|
import { screen, render, waitFor } from "jest-matrix-react";
|
||||||
import userEvent from "@testing-library/user-event";
|
import userEvent from "@testing-library/user-event";
|
||||||
|
import { MatrixClient } from "matrix-js-sdk/src/matrix";
|
||||||
|
import { KeyBackupInfo } from "matrix-js-sdk/src/crypto-api";
|
||||||
// Needed to be able to mock decodeRecoveryKey
|
// Needed to be able to mock decodeRecoveryKey
|
||||||
// eslint-disable-next-line no-restricted-imports
|
// eslint-disable-next-line no-restricted-imports
|
||||||
import * as recoveryKeyModule from "matrix-js-sdk/src/crypto-api/recovery-key";
|
import * as recoveryKeyModule from "matrix-js-sdk/src/crypto-api/recovery-key";
|
||||||
|
@ -17,9 +19,16 @@ import RestoreKeyBackupDialog from "../../../../../../src/components/views/dialo
|
||||||
import { stubClient } from "../../../../../test-utils";
|
import { stubClient } from "../../../../../test-utils";
|
||||||
|
|
||||||
describe("<RestoreKeyBackupDialog />", () => {
|
describe("<RestoreKeyBackupDialog />", () => {
|
||||||
|
const keyBackupRestoreResult = {
|
||||||
|
total: 2,
|
||||||
|
imported: 1,
|
||||||
|
};
|
||||||
|
|
||||||
|
let matrixClient: MatrixClient;
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
stubClient();
|
matrixClient = stubClient();
|
||||||
jest.spyOn(recoveryKeyModule, "decodeRecoveryKey").mockReturnValue(new Uint8Array(32));
|
jest.spyOn(recoveryKeyModule, "decodeRecoveryKey").mockReturnValue(new Uint8Array(32));
|
||||||
|
jest.spyOn(matrixClient, "getKeyBackupVersion").mockResolvedValue({ version: "1" } as KeyBackupInfo);
|
||||||
});
|
});
|
||||||
|
|
||||||
it("should render", async () => {
|
it("should render", async () => {
|
||||||
|
@ -48,4 +57,71 @@ describe("<RestoreKeyBackupDialog />", () => {
|
||||||
await waitFor(() => expect(screen.getByText("👍 This looks like a valid Security Key!")).toBeInTheDocument());
|
await waitFor(() => expect(screen.getByText("👍 This looks like a valid Security Key!")).toBeInTheDocument());
|
||||||
expect(asFragment()).toMatchSnapshot();
|
expect(asFragment()).toMatchSnapshot();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("should restore key backup when the key is cached", async () => {
|
||||||
|
jest.spyOn(matrixClient.getCrypto()!, "restoreKeyBackup").mockResolvedValue(keyBackupRestoreResult);
|
||||||
|
|
||||||
|
const { asFragment } = render(<RestoreKeyBackupDialog onFinished={jest.fn()} />);
|
||||||
|
await waitFor(() => expect(screen.getByText("Successfully restored 1 keys")).toBeInTheDocument());
|
||||||
|
expect(asFragment()).toMatchSnapshot();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should restore key backup when the key is in secret storage", async () => {
|
||||||
|
jest.spyOn(matrixClient.getCrypto()!, "restoreKeyBackup")
|
||||||
|
// Reject when trying to restore from cache
|
||||||
|
.mockRejectedValueOnce(new Error("key backup not found"))
|
||||||
|
// Resolve when trying to restore from secret storage
|
||||||
|
.mockResolvedValue(keyBackupRestoreResult);
|
||||||
|
jest.spyOn(matrixClient.secretStorage, "hasKey").mockResolvedValue(true);
|
||||||
|
jest.spyOn(matrixClient, "isKeyBackupKeyStored").mockResolvedValue({});
|
||||||
|
|
||||||
|
const { asFragment } = render(<RestoreKeyBackupDialog onFinished={jest.fn()} />);
|
||||||
|
await waitFor(() => expect(screen.getByText("Successfully restored 1 keys")).toBeInTheDocument());
|
||||||
|
expect(asFragment()).toMatchSnapshot();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should restore key backup when security key is filled by user", async () => {
|
||||||
|
jest.spyOn(matrixClient.getCrypto()!, "restoreKeyBackup")
|
||||||
|
// Reject when trying to restore from cache
|
||||||
|
.mockRejectedValueOnce(new Error("key backup not found"))
|
||||||
|
// Resolve when trying to restore from recovery key
|
||||||
|
.mockResolvedValue(keyBackupRestoreResult);
|
||||||
|
|
||||||
|
const { asFragment } = render(<RestoreKeyBackupDialog onFinished={jest.fn()} />);
|
||||||
|
await waitFor(() => expect(screen.getByText("Enter Security Key")).toBeInTheDocument());
|
||||||
|
|
||||||
|
await userEvent.type(screen.getByRole("textbox"), "my security key");
|
||||||
|
await userEvent.click(screen.getByRole("button", { name: "Next" }));
|
||||||
|
|
||||||
|
await waitFor(() => expect(screen.getByText("Successfully restored 1 keys")).toBeInTheDocument());
|
||||||
|
expect(asFragment()).toMatchSnapshot();
|
||||||
|
});
|
||||||
|
|
||||||
|
test("should restore key backup when passphrase is filled", async () => {
|
||||||
|
// Determine that the passphrase is required
|
||||||
|
jest.spyOn(matrixClient, "getKeyBackupVersion").mockResolvedValue({
|
||||||
|
version: "1",
|
||||||
|
auth_data: {
|
||||||
|
private_key_salt: "salt",
|
||||||
|
private_key_iterations: 1,
|
||||||
|
},
|
||||||
|
} as KeyBackupInfo);
|
||||||
|
|
||||||
|
jest.spyOn(matrixClient.getCrypto()!, "restoreKeyBackup")
|
||||||
|
// Reject when trying to restore from cache
|
||||||
|
.mockRejectedValue(new Error("key backup not found"));
|
||||||
|
|
||||||
|
jest.spyOn(matrixClient.getCrypto()!, "restoreKeyBackupWithPassphrase").mockResolvedValue(
|
||||||
|
keyBackupRestoreResult,
|
||||||
|
);
|
||||||
|
|
||||||
|
const { asFragment } = render(<RestoreKeyBackupDialog onFinished={jest.fn()} />);
|
||||||
|
await waitFor(() => expect(screen.getByText("Enter Security Phrase")).toBeInTheDocument());
|
||||||
|
// Not role for password https://github.com/w3c/aria/issues/935
|
||||||
|
await userEvent.type(screen.getByTestId("passphraseInput"), "my passphrase");
|
||||||
|
await userEvent.click(screen.getByRole("button", { name: "Next" }));
|
||||||
|
|
||||||
|
await waitFor(() => expect(screen.getByText("Successfully restored 1 keys")).toBeInTheDocument());
|
||||||
|
expect(asFragment()).toMatchSnapshot();
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
|
@ -296,3 +296,263 @@ exports[`<RestoreKeyBackupDialog /> should render 1`] = `
|
||||||
/>
|
/>
|
||||||
</DocumentFragment>
|
</DocumentFragment>
|
||||||
`;
|
`;
|
||||||
|
|
||||||
|
exports[`<RestoreKeyBackupDialog /> should restore key backup when passphrase is filled 1`] = `
|
||||||
|
<DocumentFragment>
|
||||||
|
<div
|
||||||
|
data-focus-guard="true"
|
||||||
|
style="width: 1px; height: 0px; padding: 0px; overflow: hidden; position: fixed; top: 1px; left: 1px;"
|
||||||
|
tabindex="0"
|
||||||
|
/>
|
||||||
|
<div
|
||||||
|
aria-labelledby="mx_BaseDialog_title"
|
||||||
|
class="mx_RestoreKeyBackupDialog mx_Dialog_fixedWidth"
|
||||||
|
data-focus-lock-disabled="false"
|
||||||
|
role="dialog"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class="mx_Dialog_header"
|
||||||
|
>
|
||||||
|
<h1
|
||||||
|
class="mx_Heading_h3 mx_Dialog_title"
|
||||||
|
id="mx_BaseDialog_title"
|
||||||
|
>
|
||||||
|
Keys restored
|
||||||
|
</h1>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
class="mx_RestoreKeyBackupDialog_content"
|
||||||
|
>
|
||||||
|
<div>
|
||||||
|
<p>
|
||||||
|
Successfully restored 1 keys
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
Failed to decrypt 1 sessions!
|
||||||
|
</p>
|
||||||
|
<div
|
||||||
|
class="mx_Dialog_buttons"
|
||||||
|
>
|
||||||
|
<span
|
||||||
|
class="mx_Dialog_buttons_row"
|
||||||
|
>
|
||||||
|
<button
|
||||||
|
class="mx_Dialog_primary"
|
||||||
|
data-testid="dialog-primary-button"
|
||||||
|
type="button"
|
||||||
|
>
|
||||||
|
OK
|
||||||
|
</button>
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
aria-label="Close dialog"
|
||||||
|
class="mx_AccessibleButton mx_Dialog_cancelButton"
|
||||||
|
role="button"
|
||||||
|
tabindex="0"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
data-focus-guard="true"
|
||||||
|
style="width: 1px; height: 0px; padding: 0px; overflow: hidden; position: fixed; top: 1px; left: 1px;"
|
||||||
|
tabindex="0"
|
||||||
|
/>
|
||||||
|
</DocumentFragment>
|
||||||
|
`;
|
||||||
|
|
||||||
|
exports[`<RestoreKeyBackupDialog /> should restore key backup when security key is filled by user 1`] = `
|
||||||
|
<DocumentFragment>
|
||||||
|
<div
|
||||||
|
data-focus-guard="true"
|
||||||
|
style="width: 1px; height: 0px; padding: 0px; overflow: hidden; position: fixed; top: 1px; left: 1px;"
|
||||||
|
tabindex="0"
|
||||||
|
/>
|
||||||
|
<div
|
||||||
|
aria-labelledby="mx_BaseDialog_title"
|
||||||
|
class="mx_RestoreKeyBackupDialog mx_Dialog_fixedWidth"
|
||||||
|
data-focus-lock-disabled="false"
|
||||||
|
role="dialog"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class="mx_Dialog_header"
|
||||||
|
>
|
||||||
|
<h1
|
||||||
|
class="mx_Heading_h3 mx_Dialog_title"
|
||||||
|
id="mx_BaseDialog_title"
|
||||||
|
>
|
||||||
|
Keys restored
|
||||||
|
</h1>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
class="mx_RestoreKeyBackupDialog_content"
|
||||||
|
>
|
||||||
|
<div>
|
||||||
|
<p>
|
||||||
|
Successfully restored 1 keys
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
Failed to decrypt 1 sessions!
|
||||||
|
</p>
|
||||||
|
<div
|
||||||
|
class="mx_Dialog_buttons"
|
||||||
|
>
|
||||||
|
<span
|
||||||
|
class="mx_Dialog_buttons_row"
|
||||||
|
>
|
||||||
|
<button
|
||||||
|
class="mx_Dialog_primary"
|
||||||
|
data-testid="dialog-primary-button"
|
||||||
|
type="button"
|
||||||
|
>
|
||||||
|
OK
|
||||||
|
</button>
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
aria-label="Close dialog"
|
||||||
|
class="mx_AccessibleButton mx_Dialog_cancelButton"
|
||||||
|
role="button"
|
||||||
|
tabindex="0"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
data-focus-guard="true"
|
||||||
|
style="width: 1px; height: 0px; padding: 0px; overflow: hidden; position: fixed; top: 1px; left: 1px;"
|
||||||
|
tabindex="0"
|
||||||
|
/>
|
||||||
|
</DocumentFragment>
|
||||||
|
`;
|
||||||
|
|
||||||
|
exports[`<RestoreKeyBackupDialog /> should restore key backup when the key is cached 1`] = `
|
||||||
|
<DocumentFragment>
|
||||||
|
<div
|
||||||
|
data-focus-guard="true"
|
||||||
|
style="width: 1px; height: 0px; padding: 0px; overflow: hidden; position: fixed; top: 1px; left: 1px;"
|
||||||
|
tabindex="0"
|
||||||
|
/>
|
||||||
|
<div
|
||||||
|
aria-labelledby="mx_BaseDialog_title"
|
||||||
|
class="mx_RestoreKeyBackupDialog mx_Dialog_fixedWidth"
|
||||||
|
data-focus-lock-disabled="false"
|
||||||
|
role="dialog"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class="mx_Dialog_header"
|
||||||
|
>
|
||||||
|
<h1
|
||||||
|
class="mx_Heading_h3 mx_Dialog_title"
|
||||||
|
id="mx_BaseDialog_title"
|
||||||
|
>
|
||||||
|
Keys restored
|
||||||
|
</h1>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
class="mx_RestoreKeyBackupDialog_content"
|
||||||
|
>
|
||||||
|
<div>
|
||||||
|
<p>
|
||||||
|
Successfully restored 1 keys
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
Failed to decrypt 1 sessions!
|
||||||
|
</p>
|
||||||
|
<div
|
||||||
|
class="mx_Dialog_buttons"
|
||||||
|
>
|
||||||
|
<span
|
||||||
|
class="mx_Dialog_buttons_row"
|
||||||
|
>
|
||||||
|
<button
|
||||||
|
class="mx_Dialog_primary"
|
||||||
|
data-testid="dialog-primary-button"
|
||||||
|
type="button"
|
||||||
|
>
|
||||||
|
OK
|
||||||
|
</button>
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
aria-label="Close dialog"
|
||||||
|
class="mx_AccessibleButton mx_Dialog_cancelButton"
|
||||||
|
role="button"
|
||||||
|
tabindex="0"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
data-focus-guard="true"
|
||||||
|
style="width: 1px; height: 0px; padding: 0px; overflow: hidden; position: fixed; top: 1px; left: 1px;"
|
||||||
|
tabindex="0"
|
||||||
|
/>
|
||||||
|
</DocumentFragment>
|
||||||
|
`;
|
||||||
|
|
||||||
|
exports[`<RestoreKeyBackupDialog /> should restore key backup when the key is in secret storage 1`] = `
|
||||||
|
<DocumentFragment>
|
||||||
|
<div
|
||||||
|
data-focus-guard="true"
|
||||||
|
style="width: 1px; height: 0px; padding: 0px; overflow: hidden; position: fixed; top: 1px; left: 1px;"
|
||||||
|
tabindex="0"
|
||||||
|
/>
|
||||||
|
<div
|
||||||
|
aria-labelledby="mx_BaseDialog_title"
|
||||||
|
class="mx_RestoreKeyBackupDialog mx_Dialog_fixedWidth"
|
||||||
|
data-focus-lock-disabled="false"
|
||||||
|
role="dialog"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class="mx_Dialog_header"
|
||||||
|
>
|
||||||
|
<h1
|
||||||
|
class="mx_Heading_h3 mx_Dialog_title"
|
||||||
|
id="mx_BaseDialog_title"
|
||||||
|
>
|
||||||
|
Keys restored
|
||||||
|
</h1>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
class="mx_RestoreKeyBackupDialog_content"
|
||||||
|
>
|
||||||
|
<div>
|
||||||
|
<p>
|
||||||
|
Successfully restored 1 keys
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
Failed to decrypt 1 sessions!
|
||||||
|
</p>
|
||||||
|
<div
|
||||||
|
class="mx_Dialog_buttons"
|
||||||
|
>
|
||||||
|
<span
|
||||||
|
class="mx_Dialog_buttons_row"
|
||||||
|
>
|
||||||
|
<button
|
||||||
|
class="mx_Dialog_primary"
|
||||||
|
data-testid="dialog-primary-button"
|
||||||
|
type="button"
|
||||||
|
>
|
||||||
|
OK
|
||||||
|
</button>
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
aria-label="Close dialog"
|
||||||
|
class="mx_AccessibleButton mx_Dialog_cancelButton"
|
||||||
|
role="button"
|
||||||
|
tabindex="0"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
data-focus-guard="true"
|
||||||
|
style="width: 1px; height: 0px; padding: 0px; overflow: hidden; position: fixed; top: 1px; left: 1px;"
|
||||||
|
tabindex="0"
|
||||||
|
/>
|
||||||
|
</DocumentFragment>
|
||||||
|
`;
|
||||||
|
|
Loading…
Reference in a new issue