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:
@@ -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">
|
||||
|
||||
@@ -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>
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -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>
|
||||
);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user