replace zxcvbn field in CreateSecretStorageDialog with PassphraseField
Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>pull/21833/head
parent
cf3c4d9e5f
commit
865495dd69
|
@ -68,17 +68,6 @@ limitations under the License.
|
|||
margin-top: 0px;
|
||||
}
|
||||
|
||||
.mx_CreateSecretStorageDialog_passPhraseHelp {
|
||||
flex: 1;
|
||||
height: 64px;
|
||||
margin-left: 20px;
|
||||
font-size: 80%;
|
||||
}
|
||||
|
||||
.mx_CreateSecretStorageDialog_passPhraseHelp progress {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.mx_CreateSecretStorageDialog_passPhraseMatch {
|
||||
width: 200px;
|
||||
margin-left: 20px;
|
||||
|
|
|
@ -15,18 +15,17 @@ See the License for the specific language governing permissions and
|
|||
limitations under the License.
|
||||
*/
|
||||
|
||||
import React from 'react';
|
||||
import React, {createRef} from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import * as sdk from '../../../../index';
|
||||
import {MatrixClientPeg} from '../../../../MatrixClientPeg';
|
||||
import { scorePassword } from '../../../../utils/PasswordScorer';
|
||||
import FileSaver from 'file-saver';
|
||||
import { _t } from '../../../../languageHandler';
|
||||
import {_t, _td} from '../../../../languageHandler';
|
||||
import Modal from '../../../../Modal';
|
||||
import { promptForBackupPassphrase } from '../../../../CrossSigningManager';
|
||||
import {copyNode} from "../../../../utils/strings";
|
||||
import {SSOAuthEntry} from "../../../../components/views/auth/InteractiveAuthEntryComponents";
|
||||
import ZxcvbnProgressBar from "../../../../components/views/elements/ZxcvbnProgressBar";
|
||||
import PassphraseField from "../../../../components/views/auth/PassphraseField";
|
||||
|
||||
const PHASE_LOADING = 0;
|
||||
const PHASE_LOADERROR = 1;
|
||||
|
@ -40,7 +39,6 @@ const PHASE_DONE = 8;
|
|||
const PHASE_CONFIRM_SKIP = 9;
|
||||
|
||||
const PASSWORD_MIN_SCORE = 4; // So secure, many characters, much complex, wow, etc, etc.
|
||||
const PASSPHRASE_FEEDBACK_DELAY = 500; // How long after keystroke to offer passphrase feedback, ms.
|
||||
|
||||
/*
|
||||
* Walks the user through the process of creating a passphrase to guard Secure
|
||||
|
@ -69,10 +67,10 @@ export default class CreateSecretStorageDialog extends React.PureComponent {
|
|||
this.state = {
|
||||
phase: PHASE_LOADING,
|
||||
passPhrase: '',
|
||||
passPhraseValid: false,
|
||||
passPhraseConfirm: '',
|
||||
copied: false,
|
||||
downloaded: false,
|
||||
zxcvbnResult: null,
|
||||
backupInfo: null,
|
||||
backupSigStatus: null,
|
||||
// does the server offer a UI auth flow with just m.login.password
|
||||
|
@ -84,6 +82,8 @@ export default class CreateSecretStorageDialog extends React.PureComponent {
|
|||
useKeyBackup: true,
|
||||
};
|
||||
|
||||
this._passphraseField = createRef();
|
||||
|
||||
this._fetchBackupInfo();
|
||||
if (this.state.accountPassword) {
|
||||
// If we have an account password in memory, let's simplify and
|
||||
|
@ -365,22 +365,16 @@ export default class CreateSecretStorageDialog extends React.PureComponent {
|
|||
|
||||
_onPassPhraseNextClick = async (e) => {
|
||||
e.preventDefault();
|
||||
if (!this._passphraseField.current) return; // unmounting
|
||||
|
||||
// If we're waiting for the timeout before updating the result at this point,
|
||||
// skip ahead and do it now, otherwise we'll deny the attempt to proceed
|
||||
// even if the user entered a valid passphrase
|
||||
if (this._setZxcvbnResultTimeout !== null) {
|
||||
clearTimeout(this._setZxcvbnResultTimeout);
|
||||
this._setZxcvbnResultTimeout = null;
|
||||
await new Promise((resolve) => {
|
||||
this.setState({
|
||||
zxcvbnResult: scorePassword(this.state.passPhrase),
|
||||
}, resolve);
|
||||
});
|
||||
}
|
||||
if (this._passPhraseIsValid()) {
|
||||
this.setState({phase: PHASE_PASSPHRASE_CONFIRM});
|
||||
await this._passphraseField.current.validate({ allowEmpty: false });
|
||||
if (!this._passphraseField.current.state.valid) {
|
||||
this._passphraseField.current.focus();
|
||||
this._passphraseField.current.validate({ allowEmpty: false, focused: true });
|
||||
return;
|
||||
}
|
||||
|
||||
this.setState({phase: PHASE_PASSPHRASE_CONFIRM});
|
||||
};
|
||||
|
||||
_onPassPhraseConfirmNextClick = async (e) => {
|
||||
|
@ -400,9 +394,9 @@ export default class CreateSecretStorageDialog extends React.PureComponent {
|
|||
_onSetAgainClick = () => {
|
||||
this.setState({
|
||||
passPhrase: '',
|
||||
passPhraseValid: false,
|
||||
passPhraseConfirm: '',
|
||||
phase: PHASE_PASSPHRASE,
|
||||
zxcvbnResult: null,
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -412,23 +406,16 @@ export default class CreateSecretStorageDialog extends React.PureComponent {
|
|||
});
|
||||
}
|
||||
|
||||
_onPassPhraseValidate = (result) => {
|
||||
this.setState({
|
||||
passPhraseValid: result.valid,
|
||||
});
|
||||
};
|
||||
|
||||
_onPassPhraseChange = (e) => {
|
||||
this.setState({
|
||||
passPhrase: e.target.value,
|
||||
});
|
||||
|
||||
if (this._setZxcvbnResultTimeout !== null) {
|
||||
clearTimeout(this._setZxcvbnResultTimeout);
|
||||
}
|
||||
this._setZxcvbnResultTimeout = setTimeout(() => {
|
||||
this._setZxcvbnResultTimeout = null;
|
||||
this.setState({
|
||||
// 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(this.state.passPhrase),
|
||||
});
|
||||
}, PASSPHRASE_FEEDBACK_DELAY);
|
||||
}
|
||||
|
||||
_onPassPhraseConfirmChange = (e) => {
|
||||
|
@ -437,10 +424,6 @@ export default class CreateSecretStorageDialog extends React.PureComponent {
|
|||
});
|
||||
}
|
||||
|
||||
_passPhraseIsValid() {
|
||||
return this.state.zxcvbnResult && this.state.zxcvbnResult.score >= PASSWORD_MIN_SCORE;
|
||||
}
|
||||
|
||||
_onAccountPasswordChange = (e) => {
|
||||
this.setState({
|
||||
accountPassword: e.target.value,
|
||||
|
@ -503,37 +486,9 @@ export default class CreateSecretStorageDialog extends React.PureComponent {
|
|||
|
||||
_renderPhasePassPhrase() {
|
||||
const DialogButtons = sdk.getComponent('views.elements.DialogButtons');
|
||||
const Field = sdk.getComponent('views.elements.Field');
|
||||
const AccessibleButton = sdk.getComponent('elements.AccessibleButton');
|
||||
const LabelledToggleSwitch = sdk.getComponent('views.elements.LabelledToggleSwitch');
|
||||
|
||||
let strengthMeter;
|
||||
let helpText;
|
||||
if (this.state.zxcvbnResult) {
|
||||
if (this.state.zxcvbnResult.score >= PASSWORD_MIN_SCORE) {
|
||||
helpText = _t("Great! This recovery passphrase looks strong enough.");
|
||||
} else {
|
||||
// We take the warning from zxcvbn or failing that, the first
|
||||
// suggestion. In practice The first is generally the most relevant
|
||||
// and it's probably better to present the user with one thing to
|
||||
// improve about their password than a whole collection - it can
|
||||
// spit out a warning and multiple suggestions which starts getting
|
||||
// very information-dense.
|
||||
const suggestion = (
|
||||
this.state.zxcvbnResult.feedback.warning ||
|
||||
this.state.zxcvbnResult.feedback.suggestions[0]
|
||||
);
|
||||
const suggestionBlock = <div>{suggestion || _t("Keep going...")}</div>;
|
||||
|
||||
helpText = <div>
|
||||
{suggestionBlock}
|
||||
</div>;
|
||||
}
|
||||
strengthMeter = <div>
|
||||
<ZxcvbnProgressBar value={this.state.zxcvbnResult.score} />
|
||||
</div>;
|
||||
}
|
||||
|
||||
return <form onSubmit={this._onPassPhraseNextClick}>
|
||||
<p>{_t(
|
||||
"Set a recovery passphrase to secure encrypted information and recover it if you log out. " +
|
||||
|
@ -541,19 +496,20 @@ export default class CreateSecretStorageDialog extends React.PureComponent {
|
|||
)}</p>
|
||||
|
||||
<div className="mx_CreateSecretStorageDialog_passPhraseContainer">
|
||||
<Field
|
||||
type="password"
|
||||
<PassphraseField
|
||||
className="mx_CreateSecretStorageDialog_passPhraseField"
|
||||
onChange={this._onPassPhraseChange}
|
||||
minScore={PASSWORD_MIN_SCORE}
|
||||
value={this.state.passPhrase}
|
||||
label={_t("Enter a recovery passphrase")}
|
||||
onValidate={this._onPassPhraseValidate}
|
||||
fieldRef={this._passphraseField}
|
||||
autoFocus={true}
|
||||
autoComplete="new-password"
|
||||
|
||||
label={_td("Enter a recovery passphrase")}
|
||||
labelEnterPassword={_td("Enter a recovery passphrase")}
|
||||
labelStrongPassword={_td("Great! This recovery passphrase looks strong enough.")}
|
||||
labelAllowedButUnsafe={_td("Great! This recovery passphrase looks strong enough.")}
|
||||
/>
|
||||
<div className="mx_CreateSecretStorageDialog_passPhraseHelp">
|
||||
{strengthMeter}
|
||||
{helpText}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<LabelledToggleSwitch
|
||||
|
@ -565,7 +521,7 @@ export default class CreateSecretStorageDialog extends React.PureComponent {
|
|||
primaryButton={_t('Continue')}
|
||||
onPrimaryButtonClick={this._onPassPhraseNextClick}
|
||||
hasCancel={false}
|
||||
disabled={!this._passPhraseIsValid()}
|
||||
disabled={!this.state.passPhraseValid}
|
||||
>
|
||||
<button type="button"
|
||||
onClick={this._onSkipSetupClick}
|
||||
|
|
|
@ -24,11 +24,12 @@ import {_t, _td} from "../../../languageHandler";
|
|||
import Field from "../elements/Field";
|
||||
|
||||
interface IProps {
|
||||
autoFocus?: boolean;
|
||||
id?: string;
|
||||
className?: string;
|
||||
minScore: 0 | 1 | 2 | 3 | 4;
|
||||
value: string;
|
||||
fieldRef: RefCallback<Field> | RefObject<Field>;
|
||||
fieldRef?: RefCallback<Field> | RefObject<Field>;
|
||||
|
||||
label?: string;
|
||||
labelEnterPassword?: string;
|
||||
|
@ -51,6 +52,8 @@ class PassphraseField extends PureComponent<IProps, IState> {
|
|||
labelAllowedButUnsafe: _td("Password is allowed, but unsafe"),
|
||||
};
|
||||
|
||||
state = { complexity: null };
|
||||
|
||||
public readonly validate = withValidation<this>({
|
||||
description: function() {
|
||||
const complexity = this.state.complexity;
|
||||
|
@ -106,6 +109,7 @@ class PassphraseField extends PureComponent<IProps, IState> {
|
|||
render() {
|
||||
return <Field
|
||||
id={this.props.id}
|
||||
autoFocus={this.props.autoFocus}
|
||||
className={classNames("mx_PassphraseField", this.props.className)}
|
||||
ref={this.props.fieldRef}
|
||||
type="password"
|
||||
|
|
Loading…
Reference in New Issue