Partial merge of develop to experimental

Does not include #2336 as the file has been moved out from underneath it:
will do this separately
This commit is contained in:
David Baker
2019-01-03 15:02:58 +00:00
parent 00405e7f22
commit 7d161de35b
77 changed files with 3526 additions and 598 deletions

View File

@@ -17,6 +17,7 @@ limitations under the License.
import React from 'react';
import sdk from '../../../../index';
import MatrixClientPeg from '../../../../MatrixClientPeg';
import { scorePassword } from '../../../../utils/PasswordScorer';
import FileSaver from 'file-saver';
@@ -30,6 +31,8 @@ const PHASE_BACKINGUP = 4;
const PHASE_DONE = 5;
const PHASE_OPTOUT_CONFIRM = 6;
const PASSWORD_MIN_SCORE = 4; // So secure, many characters, much complex, wow, etc, etc.
// XXX: copied from ShareDialog: factor out into utils
function selectText(target) {
const range = document.createRange();
@@ -52,6 +55,8 @@ export default React.createClass({
passPhraseConfirm: '',
copied: false,
downloaded: false,
zxcvbnResult: null,
setPassPhrase: false,
};
},
@@ -87,25 +92,33 @@ export default React.createClass({
});
},
_createBackup: function() {
_createBackup: async function() {
this.setState({
phase: PHASE_BACKINGUP,
error: null,
});
this._createBackupPromise = MatrixClientPeg.get().createKeyBackupVersion(
this._keyBackupInfo,
).then((info) => {
return MatrixClientPeg.get().backupAllGroupSessions(info.version);
}).then(() => {
let info;
try {
info = await MatrixClientPeg.get().createKeyBackupVersion(
this._keyBackupInfo,
);
await MatrixClientPeg.get().backupAllGroupSessions(info.version);
this.setState({
phase: PHASE_DONE,
});
}).catch(e => {
} catch (e) {
console.log("Error creating key backup", e);
// TODO: If creating a version succeeds, but backup fails, should we
// delete the version, disable backup, or do nothing? If we just
// disable without deleting, we'll enable on next app reload since
// it is trusted.
if (info) {
MatrixClientPeg.get().deleteKeyBackupVersion(info.version);
}
this.setState({
error: e,
});
});
}
},
_onCancel: function() {
@@ -128,6 +141,7 @@ export default React.createClass({
this._keyBackupInfo = await MatrixClientPeg.get().prepareKeyBackupVersion();
this.setState({
copied: false,
downloaded: false,
phase: PHASE_SHOWKEY,
});
},
@@ -145,7 +159,9 @@ export default React.createClass({
_onPassPhraseConfirmNextClick: async function() {
this._keyBackupInfo = await MatrixClientPeg.get().prepareKeyBackupVersion(this.state.passPhrase);
this.setState({
setPassPhrase: true,
copied: false,
downloaded: false,
phase: PHASE_SHOWKEY,
});
},
@@ -173,6 +189,10 @@ export default React.createClass({
_onPassPhraseChange: function(e) {
this.setState({
passPhrase: e.target.value,
// precompute this and keep it in state: zxcvbn is fast but
// we use it in a couple of different places so no point recomputing
// it unnecessarily.
zxcvbnResult: scorePassword(e.target.value),
});
},
@@ -183,17 +203,46 @@ export default React.createClass({
},
_passPhraseIsValid: function() {
return this.state.passPhrase !== '';
return this.state.zxcvbnResult && this.state.zxcvbnResult.score >= PASSWORD_MIN_SCORE;
},
_renderPhasePassPhrase: function() {
const DialogButtons = sdk.getComponent('views.elements.DialogButtons');
const AccessibleButton = sdk.getComponent('elements.AccessibleButton');
let strengthMeter;
let helpText;
if (this.state.zxcvbnResult) {
if (this.state.zxcvbnResult.score >= PASSWORD_MIN_SCORE) {
helpText = _t("Great! This passphrase looks strong enough.");
} else {
const suggestions = [];
for (let i = 0; i < this.state.zxcvbnResult.feedback.suggestions.length; ++i) {
suggestions.push(<div key={i}>{this.state.zxcvbnResult.feedback.suggestions[i]}</div>);
}
const suggestionBlock = suggestions.length > 0 ? <div>
{suggestions}
</div> : null;
helpText = <div>
{this.state.zxcvbnResult.feedback.warning}
{suggestionBlock}
</div>;
}
strengthMeter = <div>
<progress max={PASSWORD_MIN_SCORE} value={this.state.zxcvbnResult.score} />
</div>;
}
return <div>
<p>{_t("Secure your encrypted message history with a Recovery Passphrase.")}</p>
<p>{_t("You'll need it if you log out or lose access to this device.")}</p>
<div className="mx_CreateKeyBackupDialog_primaryContainer">
<div className="mx_CreateKeyBackupDialog_passPhraseHelp">
{strengthMeter}
{helpText}
</div>
<input type="password"
onChange={this._onPassPhraseChange}
onKeyPress={this._onPassPhraseKeyPress}
@@ -210,7 +259,7 @@ export default React.createClass({
/>
<p>{_t(
"If you don't want encrypted message history to be availble on other devices, "+
"If you don't want encrypted message history to be available on other devices, "+
"<button>opt out</button>.",
{},
{
@@ -290,9 +339,17 @@ export default React.createClass({
_renderPhaseShowKey: function() {
const DialogButtons = sdk.getComponent('views.elements.DialogButtons');
let bodyText;
if (this.state.setPassPhrase) {
bodyText = _t("As a safety net, you can use it to restore your encrypted message history if you forget your Recovery Passphrase.");
} else {
bodyText = _t("As a safety net, you can use it to restore your encrypted message history.");
}
return <div>
<p>{_t("Make a copy of this Recovery Key and keep it safe.")}</p>
<p>{_t("As a safety net, you can use it to restore your encrypted message history if you forget your Recovery Passphrase.")}</p>
<p>{bodyText}</p>
<p className="mx_CreateKeyBackupDialog_primaryContainer">
<div>{_t("Your Recovery Key")}</div>
<div className="mx_CreateKeyBackupDialog_recoveryKeyButtons">

View File

@@ -0,0 +1,70 @@
/*
Copyright 2018 New Vector Ltd
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 sdk from "../../../../index";
import { _t } from "../../../../languageHandler";
export default class IgnoreRecoveryReminderDialog extends React.PureComponent {
static propTypes = {
onDontAskAgain: PropTypes.func.isRequired,
onFinished: PropTypes.func.isRequired,
onSetup: PropTypes.func.isRequired,
}
onDontAskAgainClick = () => {
this.props.onFinished();
this.props.onDontAskAgain();
}
onSetupClick = () => {
this.props.onFinished();
this.props.onSetup();
}
render() {
const BaseDialog = sdk.getComponent("views.dialogs.BaseDialog");
const DialogButtons = sdk.getComponent("views.elements.DialogButtons");
return (
<BaseDialog className="mx_IgnoreRecoveryReminderDialog"
onFinished={this.props.onFinished}
title={_t("Are you sure?")}
>
<div>
<p>{_t(
"Without setting up Secure Message Recovery, " +
"you'll lose your secure message history when you " +
"log out.",
)}</p>
<p>{_t(
"If you don't want to set this up now, you can later " +
"in Settings.",
)}</p>
<div className="mx_Dialog_buttons">
<DialogButtons
primaryButton={_t("Set up")}
onPrimaryButtonClick={this.onSetupClick}
cancelButton={_t("Don't ask again")}
onCancel={this.onDontAskAgainClick}
/>
</div>
</div>
</BaseDialog>
);
}
}

View File

@@ -0,0 +1,110 @@
/*
Copyright 2018 New Vector Ltd
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 sdk from "../../../../index";
import MatrixClientPeg from '../../../../MatrixClientPeg';
import dis from "../../../../dispatcher";
import { _t } from "../../../../languageHandler";
import Modal from "../../../../Modal";
export default class NewRecoveryMethodDialog extends React.PureComponent {
static propTypes = {
onFinished: PropTypes.func.isRequired,
}
onGoToSettingsClick = () => {
this.props.onFinished();
dis.dispatch({ action: 'view_user_settings' });
}
onSetupClick = async() => {
// TODO: Should change to a restore key backup flow that checks the
// recovery passphrase while at the same time also cross-signing the
// device as well in a single flow. Since we don't have that yet, we'll
// look for an unverified device and verify it. Note that this means
// we won't restore keys yet; instead we'll only trust the backup for
// sending our own new keys to it.
let backupSigStatus;
try {
const backupInfo = await MatrixClientPeg.get().getKeyBackupVersion();
backupSigStatus = await MatrixClientPeg.get().isKeyBackupTrusted(backupInfo);
} catch (e) {
console.log("Unable to fetch key backup status", e);
return;
}
let unverifiedDevice;
for (const sig of backupSigStatus.sigs) {
if (!sig.device.isVerified()) {
unverifiedDevice = sig.device;
break;
}
}
if (!unverifiedDevice) {
console.log("Unable to find a device to verify.");
return;
}
const DeviceVerifyDialog = sdk.getComponent('views.dialogs.DeviceVerifyDialog');
Modal.createTrackedDialog('Device Verify Dialog', '', DeviceVerifyDialog, {
userId: MatrixClientPeg.get().credentials.userId,
device: unverifiedDevice,
onFinished: this.props.onFinished,
});
}
render() {
const BaseDialog = sdk.getComponent("views.dialogs.BaseDialog");
const DialogButtons = sdk.getComponent("views.elements.DialogButtons");
const title = <span className="mx_NewRecoveryMethodDialog_title">
{_t("New Recovery Method")}
</span>;
return (
<BaseDialog className="mx_NewRecoveryMethodDialog"
onFinished={this.props.onFinished}
title={title}
hasCancel={false}
>
<div>
<p>{_t(
"A new recovery passphrase and key for Secure " +
"Messages has been detected.",
)}</p>
<p>{_t(
"Setting up Secure Messages on this device " +
"will re-encrypt this device's message history with " +
"the new recovery method.",
)}</p>
<p className="warning">{_t(
"If you didn't set the new recovery method, an " +
"attacker may be trying to access your account. " +
"Change your account password and set a new recovery " +
"method immediately in Settings.",
)}</p>
<DialogButtons
primaryButton={_t("Set up Secure Messages")}
onPrimaryButtonClick={this.onSetupClick}
cancelButton={_t("Go to Settings")}
onCancel={this.onGoToSettingsClick}
/>
</div>
</BaseDialog>
);
}
}