Merge pull request #4267 from matrix-org/bwindels/setupcrosssigningafterskip
Fix: make self-verification wait for incoming requestpull/21833/head
commit
44ce5b5764
|
@ -18,13 +18,14 @@ import React from 'react';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import { _t } from '../../../languageHandler';
|
import { _t } from '../../../languageHandler';
|
||||||
import * as sdk from '../../../index';
|
import * as sdk from '../../../index';
|
||||||
import { MatrixClientPeg } from '../../../MatrixClientPeg';
|
import {
|
||||||
import { accessSecretStorage, AccessCancelledError } from '../../../CrossSigningManager';
|
SetupEncryptionStore,
|
||||||
|
PHASE_INTRO,
|
||||||
const PHASE_INTRO = 0;
|
PHASE_BUSY,
|
||||||
const PHASE_BUSY = 1;
|
PHASE_DONE,
|
||||||
const PHASE_DONE = 2;
|
PHASE_CONFIRM_SKIP,
|
||||||
const PHASE_CONFIRM_SKIP = 3;
|
} from '../../../stores/SetupEncryptionStore';
|
||||||
|
import SetupEncryptionBody from "./SetupEncryptionBody";
|
||||||
|
|
||||||
export default class CompleteSecurity extends React.Component {
|
export default class CompleteSecurity extends React.Component {
|
||||||
static propTypes = {
|
static propTypes = {
|
||||||
|
@ -33,232 +34,42 @@ export default class CompleteSecurity extends React.Component {
|
||||||
|
|
||||||
constructor() {
|
constructor() {
|
||||||
super();
|
super();
|
||||||
|
const store = SetupEncryptionStore.sharedInstance();
|
||||||
this.state = {
|
store.on("update", this._onStoreUpdate);
|
||||||
phase: PHASE_INTRO,
|
store.start();
|
||||||
// this serves dual purpose as the object for the request logic and
|
this.state = {phase: store.phase};
|
||||||
// the presence of it insidicating that we're in 'verify mode'.
|
|
||||||
// Because of the latter, it lives in the state.
|
|
||||||
verificationRequest: null,
|
|
||||||
backupInfo: null,
|
|
||||||
};
|
|
||||||
MatrixClientPeg.get().on("crypto.verification.request", this.onVerificationRequest);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
_onStoreUpdate = () => {
|
||||||
|
const store = SetupEncryptionStore.sharedInstance();
|
||||||
|
this.setState({phase: store.phase});
|
||||||
|
};
|
||||||
|
|
||||||
componentWillUnmount() {
|
componentWillUnmount() {
|
||||||
if (this.state.verificationRequest) {
|
const store = SetupEncryptionStore.sharedInstance();
|
||||||
this.state.verificationRequest.off("change", this.onVerificationRequestChange);
|
store.off("update", this._onStoreUpdate);
|
||||||
}
|
store.stop();
|
||||||
if (MatrixClientPeg.get()) {
|
|
||||||
MatrixClientPeg.get().removeListener("crypto.verification.request", this.onVerificationRequest);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
_onUsePassphraseClick = async () => {
|
|
||||||
this.setState({
|
|
||||||
phase: PHASE_BUSY,
|
|
||||||
});
|
|
||||||
const cli = MatrixClientPeg.get();
|
|
||||||
try {
|
|
||||||
const backupInfo = await cli.getKeyBackupVersion();
|
|
||||||
this.setState({backupInfo});
|
|
||||||
|
|
||||||
// The control flow is fairly twisted here...
|
|
||||||
// For the purposes of completing security, we only wait on getting
|
|
||||||
// as far as the trust check and then show a green shield.
|
|
||||||
// We also begin the key backup restore as well, which we're
|
|
||||||
// awaiting inside `accessSecretStorage` only so that it keeps your
|
|
||||||
// passphase cached for that work. This dialog itself will only wait
|
|
||||||
// on the first trust check, and the key backup restore will happen
|
|
||||||
// in the background.
|
|
||||||
await new Promise((resolve, reject) => {
|
|
||||||
try {
|
|
||||||
accessSecretStorage(async () => {
|
|
||||||
await cli.checkOwnCrossSigningTrust();
|
|
||||||
resolve();
|
|
||||||
if (backupInfo) {
|
|
||||||
// A complete restore can take many minutes for large
|
|
||||||
// accounts / slow servers, so we allow the dialog
|
|
||||||
// to advance before this.
|
|
||||||
await cli.restoreKeyBackupWithSecretStorage(backupInfo);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
} catch (e) {
|
|
||||||
console.error(e);
|
|
||||||
reject(e);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
if (cli.getCrossSigningId()) {
|
|
||||||
this.setState({
|
|
||||||
phase: PHASE_DONE,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
} catch (e) {
|
|
||||||
if (!(e instanceof AccessCancelledError)) {
|
|
||||||
console.log(e);
|
|
||||||
}
|
|
||||||
// this will throw if the user hits cancel, so ignore
|
|
||||||
this.setState({
|
|
||||||
phase: PHASE_INTRO,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
onVerificationRequest = async (request) => {
|
|
||||||
if (request.otherUserId !== MatrixClientPeg.get().getUserId()) return;
|
|
||||||
|
|
||||||
if (this.state.verificationRequest) {
|
|
||||||
this.state.verificationRequest.off("change", this.onVerificationRequestChange);
|
|
||||||
}
|
|
||||||
await request.accept();
|
|
||||||
request.on("change", this.onVerificationRequestChange);
|
|
||||||
this.setState({
|
|
||||||
verificationRequest: request,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
onVerificationRequestChange = () => {
|
|
||||||
if (this.state.verificationRequest.cancelled) {
|
|
||||||
this.state.verificationRequest.off("change", this.onVerificationRequestChange);
|
|
||||||
this.setState({
|
|
||||||
verificationRequest: null,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
onSkipClick = () => {
|
|
||||||
this.setState({
|
|
||||||
phase: PHASE_CONFIRM_SKIP,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
onSkipConfirmClick = () => {
|
|
||||||
this.props.onFinished();
|
|
||||||
}
|
|
||||||
|
|
||||||
onSkipBackClick = () => {
|
|
||||||
this.setState({
|
|
||||||
phase: PHASE_INTRO,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
onDoneClick = () => {
|
|
||||||
this.props.onFinished();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const AuthPage = sdk.getComponent("auth.AuthPage");
|
const AuthPage = sdk.getComponent("auth.AuthPage");
|
||||||
const CompleteSecurityBody = sdk.getComponent("auth.CompleteSecurityBody");
|
const CompleteSecurityBody = sdk.getComponent("auth.CompleteSecurityBody");
|
||||||
const AccessibleButton = sdk.getComponent("elements.AccessibleButton");
|
const {phase} = this.state;
|
||||||
|
|
||||||
const {
|
|
||||||
phase,
|
|
||||||
} = this.state;
|
|
||||||
|
|
||||||
let icon;
|
let icon;
|
||||||
let title;
|
let title;
|
||||||
let body;
|
|
||||||
|
|
||||||
if (this.state.verificationRequest) {
|
|
||||||
const EncryptionPanel = sdk.getComponent("views.right_panel.EncryptionPanel");
|
|
||||||
body = <EncryptionPanel
|
|
||||||
layout="dialog"
|
|
||||||
verificationRequest={this.state.verificationRequest}
|
|
||||||
onClose={this.props.onFinished}
|
|
||||||
member={MatrixClientPeg.get().getUser(this.state.verificationRequest.otherUserId)}
|
|
||||||
/>;
|
|
||||||
} else if (phase === PHASE_INTRO) {
|
|
||||||
const InlineSpinner = sdk.getComponent('elements.InlineSpinner');
|
|
||||||
|
|
||||||
|
if (phase === PHASE_INTRO) {
|
||||||
icon = <span className="mx_CompleteSecurity_headerIcon mx_E2EIcon_warning"></span>;
|
icon = <span className="mx_CompleteSecurity_headerIcon mx_E2EIcon_warning"></span>;
|
||||||
title = _t("Complete security");
|
title = _t("Complete security");
|
||||||
body = (
|
|
||||||
<div>
|
|
||||||
<p>{_t(
|
|
||||||
"Open an existing session & use it to verify this one, " +
|
|
||||||
"granting it access to encrypted messages.",
|
|
||||||
)}</p>
|
|
||||||
<p className="mx_CompleteSecurity_waiting"><InlineSpinner />{_t("Waiting…")}</p>
|
|
||||||
<p>{_t(
|
|
||||||
"If you can’t access one, <button>use your recovery key or passphrase.</button>",
|
|
||||||
{}, {
|
|
||||||
button: sub => <AccessibleButton element="span"
|
|
||||||
className="mx_linkButton"
|
|
||||||
onClick={this._onUsePassphraseClick}
|
|
||||||
>
|
|
||||||
{sub}
|
|
||||||
</AccessibleButton>,
|
|
||||||
})}</p>
|
|
||||||
<div className="mx_CompleteSecurity_actionRow">
|
|
||||||
<AccessibleButton
|
|
||||||
kind="danger"
|
|
||||||
onClick={this.onSkipClick}
|
|
||||||
>
|
|
||||||
{_t("Skip")}
|
|
||||||
</AccessibleButton>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
} else if (phase === PHASE_DONE) {
|
} else if (phase === PHASE_DONE) {
|
||||||
icon = <span className="mx_CompleteSecurity_headerIcon mx_E2EIcon_verified"></span>;
|
icon = <span className="mx_CompleteSecurity_headerIcon mx_E2EIcon_verified"></span>;
|
||||||
title = _t("Session verified");
|
title = _t("Session verified");
|
||||||
let message;
|
|
||||||
if (this.state.backupInfo) {
|
|
||||||
message = <p>{_t(
|
|
||||||
"Your new session is now verified. It has access to your " +
|
|
||||||
"encrypted messages, and other users will see it as trusted.",
|
|
||||||
)}</p>;
|
|
||||||
} else {
|
|
||||||
message = <p>{_t(
|
|
||||||
"Your new session is now verified. Other users will see it as trusted.",
|
|
||||||
)}</p>;
|
|
||||||
}
|
|
||||||
body = (
|
|
||||||
<div>
|
|
||||||
<div className="mx_CompleteSecurity_heroIcon mx_E2EIcon_verified"></div>
|
|
||||||
{message}
|
|
||||||
<div className="mx_CompleteSecurity_actionRow">
|
|
||||||
<AccessibleButton
|
|
||||||
kind="primary"
|
|
||||||
onClick={this.onDoneClick}
|
|
||||||
>
|
|
||||||
{_t("Done")}
|
|
||||||
</AccessibleButton>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
} else if (phase === PHASE_CONFIRM_SKIP) {
|
} else if (phase === PHASE_CONFIRM_SKIP) {
|
||||||
icon = <span className="mx_CompleteSecurity_headerIcon mx_E2EIcon_warning"></span>;
|
icon = <span className="mx_CompleteSecurity_headerIcon mx_E2EIcon_warning"></span>;
|
||||||
title = _t("Are you sure?");
|
title = _t("Are you sure?");
|
||||||
body = (
|
|
||||||
<div>
|
|
||||||
<p>{_t(
|
|
||||||
"Without completing security on this session, it won’t have " +
|
|
||||||
"access to encrypted messages.",
|
|
||||||
)}</p>
|
|
||||||
<div className="mx_CompleteSecurity_actionRow">
|
|
||||||
<AccessibleButton
|
|
||||||
className="warning"
|
|
||||||
kind="secondary"
|
|
||||||
onClick={this.onSkipConfirmClick}
|
|
||||||
>
|
|
||||||
{_t("Skip")}
|
|
||||||
</AccessibleButton>
|
|
||||||
<AccessibleButton
|
|
||||||
kind="danger"
|
|
||||||
onClick={this.onSkipBackClick}
|
|
||||||
>
|
|
||||||
{_t("Go Back")}
|
|
||||||
</AccessibleButton>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
} else if (phase === PHASE_BUSY) {
|
} else if (phase === PHASE_BUSY) {
|
||||||
const Spinner = sdk.getComponent('views.elements.Spinner');
|
|
||||||
icon = <span className="mx_CompleteSecurity_headerIcon mx_E2EIcon_warning"></span>;
|
icon = <span className="mx_CompleteSecurity_headerIcon mx_E2EIcon_warning"></span>;
|
||||||
title = _t("Complete security");
|
title = _t("Complete security");
|
||||||
body = <Spinner />;
|
|
||||||
} else {
|
} else {
|
||||||
throw new Error(`Unknown phase ${phase}`);
|
throw new Error(`Unknown phase ${phase}`);
|
||||||
}
|
}
|
||||||
|
@ -271,7 +82,7 @@ export default class CompleteSecurity extends React.Component {
|
||||||
{title}
|
{title}
|
||||||
</h2>
|
</h2>
|
||||||
<div className="mx_CompleteSecurity_body">
|
<div className="mx_CompleteSecurity_body">
|
||||||
{body}
|
<SetupEncryptionBody onFinished={this.props.onFinished} />
|
||||||
</div>
|
</div>
|
||||||
</CompleteSecurityBody>
|
</CompleteSecurityBody>
|
||||||
</AuthPage>
|
</AuthPage>
|
||||||
|
|
|
@ -0,0 +1,196 @@
|
||||||
|
/*
|
||||||
|
Copyright 2020 The Matrix.org Foundation C.I.C.
|
||||||
|
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
you may not use this file except in compliance with the License.
|
||||||
|
You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing, software
|
||||||
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
|
limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import React from 'react';
|
||||||
|
import PropTypes from 'prop-types';
|
||||||
|
import { _t } from '../../../languageHandler';
|
||||||
|
import { MatrixClientPeg } from '../../../MatrixClientPeg';
|
||||||
|
import * as sdk from '../../../index';
|
||||||
|
import {
|
||||||
|
SetupEncryptionStore,
|
||||||
|
PHASE_INTRO,
|
||||||
|
PHASE_BUSY,
|
||||||
|
PHASE_DONE,
|
||||||
|
PHASE_CONFIRM_SKIP,
|
||||||
|
PHASE_FINISHED,
|
||||||
|
} from '../../../stores/SetupEncryptionStore';
|
||||||
|
|
||||||
|
export default class SetupEncryptionBody extends React.Component {
|
||||||
|
static propTypes = {
|
||||||
|
onFinished: PropTypes.func.isRequired,
|
||||||
|
};
|
||||||
|
|
||||||
|
constructor() {
|
||||||
|
super();
|
||||||
|
const store = SetupEncryptionStore.sharedInstance();
|
||||||
|
store.on("update", this._onStoreUpdate);
|
||||||
|
store.start();
|
||||||
|
this.state = {
|
||||||
|
phase: store.phase,
|
||||||
|
// this serves dual purpose as the object for the request logic and
|
||||||
|
// the presence of it indicating that we're in 'verify mode'.
|
||||||
|
// Because of the latter, it lives in the state.
|
||||||
|
verificationRequest: store.verificationRequest,
|
||||||
|
backupInfo: store.backupInfo,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
_onStoreUpdate = () => {
|
||||||
|
const store = SetupEncryptionStore.sharedInstance();
|
||||||
|
if (store.phase === PHASE_FINISHED) {
|
||||||
|
this.props.onFinished();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
this.setState({
|
||||||
|
phase: store.phase,
|
||||||
|
verificationRequest: store.verificationRequest,
|
||||||
|
backupInfo: store.backupInfo,
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
componentWillUnmount() {
|
||||||
|
const store = SetupEncryptionStore.sharedInstance();
|
||||||
|
store.off("update", this._onStoreUpdate);
|
||||||
|
store.stop();
|
||||||
|
}
|
||||||
|
|
||||||
|
_onUsePassphraseClick = async () => {
|
||||||
|
const store = SetupEncryptionStore.sharedInstance();
|
||||||
|
store.usePassPhrase();
|
||||||
|
}
|
||||||
|
|
||||||
|
onSkipClick = () => {
|
||||||
|
const store = SetupEncryptionStore.sharedInstance();
|
||||||
|
store.skip();
|
||||||
|
}
|
||||||
|
|
||||||
|
onSkipConfirmClick = () => {
|
||||||
|
const store = SetupEncryptionStore.sharedInstance();
|
||||||
|
store.skipConfirm();
|
||||||
|
}
|
||||||
|
|
||||||
|
onSkipBackClick = () => {
|
||||||
|
const store = SetupEncryptionStore.sharedInstance();
|
||||||
|
store.returnAfterSkip();
|
||||||
|
}
|
||||||
|
|
||||||
|
onDoneClick = () => {
|
||||||
|
const store = SetupEncryptionStore.sharedInstance();
|
||||||
|
store.done();
|
||||||
|
}
|
||||||
|
|
||||||
|
render() {
|
||||||
|
const AccessibleButton = sdk.getComponent("elements.AccessibleButton");
|
||||||
|
|
||||||
|
const {
|
||||||
|
phase,
|
||||||
|
} = this.state;
|
||||||
|
|
||||||
|
if (this.state.verificationRequest) {
|
||||||
|
const EncryptionPanel = sdk.getComponent("views.right_panel.EncryptionPanel");
|
||||||
|
return <EncryptionPanel
|
||||||
|
layout="dialog"
|
||||||
|
verificationRequest={this.state.verificationRequest}
|
||||||
|
onClose={this.props.onFinished}
|
||||||
|
member={MatrixClientPeg.get().getUser(this.state.verificationRequest.otherUserId)}
|
||||||
|
/>;
|
||||||
|
} else if (phase === PHASE_INTRO) {
|
||||||
|
const InlineSpinner = sdk.getComponent('elements.InlineSpinner');
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<p>{_t(
|
||||||
|
"Open an existing session & use it to verify this one, " +
|
||||||
|
"granting it access to encrypted messages.",
|
||||||
|
)}</p>
|
||||||
|
<p className="mx_CompleteSecurity_waiting"><InlineSpinner />{_t("Waiting…")}</p>
|
||||||
|
<p>{_t(
|
||||||
|
"If you can’t access one, <button>use your recovery key or passphrase.</button>",
|
||||||
|
{}, {
|
||||||
|
button: sub => <AccessibleButton element="span"
|
||||||
|
className="mx_linkButton"
|
||||||
|
onClick={this._onUsePassphraseClick}
|
||||||
|
>
|
||||||
|
{sub}
|
||||||
|
</AccessibleButton>,
|
||||||
|
})}</p>
|
||||||
|
<div className="mx_CompleteSecurity_actionRow">
|
||||||
|
<AccessibleButton
|
||||||
|
kind="danger"
|
||||||
|
onClick={this.onSkipClick}
|
||||||
|
>
|
||||||
|
{_t("Skip")}
|
||||||
|
</AccessibleButton>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
} else if (phase === PHASE_DONE) {
|
||||||
|
let message;
|
||||||
|
if (this.state.backupInfo) {
|
||||||
|
message = <p>{_t(
|
||||||
|
"Your new session is now verified. It has access to your " +
|
||||||
|
"encrypted messages, and other users will see it as trusted.",
|
||||||
|
)}</p>;
|
||||||
|
} else {
|
||||||
|
message = <p>{_t(
|
||||||
|
"Your new session is now verified. Other users will see it as trusted.",
|
||||||
|
)}</p>;
|
||||||
|
}
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<div className="mx_CompleteSecurity_heroIcon mx_E2EIcon_verified"></div>
|
||||||
|
{message}
|
||||||
|
<div className="mx_CompleteSecurity_actionRow">
|
||||||
|
<AccessibleButton
|
||||||
|
kind="primary"
|
||||||
|
onClick={this.onDoneClick}
|
||||||
|
>
|
||||||
|
{_t("Done")}
|
||||||
|
</AccessibleButton>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
} else if (phase === PHASE_CONFIRM_SKIP) {
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<p>{_t(
|
||||||
|
"Without completing security on this session, it won’t have " +
|
||||||
|
"access to encrypted messages.",
|
||||||
|
)}</p>
|
||||||
|
<div className="mx_CompleteSecurity_actionRow">
|
||||||
|
<AccessibleButton
|
||||||
|
className="warning"
|
||||||
|
kind="secondary"
|
||||||
|
onClick={this.onSkipConfirmClick}
|
||||||
|
>
|
||||||
|
{_t("Skip")}
|
||||||
|
</AccessibleButton>
|
||||||
|
<AccessibleButton
|
||||||
|
kind="danger"
|
||||||
|
onClick={this.onSkipBackClick}
|
||||||
|
>
|
||||||
|
{_t("Go Back")}
|
||||||
|
</AccessibleButton>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
} else if (phase === PHASE_BUSY) {
|
||||||
|
const Spinner = sdk.getComponent('views.elements.Spinner');
|
||||||
|
return <Spinner />;
|
||||||
|
} else {
|
||||||
|
console.log(`SetupEncryptionBody: Unknown phase ${phase}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,29 @@
|
||||||
|
/*
|
||||||
|
Copyright 2020 The Matrix.org Foundation C.I.C.
|
||||||
|
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
you may not use this file except in compliance with the License.
|
||||||
|
You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing, software
|
||||||
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
|
limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import SetupEncryptionBody from '../../structures/auth/SetupEncryptionBody';
|
||||||
|
import BaseDialog from './BaseDialog';
|
||||||
|
import { _t } from '../../../languageHandler';
|
||||||
|
|
||||||
|
export default function SetupEncryptionDialog({onFinished}) {
|
||||||
|
return <BaseDialog
|
||||||
|
headerImage={require("../../../../res/img/e2e/warning.svg")}
|
||||||
|
onFinished={onFinished}
|
||||||
|
title={_t("Verify this session")}
|
||||||
|
>
|
||||||
|
<SetupEncryptionBody onFinished={onFinished} />
|
||||||
|
</BaseDialog>;
|
||||||
|
}
|
|
@ -18,7 +18,9 @@ import React from 'react';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import * as sdk from "../../../index";
|
import * as sdk from "../../../index";
|
||||||
import { _t } from '../../../languageHandler';
|
import { _t } from '../../../languageHandler';
|
||||||
|
import Modal from '../../../Modal';
|
||||||
import DeviceListener from '../../../DeviceListener';
|
import DeviceListener from '../../../DeviceListener';
|
||||||
|
import SetupEncryptionDialog from "../dialogs/SetupEncryptionDialog";
|
||||||
import { accessSecretStorage } from '../../../CrossSigningManager';
|
import { accessSecretStorage } from '../../../CrossSigningManager';
|
||||||
|
|
||||||
export default class SetupEncryptionToast extends React.PureComponent {
|
export default class SetupEncryptionToast extends React.PureComponent {
|
||||||
|
@ -32,7 +34,12 @@ export default class SetupEncryptionToast extends React.PureComponent {
|
||||||
};
|
};
|
||||||
|
|
||||||
_onSetupClick = async () => {
|
_onSetupClick = async () => {
|
||||||
accessSecretStorage();
|
if (this.props.kind === "verify_this_session") {
|
||||||
|
Modal.createTrackedDialog('Verify session', 'Verify session', SetupEncryptionDialog,
|
||||||
|
{}, null, /* priority = */ false, /* static = */ true);
|
||||||
|
} else {
|
||||||
|
accessSecretStorage();
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
getDescription() {
|
getDescription() {
|
||||||
|
|
|
@ -2010,14 +2010,7 @@
|
||||||
"Uploading %(filename)s and %(count)s others|one": "Uploading %(filename)s and %(count)s other",
|
"Uploading %(filename)s and %(count)s others|one": "Uploading %(filename)s and %(count)s other",
|
||||||
"Could not load user profile": "Could not load user profile",
|
"Could not load user profile": "Could not load user profile",
|
||||||
"Complete security": "Complete security",
|
"Complete security": "Complete security",
|
||||||
"Open an existing session & use it to verify this one, granting it access to encrypted messages.": "Open an existing session & use it to verify this one, granting it access to encrypted messages.",
|
|
||||||
"Waiting…": "Waiting…",
|
|
||||||
"If you can’t access one, <button>use your recovery key or passphrase.</button>": "If you can’t access one, <button>use your recovery key or passphrase.</button>",
|
|
||||||
"Session verified": "Session verified",
|
"Session verified": "Session verified",
|
||||||
"Your new session is now verified. It has access to your encrypted messages, and other users will see it as trusted.": "Your new session is now verified. It has access to your encrypted messages, and other users will see it as trusted.",
|
|
||||||
"Your new session is now verified. Other users will see it as trusted.": "Your new session is now verified. Other users will see it as trusted.",
|
|
||||||
"Without completing security on this session, it won’t have access to encrypted messages.": "Without completing security on this session, it won’t have access to encrypted messages.",
|
|
||||||
"Go Back": "Go Back",
|
|
||||||
"Failed to send email": "Failed to send email",
|
"Failed to send email": "Failed to send email",
|
||||||
"The email address linked to your account must be entered.": "The email address linked to your account must be entered.",
|
"The email address linked to your account must be entered.": "The email address linked to your account must be entered.",
|
||||||
"A new password must be entered.": "A new password must be entered.",
|
"A new password must be entered.": "A new password must be entered.",
|
||||||
|
@ -2067,6 +2060,13 @@
|
||||||
"You can now close this window or <a>log in</a> to your new account.": "You can now close this window or <a>log in</a> to your new account.",
|
"You can now close this window or <a>log in</a> to your new account.": "You can now close this window or <a>log in</a> to your new account.",
|
||||||
"Registration Successful": "Registration Successful",
|
"Registration Successful": "Registration Successful",
|
||||||
"Create your account": "Create your account",
|
"Create your account": "Create your account",
|
||||||
|
"Open an existing session & use it to verify this one, granting it access to encrypted messages.": "Open an existing session & use it to verify this one, granting it access to encrypted messages.",
|
||||||
|
"Waiting…": "Waiting…",
|
||||||
|
"If you can’t access one, <button>use your recovery key or passphrase.</button>": "If you can’t access one, <button>use your recovery key or passphrase.</button>",
|
||||||
|
"Your new session is now verified. It has access to your encrypted messages, and other users will see it as trusted.": "Your new session is now verified. It has access to your encrypted messages, and other users will see it as trusted.",
|
||||||
|
"Your new session is now verified. Other users will see it as trusted.": "Your new session is now verified. Other users will see it as trusted.",
|
||||||
|
"Without completing security on this session, it won’t have access to encrypted messages.": "Without completing security on this session, it won’t have access to encrypted messages.",
|
||||||
|
"Go Back": "Go Back",
|
||||||
"Failed to re-authenticate due to a homeserver problem": "Failed to re-authenticate due to a homeserver problem",
|
"Failed to re-authenticate due to a homeserver problem": "Failed to re-authenticate due to a homeserver problem",
|
||||||
"Failed to re-authenticate": "Failed to re-authenticate",
|
"Failed to re-authenticate": "Failed to re-authenticate",
|
||||||
"Regain access to your account and recover encryption keys stored in this session. Without them, you won’t be able to read all of your secure messages in any session.": "Regain access to your account and recover encryption keys stored in this session. Without them, you won’t be able to read all of your secure messages in any session.",
|
"Regain access to your account and recover encryption keys stored in this session. Without them, you won’t be able to read all of your secure messages in any session.": "Regain access to your account and recover encryption keys stored in this session. Without them, you won’t be able to read all of your secure messages in any session.",
|
||||||
|
|
|
@ -0,0 +1,144 @@
|
||||||
|
/*
|
||||||
|
Copyright 2020 The Matrix.org Foundation C.I.C.
|
||||||
|
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
you may not use this file except in compliance with the License.
|
||||||
|
You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing, software
|
||||||
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
|
limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import EventEmitter from 'events';
|
||||||
|
import { MatrixClientPeg } from '../MatrixClientPeg';
|
||||||
|
import { accessSecretStorage, AccessCancelledError } from '../CrossSigningManager';
|
||||||
|
|
||||||
|
export const PHASE_INTRO = 0;
|
||||||
|
export const PHASE_BUSY = 1;
|
||||||
|
export const PHASE_DONE = 2; //final done stage, but still showing UX
|
||||||
|
export const PHASE_CONFIRM_SKIP = 3;
|
||||||
|
export const PHASE_FINISHED = 4; //UX can be closed
|
||||||
|
|
||||||
|
export class SetupEncryptionStore extends EventEmitter {
|
||||||
|
static sharedInstance() {
|
||||||
|
if (!global.mx_SetupEncryptionStore) global.mx_SetupEncryptionStore = new SetupEncryptionStore();
|
||||||
|
return global.mx_SetupEncryptionStore;
|
||||||
|
}
|
||||||
|
|
||||||
|
start() {
|
||||||
|
if (this._started) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
this._started = true;
|
||||||
|
this.phase = PHASE_INTRO;
|
||||||
|
this.verificationRequest = null;
|
||||||
|
this.backupInfo = null;
|
||||||
|
MatrixClientPeg.get().on("crypto.verification.request", this.onVerificationRequest);
|
||||||
|
}
|
||||||
|
|
||||||
|
stop() {
|
||||||
|
if (!this._started) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
this._started = false;
|
||||||
|
if (this.verificationRequest) {
|
||||||
|
this.verificationRequest.off("change", this.onVerificationRequestChange);
|
||||||
|
}
|
||||||
|
if (MatrixClientPeg.get()) {
|
||||||
|
MatrixClientPeg.get().removeListener("crypto.verification.request", this.onVerificationRequest);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async usePassPhrase() {
|
||||||
|
this.phase = PHASE_BUSY;
|
||||||
|
this.emit("update");
|
||||||
|
const cli = MatrixClientPeg.get();
|
||||||
|
try {
|
||||||
|
const backupInfo = await cli.getKeyBackupVersion();
|
||||||
|
this.backupInfo = backupInfo;
|
||||||
|
this.emit("update");
|
||||||
|
// The control flow is fairly twisted here...
|
||||||
|
// For the purposes of completing security, we only wait on getting
|
||||||
|
// as far as the trust check and then show a green shield.
|
||||||
|
// We also begin the key backup restore as well, which we're
|
||||||
|
// awaiting inside `accessSecretStorage` only so that it keeps your
|
||||||
|
// passphase cached for that work. This dialog itself will only wait
|
||||||
|
// on the first trust check, and the key backup restore will happen
|
||||||
|
// in the background.
|
||||||
|
await new Promise((resolve, reject) => {
|
||||||
|
try {
|
||||||
|
accessSecretStorage(async () => {
|
||||||
|
await cli.checkOwnCrossSigningTrust();
|
||||||
|
resolve();
|
||||||
|
if (backupInfo) {
|
||||||
|
// A complete restore can take many minutes for large
|
||||||
|
// accounts / slow servers, so we allow the dialog
|
||||||
|
// to advance before this.
|
||||||
|
await cli.restoreKeyBackupWithSecretStorage(backupInfo);
|
||||||
|
}
|
||||||
|
}).catch(reject);
|
||||||
|
} catch (e) {
|
||||||
|
console.error(e);
|
||||||
|
reject(e);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
if (cli.getCrossSigningId()) {
|
||||||
|
this.phase = PHASE_DONE;
|
||||||
|
this.emit("update");
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
if (!(e instanceof AccessCancelledError)) {
|
||||||
|
console.log(e);
|
||||||
|
}
|
||||||
|
// this will throw if the user hits cancel, so ignore
|
||||||
|
this.phase = PHASE_INTRO;
|
||||||
|
this.emit("update");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
onVerificationRequest = async (request) => {
|
||||||
|
if (request.otherUserId !== MatrixClientPeg.get().getUserId()) return;
|
||||||
|
|
||||||
|
if (this.verificationRequest) {
|
||||||
|
this.verificationRequest.off("change", this.onVerificationRequestChange);
|
||||||
|
}
|
||||||
|
this.verificationRequest = request;
|
||||||
|
await request.accept();
|
||||||
|
request.on("change", this.onVerificationRequestChange);
|
||||||
|
this.emit("update");
|
||||||
|
}
|
||||||
|
|
||||||
|
onVerificationRequestChange = () => {
|
||||||
|
if (this.verificationRequest.cancelled) {
|
||||||
|
this.verificationRequest.off("change", this.onVerificationRequestChange);
|
||||||
|
this.verificationRequest = null;
|
||||||
|
this.emit("update");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
skip() {
|
||||||
|
this.phase = PHASE_CONFIRM_SKIP;
|
||||||
|
this.emit("update");
|
||||||
|
}
|
||||||
|
|
||||||
|
skipConfirm() {
|
||||||
|
this.phase = PHASE_FINISHED;
|
||||||
|
this.emit("update");
|
||||||
|
}
|
||||||
|
|
||||||
|
returnAfterSkip() {
|
||||||
|
this.phase = PHASE_INTRO;
|
||||||
|
this.emit("update");
|
||||||
|
}
|
||||||
|
|
||||||
|
done() {
|
||||||
|
this.phase = PHASE_FINISHED;
|
||||||
|
this.emit("update");
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue