From 7e786e67a8a8d615999d336e15c2c4a7ec14b0cd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=A0imon=20Brandner?= Date: Sat, 21 Nov 2020 20:10:38 +0100 Subject: [PATCH] Added live validation --- .../views/settings/ChangePassword.js | 54 +++++++++++++++++-- 1 file changed, 50 insertions(+), 4 deletions(-) diff --git a/src/components/views/settings/ChangePassword.js b/src/components/views/settings/ChangePassword.js index bafbc816b9..3e3254c666 100644 --- a/src/components/views/settings/ChangePassword.js +++ b/src/components/views/settings/ChangePassword.js @@ -21,9 +21,16 @@ import PropTypes from 'prop-types'; import {MatrixClientPeg} from "../../../MatrixClientPeg"; import AccessibleButton from '../elements/AccessibleButton'; import Spinner from '../elements/Spinner'; +import withValidation from '../elements/Validation'; import { _t } from '../../../languageHandler'; import * as sdk from "../../../index"; import Modal from "../../../Modal"; +import PassphraseField from "../auth/PassphraseField"; + +const FIELD_NEW_PASSWORD = 'field_new_password'; +const FIELD_NEW_PASSWORD_CONFIRM = 'field_new_password_confirm'; + +const PASSWORD_MIN_SCORE = 3; // safely unguessable: moderate protection from offline slow-hash scenario. export default class ChangePassword extends React.Component { static propTypes = { @@ -63,6 +70,7 @@ export default class ChangePassword extends React.Component { } state = { + fieldValid: {}, phase: ChangePassword.Phases.Edit, oldPassword: "", newPassword: "", @@ -168,6 +176,14 @@ export default class ChangePassword extends React.Component { ); }; + markFieldValid(fieldID, valid) { + const { fieldValid } = this.state; + fieldValid[fieldID] = valid; + this.setState({ + fieldValid, + }); + } + onChangeOldPassword = (ev) => { this.setState({ oldPassword: ev.target.value, @@ -180,12 +196,39 @@ export default class ChangePassword extends React.Component { }); }; + onNewPasswordValidate = result => { + this.markFieldValid(FIELD_NEW_PASSWORD, result.valid); + }; + onChangeNewPasswordConfirm = (ev) => { this.setState({ newPasswordConfirm: ev.target.value, }); }; + onNewPasswordConfirmValidate = async fieldState => { + const result = await this.validatePasswordConfirmRules(fieldState); + this.markFieldValid(FIELD_NEW_PASSWORD_CONFIRM, result.valid); + return result; + }; + + validatePasswordConfirmRules = withValidation({ + rules: [ + { + key: "required", + test: ({ value, allowEmpty }) => allowEmpty || !!value, + invalid: () => _t("Confirm password"), + }, + { + key: "match", + test({ value }) { + return !value || value === this.state.newPassword; + }, + invalid: () => _t("Passwords don't match"), + }, + ], + }); + onClickChange = (ev) => { ev.preventDefault(); const oldPassword = this.state.oldPassword; @@ -202,8 +245,6 @@ export default class ChangePassword extends React.Component { }; render() { - // TODO: Live validation on `new pw == confirm pw` - const rowClassName = this.props.rowClassName; const buttonClassName = this.props.buttonClassName; @@ -220,21 +261,26 @@ export default class ChangePassword extends React.Component { />
- this[FIELD_NEW_PASSWORD] = field} type="password" - label={_t('New Password')} + label='New Password' + minScore={PASSWORD_MIN_SCORE} value={this.state.newPassword} autoFocus={this.props.autoFocusNewPasswordInput} onChange={this.onChangeNewPassword} + onValidate={this.onNewPasswordValidate} autoComplete="new-password" />
this[FIELD_NEW_PASSWORD_CONFIRM] = field} type="password" label={_t("Confirm password")} value={this.state.newPasswordConfirm} onChange={this.onChangeNewPasswordConfirm} + onValidate={this.onNewPasswordConfirmValidate} autoComplete="new-password" />