Convert /src/async-components/views/dialogs/security
to TS (#6923)
* Convert RecoveryMethodRemovedDialog to TS Signed-off-by: Šimon Brandner <simon.bra.ag@gmail.com> * Convert NewRecoveryMethodDialog to TS Signed-off-by: Šimon Brandner <simon.bra.ag@gmail.com> * Convert ImportE2eKeysDialog to TS Signed-off-by: Šimon Brandner <simon.bra.ag@gmail.com> * Convert ExportE2eKeysDialog to TS Signed-off-by: Šimon Brandner <simon.bra.ag@gmail.com> * Convert CreateSecretStorageDialog to TS Signed-off-by: Šimon Brandner <simon.bra.ag@gmail.com> * Convert CreateKeyBackupDialog to TS Signed-off-by: Šimon Brandner <simon.bra.ag@gmail.com> * Fix types This is somewhat hacky though I don't know of a better way to do this Signed-off-by: Šimon Brandner <simon.bra.ag@gmail.com>
This commit is contained in:
parent
39e98b9d7f
commit
c5bd1fb32d
12 changed files with 535 additions and 505 deletions
|
@ -32,6 +32,7 @@ import SecurityCustomisations from "./customisations/Security";
|
||||||
import { DeviceTrustLevel } from 'matrix-js-sdk/src/crypto/CrossSigning';
|
import { DeviceTrustLevel } from 'matrix-js-sdk/src/crypto/CrossSigning';
|
||||||
|
|
||||||
import { logger } from "matrix-js-sdk/src/logger";
|
import { logger } from "matrix-js-sdk/src/logger";
|
||||||
|
import { ComponentType } from "react";
|
||||||
|
|
||||||
// This stores the secret storage private keys in memory for the JS SDK. This is
|
// This stores the secret storage private keys in memory for the JS SDK. This is
|
||||||
// only meant to act as a cache to avoid prompting the user multiple times
|
// only meant to act as a cache to avoid prompting the user multiple times
|
||||||
|
@ -335,7 +336,9 @@ export async function accessSecretStorage(func = async () => { }, forceReset = f
|
||||||
// This dialog calls bootstrap itself after guiding the user through
|
// This dialog calls bootstrap itself after guiding the user through
|
||||||
// passphrase creation.
|
// passphrase creation.
|
||||||
const { finished } = Modal.createTrackedDialogAsync('Create Secret Storage dialog', '',
|
const { finished } = Modal.createTrackedDialogAsync('Create Secret Storage dialog', '',
|
||||||
import("./async-components/views/dialogs/security/CreateSecretStorageDialog"),
|
import(
|
||||||
|
"./async-components/views/dialogs/security/CreateSecretStorageDialog"
|
||||||
|
) as unknown as Promise<ComponentType<{}>>,
|
||||||
{
|
{
|
||||||
forceReset,
|
forceReset,
|
||||||
},
|
},
|
||||||
|
|
|
@ -17,56 +17,70 @@ limitations under the License.
|
||||||
|
|
||||||
import React, { createRef } from 'react';
|
import React, { createRef } from 'react';
|
||||||
import FileSaver from 'file-saver';
|
import FileSaver from 'file-saver';
|
||||||
import * as sdk from '../../../../index';
|
|
||||||
import { MatrixClientPeg } from '../../../../MatrixClientPeg';
|
import { MatrixClientPeg } from '../../../../MatrixClientPeg';
|
||||||
import PropTypes from 'prop-types';
|
|
||||||
import { _t, _td } from '../../../../languageHandler';
|
import { _t, _td } from '../../../../languageHandler';
|
||||||
import { accessSecretStorage } from '../../../../SecurityManager';
|
import { accessSecretStorage } from '../../../../SecurityManager';
|
||||||
import AccessibleButton from "../../../../components/views/elements/AccessibleButton";
|
import AccessibleButton from "../../../../components/views/elements/AccessibleButton";
|
||||||
import { copyNode } from "../../../../utils/strings";
|
import { copyNode } from "../../../../utils/strings";
|
||||||
import PassphraseField from "../../../../components/views/auth/PassphraseField";
|
import PassphraseField from "../../../../components/views/auth/PassphraseField";
|
||||||
|
import { IDialogProps } from "../../../../components/views/dialogs/IDialogProps";
|
||||||
|
import Field from "../../../../components/views/elements/Field";
|
||||||
|
import Spinner from "../../../../components/views/elements/Spinner";
|
||||||
|
import BaseDialog from "../../../../components/views/dialogs/BaseDialog";
|
||||||
|
import DialogButtons from "../../../../components/views/elements/DialogButtons";
|
||||||
|
import { IValidationResult } from "../../../../components/views/elements/Validation";
|
||||||
|
import { IPreparedKeyBackupVersion } from "matrix-js-sdk/src/crypto/backup";
|
||||||
import { logger } from "matrix-js-sdk/src/logger";
|
import { logger } from "matrix-js-sdk/src/logger";
|
||||||
|
|
||||||
const PHASE_PASSPHRASE = 0;
|
enum Phase {
|
||||||
const PHASE_PASSPHRASE_CONFIRM = 1;
|
Passphrase = "passphrase",
|
||||||
const PHASE_SHOWKEY = 2;
|
PassphraseConfirm = "passphrase_confirm",
|
||||||
const PHASE_KEEPITSAFE = 3;
|
ShowKey = "show_key",
|
||||||
const PHASE_BACKINGUP = 4;
|
KeepItSafe = "keep_it_safe",
|
||||||
const PHASE_DONE = 5;
|
BackingUp = "backing_up",
|
||||||
const PHASE_OPTOUT_CONFIRM = 6;
|
Done = "done",
|
||||||
|
OptOutConfirm = "opt_out_confirm",
|
||||||
|
}
|
||||||
|
|
||||||
const PASSWORD_MIN_SCORE = 4; // So secure, many characters, much complex, wow, etc, etc.
|
const PASSWORD_MIN_SCORE = 4; // So secure, many characters, much complex, wow, etc, etc.
|
||||||
|
|
||||||
|
interface IProps extends IDialogProps {}
|
||||||
|
|
||||||
|
interface IState {
|
||||||
|
secureSecretStorage: boolean;
|
||||||
|
phase: Phase;
|
||||||
|
passPhrase: string;
|
||||||
|
passPhraseValid: boolean;
|
||||||
|
passPhraseConfirm: string;
|
||||||
|
copied: boolean;
|
||||||
|
downloaded: boolean;
|
||||||
|
error?: string;
|
||||||
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Walks the user through the process of creating an e2e key backup
|
* Walks the user through the process of creating an e2e key backup
|
||||||
* on the server.
|
* on the server.
|
||||||
*/
|
*/
|
||||||
export default class CreateKeyBackupDialog extends React.PureComponent {
|
export default class CreateKeyBackupDialog extends React.PureComponent<IProps, IState> {
|
||||||
static propTypes = {
|
private keyBackupInfo: Pick<IPreparedKeyBackupVersion, "recovery_key" | "algorithm" | "auth_data">;
|
||||||
onFinished: PropTypes.func.isRequired,
|
private recoveryKeyNode = createRef<HTMLElement>();
|
||||||
}
|
private passphraseField = createRef<Field>();
|
||||||
|
|
||||||
constructor(props) {
|
constructor(props: IProps) {
|
||||||
super(props);
|
super(props);
|
||||||
|
|
||||||
this._recoveryKeyNode = null;
|
|
||||||
this._keyBackupInfo = null;
|
|
||||||
|
|
||||||
this.state = {
|
this.state = {
|
||||||
secureSecretStorage: null,
|
secureSecretStorage: null,
|
||||||
phase: PHASE_PASSPHRASE,
|
phase: Phase.Passphrase,
|
||||||
passPhrase: '',
|
passPhrase: '',
|
||||||
passPhraseValid: false,
|
passPhraseValid: false,
|
||||||
passPhraseConfirm: '',
|
passPhraseConfirm: '',
|
||||||
copied: false,
|
copied: false,
|
||||||
downloaded: false,
|
downloaded: false,
|
||||||
};
|
};
|
||||||
|
|
||||||
this._passphraseField = createRef();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async componentDidMount() {
|
public async componentDidMount(): Promise<void> {
|
||||||
const cli = MatrixClientPeg.get();
|
const cli = MatrixClientPeg.get();
|
||||||
const secureSecretStorage = await cli.doesServerSupportUnstableFeature("org.matrix.e2e_cross_signing");
|
const secureSecretStorage = await cli.doesServerSupportUnstableFeature("org.matrix.e2e_cross_signing");
|
||||||
this.setState({ secureSecretStorage });
|
this.setState({ secureSecretStorage });
|
||||||
|
@ -74,41 +88,37 @@ export default class CreateKeyBackupDialog extends React.PureComponent {
|
||||||
// If we're using secret storage, skip ahead to the backing up step, as
|
// If we're using secret storage, skip ahead to the backing up step, as
|
||||||
// `accessSecretStorage` will handle passphrases as needed.
|
// `accessSecretStorage` will handle passphrases as needed.
|
||||||
if (secureSecretStorage) {
|
if (secureSecretStorage) {
|
||||||
this.setState({ phase: PHASE_BACKINGUP });
|
this.setState({ phase: Phase.BackingUp });
|
||||||
this._createBackup();
|
this.createBackup();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
_collectRecoveryKeyNode = (n) => {
|
private onCopyClick = (): void => {
|
||||||
this._recoveryKeyNode = n;
|
const successful = copyNode(this.recoveryKeyNode.current);
|
||||||
}
|
|
||||||
|
|
||||||
_onCopyClick = () => {
|
|
||||||
const successful = copyNode(this._recoveryKeyNode);
|
|
||||||
if (successful) {
|
if (successful) {
|
||||||
this.setState({
|
this.setState({
|
||||||
copied: true,
|
copied: true,
|
||||||
phase: PHASE_KEEPITSAFE,
|
phase: Phase.KeepItSafe,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
};
|
||||||
|
|
||||||
_onDownloadClick = () => {
|
private onDownloadClick = (): void => {
|
||||||
const blob = new Blob([this._keyBackupInfo.recovery_key], {
|
const blob = new Blob([this.keyBackupInfo.recovery_key], {
|
||||||
type: 'text/plain;charset=us-ascii',
|
type: 'text/plain;charset=us-ascii',
|
||||||
});
|
});
|
||||||
FileSaver.saveAs(blob, 'security-key.txt');
|
FileSaver.saveAs(blob, 'security-key.txt');
|
||||||
|
|
||||||
this.setState({
|
this.setState({
|
||||||
downloaded: true,
|
downloaded: true,
|
||||||
phase: PHASE_KEEPITSAFE,
|
phase: Phase.KeepItSafe,
|
||||||
});
|
});
|
||||||
}
|
};
|
||||||
|
|
||||||
_createBackup = async () => {
|
private createBackup = async (): Promise<void> => {
|
||||||
const { secureSecretStorage } = this.state;
|
const { secureSecretStorage } = this.state;
|
||||||
this.setState({
|
this.setState({
|
||||||
phase: PHASE_BACKINGUP,
|
phase: Phase.BackingUp,
|
||||||
error: null,
|
error: null,
|
||||||
});
|
});
|
||||||
let info;
|
let info;
|
||||||
|
@ -123,12 +133,12 @@ export default class CreateKeyBackupDialog extends React.PureComponent {
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
info = await MatrixClientPeg.get().createKeyBackupVersion(
|
info = await MatrixClientPeg.get().createKeyBackupVersion(
|
||||||
this._keyBackupInfo,
|
this.keyBackupInfo,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
await MatrixClientPeg.get().scheduleAllGroupSessionsForBackup();
|
await MatrixClientPeg.get().scheduleAllGroupSessionsForBackup();
|
||||||
this.setState({
|
this.setState({
|
||||||
phase: PHASE_DONE,
|
phase: Phase.Done,
|
||||||
});
|
});
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
logger.error("Error creating key backup", e);
|
logger.error("Error creating key backup", e);
|
||||||
|
@ -143,97 +153,91 @@ export default class CreateKeyBackupDialog extends React.PureComponent {
|
||||||
error: e,
|
error: e,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
};
|
||||||
|
|
||||||
_onCancel = () => {
|
private onCancel = (): void => {
|
||||||
this.props.onFinished(false);
|
this.props.onFinished(false);
|
||||||
}
|
};
|
||||||
|
|
||||||
_onDone = () => {
|
private onDone = (): void => {
|
||||||
this.props.onFinished(true);
|
this.props.onFinished(true);
|
||||||
}
|
};
|
||||||
|
|
||||||
_onOptOutClick = () => {
|
private onSetUpClick = (): void => {
|
||||||
this.setState({ phase: PHASE_OPTOUT_CONFIRM });
|
this.setState({ phase: Phase.Passphrase });
|
||||||
}
|
};
|
||||||
|
|
||||||
_onSetUpClick = () => {
|
private onSkipPassPhraseClick = async (): Promise<void> => {
|
||||||
this.setState({ phase: PHASE_PASSPHRASE });
|
this.keyBackupInfo = await MatrixClientPeg.get().prepareKeyBackupVersion();
|
||||||
}
|
|
||||||
|
|
||||||
_onSkipPassPhraseClick = async () => {
|
|
||||||
this._keyBackupInfo = await MatrixClientPeg.get().prepareKeyBackupVersion();
|
|
||||||
this.setState({
|
this.setState({
|
||||||
copied: false,
|
copied: false,
|
||||||
downloaded: false,
|
downloaded: false,
|
||||||
phase: PHASE_SHOWKEY,
|
phase: Phase.ShowKey,
|
||||||
});
|
});
|
||||||
}
|
};
|
||||||
|
|
||||||
_onPassPhraseNextClick = async (e) => {
|
private onPassPhraseNextClick = async (e: React.FormEvent): Promise<void> => {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
if (!this._passphraseField.current) return; // unmounting
|
if (!this.passphraseField.current) return; // unmounting
|
||||||
|
|
||||||
await this._passphraseField.current.validate({ allowEmpty: false });
|
await this.passphraseField.current.validate({ allowEmpty: false });
|
||||||
if (!this._passphraseField.current.state.valid) {
|
if (!this.passphraseField.current.state.valid) {
|
||||||
this._passphraseField.current.focus();
|
this.passphraseField.current.focus();
|
||||||
this._passphraseField.current.validate({ allowEmpty: false, focused: true });
|
this.passphraseField.current.validate({ allowEmpty: false, focused: true });
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
this.setState({ phase: PHASE_PASSPHRASE_CONFIRM });
|
this.setState({ phase: Phase.PassphraseConfirm });
|
||||||
};
|
};
|
||||||
|
|
||||||
_onPassPhraseConfirmNextClick = async (e) => {
|
private onPassPhraseConfirmNextClick = async (e: React.FormEvent): Promise<void> => {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
|
|
||||||
if (this.state.passPhrase !== this.state.passPhraseConfirm) return;
|
if (this.state.passPhrase !== this.state.passPhraseConfirm) return;
|
||||||
|
|
||||||
this._keyBackupInfo = await MatrixClientPeg.get().prepareKeyBackupVersion(this.state.passPhrase);
|
this.keyBackupInfo = await MatrixClientPeg.get().prepareKeyBackupVersion(this.state.passPhrase);
|
||||||
this.setState({
|
this.setState({
|
||||||
copied: false,
|
copied: false,
|
||||||
downloaded: false,
|
downloaded: false,
|
||||||
phase: PHASE_SHOWKEY,
|
phase: Phase.ShowKey,
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
_onSetAgainClick = () => {
|
private onSetAgainClick = (): void => {
|
||||||
this.setState({
|
this.setState({
|
||||||
passPhrase: '',
|
passPhrase: '',
|
||||||
passPhraseValid: false,
|
passPhraseValid: false,
|
||||||
passPhraseConfirm: '',
|
passPhraseConfirm: '',
|
||||||
phase: PHASE_PASSPHRASE,
|
phase: Phase.Passphrase,
|
||||||
});
|
});
|
||||||
}
|
};
|
||||||
|
|
||||||
_onKeepItSafeBackClick = () => {
|
private onKeepItSafeBackClick = (): void => {
|
||||||
this.setState({
|
this.setState({
|
||||||
phase: PHASE_SHOWKEY,
|
phase: Phase.ShowKey,
|
||||||
});
|
});
|
||||||
}
|
};
|
||||||
|
|
||||||
_onPassPhraseValidate = (result) => {
|
private onPassPhraseValidate = (result: IValidationResult): void => {
|
||||||
this.setState({
|
this.setState({
|
||||||
passPhraseValid: result.valid,
|
passPhraseValid: result.valid,
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
_onPassPhraseChange = (e) => {
|
private onPassPhraseChange = (e: React.ChangeEvent<HTMLInputElement>): void => {
|
||||||
this.setState({
|
this.setState({
|
||||||
passPhrase: e.target.value,
|
passPhrase: e.target.value,
|
||||||
});
|
});
|
||||||
}
|
};
|
||||||
|
|
||||||
_onPassPhraseConfirmChange = (e) => {
|
private onPassPhraseConfirmChange = (e: React.ChangeEvent<HTMLInputElement>): void => {
|
||||||
this.setState({
|
this.setState({
|
||||||
passPhraseConfirm: e.target.value,
|
passPhraseConfirm: e.target.value,
|
||||||
});
|
});
|
||||||
}
|
};
|
||||||
|
|
||||||
_renderPhasePassPhrase() {
|
private renderPhasePassPhrase(): JSX.Element {
|
||||||
const DialogButtons = sdk.getComponent('views.elements.DialogButtons');
|
return <form onSubmit={this.onPassPhraseNextClick}>
|
||||||
|
|
||||||
return <form onSubmit={this._onPassPhraseNextClick}>
|
|
||||||
<p>{ _t(
|
<p>{ _t(
|
||||||
"<b>Warning</b>: You should only set up key backup from a trusted computer.", {},
|
"<b>Warning</b>: You should only set up key backup from a trusted computer.", {},
|
||||||
{ b: sub => <b>{ sub }</b> },
|
{ b: sub => <b>{ sub }</b> },
|
||||||
|
@ -248,11 +252,11 @@ export default class CreateKeyBackupDialog extends React.PureComponent {
|
||||||
<div className="mx_CreateKeyBackupDialog_passPhraseContainer">
|
<div className="mx_CreateKeyBackupDialog_passPhraseContainer">
|
||||||
<PassphraseField
|
<PassphraseField
|
||||||
className="mx_CreateKeyBackupDialog_passPhraseInput"
|
className="mx_CreateKeyBackupDialog_passPhraseInput"
|
||||||
onChange={this._onPassPhraseChange}
|
onChange={this.onPassPhraseChange}
|
||||||
minScore={PASSWORD_MIN_SCORE}
|
minScore={PASSWORD_MIN_SCORE}
|
||||||
value={this.state.passPhrase}
|
value={this.state.passPhrase}
|
||||||
onValidate={this._onPassPhraseValidate}
|
onValidate={this.onPassPhraseValidate}
|
||||||
fieldRef={this._passphraseField}
|
fieldRef={this.passphraseField}
|
||||||
autoFocus={true}
|
autoFocus={true}
|
||||||
label={_td("Enter a Security Phrase")}
|
label={_td("Enter a Security Phrase")}
|
||||||
labelEnterPassword={_td("Enter a Security Phrase")}
|
labelEnterPassword={_td("Enter a Security Phrase")}
|
||||||
|
@ -264,23 +268,21 @@ export default class CreateKeyBackupDialog extends React.PureComponent {
|
||||||
|
|
||||||
<DialogButtons
|
<DialogButtons
|
||||||
primaryButton={_t('Next')}
|
primaryButton={_t('Next')}
|
||||||
onPrimaryButtonClick={this._onPassPhraseNextClick}
|
onPrimaryButtonClick={this.onPassPhraseNextClick}
|
||||||
hasCancel={false}
|
hasCancel={false}
|
||||||
disabled={!this.state.passPhraseValid}
|
disabled={!this.state.passPhraseValid}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<details>
|
<details>
|
||||||
<summary>{ _t("Advanced") }</summary>
|
<summary>{ _t("Advanced") }</summary>
|
||||||
<AccessibleButton kind='primary' onClick={this._onSkipPassPhraseClick}>
|
<AccessibleButton kind='primary' onClick={this.onSkipPassPhraseClick}>
|
||||||
{ _t("Set up with a Security Key") }
|
{ _t("Set up with a Security Key") }
|
||||||
</AccessibleButton>
|
</AccessibleButton>
|
||||||
</details>
|
</details>
|
||||||
</form>;
|
</form>;
|
||||||
}
|
}
|
||||||
|
|
||||||
_renderPhasePassPhraseConfirm() {
|
private renderPhasePassPhraseConfirm(): JSX.Element {
|
||||||
const AccessibleButton = sdk.getComponent('elements.AccessibleButton');
|
|
||||||
|
|
||||||
let matchText;
|
let matchText;
|
||||||
let changeText;
|
let changeText;
|
||||||
if (this.state.passPhraseConfirm === this.state.passPhrase) {
|
if (this.state.passPhraseConfirm === this.state.passPhrase) {
|
||||||
|
@ -303,14 +305,13 @@ export default class CreateKeyBackupDialog extends React.PureComponent {
|
||||||
passPhraseMatch = <div className="mx_CreateKeyBackupDialog_passPhraseMatch">
|
passPhraseMatch = <div className="mx_CreateKeyBackupDialog_passPhraseMatch">
|
||||||
<div>{ matchText }</div>
|
<div>{ matchText }</div>
|
||||||
<div>
|
<div>
|
||||||
<AccessibleButton element="span" className="mx_linkButton" onClick={this._onSetAgainClick}>
|
<AccessibleButton element="span" className="mx_linkButton" onClick={this.onSetAgainClick}>
|
||||||
{ changeText }
|
{ changeText }
|
||||||
</AccessibleButton>
|
</AccessibleButton>
|
||||||
</div>
|
</div>
|
||||||
</div>;
|
</div>;
|
||||||
}
|
}
|
||||||
const DialogButtons = sdk.getComponent('views.elements.DialogButtons');
|
return <form onSubmit={this.onPassPhraseConfirmNextClick}>
|
||||||
return <form onSubmit={this._onPassPhraseConfirmNextClick}>
|
|
||||||
<p>{ _t(
|
<p>{ _t(
|
||||||
"Enter your Security Phrase a second time to confirm it.",
|
"Enter your Security Phrase a second time to confirm it.",
|
||||||
) }</p>
|
) }</p>
|
||||||
|
@ -318,7 +319,7 @@ export default class CreateKeyBackupDialog extends React.PureComponent {
|
||||||
<div className="mx_CreateKeyBackupDialog_passPhraseContainer">
|
<div className="mx_CreateKeyBackupDialog_passPhraseContainer">
|
||||||
<div>
|
<div>
|
||||||
<input type="password"
|
<input type="password"
|
||||||
onChange={this._onPassPhraseConfirmChange}
|
onChange={this.onPassPhraseConfirmChange}
|
||||||
value={this.state.passPhraseConfirm}
|
value={this.state.passPhraseConfirm}
|
||||||
className="mx_CreateKeyBackupDialog_passPhraseInput"
|
className="mx_CreateKeyBackupDialog_passPhraseInput"
|
||||||
placeholder={_t("Repeat your Security Phrase...")}
|
placeholder={_t("Repeat your Security Phrase...")}
|
||||||
|
@ -330,14 +331,14 @@ export default class CreateKeyBackupDialog extends React.PureComponent {
|
||||||
</div>
|
</div>
|
||||||
<DialogButtons
|
<DialogButtons
|
||||||
primaryButton={_t('Next')}
|
primaryButton={_t('Next')}
|
||||||
onPrimaryButtonClick={this._onPassPhraseConfirmNextClick}
|
onPrimaryButtonClick={this.onPassPhraseConfirmNextClick}
|
||||||
hasCancel={false}
|
hasCancel={false}
|
||||||
disabled={this.state.passPhrase !== this.state.passPhraseConfirm}
|
disabled={this.state.passPhrase !== this.state.passPhraseConfirm}
|
||||||
/>
|
/>
|
||||||
</form>;
|
</form>;
|
||||||
}
|
}
|
||||||
|
|
||||||
_renderPhaseShowKey() {
|
private renderPhaseShowKey(): JSX.Element {
|
||||||
return <div>
|
return <div>
|
||||||
<p>{ _t(
|
<p>{ _t(
|
||||||
"Your Security Key is a safety net - you can use it to restore " +
|
"Your Security Key is a safety net - you can use it to restore " +
|
||||||
|
@ -352,13 +353,13 @@ export default class CreateKeyBackupDialog extends React.PureComponent {
|
||||||
</div>
|
</div>
|
||||||
<div className="mx_CreateKeyBackupDialog_recoveryKeyContainer">
|
<div className="mx_CreateKeyBackupDialog_recoveryKeyContainer">
|
||||||
<div className="mx_CreateKeyBackupDialog_recoveryKey">
|
<div className="mx_CreateKeyBackupDialog_recoveryKey">
|
||||||
<code ref={this._collectRecoveryKeyNode}>{ this._keyBackupInfo.recovery_key }</code>
|
<code ref={this.recoveryKeyNode}>{ this.keyBackupInfo.recovery_key }</code>
|
||||||
</div>
|
</div>
|
||||||
<div className="mx_CreateKeyBackupDialog_recoveryKeyButtons">
|
<div className="mx_CreateKeyBackupDialog_recoveryKeyButtons">
|
||||||
<button className="mx_Dialog_primary" onClick={this._onCopyClick}>
|
<button className="mx_Dialog_primary" onClick={this.onCopyClick}>
|
||||||
{ _t("Copy") }
|
{ _t("Copy") }
|
||||||
</button>
|
</button>
|
||||||
<button className="mx_Dialog_primary" onClick={this._onDownloadClick}>
|
<button className="mx_Dialog_primary" onClick={this.onDownloadClick}>
|
||||||
{ _t("Download") }
|
{ _t("Download") }
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
@ -367,7 +368,7 @@ export default class CreateKeyBackupDialog extends React.PureComponent {
|
||||||
</div>;
|
</div>;
|
||||||
}
|
}
|
||||||
|
|
||||||
_renderPhaseKeepItSafe() {
|
private renderPhaseKeepItSafe(): JSX.Element {
|
||||||
let introText;
|
let introText;
|
||||||
if (this.state.copied) {
|
if (this.state.copied) {
|
||||||
introText = _t(
|
introText = _t(
|
||||||
|
@ -380,7 +381,6 @@ export default class CreateKeyBackupDialog extends React.PureComponent {
|
||||||
{}, { b: s => <b>{ s }</b> },
|
{}, { b: s => <b>{ s }</b> },
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
const DialogButtons = sdk.getComponent('views.elements.DialogButtons');
|
|
||||||
return <div>
|
return <div>
|
||||||
{ introText }
|
{ introText }
|
||||||
<ul>
|
<ul>
|
||||||
|
@ -389,107 +389,101 @@ export default class CreateKeyBackupDialog extends React.PureComponent {
|
||||||
<li>{ _t("<b>Copy it</b> to your personal cloud storage", {}, { b: s => <b>{ s }</b> }) }</li>
|
<li>{ _t("<b>Copy it</b> to your personal cloud storage", {}, { b: s => <b>{ s }</b> }) }</li>
|
||||||
</ul>
|
</ul>
|
||||||
<DialogButtons primaryButton={_t("Continue")}
|
<DialogButtons primaryButton={_t("Continue")}
|
||||||
onPrimaryButtonClick={this._createBackup}
|
onPrimaryButtonClick={this.createBackup}
|
||||||
hasCancel={false}>
|
hasCancel={false}>
|
||||||
<button onClick={this._onKeepItSafeBackClick}>{ _t("Back") }</button>
|
<button onClick={this.onKeepItSafeBackClick}>{ _t("Back") }</button>
|
||||||
</DialogButtons>
|
</DialogButtons>
|
||||||
</div>;
|
</div>;
|
||||||
}
|
}
|
||||||
|
|
||||||
_renderBusyPhase(text) {
|
private renderBusyPhase(): JSX.Element {
|
||||||
const Spinner = sdk.getComponent('views.elements.Spinner');
|
|
||||||
return <div>
|
return <div>
|
||||||
<Spinner />
|
<Spinner />
|
||||||
</div>;
|
</div>;
|
||||||
}
|
}
|
||||||
|
|
||||||
_renderPhaseDone() {
|
private renderPhaseDone(): JSX.Element {
|
||||||
const DialogButtons = sdk.getComponent('views.elements.DialogButtons');
|
|
||||||
return <div>
|
return <div>
|
||||||
<p>{ _t(
|
<p>{ _t(
|
||||||
"Your keys are being backed up (the first backup could take a few minutes).",
|
"Your keys are being backed up (the first backup could take a few minutes).",
|
||||||
) }</p>
|
) }</p>
|
||||||
<DialogButtons primaryButton={_t('OK')}
|
<DialogButtons primaryButton={_t('OK')}
|
||||||
onPrimaryButtonClick={this._onDone}
|
onPrimaryButtonClick={this.onDone}
|
||||||
hasCancel={false}
|
hasCancel={false}
|
||||||
/>
|
/>
|
||||||
</div>;
|
</div>;
|
||||||
}
|
}
|
||||||
|
|
||||||
_renderPhaseOptOutConfirm() {
|
private renderPhaseOptOutConfirm(): JSX.Element {
|
||||||
const DialogButtons = sdk.getComponent('views.elements.DialogButtons');
|
|
||||||
return <div>
|
return <div>
|
||||||
{ _t(
|
{ _t(
|
||||||
"Without setting up Secure Message Recovery, you won't be able to restore your " +
|
"Without setting up Secure Message Recovery, you won't be able to restore your " +
|
||||||
"encrypted message history if you log out or use another session.",
|
"encrypted message history if you log out or use another session.",
|
||||||
) }
|
) }
|
||||||
<DialogButtons primaryButton={_t('Set up Secure Message Recovery')}
|
<DialogButtons primaryButton={_t('Set up Secure Message Recovery')}
|
||||||
onPrimaryButtonClick={this._onSetUpClick}
|
onPrimaryButtonClick={this.onSetUpClick}
|
||||||
hasCancel={false}
|
hasCancel={false}
|
||||||
>
|
>
|
||||||
<button onClick={this._onCancel}>I understand, continue without</button>
|
<button onClick={this.onCancel}>I understand, continue without</button>
|
||||||
</DialogButtons>
|
</DialogButtons>
|
||||||
</div>;
|
</div>;
|
||||||
}
|
}
|
||||||
|
|
||||||
_titleForPhase(phase) {
|
private titleForPhase(phase: Phase): string {
|
||||||
switch (phase) {
|
switch (phase) {
|
||||||
case PHASE_PASSPHRASE:
|
case Phase.Passphrase:
|
||||||
return _t('Secure your backup with a Security Phrase');
|
return _t('Secure your backup with a Security Phrase');
|
||||||
case PHASE_PASSPHRASE_CONFIRM:
|
case Phase.PassphraseConfirm:
|
||||||
return _t('Confirm your Security Phrase');
|
return _t('Confirm your Security Phrase');
|
||||||
case PHASE_OPTOUT_CONFIRM:
|
case Phase.OptOutConfirm:
|
||||||
return _t('Warning!');
|
return _t('Warning!');
|
||||||
case PHASE_SHOWKEY:
|
case Phase.ShowKey:
|
||||||
case PHASE_KEEPITSAFE:
|
case Phase.KeepItSafe:
|
||||||
return _t('Make a copy of your Security Key');
|
return _t('Make a copy of your Security Key');
|
||||||
case PHASE_BACKINGUP:
|
case Phase.BackingUp:
|
||||||
return _t('Starting backup...');
|
return _t('Starting backup...');
|
||||||
case PHASE_DONE:
|
case Phase.Done:
|
||||||
return _t('Success!');
|
return _t('Success!');
|
||||||
default:
|
default:
|
||||||
return _t("Create key backup");
|
return _t("Create key backup");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
render() {
|
public render(): JSX.Element {
|
||||||
const BaseDialog = sdk.getComponent('views.dialogs.BaseDialog');
|
|
||||||
|
|
||||||
let content;
|
let content;
|
||||||
if (this.state.error) {
|
if (this.state.error) {
|
||||||
const DialogButtons = sdk.getComponent('views.elements.DialogButtons');
|
|
||||||
content = <div>
|
content = <div>
|
||||||
<p>{ _t("Unable to create key backup") }</p>
|
<p>{ _t("Unable to create key backup") }</p>
|
||||||
<div className="mx_Dialog_buttons">
|
<div className="mx_Dialog_buttons">
|
||||||
<DialogButtons primaryButton={_t('Retry')}
|
<DialogButtons primaryButton={_t('Retry')}
|
||||||
onPrimaryButtonClick={this._createBackup}
|
onPrimaryButtonClick={this.createBackup}
|
||||||
hasCancel={true}
|
hasCancel={true}
|
||||||
onCancel={this._onCancel}
|
onCancel={this.onCancel}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>;
|
</div>;
|
||||||
} else {
|
} else {
|
||||||
switch (this.state.phase) {
|
switch (this.state.phase) {
|
||||||
case PHASE_PASSPHRASE:
|
case Phase.Passphrase:
|
||||||
content = this._renderPhasePassPhrase();
|
content = this.renderPhasePassPhrase();
|
||||||
break;
|
break;
|
||||||
case PHASE_PASSPHRASE_CONFIRM:
|
case Phase.PassphraseConfirm:
|
||||||
content = this._renderPhasePassPhraseConfirm();
|
content = this.renderPhasePassPhraseConfirm();
|
||||||
break;
|
break;
|
||||||
case PHASE_SHOWKEY:
|
case Phase.ShowKey:
|
||||||
content = this._renderPhaseShowKey();
|
content = this.renderPhaseShowKey();
|
||||||
break;
|
break;
|
||||||
case PHASE_KEEPITSAFE:
|
case Phase.KeepItSafe:
|
||||||
content = this._renderPhaseKeepItSafe();
|
content = this.renderPhaseKeepItSafe();
|
||||||
break;
|
break;
|
||||||
case PHASE_BACKINGUP:
|
case Phase.BackingUp:
|
||||||
content = this._renderBusyPhase();
|
content = this.renderBusyPhase();
|
||||||
break;
|
break;
|
||||||
case PHASE_DONE:
|
case Phase.Done:
|
||||||
content = this._renderPhaseDone();
|
content = this.renderPhaseDone();
|
||||||
break;
|
break;
|
||||||
case PHASE_OPTOUT_CONFIRM:
|
case Phase.OptOutConfirm:
|
||||||
content = this._renderPhaseOptOutConfirm();
|
content = this.renderPhaseOptOutConfirm();
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -497,8 +491,8 @@ export default class CreateKeyBackupDialog extends React.PureComponent {
|
||||||
return (
|
return (
|
||||||
<BaseDialog className='mx_CreateKeyBackupDialog'
|
<BaseDialog className='mx_CreateKeyBackupDialog'
|
||||||
onFinished={this.props.onFinished}
|
onFinished={this.props.onFinished}
|
||||||
title={this._titleForPhase(this.state.phase)}
|
title={this.titleForPhase(this.state.phase)}
|
||||||
hasCancel={[PHASE_PASSPHRASE, PHASE_DONE].includes(this.state.phase)}
|
hasCancel={[Phase.Passphrase, Phase.Done].includes(this.state.phase)}
|
||||||
>
|
>
|
||||||
<div>
|
<div>
|
||||||
{ content }
|
{ content }
|
|
@ -16,8 +16,6 @@ limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import React, { createRef } from 'react';
|
import React, { createRef } from 'react';
|
||||||
import PropTypes from 'prop-types';
|
|
||||||
import * as sdk from '../../../../index';
|
|
||||||
import { MatrixClientPeg } from '../../../../MatrixClientPeg';
|
import { MatrixClientPeg } from '../../../../MatrixClientPeg';
|
||||||
import FileSaver from 'file-saver';
|
import FileSaver from 'file-saver';
|
||||||
import { _t, _td } from '../../../../languageHandler';
|
import { _t, _td } from '../../../../languageHandler';
|
||||||
|
@ -31,52 +29,105 @@ import AccessibleButton from "../../../../components/views/elements/AccessibleBu
|
||||||
import DialogButtons from "../../../../components/views/elements/DialogButtons";
|
import DialogButtons from "../../../../components/views/elements/DialogButtons";
|
||||||
import InlineSpinner from "../../../../components/views/elements/InlineSpinner";
|
import InlineSpinner from "../../../../components/views/elements/InlineSpinner";
|
||||||
import RestoreKeyBackupDialog from "../../../../components/views/dialogs/security/RestoreKeyBackupDialog";
|
import RestoreKeyBackupDialog from "../../../../components/views/dialogs/security/RestoreKeyBackupDialog";
|
||||||
import { getSecureBackupSetupMethods, isSecureBackupRequired } from '../../../../utils/WellKnownUtils';
|
import {
|
||||||
|
getSecureBackupSetupMethods,
|
||||||
|
isSecureBackupRequired,
|
||||||
|
SecureBackupSetupMethod,
|
||||||
|
} from '../../../../utils/WellKnownUtils';
|
||||||
import SecurityCustomisations from "../../../../customisations/Security";
|
import SecurityCustomisations from "../../../../customisations/Security";
|
||||||
|
|
||||||
import { logger } from "matrix-js-sdk/src/logger";
|
import { logger } from "matrix-js-sdk/src/logger";
|
||||||
|
import { IKeyBackupInfo } from "matrix-js-sdk/src/crypto/keybackup";
|
||||||
|
import { IDialogProps } from "../../../../components/views/dialogs/IDialogProps";
|
||||||
|
import Field from "../../../../components/views/elements/Field";
|
||||||
|
import BaseDialog from "../../../../components/views/dialogs/BaseDialog";
|
||||||
|
import Spinner from "../../../../components/views/elements/Spinner";
|
||||||
|
import { TrustInfo } from "matrix-js-sdk/src/crypto/backup";
|
||||||
|
import { CrossSigningKeys } from "matrix-js-sdk";
|
||||||
|
import InteractiveAuthDialog from "../../../../components/views/dialogs/InteractiveAuthDialog";
|
||||||
|
import { IRecoveryKey } from "matrix-js-sdk/src/crypto/api";
|
||||||
|
import { IValidationResult } from "../../../../components/views/elements/Validation";
|
||||||
|
|
||||||
const PHASE_LOADING = 0;
|
// I made a mistake while converting this and it has to be fixed!
|
||||||
const PHASE_LOADERROR = 1;
|
enum Phase {
|
||||||
const PHASE_CHOOSE_KEY_PASSPHRASE = 2;
|
Loading = "loading",
|
||||||
const PHASE_MIGRATE = 3;
|
LoadError = "load_error",
|
||||||
const PHASE_PASSPHRASE = 4;
|
ChooseKeyPassphrase = "choose_key_passphrase",
|
||||||
const PHASE_PASSPHRASE_CONFIRM = 5;
|
Migrate = "migrate",
|
||||||
const PHASE_SHOWKEY = 6;
|
Passphrase = "passphrase",
|
||||||
const PHASE_STORING = 8;
|
PassphraseConfirm = "passphrase_confirm",
|
||||||
const PHASE_CONFIRM_SKIP = 10;
|
ShowKey = "show_key",
|
||||||
|
Storing = "storing",
|
||||||
|
ConfirmSkip = "confirm_skip",
|
||||||
|
}
|
||||||
|
|
||||||
const PASSWORD_MIN_SCORE = 4; // So secure, many characters, much complex, wow, etc, etc.
|
const PASSWORD_MIN_SCORE = 4; // So secure, many characters, much complex, wow, etc, etc.
|
||||||
|
|
||||||
// these end up as strings from being values in the radio buttons, so just use strings
|
interface IProps extends IDialogProps {
|
||||||
const CREATE_STORAGE_OPTION_KEY = 'key';
|
hasCancel: boolean;
|
||||||
const CREATE_STORAGE_OPTION_PASSPHRASE = 'passphrase';
|
accountPassword: string;
|
||||||
|
forceReset: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface IState {
|
||||||
|
phase: Phase;
|
||||||
|
passPhrase: string;
|
||||||
|
passPhraseValid: boolean;
|
||||||
|
passPhraseConfirm: string;
|
||||||
|
copied: boolean;
|
||||||
|
downloaded: boolean;
|
||||||
|
setPassphrase: boolean;
|
||||||
|
backupInfo: IKeyBackupInfo;
|
||||||
|
backupSigStatus: TrustInfo;
|
||||||
|
// does the server offer a UI auth flow with just m.login.password
|
||||||
|
// for /keys/device_signing/upload?
|
||||||
|
canUploadKeysWithPasswordOnly: boolean;
|
||||||
|
accountPassword: string;
|
||||||
|
accountPasswordCorrect: boolean;
|
||||||
|
canSkip: boolean;
|
||||||
|
passPhraseKeySelected: string;
|
||||||
|
error?: string;
|
||||||
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Walks the user through the process of creating a passphrase to guard Secure
|
* Walks the user through the process of creating a passphrase to guard Secure
|
||||||
* Secret Storage in account data.
|
* Secret Storage in account data.
|
||||||
*/
|
*/
|
||||||
export default class CreateSecretStorageDialog extends React.PureComponent {
|
export default class CreateSecretStorageDialog extends React.PureComponent<IProps, IState> {
|
||||||
static propTypes = {
|
public static defaultProps: Partial<IProps> = {
|
||||||
hasCancel: PropTypes.bool,
|
|
||||||
accountPassword: PropTypes.string,
|
|
||||||
forceReset: PropTypes.bool,
|
|
||||||
};
|
|
||||||
|
|
||||||
static defaultProps = {
|
|
||||||
hasCancel: true,
|
hasCancel: true,
|
||||||
forceReset: false,
|
forceReset: false,
|
||||||
};
|
};
|
||||||
|
private recoveryKey: IRecoveryKey;
|
||||||
|
private backupKey: Uint8Array;
|
||||||
|
private recoveryKeyNode = createRef<HTMLElement>();
|
||||||
|
private passphraseField = createRef<Field>();
|
||||||
|
|
||||||
constructor(props) {
|
constructor(props: IProps) {
|
||||||
super(props);
|
super(props);
|
||||||
|
|
||||||
this._recoveryKey = null;
|
let passPhraseKeySelected;
|
||||||
this._recoveryKeyNode = null;
|
const setupMethods = getSecureBackupSetupMethods();
|
||||||
this._backupKey = null;
|
if (setupMethods.includes(SecureBackupSetupMethod.Key)) {
|
||||||
|
passPhraseKeySelected = SecureBackupSetupMethod.Key;
|
||||||
|
} else {
|
||||||
|
passPhraseKeySelected = SecureBackupSetupMethod.Passphrase;
|
||||||
|
}
|
||||||
|
|
||||||
|
const accountPassword = props.accountPassword || "";
|
||||||
|
let canUploadKeysWithPasswordOnly = null;
|
||||||
|
if (accountPassword) {
|
||||||
|
// If we have an account password in memory, let's simplify and
|
||||||
|
// assume it means password auth is also supported for device
|
||||||
|
// signing key upload as well. This avoids hitting the server to
|
||||||
|
// test auth flows, which may be slow under high load.
|
||||||
|
canUploadKeysWithPasswordOnly = true;
|
||||||
|
} else {
|
||||||
|
this.queryKeyUploadAuth();
|
||||||
|
}
|
||||||
|
|
||||||
this.state = {
|
this.state = {
|
||||||
phase: PHASE_LOADING,
|
phase: Phase.Loading,
|
||||||
passPhrase: '',
|
passPhrase: '',
|
||||||
passPhraseValid: false,
|
passPhraseValid: false,
|
||||||
passPhraseConfirm: '',
|
passPhraseConfirm: '',
|
||||||
|
@ -87,55 +138,37 @@ export default class CreateSecretStorageDialog extends React.PureComponent {
|
||||||
backupSigStatus: null,
|
backupSigStatus: null,
|
||||||
// does the server offer a UI auth flow with just m.login.password
|
// does the server offer a UI auth flow with just m.login.password
|
||||||
// for /keys/device_signing/upload?
|
// for /keys/device_signing/upload?
|
||||||
canUploadKeysWithPasswordOnly: null,
|
|
||||||
accountPassword: props.accountPassword || "",
|
|
||||||
accountPasswordCorrect: null,
|
accountPasswordCorrect: null,
|
||||||
canSkip: !isSecureBackupRequired(),
|
canSkip: !isSecureBackupRequired(),
|
||||||
|
canUploadKeysWithPasswordOnly,
|
||||||
|
passPhraseKeySelected,
|
||||||
|
accountPassword,
|
||||||
};
|
};
|
||||||
|
|
||||||
const setupMethods = getSecureBackupSetupMethods();
|
MatrixClientPeg.get().on('crypto.keyBackupStatus', this.onKeyBackupStatusChange);
|
||||||
if (setupMethods.includes("key")) {
|
|
||||||
this.state.passPhraseKeySelected = CREATE_STORAGE_OPTION_KEY;
|
|
||||||
} else {
|
|
||||||
this.state.passPhraseKeySelected = CREATE_STORAGE_OPTION_PASSPHRASE;
|
|
||||||
}
|
|
||||||
|
|
||||||
this._passphraseField = createRef();
|
this.getInitialPhase();
|
||||||
|
|
||||||
MatrixClientPeg.get().on('crypto.keyBackupStatus', this._onKeyBackupStatusChange);
|
|
||||||
|
|
||||||
if (this.state.accountPassword) {
|
|
||||||
// If we have an account password in memory, let's simplify and
|
|
||||||
// assume it means password auth is also supported for device
|
|
||||||
// signing key upload as well. This avoids hitting the server to
|
|
||||||
// test auth flows, which may be slow under high load.
|
|
||||||
this.state.canUploadKeysWithPasswordOnly = true;
|
|
||||||
} else {
|
|
||||||
this._queryKeyUploadAuth();
|
|
||||||
}
|
|
||||||
|
|
||||||
this._getInitialPhase();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
componentWillUnmount() {
|
public componentWillUnmount(): void {
|
||||||
MatrixClientPeg.get().removeListener('crypto.keyBackupStatus', this._onKeyBackupStatusChange);
|
MatrixClientPeg.get().removeListener('crypto.keyBackupStatus', this.onKeyBackupStatusChange);
|
||||||
}
|
}
|
||||||
|
|
||||||
_getInitialPhase() {
|
private getInitialPhase(): void {
|
||||||
const keyFromCustomisations = SecurityCustomisations.createSecretStorageKey?.();
|
const keyFromCustomisations = SecurityCustomisations.createSecretStorageKey?.();
|
||||||
if (keyFromCustomisations) {
|
if (keyFromCustomisations) {
|
||||||
logger.log("Created key via customisations, jumping to bootstrap step");
|
logger.log("Created key via customisations, jumping to bootstrap step");
|
||||||
this._recoveryKey = {
|
this.recoveryKey = {
|
||||||
privateKey: keyFromCustomisations,
|
privateKey: keyFromCustomisations,
|
||||||
};
|
};
|
||||||
this._bootstrapSecretStorage();
|
this.bootstrapSecretStorage();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
this._fetchBackupInfo();
|
this.fetchBackupInfo();
|
||||||
}
|
}
|
||||||
|
|
||||||
async _fetchBackupInfo() {
|
private async fetchBackupInfo(): Promise<{ backupInfo: IKeyBackupInfo, backupSigStatus: TrustInfo }> {
|
||||||
try {
|
try {
|
||||||
const backupInfo = await MatrixClientPeg.get().getKeyBackupVersion();
|
const backupInfo = await MatrixClientPeg.get().getKeyBackupVersion();
|
||||||
const backupSigStatus = (
|
const backupSigStatus = (
|
||||||
|
@ -144,7 +177,7 @@ export default class CreateSecretStorageDialog extends React.PureComponent {
|
||||||
);
|
);
|
||||||
|
|
||||||
const { forceReset } = this.props;
|
const { forceReset } = this.props;
|
||||||
const phase = (backupInfo && !forceReset) ? PHASE_MIGRATE : PHASE_CHOOSE_KEY_PASSPHRASE;
|
const phase = (backupInfo && !forceReset) ? Phase.Migrate : Phase.ChooseKeyPassphrase;
|
||||||
|
|
||||||
this.setState({
|
this.setState({
|
||||||
phase,
|
phase,
|
||||||
|
@ -157,13 +190,13 @@ export default class CreateSecretStorageDialog extends React.PureComponent {
|
||||||
backupSigStatus,
|
backupSigStatus,
|
||||||
};
|
};
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
this.setState({ phase: PHASE_LOADERROR });
|
this.setState({ phase: Phase.LoadError });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async _queryKeyUploadAuth() {
|
private async queryKeyUploadAuth(): Promise<void> {
|
||||||
try {
|
try {
|
||||||
await MatrixClientPeg.get().uploadDeviceSigningKeys(null, {});
|
await MatrixClientPeg.get().uploadDeviceSigningKeys(null, {} as CrossSigningKeys);
|
||||||
// We should never get here: the server should always require
|
// We should never get here: the server should always require
|
||||||
// UI auth to upload device signing keys. If we do, we upload
|
// UI auth to upload device signing keys. If we do, we upload
|
||||||
// no keys which would be a no-op.
|
// no keys which would be a no-op.
|
||||||
|
@ -182,59 +215,55 @@ export default class CreateSecretStorageDialog extends React.PureComponent {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
_onKeyBackupStatusChange = () => {
|
private onKeyBackupStatusChange = (): void => {
|
||||||
if (this.state.phase === PHASE_MIGRATE) this._fetchBackupInfo();
|
if (this.state.phase === Phase.Migrate) this.fetchBackupInfo();
|
||||||
}
|
};
|
||||||
|
|
||||||
_onKeyPassphraseChange = e => {
|
private onKeyPassphraseChange = (e: React.ChangeEvent<HTMLInputElement>): void => {
|
||||||
this.setState({
|
this.setState({
|
||||||
passPhraseKeySelected: e.target.value,
|
passPhraseKeySelected: e.target.value,
|
||||||
});
|
});
|
||||||
}
|
};
|
||||||
|
|
||||||
_collectRecoveryKeyNode = (n) => {
|
private onChooseKeyPassphraseFormSubmit = async (): Promise<void> => {
|
||||||
this._recoveryKeyNode = n;
|
if (this.state.passPhraseKeySelected === SecureBackupSetupMethod.Key) {
|
||||||
}
|
this.recoveryKey =
|
||||||
|
|
||||||
_onChooseKeyPassphraseFormSubmit = async () => {
|
|
||||||
if (this.state.passPhraseKeySelected === CREATE_STORAGE_OPTION_KEY) {
|
|
||||||
this._recoveryKey =
|
|
||||||
await MatrixClientPeg.get().createRecoveryKeyFromPassphrase();
|
await MatrixClientPeg.get().createRecoveryKeyFromPassphrase();
|
||||||
this.setState({
|
this.setState({
|
||||||
copied: false,
|
copied: false,
|
||||||
downloaded: false,
|
downloaded: false,
|
||||||
setPassphrase: false,
|
setPassphrase: false,
|
||||||
phase: PHASE_SHOWKEY,
|
phase: Phase.ShowKey,
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
this.setState({
|
this.setState({
|
||||||
copied: false,
|
copied: false,
|
||||||
downloaded: false,
|
downloaded: false,
|
||||||
phase: PHASE_PASSPHRASE,
|
phase: Phase.Passphrase,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
};
|
||||||
|
|
||||||
_onMigrateFormSubmit = (e) => {
|
private onMigrateFormSubmit = (e: React.FormEvent): void => {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
if (this.state.backupSigStatus.usable) {
|
if (this.state.backupSigStatus.usable) {
|
||||||
this._bootstrapSecretStorage();
|
this.bootstrapSecretStorage();
|
||||||
} else {
|
} else {
|
||||||
this._restoreBackup();
|
this.restoreBackup();
|
||||||
}
|
}
|
||||||
}
|
};
|
||||||
|
|
||||||
_onCopyClick = () => {
|
private onCopyClick = (): void => {
|
||||||
const successful = copyNode(this._recoveryKeyNode);
|
const successful = copyNode(this.recoveryKeyNode.current);
|
||||||
if (successful) {
|
if (successful) {
|
||||||
this.setState({
|
this.setState({
|
||||||
copied: true,
|
copied: true,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
};
|
||||||
|
|
||||||
_onDownloadClick = () => {
|
private onDownloadClick = (): void => {
|
||||||
const blob = new Blob([this._recoveryKey.encodedPrivateKey], {
|
const blob = new Blob([this.recoveryKey.encodedPrivateKey], {
|
||||||
type: 'text/plain;charset=us-ascii',
|
type: 'text/plain;charset=us-ascii',
|
||||||
});
|
});
|
||||||
FileSaver.saveAs(blob, 'security-key.txt');
|
FileSaver.saveAs(blob, 'security-key.txt');
|
||||||
|
@ -242,9 +271,9 @@ export default class CreateSecretStorageDialog extends React.PureComponent {
|
||||||
this.setState({
|
this.setState({
|
||||||
downloaded: true,
|
downloaded: true,
|
||||||
});
|
});
|
||||||
}
|
};
|
||||||
|
|
||||||
_doBootstrapUIAuth = async (makeRequest) => {
|
private doBootstrapUIAuth = async (makeRequest: (authData: any) => void): Promise<void> => {
|
||||||
if (this.state.canUploadKeysWithPasswordOnly && this.state.accountPassword) {
|
if (this.state.canUploadKeysWithPasswordOnly && this.state.accountPassword) {
|
||||||
await makeRequest({
|
await makeRequest({
|
||||||
type: 'm.login.password',
|
type: 'm.login.password',
|
||||||
|
@ -258,8 +287,6 @@ export default class CreateSecretStorageDialog extends React.PureComponent {
|
||||||
password: this.state.accountPassword,
|
password: this.state.accountPassword,
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
const InteractiveAuthDialog = sdk.getComponent("dialogs.InteractiveAuthDialog");
|
|
||||||
|
|
||||||
const dialogAesthetics = {
|
const dialogAesthetics = {
|
||||||
[SSOAuthEntry.PHASE_PREAUTH]: {
|
[SSOAuthEntry.PHASE_PREAUTH]: {
|
||||||
title: _t("Use Single Sign On to continue"),
|
title: _t("Use Single Sign On to continue"),
|
||||||
|
@ -292,11 +319,11 @@ export default class CreateSecretStorageDialog extends React.PureComponent {
|
||||||
throw new Error("Cross-signing key upload auth canceled");
|
throw new Error("Cross-signing key upload auth canceled");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
};
|
||||||
|
|
||||||
_bootstrapSecretStorage = async () => {
|
private bootstrapSecretStorage = async (): Promise<void> => {
|
||||||
this.setState({
|
this.setState({
|
||||||
phase: PHASE_STORING,
|
phase: Phase.Storing,
|
||||||
error: null,
|
error: null,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -308,7 +335,7 @@ export default class CreateSecretStorageDialog extends React.PureComponent {
|
||||||
if (forceReset) {
|
if (forceReset) {
|
||||||
logger.log("Forcing secret storage reset");
|
logger.log("Forcing secret storage reset");
|
||||||
await cli.bootstrapSecretStorage({
|
await cli.bootstrapSecretStorage({
|
||||||
createSecretStorageKey: async () => this._recoveryKey,
|
createSecretStorageKey: async () => this.recoveryKey,
|
||||||
setupNewKeyBackup: true,
|
setupNewKeyBackup: true,
|
||||||
setupNewSecretStorage: true,
|
setupNewSecretStorage: true,
|
||||||
});
|
});
|
||||||
|
@ -321,18 +348,18 @@ export default class CreateSecretStorageDialog extends React.PureComponent {
|
||||||
// keys (and also happen to skip all post-authentication flows at the
|
// keys (and also happen to skip all post-authentication flows at the
|
||||||
// moment via token login)
|
// moment via token login)
|
||||||
await cli.bootstrapCrossSigning({
|
await cli.bootstrapCrossSigning({
|
||||||
authUploadDeviceSigningKeys: this._doBootstrapUIAuth,
|
authUploadDeviceSigningKeys: this.doBootstrapUIAuth,
|
||||||
});
|
});
|
||||||
await cli.bootstrapSecretStorage({
|
await cli.bootstrapSecretStorage({
|
||||||
createSecretStorageKey: async () => this._recoveryKey,
|
createSecretStorageKey: async () => this.recoveryKey,
|
||||||
keyBackupInfo: this.state.backupInfo,
|
keyBackupInfo: this.state.backupInfo,
|
||||||
setupNewKeyBackup: !this.state.backupInfo,
|
setupNewKeyBackup: !this.state.backupInfo,
|
||||||
getKeyBackupPassphrase: () => {
|
getKeyBackupPassphrase: async () => {
|
||||||
// We may already have the backup key if we earlier went
|
// We may already have the backup key if we earlier went
|
||||||
// through the restore backup path, so pass it along
|
// through the restore backup path, so pass it along
|
||||||
// rather than prompting again.
|
// rather than prompting again.
|
||||||
if (this._backupKey) {
|
if (this.backupKey) {
|
||||||
return this._backupKey;
|
return this.backupKey;
|
||||||
}
|
}
|
||||||
return promptForBackupPassphrase();
|
return promptForBackupPassphrase();
|
||||||
},
|
},
|
||||||
|
@ -344,27 +371,23 @@ export default class CreateSecretStorageDialog extends React.PureComponent {
|
||||||
this.setState({
|
this.setState({
|
||||||
accountPassword: '',
|
accountPassword: '',
|
||||||
accountPasswordCorrect: false,
|
accountPasswordCorrect: false,
|
||||||
phase: PHASE_MIGRATE,
|
phase: Phase.Migrate,
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
this.setState({ error: e });
|
this.setState({ error: e });
|
||||||
}
|
}
|
||||||
logger.error("Error bootstrapping secret storage", e);
|
logger.error("Error bootstrapping secret storage", e);
|
||||||
}
|
}
|
||||||
}
|
};
|
||||||
|
|
||||||
_onCancel = () => {
|
private onCancel = (): void => {
|
||||||
this.props.onFinished(false);
|
this.props.onFinished(false);
|
||||||
}
|
};
|
||||||
|
|
||||||
_onDone = () => {
|
private restoreBackup = async (): Promise<void> => {
|
||||||
this.props.onFinished(true);
|
|
||||||
}
|
|
||||||
|
|
||||||
_restoreBackup = async () => {
|
|
||||||
// It's possible we'll need the backup key later on for bootstrapping,
|
// It's possible we'll need the backup key later on for bootstrapping,
|
||||||
// so let's stash it here, rather than prompting for it twice.
|
// so let's stash it here, rather than prompting for it twice.
|
||||||
const keyCallback = k => this._backupKey = k;
|
const keyCallback = k => this.backupKey = k;
|
||||||
|
|
||||||
const { finished } = Modal.createTrackedDialog(
|
const { finished } = Modal.createTrackedDialog(
|
||||||
'Restore Backup', '', RestoreKeyBackupDialog,
|
'Restore Backup', '', RestoreKeyBackupDialog,
|
||||||
|
@ -376,103 +399,103 @@ export default class CreateSecretStorageDialog extends React.PureComponent {
|
||||||
);
|
);
|
||||||
|
|
||||||
await finished;
|
await finished;
|
||||||
const { backupSigStatus } = await this._fetchBackupInfo();
|
const { backupSigStatus } = await this.fetchBackupInfo();
|
||||||
if (
|
if (
|
||||||
backupSigStatus.usable &&
|
backupSigStatus.usable &&
|
||||||
this.state.canUploadKeysWithPasswordOnly &&
|
this.state.canUploadKeysWithPasswordOnly &&
|
||||||
this.state.accountPassword
|
this.state.accountPassword
|
||||||
) {
|
) {
|
||||||
this._bootstrapSecretStorage();
|
this.bootstrapSecretStorage();
|
||||||
}
|
}
|
||||||
}
|
};
|
||||||
|
|
||||||
_onLoadRetryClick = () => {
|
private onLoadRetryClick = (): void => {
|
||||||
this.setState({ phase: PHASE_LOADING });
|
this.setState({ phase: Phase.Loading });
|
||||||
this._fetchBackupInfo();
|
this.fetchBackupInfo();
|
||||||
}
|
};
|
||||||
|
|
||||||
_onShowKeyContinueClick = () => {
|
private onShowKeyContinueClick = (): void => {
|
||||||
this._bootstrapSecretStorage();
|
this.bootstrapSecretStorage();
|
||||||
}
|
};
|
||||||
|
|
||||||
_onCancelClick = () => {
|
private onCancelClick = (): void => {
|
||||||
this.setState({ phase: PHASE_CONFIRM_SKIP });
|
this.setState({ phase: Phase.ConfirmSkip });
|
||||||
}
|
};
|
||||||
|
|
||||||
_onGoBackClick = () => {
|
private onGoBackClick = (): void => {
|
||||||
this.setState({ phase: PHASE_CHOOSE_KEY_PASSPHRASE });
|
this.setState({ phase: Phase.ChooseKeyPassphrase });
|
||||||
}
|
};
|
||||||
|
|
||||||
_onPassPhraseNextClick = async (e) => {
|
private onPassPhraseNextClick = async (e: React.FormEvent) => {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
if (!this._passphraseField.current) return; // unmounting
|
if (!this.passphraseField.current) return; // unmounting
|
||||||
|
|
||||||
await this._passphraseField.current.validate({ allowEmpty: false });
|
await this.passphraseField.current.validate({ allowEmpty: false });
|
||||||
if (!this._passphraseField.current.state.valid) {
|
if (!this.passphraseField.current.state.valid) {
|
||||||
this._passphraseField.current.focus();
|
this.passphraseField.current.focus();
|
||||||
this._passphraseField.current.validate({ allowEmpty: false, focused: true });
|
this.passphraseField.current.validate({ allowEmpty: false, focused: true });
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
this.setState({ phase: PHASE_PASSPHRASE_CONFIRM });
|
this.setState({ phase: Phase.PassphraseConfirm });
|
||||||
};
|
};
|
||||||
|
|
||||||
_onPassPhraseConfirmNextClick = async (e) => {
|
private onPassPhraseConfirmNextClick = async (e: React.FormEvent) => {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
|
|
||||||
if (this.state.passPhrase !== this.state.passPhraseConfirm) return;
|
if (this.state.passPhrase !== this.state.passPhraseConfirm) return;
|
||||||
|
|
||||||
this._recoveryKey =
|
this.recoveryKey =
|
||||||
await MatrixClientPeg.get().createRecoveryKeyFromPassphrase(this.state.passPhrase);
|
await MatrixClientPeg.get().createRecoveryKeyFromPassphrase(this.state.passPhrase);
|
||||||
this.setState({
|
this.setState({
|
||||||
copied: false,
|
copied: false,
|
||||||
downloaded: false,
|
downloaded: false,
|
||||||
setPassphrase: true,
|
setPassphrase: true,
|
||||||
phase: PHASE_SHOWKEY,
|
phase: Phase.ShowKey,
|
||||||
});
|
});
|
||||||
}
|
};
|
||||||
|
|
||||||
_onSetAgainClick = () => {
|
private onSetAgainClick = (): void => {
|
||||||
this.setState({
|
this.setState({
|
||||||
passPhrase: '',
|
passPhrase: '',
|
||||||
passPhraseValid: false,
|
passPhraseValid: false,
|
||||||
passPhraseConfirm: '',
|
passPhraseConfirm: '',
|
||||||
phase: PHASE_PASSPHRASE,
|
phase: Phase.Passphrase,
|
||||||
});
|
});
|
||||||
}
|
};
|
||||||
|
|
||||||
_onPassPhraseValidate = (result) => {
|
private onPassPhraseValidate = (result: IValidationResult): void => {
|
||||||
this.setState({
|
this.setState({
|
||||||
passPhraseValid: result.valid,
|
passPhraseValid: result.valid,
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
_onPassPhraseChange = (e) => {
|
private onPassPhraseChange = (e: React.ChangeEvent<HTMLInputElement>): void => {
|
||||||
this.setState({
|
this.setState({
|
||||||
passPhrase: e.target.value,
|
passPhrase: e.target.value,
|
||||||
});
|
});
|
||||||
}
|
};
|
||||||
|
|
||||||
_onPassPhraseConfirmChange = (e) => {
|
private onPassPhraseConfirmChange = (e: React.ChangeEvent<HTMLInputElement>): void => {
|
||||||
this.setState({
|
this.setState({
|
||||||
passPhraseConfirm: e.target.value,
|
passPhraseConfirm: e.target.value,
|
||||||
});
|
});
|
||||||
}
|
};
|
||||||
|
|
||||||
_onAccountPasswordChange = (e) => {
|
private onAccountPasswordChange = (e: React.ChangeEvent<HTMLInputElement>): void => {
|
||||||
this.setState({
|
this.setState({
|
||||||
accountPassword: e.target.value,
|
accountPassword: e.target.value,
|
||||||
});
|
});
|
||||||
}
|
};
|
||||||
|
|
||||||
_renderOptionKey() {
|
private renderOptionKey(): JSX.Element {
|
||||||
return (
|
return (
|
||||||
<StyledRadioButton
|
<StyledRadioButton
|
||||||
key={CREATE_STORAGE_OPTION_KEY}
|
key={SecureBackupSetupMethod.Key}
|
||||||
value={CREATE_STORAGE_OPTION_KEY}
|
value={SecureBackupSetupMethod.Key}
|
||||||
name="keyPassphrase"
|
name="keyPassphrase"
|
||||||
checked={this.state.passPhraseKeySelected === CREATE_STORAGE_OPTION_KEY}
|
checked={this.state.passPhraseKeySelected === SecureBackupSetupMethod.Key}
|
||||||
onChange={this._onKeyPassphraseChange}
|
onChange={this.onKeyPassphraseChange}
|
||||||
outlined
|
outlined
|
||||||
>
|
>
|
||||||
<div className="mx_CreateSecretStorageDialog_optionTitle">
|
<div className="mx_CreateSecretStorageDialog_optionTitle">
|
||||||
|
@ -484,14 +507,14 @@ export default class CreateSecretStorageDialog extends React.PureComponent {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
_renderOptionPassphrase() {
|
private renderOptionPassphrase(): JSX.Element {
|
||||||
return (
|
return (
|
||||||
<StyledRadioButton
|
<StyledRadioButton
|
||||||
key={CREATE_STORAGE_OPTION_PASSPHRASE}
|
key={SecureBackupSetupMethod.Passphrase}
|
||||||
value={CREATE_STORAGE_OPTION_PASSPHRASE}
|
value={SecureBackupSetupMethod.Passphrase}
|
||||||
name="keyPassphrase"
|
name="keyPassphrase"
|
||||||
checked={this.state.passPhraseKeySelected === CREATE_STORAGE_OPTION_PASSPHRASE}
|
checked={this.state.passPhraseKeySelected === SecureBackupSetupMethod.Passphrase}
|
||||||
onChange={this._onKeyPassphraseChange}
|
onChange={this.onKeyPassphraseChange}
|
||||||
outlined
|
outlined
|
||||||
>
|
>
|
||||||
<div className="mx_CreateSecretStorageDialog_optionTitle">
|
<div className="mx_CreateSecretStorageDialog_optionTitle">
|
||||||
|
@ -503,12 +526,14 @@ export default class CreateSecretStorageDialog extends React.PureComponent {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
_renderPhaseChooseKeyPassphrase() {
|
private renderPhaseChooseKeyPassphrase(): JSX.Element {
|
||||||
const setupMethods = getSecureBackupSetupMethods();
|
const setupMethods = getSecureBackupSetupMethods();
|
||||||
const optionKey = setupMethods.includes("key") ? this._renderOptionKey() : null;
|
const optionKey = setupMethods.includes(SecureBackupSetupMethod.Key) ? this.renderOptionKey() : null;
|
||||||
const optionPassphrase = setupMethods.includes("passphrase") ? this._renderOptionPassphrase() : null;
|
const optionPassphrase = setupMethods.includes(SecureBackupSetupMethod.Passphrase)
|
||||||
|
? this.renderOptionPassphrase()
|
||||||
|
: null;
|
||||||
|
|
||||||
return <form onSubmit={this._onChooseKeyPassphraseFormSubmit}>
|
return <form onSubmit={this.onChooseKeyPassphraseFormSubmit}>
|
||||||
<p className="mx_CreateSecretStorageDialog_centeredBody">{ _t(
|
<p className="mx_CreateSecretStorageDialog_centeredBody">{ _t(
|
||||||
"Safeguard against losing access to encrypted messages & data by " +
|
"Safeguard against losing access to encrypted messages & data by " +
|
||||||
"backing up encryption keys on your server.",
|
"backing up encryption keys on your server.",
|
||||||
|
@ -519,20 +544,19 @@ export default class CreateSecretStorageDialog extends React.PureComponent {
|
||||||
</div>
|
</div>
|
||||||
<DialogButtons
|
<DialogButtons
|
||||||
primaryButton={_t("Continue")}
|
primaryButton={_t("Continue")}
|
||||||
onPrimaryButtonClick={this._onChooseKeyPassphraseFormSubmit}
|
onPrimaryButtonClick={this.onChooseKeyPassphraseFormSubmit}
|
||||||
onCancel={this._onCancelClick}
|
onCancel={this.onCancelClick}
|
||||||
hasCancel={this.state.canSkip}
|
hasCancel={this.state.canSkip}
|
||||||
/>
|
/>
|
||||||
</form>;
|
</form>;
|
||||||
}
|
}
|
||||||
|
|
||||||
_renderPhaseMigrate() {
|
private renderPhaseMigrate(): JSX.Element {
|
||||||
// TODO: This is a temporary screen so people who have the labs flag turned on and
|
// TODO: This is a temporary screen so people who have the labs flag turned on and
|
||||||
// click the button are aware they're making a change to their account.
|
// click the button are aware they're making a change to their account.
|
||||||
// Once we're confident enough in this (and it's supported enough) we can do
|
// Once we're confident enough in this (and it's supported enough) we can do
|
||||||
// it automatically.
|
// it automatically.
|
||||||
// https://github.com/vector-im/element-web/issues/11696
|
// https://github.com/vector-im/element-web/issues/11696
|
||||||
const Field = sdk.getComponent('views.elements.Field');
|
|
||||||
|
|
||||||
let authPrompt;
|
let authPrompt;
|
||||||
let nextCaption = _t("Next");
|
let nextCaption = _t("Next");
|
||||||
|
@ -543,7 +567,7 @@ export default class CreateSecretStorageDialog extends React.PureComponent {
|
||||||
type="password"
|
type="password"
|
||||||
label={_t("Password")}
|
label={_t("Password")}
|
||||||
value={this.state.accountPassword}
|
value={this.state.accountPassword}
|
||||||
onChange={this._onAccountPasswordChange}
|
onChange={this.onAccountPasswordChange}
|
||||||
forceValidity={this.state.accountPasswordCorrect === false ? false : null}
|
forceValidity={this.state.accountPasswordCorrect === false ? false : null}
|
||||||
autoFocus={true}
|
autoFocus={true}
|
||||||
/></div>
|
/></div>
|
||||||
|
@ -559,7 +583,7 @@ export default class CreateSecretStorageDialog extends React.PureComponent {
|
||||||
</p>;
|
</p>;
|
||||||
}
|
}
|
||||||
|
|
||||||
return <form onSubmit={this._onMigrateFormSubmit}>
|
return <form onSubmit={this.onMigrateFormSubmit}>
|
||||||
<p>{ _t(
|
<p>{ _t(
|
||||||
"Upgrade this session to allow it to verify other sessions, " +
|
"Upgrade this session to allow it to verify other sessions, " +
|
||||||
"granting them access to encrypted messages and marking them " +
|
"granting them access to encrypted messages and marking them " +
|
||||||
|
@ -568,19 +592,19 @@ export default class CreateSecretStorageDialog extends React.PureComponent {
|
||||||
<div>{ authPrompt }</div>
|
<div>{ authPrompt }</div>
|
||||||
<DialogButtons
|
<DialogButtons
|
||||||
primaryButton={nextCaption}
|
primaryButton={nextCaption}
|
||||||
onPrimaryButtonClick={this._onMigrateFormSubmit}
|
onPrimaryButtonClick={this.onMigrateFormSubmit}
|
||||||
hasCancel={false}
|
hasCancel={false}
|
||||||
primaryDisabled={this.state.canUploadKeysWithPasswordOnly && !this.state.accountPassword}
|
primaryDisabled={this.state.canUploadKeysWithPasswordOnly && !this.state.accountPassword}
|
||||||
>
|
>
|
||||||
<button type="button" className="danger" onClick={this._onCancelClick}>
|
<button type="button" className="danger" onClick={this.onCancelClick}>
|
||||||
{ _t('Skip') }
|
{ _t('Skip') }
|
||||||
</button>
|
</button>
|
||||||
</DialogButtons>
|
</DialogButtons>
|
||||||
</form>;
|
</form>;
|
||||||
}
|
}
|
||||||
|
|
||||||
_renderPhasePassPhrase() {
|
private renderPhasePassPhrase(): JSX.Element {
|
||||||
return <form onSubmit={this._onPassPhraseNextClick}>
|
return <form onSubmit={this.onPassPhraseNextClick}>
|
||||||
<p>{ _t(
|
<p>{ _t(
|
||||||
"Enter a security phrase only you know, as it’s used to safeguard your data. " +
|
"Enter a security phrase only you know, as it’s used to safeguard your data. " +
|
||||||
"To be secure, you shouldn’t re-use your account password.",
|
"To be secure, you shouldn’t re-use your account password.",
|
||||||
|
@ -589,11 +613,11 @@ export default class CreateSecretStorageDialog extends React.PureComponent {
|
||||||
<div className="mx_CreateSecretStorageDialog_passPhraseContainer">
|
<div className="mx_CreateSecretStorageDialog_passPhraseContainer">
|
||||||
<PassphraseField
|
<PassphraseField
|
||||||
className="mx_CreateSecretStorageDialog_passPhraseField"
|
className="mx_CreateSecretStorageDialog_passPhraseField"
|
||||||
onChange={this._onPassPhraseChange}
|
onChange={this.onPassPhraseChange}
|
||||||
minScore={PASSWORD_MIN_SCORE}
|
minScore={PASSWORD_MIN_SCORE}
|
||||||
value={this.state.passPhrase}
|
value={this.state.passPhrase}
|
||||||
onValidate={this._onPassPhraseValidate}
|
onValidate={this.onPassPhraseValidate}
|
||||||
fieldRef={this._passphraseField}
|
fieldRef={this.passphraseField}
|
||||||
autoFocus={true}
|
autoFocus={true}
|
||||||
label={_td("Enter a Security Phrase")}
|
label={_td("Enter a Security Phrase")}
|
||||||
labelEnterPassword={_td("Enter a Security Phrase")}
|
labelEnterPassword={_td("Enter a Security Phrase")}
|
||||||
|
@ -604,21 +628,19 @@ export default class CreateSecretStorageDialog extends React.PureComponent {
|
||||||
|
|
||||||
<DialogButtons
|
<DialogButtons
|
||||||
primaryButton={_t('Continue')}
|
primaryButton={_t('Continue')}
|
||||||
onPrimaryButtonClick={this._onPassPhraseNextClick}
|
onPrimaryButtonClick={this.onPassPhraseNextClick}
|
||||||
hasCancel={false}
|
hasCancel={false}
|
||||||
disabled={!this.state.passPhraseValid}
|
disabled={!this.state.passPhraseValid}
|
||||||
>
|
>
|
||||||
<button type="button"
|
<button type="button"
|
||||||
onClick={this._onCancelClick}
|
onClick={this.onCancelClick}
|
||||||
className="danger"
|
className="danger"
|
||||||
>{ _t("Cancel") }</button>
|
>{ _t("Cancel") }</button>
|
||||||
</DialogButtons>
|
</DialogButtons>
|
||||||
</form>;
|
</form>;
|
||||||
}
|
}
|
||||||
|
|
||||||
_renderPhasePassPhraseConfirm() {
|
private renderPhasePassPhraseConfirm(): JSX.Element {
|
||||||
const Field = sdk.getComponent('views.elements.Field');
|
|
||||||
|
|
||||||
let matchText;
|
let matchText;
|
||||||
let changeText;
|
let changeText;
|
||||||
if (this.state.passPhraseConfirm === this.state.passPhrase) {
|
if (this.state.passPhraseConfirm === this.state.passPhrase) {
|
||||||
|
@ -641,20 +663,20 @@ export default class CreateSecretStorageDialog extends React.PureComponent {
|
||||||
passPhraseMatch = <div>
|
passPhraseMatch = <div>
|
||||||
<div>{ matchText }</div>
|
<div>{ matchText }</div>
|
||||||
<div>
|
<div>
|
||||||
<AccessibleButton element="span" className="mx_linkButton" onClick={this._onSetAgainClick}>
|
<AccessibleButton element="span" className="mx_linkButton" onClick={this.onSetAgainClick}>
|
||||||
{ changeText }
|
{ changeText }
|
||||||
</AccessibleButton>
|
</AccessibleButton>
|
||||||
</div>
|
</div>
|
||||||
</div>;
|
</div>;
|
||||||
}
|
}
|
||||||
return <form onSubmit={this._onPassPhraseConfirmNextClick}>
|
return <form onSubmit={this.onPassPhraseConfirmNextClick}>
|
||||||
<p>{ _t(
|
<p>{ _t(
|
||||||
"Enter your Security Phrase a second time to confirm it.",
|
"Enter your Security Phrase a second time to confirm it.",
|
||||||
) }</p>
|
) }</p>
|
||||||
<div className="mx_CreateSecretStorageDialog_passPhraseContainer">
|
<div className="mx_CreateSecretStorageDialog_passPhraseContainer">
|
||||||
<Field
|
<Field
|
||||||
type="password"
|
type="password"
|
||||||
onChange={this._onPassPhraseConfirmChange}
|
onChange={this.onPassPhraseConfirmChange}
|
||||||
value={this.state.passPhraseConfirm}
|
value={this.state.passPhraseConfirm}
|
||||||
className="mx_CreateSecretStorageDialog_passPhraseField"
|
className="mx_CreateSecretStorageDialog_passPhraseField"
|
||||||
label={_t("Confirm your Security Phrase")}
|
label={_t("Confirm your Security Phrase")}
|
||||||
|
@ -667,24 +689,24 @@ export default class CreateSecretStorageDialog extends React.PureComponent {
|
||||||
</div>
|
</div>
|
||||||
<DialogButtons
|
<DialogButtons
|
||||||
primaryButton={_t('Continue')}
|
primaryButton={_t('Continue')}
|
||||||
onPrimaryButtonClick={this._onPassPhraseConfirmNextClick}
|
onPrimaryButtonClick={this.onPassPhraseConfirmNextClick}
|
||||||
hasCancel={false}
|
hasCancel={false}
|
||||||
disabled={this.state.passPhrase !== this.state.passPhraseConfirm}
|
disabled={this.state.passPhrase !== this.state.passPhraseConfirm}
|
||||||
>
|
>
|
||||||
<button type="button"
|
<button type="button"
|
||||||
onClick={this._onCancelClick}
|
onClick={this.onCancelClick}
|
||||||
className="danger"
|
className="danger"
|
||||||
>{ _t("Skip") }</button>
|
>{ _t("Skip") }</button>
|
||||||
</DialogButtons>
|
</DialogButtons>
|
||||||
</form>;
|
</form>;
|
||||||
}
|
}
|
||||||
|
|
||||||
_renderPhaseShowKey() {
|
private renderPhaseShowKey(): JSX.Element {
|
||||||
let continueButton;
|
let continueButton;
|
||||||
if (this.state.phase === PHASE_SHOWKEY) {
|
if (this.state.phase === Phase.ShowKey) {
|
||||||
continueButton = <DialogButtons primaryButton={_t("Continue")}
|
continueButton = <DialogButtons primaryButton={_t("Continue")}
|
||||||
disabled={!this.state.downloaded && !this.state.copied && !this.state.setPassphrase}
|
disabled={!this.state.downloaded && !this.state.copied && !this.state.setPassphrase}
|
||||||
onPrimaryButtonClick={this._onShowKeyContinueClick}
|
onPrimaryButtonClick={this.onShowKeyContinueClick}
|
||||||
hasCancel={false}
|
hasCancel={false}
|
||||||
/>;
|
/>;
|
||||||
} else {
|
} else {
|
||||||
|
@ -700,13 +722,13 @@ export default class CreateSecretStorageDialog extends React.PureComponent {
|
||||||
<div className="mx_CreateSecretStorageDialog_primaryContainer">
|
<div className="mx_CreateSecretStorageDialog_primaryContainer">
|
||||||
<div className="mx_CreateSecretStorageDialog_recoveryKeyContainer">
|
<div className="mx_CreateSecretStorageDialog_recoveryKeyContainer">
|
||||||
<div className="mx_CreateSecretStorageDialog_recoveryKey">
|
<div className="mx_CreateSecretStorageDialog_recoveryKey">
|
||||||
<code ref={this._collectRecoveryKeyNode}>{ this._recoveryKey.encodedPrivateKey }</code>
|
<code ref={this.recoveryKeyNode}>{ this.recoveryKey.encodedPrivateKey }</code>
|
||||||
</div>
|
</div>
|
||||||
<div className="mx_CreateSecretStorageDialog_recoveryKeyButtons">
|
<div className="mx_CreateSecretStorageDialog_recoveryKeyButtons">
|
||||||
<AccessibleButton kind='primary'
|
<AccessibleButton kind='primary'
|
||||||
className="mx_Dialog_primary"
|
className="mx_Dialog_primary"
|
||||||
onClick={this._onDownloadClick}
|
onClick={this.onDownloadClick}
|
||||||
disabled={this.state.phase === PHASE_STORING}
|
disabled={this.state.phase === Phase.Storing}
|
||||||
>
|
>
|
||||||
{ _t("Download") }
|
{ _t("Download") }
|
||||||
</AccessibleButton>
|
</AccessibleButton>
|
||||||
|
@ -714,8 +736,8 @@ export default class CreateSecretStorageDialog extends React.PureComponent {
|
||||||
<AccessibleButton
|
<AccessibleButton
|
||||||
kind='primary'
|
kind='primary'
|
||||||
className="mx_Dialog_primary mx_CreateSecretStorageDialog_recoveryKeyButtons_copyBtn"
|
className="mx_Dialog_primary mx_CreateSecretStorageDialog_recoveryKeyButtons_copyBtn"
|
||||||
onClick={this._onCopyClick}
|
onClick={this.onCopyClick}
|
||||||
disabled={this.state.phase === PHASE_STORING}
|
disabled={this.state.phase === Phase.Storing}
|
||||||
>
|
>
|
||||||
{ this.state.copied ? _t("Copied!") : _t("Copy") }
|
{ this.state.copied ? _t("Copied!") : _t("Copy") }
|
||||||
</AccessibleButton>
|
</AccessibleButton>
|
||||||
|
@ -726,27 +748,26 @@ export default class CreateSecretStorageDialog extends React.PureComponent {
|
||||||
</div>;
|
</div>;
|
||||||
}
|
}
|
||||||
|
|
||||||
_renderBusyPhase() {
|
private renderBusyPhase(): JSX.Element {
|
||||||
const Spinner = sdk.getComponent('views.elements.Spinner');
|
|
||||||
return <div>
|
return <div>
|
||||||
<Spinner />
|
<Spinner />
|
||||||
</div>;
|
</div>;
|
||||||
}
|
}
|
||||||
|
|
||||||
_renderPhaseLoadError() {
|
private renderPhaseLoadError(): JSX.Element {
|
||||||
return <div>
|
return <div>
|
||||||
<p>{ _t("Unable to query secret storage status") }</p>
|
<p>{ _t("Unable to query secret storage status") }</p>
|
||||||
<div className="mx_Dialog_buttons">
|
<div className="mx_Dialog_buttons">
|
||||||
<DialogButtons primaryButton={_t('Retry')}
|
<DialogButtons primaryButton={_t('Retry')}
|
||||||
onPrimaryButtonClick={this._onLoadRetryClick}
|
onPrimaryButtonClick={this.onLoadRetryClick}
|
||||||
hasCancel={this.state.canSkip}
|
hasCancel={this.state.canSkip}
|
||||||
onCancel={this._onCancel}
|
onCancel={this.onCancel}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>;
|
</div>;
|
||||||
}
|
}
|
||||||
|
|
||||||
_renderPhaseSkipConfirm() {
|
private renderPhaseSkipConfirm(): JSX.Element {
|
||||||
return <div>
|
return <div>
|
||||||
<p>{ _t(
|
<p>{ _t(
|
||||||
"If you cancel now, you may lose encrypted messages & data if you lose access to your logins.",
|
"If you cancel now, you may lose encrypted messages & data if you lose access to your logins.",
|
||||||
|
@ -755,98 +776,96 @@ export default class CreateSecretStorageDialog extends React.PureComponent {
|
||||||
"You can also set up Secure Backup & manage your keys in Settings.",
|
"You can also set up Secure Backup & manage your keys in Settings.",
|
||||||
) }</p>
|
) }</p>
|
||||||
<DialogButtons primaryButton={_t('Go back')}
|
<DialogButtons primaryButton={_t('Go back')}
|
||||||
onPrimaryButtonClick={this._onGoBackClick}
|
onPrimaryButtonClick={this.onGoBackClick}
|
||||||
hasCancel={false}
|
hasCancel={false}
|
||||||
>
|
>
|
||||||
<button type="button" className="danger" onClick={this._onCancel}>{ _t('Cancel') }</button>
|
<button type="button" className="danger" onClick={this.onCancel}>{ _t('Cancel') }</button>
|
||||||
</DialogButtons>
|
</DialogButtons>
|
||||||
</div>;
|
</div>;
|
||||||
}
|
}
|
||||||
|
|
||||||
_titleForPhase(phase) {
|
private titleForPhase(phase: Phase): string {
|
||||||
switch (phase) {
|
switch (phase) {
|
||||||
case PHASE_CHOOSE_KEY_PASSPHRASE:
|
case Phase.ChooseKeyPassphrase:
|
||||||
return _t('Set up Secure Backup');
|
return _t('Set up Secure Backup');
|
||||||
case PHASE_MIGRATE:
|
case Phase.Migrate:
|
||||||
return _t('Upgrade your encryption');
|
return _t('Upgrade your encryption');
|
||||||
case PHASE_PASSPHRASE:
|
case Phase.Passphrase:
|
||||||
return _t('Set a Security Phrase');
|
return _t('Set a Security Phrase');
|
||||||
case PHASE_PASSPHRASE_CONFIRM:
|
case Phase.PassphraseConfirm:
|
||||||
return _t('Confirm Security Phrase');
|
return _t('Confirm Security Phrase');
|
||||||
case PHASE_CONFIRM_SKIP:
|
case Phase.ConfirmSkip:
|
||||||
return _t('Are you sure?');
|
return _t('Are you sure?');
|
||||||
case PHASE_SHOWKEY:
|
case Phase.ShowKey:
|
||||||
return _t('Save your Security Key');
|
return _t('Save your Security Key');
|
||||||
case PHASE_STORING:
|
case Phase.Storing:
|
||||||
return _t('Setting up keys');
|
return _t('Setting up keys');
|
||||||
default:
|
default:
|
||||||
return '';
|
return '';
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
render() {
|
public render(): JSX.Element {
|
||||||
const BaseDialog = sdk.getComponent('views.dialogs.BaseDialog');
|
|
||||||
|
|
||||||
let content;
|
let content;
|
||||||
if (this.state.error) {
|
if (this.state.error) {
|
||||||
content = <div>
|
content = <div>
|
||||||
<p>{ _t("Unable to set up secret storage") }</p>
|
<p>{ _t("Unable to set up secret storage") }</p>
|
||||||
<div className="mx_Dialog_buttons">
|
<div className="mx_Dialog_buttons">
|
||||||
<DialogButtons primaryButton={_t('Retry')}
|
<DialogButtons primaryButton={_t('Retry')}
|
||||||
onPrimaryButtonClick={this._bootstrapSecretStorage}
|
onPrimaryButtonClick={this.bootstrapSecretStorage}
|
||||||
hasCancel={this.state.canSkip}
|
hasCancel={this.state.canSkip}
|
||||||
onCancel={this._onCancel}
|
onCancel={this.onCancel}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>;
|
</div>;
|
||||||
} else {
|
} else {
|
||||||
switch (this.state.phase) {
|
switch (this.state.phase) {
|
||||||
case PHASE_LOADING:
|
case Phase.Loading:
|
||||||
content = this._renderBusyPhase();
|
content = this.renderBusyPhase();
|
||||||
break;
|
break;
|
||||||
case PHASE_LOADERROR:
|
case Phase.LoadError:
|
||||||
content = this._renderPhaseLoadError();
|
content = this.renderPhaseLoadError();
|
||||||
break;
|
break;
|
||||||
case PHASE_CHOOSE_KEY_PASSPHRASE:
|
case Phase.ChooseKeyPassphrase:
|
||||||
content = this._renderPhaseChooseKeyPassphrase();
|
content = this.renderPhaseChooseKeyPassphrase();
|
||||||
break;
|
break;
|
||||||
case PHASE_MIGRATE:
|
case Phase.Migrate:
|
||||||
content = this._renderPhaseMigrate();
|
content = this.renderPhaseMigrate();
|
||||||
break;
|
break;
|
||||||
case PHASE_PASSPHRASE:
|
case Phase.Passphrase:
|
||||||
content = this._renderPhasePassPhrase();
|
content = this.renderPhasePassPhrase();
|
||||||
break;
|
break;
|
||||||
case PHASE_PASSPHRASE_CONFIRM:
|
case Phase.PassphraseConfirm:
|
||||||
content = this._renderPhasePassPhraseConfirm();
|
content = this.renderPhasePassPhraseConfirm();
|
||||||
break;
|
break;
|
||||||
case PHASE_SHOWKEY:
|
case Phase.ShowKey:
|
||||||
content = this._renderPhaseShowKey();
|
content = this.renderPhaseShowKey();
|
||||||
break;
|
break;
|
||||||
case PHASE_STORING:
|
case Phase.Storing:
|
||||||
content = this._renderBusyPhase();
|
content = this.renderBusyPhase();
|
||||||
break;
|
break;
|
||||||
case PHASE_CONFIRM_SKIP:
|
case Phase.ConfirmSkip:
|
||||||
content = this._renderPhaseSkipConfirm();
|
content = this.renderPhaseSkipConfirm();
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let titleClass = null;
|
let titleClass = null;
|
||||||
switch (this.state.phase) {
|
switch (this.state.phase) {
|
||||||
case PHASE_PASSPHRASE:
|
case Phase.Passphrase:
|
||||||
case PHASE_PASSPHRASE_CONFIRM:
|
case Phase.PassphraseConfirm:
|
||||||
titleClass = [
|
titleClass = [
|
||||||
'mx_CreateSecretStorageDialog_titleWithIcon',
|
'mx_CreateSecretStorageDialog_titleWithIcon',
|
||||||
'mx_CreateSecretStorageDialog_securePhraseTitle',
|
'mx_CreateSecretStorageDialog_securePhraseTitle',
|
||||||
];
|
];
|
||||||
break;
|
break;
|
||||||
case PHASE_SHOWKEY:
|
case Phase.ShowKey:
|
||||||
titleClass = [
|
titleClass = [
|
||||||
'mx_CreateSecretStorageDialog_titleWithIcon',
|
'mx_CreateSecretStorageDialog_titleWithIcon',
|
||||||
'mx_CreateSecretStorageDialog_secureBackupTitle',
|
'mx_CreateSecretStorageDialog_secureBackupTitle',
|
||||||
];
|
];
|
||||||
break;
|
break;
|
||||||
case PHASE_CHOOSE_KEY_PASSPHRASE:
|
case Phase.ChooseKeyPassphrase:
|
||||||
titleClass = 'mx_CreateSecretStorageDialog_centeredTitle';
|
titleClass = 'mx_CreateSecretStorageDialog_centeredTitle';
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
@ -854,9 +873,9 @@ export default class CreateSecretStorageDialog extends React.PureComponent {
|
||||||
return (
|
return (
|
||||||
<BaseDialog className='mx_CreateSecretStorageDialog'
|
<BaseDialog className='mx_CreateSecretStorageDialog'
|
||||||
onFinished={this.props.onFinished}
|
onFinished={this.props.onFinished}
|
||||||
title={this._titleForPhase(this.state.phase)}
|
title={this.titleForPhase(this.state.phase)}
|
||||||
titleClass={titleClass}
|
titleClass={titleClass}
|
||||||
hasCancel={this.props.hasCancel && [PHASE_PASSPHRASE].includes(this.state.phase)}
|
hasCancel={this.props.hasCancel && [Phase.Passphrase].includes(this.state.phase)}
|
||||||
fixedWidth={false}
|
fixedWidth={false}
|
||||||
>
|
>
|
||||||
<div>
|
<div>
|
|
@ -16,47 +16,51 @@ limitations under the License.
|
||||||
|
|
||||||
import FileSaver from 'file-saver';
|
import FileSaver from 'file-saver';
|
||||||
import React, { createRef } from 'react';
|
import React, { createRef } from 'react';
|
||||||
import PropTypes from 'prop-types';
|
|
||||||
import { _t } from '../../../../languageHandler';
|
import { _t } from '../../../../languageHandler';
|
||||||
|
|
||||||
import { MatrixClient } from 'matrix-js-sdk/src/client';
|
import { MatrixClient } from 'matrix-js-sdk/src/client';
|
||||||
import * as MegolmExportEncryption from '../../../../utils/MegolmExportEncryption';
|
import * as MegolmExportEncryption from '../../../../utils/MegolmExportEncryption';
|
||||||
import * as sdk from '../../../../index';
|
import { IDialogProps } from "../../../../components/views/dialogs/IDialogProps";
|
||||||
|
import BaseDialog from "../../../../components/views/dialogs/BaseDialog";
|
||||||
import { logger } from "matrix-js-sdk/src/logger";
|
import { logger } from "matrix-js-sdk/src/logger";
|
||||||
|
|
||||||
const PHASE_EDIT = 1;
|
enum Phase {
|
||||||
const PHASE_EXPORTING = 2;
|
Edit = "edit",
|
||||||
|
Exporting = "exporting",
|
||||||
|
}
|
||||||
|
|
||||||
export default class ExportE2eKeysDialog extends React.Component {
|
interface IProps extends IDialogProps {
|
||||||
static propTypes = {
|
matrixClient: MatrixClient;
|
||||||
matrixClient: PropTypes.instanceOf(MatrixClient).isRequired,
|
}
|
||||||
onFinished: PropTypes.func.isRequired,
|
|
||||||
};
|
|
||||||
|
|
||||||
constructor(props) {
|
interface IState {
|
||||||
|
phase: Phase;
|
||||||
|
errStr: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export default class ExportE2eKeysDialog extends React.Component<IProps, IState> {
|
||||||
|
private unmounted = false;
|
||||||
|
private passphrase1 = createRef<HTMLInputElement>();
|
||||||
|
private passphrase2 = createRef<HTMLInputElement>();
|
||||||
|
|
||||||
|
constructor(props: IProps) {
|
||||||
super(props);
|
super(props);
|
||||||
|
|
||||||
this._unmounted = false;
|
|
||||||
|
|
||||||
this._passphrase1 = createRef();
|
|
||||||
this._passphrase2 = createRef();
|
|
||||||
|
|
||||||
this.state = {
|
this.state = {
|
||||||
phase: PHASE_EDIT,
|
phase: Phase.Edit,
|
||||||
errStr: null,
|
errStr: null,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
componentWillUnmount() {
|
public componentWillUnmount(): void {
|
||||||
this._unmounted = true;
|
this.unmounted = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
_onPassphraseFormSubmit = (ev) => {
|
private onPassphraseFormSubmit = (ev: React.FormEvent): boolean => {
|
||||||
ev.preventDefault();
|
ev.preventDefault();
|
||||||
|
|
||||||
const passphrase = this._passphrase1.current.value;
|
const passphrase = this.passphrase1.current.value;
|
||||||
if (passphrase !== this._passphrase2.current.value) {
|
if (passphrase !== this.passphrase2.current.value) {
|
||||||
this.setState({ errStr: _t('Passphrases must match') });
|
this.setState({ errStr: _t('Passphrases must match') });
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
@ -65,11 +69,11 @@ export default class ExportE2eKeysDialog extends React.Component {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
this._startExport(passphrase);
|
this.startExport(passphrase);
|
||||||
return false;
|
return false;
|
||||||
};
|
};
|
||||||
|
|
||||||
_startExport(passphrase) {
|
private startExport(passphrase: string): void {
|
||||||
// extra Promise.resolve() to turn synchronous exceptions into
|
// extra Promise.resolve() to turn synchronous exceptions into
|
||||||
// asynchronous ones.
|
// asynchronous ones.
|
||||||
Promise.resolve().then(() => {
|
Promise.resolve().then(() => {
|
||||||
|
@ -86,39 +90,37 @@ export default class ExportE2eKeysDialog extends React.Component {
|
||||||
this.props.onFinished(true);
|
this.props.onFinished(true);
|
||||||
}).catch((e) => {
|
}).catch((e) => {
|
||||||
logger.error("Error exporting e2e keys:", e);
|
logger.error("Error exporting e2e keys:", e);
|
||||||
if (this._unmounted) {
|
if (this.unmounted) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const msg = e.friendlyText || _t('Unknown error');
|
const msg = e.friendlyText || _t('Unknown error');
|
||||||
this.setState({
|
this.setState({
|
||||||
errStr: msg,
|
errStr: msg,
|
||||||
phase: PHASE_EDIT,
|
phase: Phase.Edit,
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
this.setState({
|
this.setState({
|
||||||
errStr: null,
|
errStr: null,
|
||||||
phase: PHASE_EXPORTING,
|
phase: Phase.Exporting,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
_onCancelClick = (ev) => {
|
private onCancelClick = (ev: React.MouseEvent): boolean => {
|
||||||
ev.preventDefault();
|
ev.preventDefault();
|
||||||
this.props.onFinished(false);
|
this.props.onFinished(false);
|
||||||
return false;
|
return false;
|
||||||
};
|
};
|
||||||
|
|
||||||
render() {
|
public render(): JSX.Element {
|
||||||
const BaseDialog = sdk.getComponent('views.dialogs.BaseDialog');
|
const disableForm = (this.state.phase === Phase.Exporting);
|
||||||
|
|
||||||
const disableForm = (this.state.phase === PHASE_EXPORTING);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<BaseDialog className='mx_exportE2eKeysDialog'
|
<BaseDialog className='mx_exportE2eKeysDialog'
|
||||||
onFinished={this.props.onFinished}
|
onFinished={this.props.onFinished}
|
||||||
title={_t("Export room keys")}
|
title={_t("Export room keys")}
|
||||||
>
|
>
|
||||||
<form onSubmit={this._onPassphraseFormSubmit}>
|
<form onSubmit={this.onPassphraseFormSubmit}>
|
||||||
<div className="mx_Dialog_content">
|
<div className="mx_Dialog_content">
|
||||||
<p>
|
<p>
|
||||||
{ _t(
|
{ _t(
|
||||||
|
@ -151,10 +153,10 @@ export default class ExportE2eKeysDialog extends React.Component {
|
||||||
</div>
|
</div>
|
||||||
<div className='mx_E2eKeysDialog_inputCell'>
|
<div className='mx_E2eKeysDialog_inputCell'>
|
||||||
<input
|
<input
|
||||||
ref={this._passphrase1}
|
ref={this.passphrase1}
|
||||||
id='passphrase1'
|
id='passphrase1'
|
||||||
autoFocus={true}
|
autoFocus={true}
|
||||||
size='64'
|
size={64}
|
||||||
type='password'
|
type='password'
|
||||||
disabled={disableForm}
|
disabled={disableForm}
|
||||||
/>
|
/>
|
||||||
|
@ -167,9 +169,9 @@ export default class ExportE2eKeysDialog extends React.Component {
|
||||||
</label>
|
</label>
|
||||||
</div>
|
</div>
|
||||||
<div className='mx_E2eKeysDialog_inputCell'>
|
<div className='mx_E2eKeysDialog_inputCell'>
|
||||||
<input ref={this._passphrase2}
|
<input ref={this.passphrase2}
|
||||||
id='passphrase2'
|
id='passphrase2'
|
||||||
size='64'
|
size={64}
|
||||||
type='password'
|
type='password'
|
||||||
disabled={disableForm}
|
disabled={disableForm}
|
||||||
/>
|
/>
|
||||||
|
@ -184,7 +186,7 @@ export default class ExportE2eKeysDialog extends React.Component {
|
||||||
value={_t('Export')}
|
value={_t('Export')}
|
||||||
disabled={disableForm}
|
disabled={disableForm}
|
||||||
/>
|
/>
|
||||||
<button onClick={this._onCancelClick} disabled={disableForm}>
|
<button onClick={this.onCancelClick} disabled={disableForm}>
|
||||||
{ _t("Cancel") }
|
{ _t("Cancel") }
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
|
@ -15,20 +15,19 @@ limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import React, { createRef } from 'react';
|
import React, { createRef } from 'react';
|
||||||
import PropTypes from 'prop-types';
|
|
||||||
|
|
||||||
import { MatrixClient } from 'matrix-js-sdk/src/client';
|
import { MatrixClient } from 'matrix-js-sdk/src/client';
|
||||||
import * as MegolmExportEncryption from '../../../../utils/MegolmExportEncryption';
|
import * as MegolmExportEncryption from '../../../../utils/MegolmExportEncryption';
|
||||||
import * as sdk from '../../../../index';
|
|
||||||
import { _t } from '../../../../languageHandler';
|
import { _t } from '../../../../languageHandler';
|
||||||
|
import { IDialogProps } from "../../../../components/views/dialogs/IDialogProps";
|
||||||
|
import BaseDialog from "../../../../components/views/dialogs/BaseDialog";
|
||||||
import { logger } from "matrix-js-sdk/src/logger";
|
import { logger } from "matrix-js-sdk/src/logger";
|
||||||
|
|
||||||
function readFileAsArrayBuffer(file) {
|
function readFileAsArrayBuffer(file: File): Promise<ArrayBuffer> {
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
const reader = new FileReader();
|
const reader = new FileReader();
|
||||||
reader.onload = (e) => {
|
reader.onload = (e) => {
|
||||||
resolve(e.target.result);
|
resolve(e.target.result as ArrayBuffer);
|
||||||
};
|
};
|
||||||
reader.onerror = reject;
|
reader.onerror = reject;
|
||||||
|
|
||||||
|
@ -36,51 +35,57 @@ function readFileAsArrayBuffer(file) {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
const PHASE_EDIT = 1;
|
enum Phase {
|
||||||
const PHASE_IMPORTING = 2;
|
Edit = "edit",
|
||||||
|
Importing = "importing",
|
||||||
|
}
|
||||||
|
|
||||||
export default class ImportE2eKeysDialog extends React.Component {
|
interface IProps extends IDialogProps {
|
||||||
static propTypes = {
|
matrixClient: MatrixClient;
|
||||||
matrixClient: PropTypes.instanceOf(MatrixClient).isRequired,
|
}
|
||||||
onFinished: PropTypes.func.isRequired,
|
|
||||||
};
|
|
||||||
|
|
||||||
constructor(props) {
|
interface IState {
|
||||||
|
enableSubmit: boolean;
|
||||||
|
phase: Phase;
|
||||||
|
errStr: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export default class ImportE2eKeysDialog extends React.Component<IProps, IState> {
|
||||||
|
private unmounted = false;
|
||||||
|
private file = createRef<HTMLInputElement>();
|
||||||
|
private passphrase = createRef<HTMLInputElement>();
|
||||||
|
|
||||||
|
constructor(props: IProps) {
|
||||||
super(props);
|
super(props);
|
||||||
|
|
||||||
this._unmounted = false;
|
|
||||||
|
|
||||||
this._file = createRef();
|
|
||||||
this._passphrase = createRef();
|
|
||||||
|
|
||||||
this.state = {
|
this.state = {
|
||||||
enableSubmit: false,
|
enableSubmit: false,
|
||||||
phase: PHASE_EDIT,
|
phase: Phase.Edit,
|
||||||
errStr: null,
|
errStr: null,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
componentWillUnmount() {
|
public componentWillUnmount(): void {
|
||||||
this._unmounted = true;
|
this.unmounted = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
_onFormChange = (ev) => {
|
private onFormChange = (ev: React.FormEvent): void => {
|
||||||
const files = this._file.current.files || [];
|
const files = this.file.current.files || [];
|
||||||
this.setState({
|
this.setState({
|
||||||
enableSubmit: (this._passphrase.current.value !== "" && files.length > 0),
|
enableSubmit: (this.passphrase.current.value !== "" && files.length > 0),
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
_onFormSubmit = (ev) => {
|
private onFormSubmit = (ev: React.FormEvent): boolean => {
|
||||||
ev.preventDefault();
|
ev.preventDefault();
|
||||||
this._startImport(this._file.current.files[0], this._passphrase.current.value);
|
this.startImport(this.file.current.files[0], this.passphrase.current.value);
|
||||||
return false;
|
return false;
|
||||||
};
|
};
|
||||||
|
|
||||||
_startImport(file, passphrase) {
|
private startImport(file: File, passphrase: string) {
|
||||||
this.setState({
|
this.setState({
|
||||||
errStr: null,
|
errStr: null,
|
||||||
phase: PHASE_IMPORTING,
|
phase: Phase.Importing,
|
||||||
});
|
});
|
||||||
|
|
||||||
return readFileAsArrayBuffer(file).then((arrayBuffer) => {
|
return readFileAsArrayBuffer(file).then((arrayBuffer) => {
|
||||||
|
@ -94,34 +99,32 @@ export default class ImportE2eKeysDialog extends React.Component {
|
||||||
this.props.onFinished(true);
|
this.props.onFinished(true);
|
||||||
}).catch((e) => {
|
}).catch((e) => {
|
||||||
logger.error("Error importing e2e keys:", e);
|
logger.error("Error importing e2e keys:", e);
|
||||||
if (this._unmounted) {
|
if (this.unmounted) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const msg = e.friendlyText || _t('Unknown error');
|
const msg = e.friendlyText || _t('Unknown error');
|
||||||
this.setState({
|
this.setState({
|
||||||
errStr: msg,
|
errStr: msg,
|
||||||
phase: PHASE_EDIT,
|
phase: Phase.Edit,
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
_onCancelClick = (ev) => {
|
private onCancelClick = (ev: React.MouseEvent): boolean => {
|
||||||
ev.preventDefault();
|
ev.preventDefault();
|
||||||
this.props.onFinished(false);
|
this.props.onFinished(false);
|
||||||
return false;
|
return false;
|
||||||
};
|
};
|
||||||
|
|
||||||
render() {
|
public render(): JSX.Element {
|
||||||
const BaseDialog = sdk.getComponent('views.dialogs.BaseDialog');
|
const disableForm = (this.state.phase !== Phase.Edit);
|
||||||
|
|
||||||
const disableForm = (this.state.phase !== PHASE_EDIT);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<BaseDialog className='mx_importE2eKeysDialog'
|
<BaseDialog className='mx_importE2eKeysDialog'
|
||||||
onFinished={this.props.onFinished}
|
onFinished={this.props.onFinished}
|
||||||
title={_t("Import room keys")}
|
title={_t("Import room keys")}
|
||||||
>
|
>
|
||||||
<form onSubmit={this._onFormSubmit}>
|
<form onSubmit={this.onFormSubmit}>
|
||||||
<div className="mx_Dialog_content">
|
<div className="mx_Dialog_content">
|
||||||
<p>
|
<p>
|
||||||
{ _t(
|
{ _t(
|
||||||
|
@ -149,11 +152,11 @@ export default class ImportE2eKeysDialog extends React.Component {
|
||||||
</div>
|
</div>
|
||||||
<div className='mx_E2eKeysDialog_inputCell'>
|
<div className='mx_E2eKeysDialog_inputCell'>
|
||||||
<input
|
<input
|
||||||
ref={this._file}
|
ref={this.file}
|
||||||
id='importFile'
|
id='importFile'
|
||||||
type='file'
|
type='file'
|
||||||
autoFocus={true}
|
autoFocus={true}
|
||||||
onChange={this._onFormChange}
|
onChange={this.onFormChange}
|
||||||
disabled={disableForm} />
|
disabled={disableForm} />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -165,11 +168,11 @@ export default class ImportE2eKeysDialog extends React.Component {
|
||||||
</div>
|
</div>
|
||||||
<div className='mx_E2eKeysDialog_inputCell'>
|
<div className='mx_E2eKeysDialog_inputCell'>
|
||||||
<input
|
<input
|
||||||
ref={this._passphrase}
|
ref={this.passphrase}
|
||||||
id='passphrase'
|
id='passphrase'
|
||||||
size='64'
|
size={64}
|
||||||
type='password'
|
type='password'
|
||||||
onChange={this._onFormChange}
|
onChange={this.onFormChange}
|
||||||
disabled={disableForm} />
|
disabled={disableForm} />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -182,7 +185,7 @@ export default class ImportE2eKeysDialog extends React.Component {
|
||||||
value={_t('Import')}
|
value={_t('Import')}
|
||||||
disabled={!this.state.enableSubmit || disableForm}
|
disabled={!this.state.enableSubmit || disableForm}
|
||||||
/>
|
/>
|
||||||
<button onClick={this._onCancelClick} disabled={disableForm}>
|
<button onClick={this.onCancelClick} disabled={disableForm}>
|
||||||
{ _t("Cancel") }
|
{ _t("Cancel") }
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
|
@ -16,43 +16,40 @@ limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import React from "react";
|
import React from "react";
|
||||||
import PropTypes from "prop-types";
|
|
||||||
import * as sdk from "../../../../index";
|
|
||||||
import { MatrixClientPeg } from '../../../../MatrixClientPeg';
|
import { MatrixClientPeg } from '../../../../MatrixClientPeg';
|
||||||
import dis from "../../../../dispatcher/dispatcher";
|
import dis from "../../../../dispatcher/dispatcher";
|
||||||
import { _t } from "../../../../languageHandler";
|
import { _t } from "../../../../languageHandler";
|
||||||
import Modal from "../../../../Modal";
|
import Modal from "../../../../Modal";
|
||||||
import RestoreKeyBackupDialog from "../../../../components/views/dialogs/security/RestoreKeyBackupDialog";
|
import RestoreKeyBackupDialog from "../../../../components/views/dialogs/security/RestoreKeyBackupDialog";
|
||||||
import { Action } from "../../../../dispatcher/actions";
|
import { Action } from "../../../../dispatcher/actions";
|
||||||
|
import { IDialogProps } from "../../../../components/views/dialogs/IDialogProps";
|
||||||
|
import DialogButtons from "../../../../components/views/elements/DialogButtons";
|
||||||
|
import BaseDialog from "../../../../components/views/dialogs/BaseDialog";
|
||||||
|
import { IKeyBackupInfo } from "matrix-js-sdk/src/crypto/keybackup";
|
||||||
|
|
||||||
export default class NewRecoveryMethodDialog extends React.PureComponent {
|
interface IProps extends IDialogProps {
|
||||||
static propTypes = {
|
newVersionInfo: IKeyBackupInfo;
|
||||||
// As returned by js-sdk getKeyBackupVersion()
|
}
|
||||||
newVersionInfo: PropTypes.object,
|
|
||||||
onFinished: PropTypes.func.isRequired,
|
|
||||||
}
|
|
||||||
|
|
||||||
onOkClick = () => {
|
export default class NewRecoveryMethodDialog extends React.PureComponent<IProps> {
|
||||||
|
private onOkClick = (): void => {
|
||||||
this.props.onFinished();
|
this.props.onFinished();
|
||||||
}
|
};
|
||||||
|
|
||||||
onGoToSettingsClick = () => {
|
private onGoToSettingsClick = (): void => {
|
||||||
this.props.onFinished();
|
this.props.onFinished();
|
||||||
dis.fire(Action.ViewUserSettings);
|
dis.fire(Action.ViewUserSettings);
|
||||||
}
|
};
|
||||||
|
|
||||||
onSetupClick = async () => {
|
private onSetupClick = async (): Promise<void> => {
|
||||||
Modal.createTrackedDialog(
|
Modal.createTrackedDialog(
|
||||||
'Restore Backup', '', RestoreKeyBackupDialog, {
|
'Restore Backup', '', RestoreKeyBackupDialog, {
|
||||||
onFinished: this.props.onFinished,
|
onFinished: this.props.onFinished,
|
||||||
}, null, /* priority = */ false, /* static = */ true,
|
}, null, /* priority = */ false, /* static = */ true,
|
||||||
);
|
);
|
||||||
}
|
};
|
||||||
|
|
||||||
render() {
|
|
||||||
const BaseDialog = sdk.getComponent("views.dialogs.BaseDialog");
|
|
||||||
const DialogButtons = sdk.getComponent("views.elements.DialogButtons");
|
|
||||||
|
|
||||||
|
public render(): JSX.Element {
|
||||||
const title = <span className="mx_KeyBackupFailedDialog_title">
|
const title = <span className="mx_KeyBackupFailedDialog_title">
|
||||||
{ _t("New Recovery Method") }
|
{ _t("New Recovery Method") }
|
||||||
</span>;
|
</span>;
|
|
@ -15,36 +15,32 @@ See the License for the specific language governing permissions and
|
||||||
limitations under the License.
|
limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import React from "react";
|
import React, { ComponentType } from "react";
|
||||||
import PropTypes from "prop-types";
|
|
||||||
import * as sdk from "../../../../index";
|
|
||||||
import dis from "../../../../dispatcher/dispatcher";
|
import dis from "../../../../dispatcher/dispatcher";
|
||||||
import { _t } from "../../../../languageHandler";
|
import { _t } from "../../../../languageHandler";
|
||||||
import Modal from "../../../../Modal";
|
import Modal from "../../../../Modal";
|
||||||
import { Action } from "../../../../dispatcher/actions";
|
import { Action } from "../../../../dispatcher/actions";
|
||||||
|
import { IDialogProps } from "../../../../components/views/dialogs/IDialogProps";
|
||||||
|
import BaseDialog from "../../../../components/views/dialogs/BaseDialog";
|
||||||
|
import DialogButtons from "../../../../components/views/elements/DialogButtons";
|
||||||
|
|
||||||
export default class RecoveryMethodRemovedDialog extends React.PureComponent {
|
interface IProps extends IDialogProps {}
|
||||||
static propTypes = {
|
|
||||||
onFinished: PropTypes.func.isRequired,
|
|
||||||
}
|
|
||||||
|
|
||||||
onGoToSettingsClick = () => {
|
export default class RecoveryMethodRemovedDialog extends React.PureComponent<IProps> {
|
||||||
|
private onGoToSettingsClick = (): void => {
|
||||||
this.props.onFinished();
|
this.props.onFinished();
|
||||||
dis.fire(Action.ViewUserSettings);
|
dis.fire(Action.ViewUserSettings);
|
||||||
}
|
};
|
||||||
|
|
||||||
onSetupClick = () => {
|
private onSetupClick = (): void => {
|
||||||
this.props.onFinished();
|
this.props.onFinished();
|
||||||
Modal.createTrackedDialogAsync("Key Backup", "Key Backup",
|
Modal.createTrackedDialogAsync("Key Backup", "Key Backup",
|
||||||
import("./CreateKeyBackupDialog"),
|
import("./CreateKeyBackupDialog") as unknown as Promise<ComponentType<{}>>,
|
||||||
null, null, /* priority = */ false, /* static = */ true,
|
null, null, /* priority = */ false, /* static = */ true,
|
||||||
);
|
);
|
||||||
}
|
};
|
||||||
|
|
||||||
render() {
|
|
||||||
const BaseDialog = sdk.getComponent("views.dialogs.BaseDialog");
|
|
||||||
const DialogButtons = sdk.getComponent("views.elements.DialogButtons");
|
|
||||||
|
|
||||||
|
public render(): JSX.Element {
|
||||||
const title = <span className="mx_KeyBackupFailedDialog_title">
|
const title = <span className="mx_KeyBackupFailedDialog_title">
|
||||||
{ _t("Recovery Method Removed") }
|
{ _t("Recovery Method Removed") }
|
||||||
</span>;
|
</span>;
|
|
@ -14,7 +14,7 @@ See the License for the specific language governing permissions and
|
||||||
limitations under the License.
|
limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import React, { createRef } from 'react';
|
import React, { ComponentType, createRef } from 'react';
|
||||||
import { createClient } from "matrix-js-sdk/src/matrix";
|
import { createClient } from "matrix-js-sdk/src/matrix";
|
||||||
import { InvalidStoreError } from "matrix-js-sdk/src/errors";
|
import { InvalidStoreError } from "matrix-js-sdk/src/errors";
|
||||||
import { RoomMember } from "matrix-js-sdk/src/models/room-member";
|
import { RoomMember } from "matrix-js-sdk/src/models/room-member";
|
||||||
|
@ -1601,12 +1601,16 @@ export default class MatrixChat extends React.PureComponent<IProps, IState> {
|
||||||
|
|
||||||
if (haveNewVersion) {
|
if (haveNewVersion) {
|
||||||
Modal.createTrackedDialogAsync('New Recovery Method', 'New Recovery Method',
|
Modal.createTrackedDialogAsync('New Recovery Method', 'New Recovery Method',
|
||||||
import('../../async-components/views/dialogs/security/NewRecoveryMethodDialog'),
|
import(
|
||||||
|
'../../async-components/views/dialogs/security/NewRecoveryMethodDialog'
|
||||||
|
) as unknown as Promise<ComponentType<{}>>,
|
||||||
{ newVersionInfo },
|
{ newVersionInfo },
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
Modal.createTrackedDialogAsync('Recovery Method Removed', 'Recovery Method Removed',
|
Modal.createTrackedDialogAsync('Recovery Method Removed', 'Recovery Method Removed',
|
||||||
import('../../async-components/views/dialogs/security/RecoveryMethodRemovedDialog'),
|
import(
|
||||||
|
'../../async-components/views/dialogs/security/RecoveryMethodRemovedDialog'
|
||||||
|
) as unknown as Promise<ComponentType<{}>>,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
|
@ -15,7 +15,7 @@ See the License for the specific language governing permissions and
|
||||||
limitations under the License.
|
limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import React from 'react';
|
import React, { ComponentType } from 'react';
|
||||||
import { IKeyBackupInfo } from "matrix-js-sdk/src/crypto/keybackup";
|
import { IKeyBackupInfo } from "matrix-js-sdk/src/crypto/keybackup";
|
||||||
import Modal from '../../../Modal';
|
import Modal from '../../../Modal';
|
||||||
import * as sdk from '../../../index';
|
import * as sdk from '../../../index';
|
||||||
|
@ -85,7 +85,9 @@ export default class LogoutDialog extends React.Component<IProps, IState> {
|
||||||
|
|
||||||
private onExportE2eKeysClicked = (): void => {
|
private onExportE2eKeysClicked = (): void => {
|
||||||
Modal.createTrackedDialogAsync('Export E2E Keys', '',
|
Modal.createTrackedDialogAsync('Export E2E Keys', '',
|
||||||
import('../../../async-components/views/dialogs/security/ExportE2eKeysDialog'),
|
import(
|
||||||
|
'../../../async-components/views/dialogs/security/ExportE2eKeysDialog'
|
||||||
|
) as unknown as Promise<ComponentType<{}>>,
|
||||||
{
|
{
|
||||||
matrixClient: MatrixClientPeg.get(),
|
matrixClient: MatrixClientPeg.get(),
|
||||||
},
|
},
|
||||||
|
@ -111,7 +113,9 @@ export default class LogoutDialog extends React.Component<IProps, IState> {
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
Modal.createTrackedDialogAsync("Key Backup", "Key Backup",
|
Modal.createTrackedDialogAsync("Key Backup", "Key Backup",
|
||||||
import("../../../async-components/views/dialogs/security/CreateKeyBackupDialog"),
|
import(
|
||||||
|
"../../../async-components/views/dialogs/security/CreateKeyBackupDialog"
|
||||||
|
) as unknown as Promise<ComponentType<{}>>,
|
||||||
null, null, /* priority = */ false, /* static = */ true,
|
null, null, /* priority = */ false, /* static = */ true,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -16,7 +16,7 @@ limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import Field from "../elements/Field";
|
import Field from "../elements/Field";
|
||||||
import React from 'react';
|
import React, { ComponentType } from 'react';
|
||||||
import { MatrixClientPeg } from "../../../MatrixClientPeg";
|
import { MatrixClientPeg } from "../../../MatrixClientPeg";
|
||||||
import AccessibleButton from '../elements/AccessibleButton';
|
import AccessibleButton from '../elements/AccessibleButton';
|
||||||
import Spinner from '../elements/Spinner';
|
import Spinner from '../elements/Spinner';
|
||||||
|
@ -186,7 +186,9 @@ export default class ChangePassword extends React.Component<IProps, IState> {
|
||||||
|
|
||||||
private onExportE2eKeysClicked = (): void => {
|
private onExportE2eKeysClicked = (): void => {
|
||||||
Modal.createTrackedDialogAsync('Export E2E Keys', 'Change Password',
|
Modal.createTrackedDialogAsync('Export E2E Keys', 'Change Password',
|
||||||
import('../../../async-components/views/dialogs/security/ExportE2eKeysDialog'),
|
import(
|
||||||
|
'../../../async-components/views/dialogs/security/ExportE2eKeysDialog'
|
||||||
|
) as unknown as Promise<ComponentType<{}>>,
|
||||||
{
|
{
|
||||||
matrixClient: MatrixClientPeg.get(),
|
matrixClient: MatrixClientPeg.get(),
|
||||||
},
|
},
|
||||||
|
|
|
@ -14,7 +14,7 @@ See the License for the specific language governing permissions and
|
||||||
limitations under the License.
|
limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import React from 'react';
|
import React, { ComponentType } from 'react';
|
||||||
|
|
||||||
import { MatrixClientPeg } from '../../../MatrixClientPeg';
|
import { MatrixClientPeg } from '../../../MatrixClientPeg';
|
||||||
import { _t } from '../../../languageHandler';
|
import { _t } from '../../../languageHandler';
|
||||||
|
@ -92,14 +92,18 @@ export default class CryptographyPanel extends React.Component<IProps, IState> {
|
||||||
|
|
||||||
private onExportE2eKeysClicked = (): void => {
|
private onExportE2eKeysClicked = (): void => {
|
||||||
Modal.createTrackedDialogAsync('Export E2E Keys', '',
|
Modal.createTrackedDialogAsync('Export E2E Keys', '',
|
||||||
import('../../../async-components/views/dialogs/security/ExportE2eKeysDialog'),
|
import(
|
||||||
|
'../../../async-components/views/dialogs/security/ExportE2eKeysDialog'
|
||||||
|
) as unknown as Promise<ComponentType<{}>>,
|
||||||
{ matrixClient: MatrixClientPeg.get() },
|
{ matrixClient: MatrixClientPeg.get() },
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
private onImportE2eKeysClicked = (): void => {
|
private onImportE2eKeysClicked = (): void => {
|
||||||
Modal.createTrackedDialogAsync('Import E2E Keys', '',
|
Modal.createTrackedDialogAsync('Import E2E Keys', '',
|
||||||
import('../../../async-components/views/dialogs/security/ImportE2eKeysDialog'),
|
import(
|
||||||
|
'../../../async-components/views/dialogs/security/ImportE2eKeysDialog'
|
||||||
|
) as unknown as Promise<ComponentType<{}>>,
|
||||||
{ matrixClient: MatrixClientPeg.get() },
|
{ matrixClient: MatrixClientPeg.get() },
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
|
@ -15,7 +15,7 @@ See the License for the specific language governing permissions and
|
||||||
limitations under the License.
|
limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import React from 'react';
|
import React, { ComponentType } from 'react';
|
||||||
|
|
||||||
import { MatrixClientPeg } from '../../../MatrixClientPeg';
|
import { MatrixClientPeg } from '../../../MatrixClientPeg';
|
||||||
import { _t } from '../../../languageHandler';
|
import { _t } from '../../../languageHandler';
|
||||||
|
@ -170,7 +170,9 @@ export default class SecureBackupPanel extends React.PureComponent<{}, IState> {
|
||||||
|
|
||||||
private startNewBackup = (): void => {
|
private startNewBackup = (): void => {
|
||||||
Modal.createTrackedDialogAsync('Key Backup', 'Key Backup',
|
Modal.createTrackedDialogAsync('Key Backup', 'Key Backup',
|
||||||
import('../../../async-components/views/dialogs/security/CreateKeyBackupDialog'),
|
import(
|
||||||
|
'../../../async-components/views/dialogs/security/CreateKeyBackupDialog'
|
||||||
|
) as unknown as Promise<ComponentType<{}>>,
|
||||||
{
|
{
|
||||||
onFinished: () => {
|
onFinished: () => {
|
||||||
this.loadBackupStatus();
|
this.loadBackupStatus();
|
||||||
|
|
Loading…
Reference in a new issue