From cb0392b78d8e84cf9bfeb39b444936adb692deb6 Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Wed, 29 Jan 2020 13:24:45 +0000 Subject: [PATCH 1/6] use forms to wrap password fields so Chrome doesn't go wild and prefill all the things Signed-off-by: Michael Telatynski <7t3chguy@gmail.com> --- .../CreateSecretStorageDialog.js | 75 +++++++++---------- src/components/structures/SearchBox.js | 1 + .../AccessSecretStorageDialog.js | 45 +++++------ 3 files changed, 57 insertions(+), 64 deletions(-) diff --git a/src/async-components/views/dialogs/secretstorage/CreateSecretStorageDialog.js b/src/async-components/views/dialogs/secretstorage/CreateSecretStorageDialog.js index 4068f72217..35cd5aa819 100644 --- a/src/async-components/views/dialogs/secretstorage/CreateSecretStorageDialog.js +++ b/src/async-components/views/dialogs/secretstorage/CreateSecretStorageDialog.js @@ -295,31 +295,27 @@ export default class CreateSecretStorageDialog extends React.PureComponent { }); } - _onPassPhraseNextClick = () => { - this.setState({phase: PHASE_PASSPHRASE_CONFIRM}); - } - - _onPassPhraseKeyPress = async (e) => { - if (e.key === 'Enter') { - // 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._onPassPhraseNextClick(); - } + _onPassPhraseNextClick = async () => { + // 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}); + } + }; _onPassPhraseConfirmNextClick = async () => { + if (this.state.passPhrase !== this.state.passPhraseConfirm) return; + const [keyInfo, encodedRecoveryKey] = await MatrixClientPeg.get().createRecoveryKeyFromPassphrase(this.state.passPhrase); this._keyInfo = keyInfo; @@ -332,12 +328,6 @@ export default class CreateSecretStorageDialog extends React.PureComponent { }); } - _onPassPhraseConfirmKeyPress = (e) => { - if (e.key === 'Enter' && this.state.passPhrase === this.state.passPhraseConfirm) { - this._onPassPhraseConfirmNextClick(); - } - } - _onSetAgainClick = () => { this.setState({ passPhrase: '', @@ -407,7 +397,8 @@ export default class CreateSecretStorageDialog extends React.PureComponent { } else if (this.state.canUploadKeysWithPasswordOnly) { authPrompt =
{_t("Enter your account password to confirm the upgrade:")}
-
; } - return
+ return

{_t( "Set up encryption on this device to allow it to verify other devices, " + "granting them access to encrypted messages and marking them as trusted for other users.", @@ -480,13 +471,14 @@ export default class CreateSecretStorageDialog extends React.PureComponent { )}

-
{strengthMeter} @@ -499,10 +491,12 @@ export default class CreateSecretStorageDialog extends React.PureComponent { onChange={this._onUseKeyBackupChange} value={this.state.useKeyBackup} /> -
; + ; } _renderPhasePassPhraseConfirm() { @@ -549,27 +543,30 @@ export default class CreateSecretStorageDialog extends React.PureComponent {
; } const DialogButtons = sdk.getComponent('views.elements.DialogButtons'); - return
+ return

{_t( "Enter your passphrase a second time to confirm it.", )}

-
{passPhraseMatch}
- -
; + ; } _renderPhaseShowKey() { diff --git a/src/components/structures/SearchBox.js b/src/components/structures/SearchBox.js index 873efb64c2..e169e09752 100644 --- a/src/components/structures/SearchBox.js +++ b/src/components/structures/SearchBox.js @@ -160,6 +160,7 @@ export default createReactClass({ onKeyDown={ this._onKeyDown } onBlur={this._onBlur} placeholder={ placeholder } + autoComplete="off" /> { clearButton }
diff --git a/src/components/views/dialogs/secretstorage/AccessSecretStorageDialog.js b/src/components/views/dialogs/secretstorage/AccessSecretStorageDialog.js index c976eb81d0..ed65c9d2a6 100644 --- a/src/components/views/dialogs/secretstorage/AccessSecretStorageDialog.js +++ b/src/components/views/dialogs/secretstorage/AccessSecretStorageDialog.js @@ -1,6 +1,6 @@ /* Copyright 2018, 2019 New Vector Ltd -Copyright 2019 The Matrix.org Foundation C.I.C. +Copyright 2019, 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. @@ -21,7 +21,6 @@ import * as sdk from '../../../../index'; import {MatrixClientPeg} from '../../../../MatrixClientPeg'; import { _t } from '../../../../languageHandler'; -import { Key } from "../../../../Keyboard"; /* * Access Secure Secret Storage by requesting the user's passphrase. @@ -69,6 +68,8 @@ export default class AccessSecretStorageDialog extends React.PureComponent { } _onPassPhraseNext = async () => { + if (this.state.passPhrase.length <= 0) return; + this.setState({ keyMatches: null }); const input = { passphrase: this.state.passPhrase }; const keyMatches = await this.props.checkPrivateKey(input); @@ -80,6 +81,8 @@ export default class AccessSecretStorageDialog extends React.PureComponent { } _onRecoveryKeyNext = async () => { + if (!this.state.recoveryKeyValid) return; + this.setState({ keyMatches: null }); const input = { recoveryKey: this.state.recoveryKey }; const keyMatches = await this.props.checkPrivateKey(input); @@ -97,18 +100,6 @@ export default class AccessSecretStorageDialog extends React.PureComponent { }); } - _onPassPhraseKeyPress = (e) => { - if (e.key === Key.ENTER && this.state.passPhrase.length > 0) { - this._onPassPhraseNext(); - } - } - - _onRecoveryKeyKeyPress = (e) => { - if (e.key === Key.ENTER && this.state.recoveryKeyValid) { - this._onRecoveryKeyNext(); - } - } - render() { const BaseDialog = sdk.getComponent('views.dialogs.BaseDialog'); @@ -135,7 +126,7 @@ export default class AccessSecretStorageDialog extends React.PureComponent { )}
; } else { - keyStatus =
; + keyStatus =
; } content =
@@ -149,23 +140,26 @@ export default class AccessSecretStorageDialog extends React.PureComponent { "identity for verifying other devices by entering your passphrase.", )}

-
- + {keyStatus} - -
+ {_t( "If you've forgotten your passphrase you can "+ "use your recovery key or " + @@ -192,7 +186,7 @@ export default class AccessSecretStorageDialog extends React.PureComponent { let keyStatus; if (this.state.recoveryKey.length === 0) { - keyStatus =
; + keyStatus =
; } else if (this.state.recoveryKeyValid) { keyStatus =
{"\uD83D\uDC4D "}{_t("This looks like a valid recovery key!")} @@ -221,22 +215,23 @@ export default class AccessSecretStorageDialog extends React.PureComponent { "identity for verifying other devices by entering your recovery key.", )}

-
+
{keyStatus} - -
+ {_t( "If you've forgotten your recovery key you can "+ "." From fe71fe6033a59e0dcc20c62527ad5b7ec7f71895 Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Wed, 29 Jan 2020 13:38:50 +0000 Subject: [PATCH 2/6] Sprinkle forms and new-password designators to make autofill and password completion less wild Signed-off-by: Michael Telatynski <7t3chguy@gmail.com> --- .../keybackup/CreateKeyBackupDialog.js | 66 ++++++++----------- src/components/views/auth/RegistrationForm.js | 2 + .../views/dialogs/DeactivateAccountDialog.js | 1 + .../keybackup/RestoreKeyBackupDialog.js | 15 ++--- .../views/settings/ChangePassword.js | 8 ++- 5 files changed, 43 insertions(+), 49 deletions(-) diff --git a/src/async-components/views/dialogs/keybackup/CreateKeyBackupDialog.js b/src/async-components/views/dialogs/keybackup/CreateKeyBackupDialog.js index 8940239cfd..1557159e5c 100644 --- a/src/async-components/views/dialogs/keybackup/CreateKeyBackupDialog.js +++ b/src/async-components/views/dialogs/keybackup/CreateKeyBackupDialog.js @@ -192,31 +192,27 @@ export default class CreateKeyBackupDialog extends React.PureComponent { }); } - _onPassPhraseNextClick = () => { - this.setState({phase: PHASE_PASSPHRASE_CONFIRM}); - } - - _onPassPhraseKeyPress = async (e) => { - if (e.key === 'Enter') { - // 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._onPassPhraseNextClick(); - } + _onPassPhraseNextClick = async () => { + // 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}); + } + }; _onPassPhraseConfirmNextClick = async () => { + if (this.state.passPhrase !== this.state.passPhraseConfirm) return; + this._keyBackupInfo = await MatrixClientPeg.get().prepareKeyBackupVersion(this.state.passPhrase); this.setState({ setPassPhrase: true, @@ -224,13 +220,7 @@ export default class CreateKeyBackupDialog extends React.PureComponent { downloaded: false, phase: PHASE_SHOWKEY, }); - } - - _onPassPhraseConfirmKeyPress = (e) => { - if (e.key === 'Enter' && this.state.passPhrase === this.state.passPhraseConfirm) { - this._onPassPhraseConfirmNextClick(); - } - } + }; _onSetAgainClick = () => { this.setState({ @@ -301,7 +291,7 @@ export default class CreateKeyBackupDialog extends React.PureComponent {
; } - return
+ return

{_t( "Warning: You should only set up key backup from a trusted computer.", {}, { b: sub => {sub} }, @@ -316,7 +306,6 @@ export default class CreateKeyBackupDialog extends React.PureComponent {

- @@ -341,7 +332,7 @@ export default class CreateKeyBackupDialog extends React.PureComponent { {_t("Set up with a Recovery Key")}

-
; + ; } _renderPhasePassPhraseConfirm() { @@ -373,7 +364,7 @@ export default class CreateKeyBackupDialog extends React.PureComponent {
; } const DialogButtons = sdk.getComponent('views.elements.DialogButtons'); - return
+ return

{_t( "Please enter your passphrase a second time to confirm.", )}

@@ -382,7 +373,6 @@ export default class CreateKeyBackupDialog extends React.PureComponent {
- -
; + ; } _renderPhaseShowKey() { diff --git a/src/components/views/auth/RegistrationForm.js b/src/components/views/auth/RegistrationForm.js index 91f8e1b226..8ca454dabd 100644 --- a/src/components/views/auth/RegistrationForm.js +++ b/src/components/views/auth/RegistrationForm.js @@ -486,6 +486,7 @@ export default createReactClass({ id="mx_RegistrationForm_password" ref={field => this[FIELD_PASSWORD] = field} type="password" + autoComplete="new-password" label={_t("Password")} value={this.state.password} onChange={this.onPasswordChange} @@ -499,6 +500,7 @@ export default createReactClass({ id="mx_RegistrationForm_passwordConfirm" ref={field => this[FIELD_PASSWORD_CONFIRM] = field} type="password" + autoComplete="new-password" label={_t("Confirm")} value={this.state.passwordConfirm} onChange={this.onPasswordConfirmChange} diff --git a/src/components/views/dialogs/DeactivateAccountDialog.js b/src/components/views/dialogs/DeactivateAccountDialog.js index 7e36232eb0..d7468933df 100644 --- a/src/components/views/dialogs/DeactivateAccountDialog.js +++ b/src/components/views/dialogs/DeactivateAccountDialog.js @@ -118,6 +118,7 @@ export default class DeactivateAccountDialog extends React.Component { const Field = sdk.getComponent('elements.Field'); + // this is on purpose not a
to prevent Enter triggering submission, to further prevent accidents return ( { - if (e.key === Key.ENTER) { - this._onPassPhraseNext(); - } - } - _onRecoveryKeyKeyPress = (e) => { if (e.key === Key.ENTER && this.state.recoveryKeyValid) { this._onRecoveryKeyNext(); @@ -305,21 +299,22 @@ export default class RestoreKeyBackupDialog extends React.PureComponent { "messaging by entering your recovery passphrase.", )}

-
+ - -
+ {_t( "If you've forgotten your recovery passphrase you can "+ "use your recovery key or " + diff --git a/src/components/views/settings/ChangePassword.js b/src/components/views/settings/ChangePassword.js index 2d8c4c4178..8cbe455af3 100644 --- a/src/components/views/settings/ChangePassword.js +++ b/src/components/views/settings/ChangePassword.js @@ -253,20 +253,24 @@ export default createReactClass({
{ currentPassword }
-
-
From 920daa3125c229697a25daa65788d909839c71e1 Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Wed, 29 Jan 2020 13:43:08 +0000 Subject: [PATCH 3/6] Get rid of custom enter handling and protect handlers, disabling button isn't foolproof Signed-off-by: Michael Telatynski <7t3chguy@gmail.com> --- .../views/dialogs/keybackup/RestoreKeyBackupDialog.js | 10 ++-------- 1 file changed, 2 insertions(+), 8 deletions(-) diff --git a/src/components/views/dialogs/keybackup/RestoreKeyBackupDialog.js b/src/components/views/dialogs/keybackup/RestoreKeyBackupDialog.js index 814f5a4980..fc9a21c51b 100644 --- a/src/components/views/dialogs/keybackup/RestoreKeyBackupDialog.js +++ b/src/components/views/dialogs/keybackup/RestoreKeyBackupDialog.js @@ -22,7 +22,6 @@ import {MatrixClientPeg} from '../../../../MatrixClientPeg'; import { MatrixClient } from 'matrix-js-sdk'; import Modal from '../../../../Modal'; import { _t } from '../../../../languageHandler'; -import {Key} from "../../../../Keyboard"; import { accessSecretStorage } from '../../../../CrossSigningManager'; const RESTORE_TYPE_PASSPHRASE = 0; @@ -125,6 +124,8 @@ export default class RestoreKeyBackupDialog extends React.PureComponent { } _onRecoveryKeyNext = async () => { + if (!this.state.recoveryKeyValid) return; + this.setState({ loading: true, restoreError: null, @@ -157,12 +158,6 @@ export default class RestoreKeyBackupDialog extends React.PureComponent { }); } - _onRecoveryKeyKeyPress = (e) => { - if (e.key === Key.ENTER && this.state.recoveryKeyValid) { - this._onRecoveryKeyNext(); - } - } - async _restoreWithSecretStorage() { this.setState({ loading: true, @@ -366,7 +361,6 @@ export default class RestoreKeyBackupDialog extends React.PureComponent {
From 0393a57b5a571c0dfc2abafa8bab56f72a8a2fe6 Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Sun, 9 Feb 2020 14:49:54 +0000 Subject: [PATCH 4/6] fix submit button disabled breaking async task --- .../keybackup/_CreateKeyBackupDialog.scss | 6 ++++++ .../keybackup/CreateKeyBackupDialog.js | 19 +++++++++++-------- .../CreateSecretStorageDialog.js | 10 ++++------ 3 files changed, 21 insertions(+), 14 deletions(-) diff --git a/res/css/views/dialogs/keybackup/_CreateKeyBackupDialog.scss b/res/css/views/dialogs/keybackup/_CreateKeyBackupDialog.scss index 04ee575867..b9babd05f5 100644 --- a/res/css/views/dialogs/keybackup/_CreateKeyBackupDialog.scss +++ b/res/css/views/dialogs/keybackup/_CreateKeyBackupDialog.scss @@ -85,3 +85,9 @@ limitations under the License. flex: 1; white-space: nowrap; } + +.mx_CreateKeyBackupDialog { + details .mx_AccessibleButton { + margin: 1em 0; // emulate paragraph spacing because we can't put this button in a paragraph due to HTML rules + } +} diff --git a/src/async-components/views/dialogs/keybackup/CreateKeyBackupDialog.js b/src/async-components/views/dialogs/keybackup/CreateKeyBackupDialog.js index 2d6a07ad2f..f89fb17448 100644 --- a/src/async-components/views/dialogs/keybackup/CreateKeyBackupDialog.js +++ b/src/async-components/views/dialogs/keybackup/CreateKeyBackupDialog.js @@ -24,6 +24,7 @@ import { scorePassword } from '../../../../utils/PasswordScorer'; import { _t } from '../../../../languageHandler'; import { accessSecretStorage } from '../../../../CrossSigningManager'; import SettingsStore from '../../../../settings/SettingsStore'; +import AccessibleButton from "../../../../components/views/elements/AccessibleButton"; const PHASE_PASSPHRASE = 0; const PHASE_PASSPHRASE_CONFIRM = 1; @@ -191,7 +192,9 @@ export default class CreateKeyBackupDialog extends React.PureComponent { }); } - _onPassPhraseNextClick = async () => { + _onPassPhraseNextClick = async (e) => { + e.preventDefault(); + // 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 @@ -209,7 +212,9 @@ export default class CreateKeyBackupDialog extends React.PureComponent { } }; - _onPassPhraseConfirmNextClick = async () => { + _onPassPhraseConfirmNextClick = async (e) => { + e.preventDefault(); + if (this.state.passPhrase !== this.state.passPhraseConfirm) return; this._keyBackupInfo = await MatrixClientPeg.get().prepareKeyBackupVersion(this.state.passPhrase); @@ -289,7 +294,7 @@ export default class CreateKeyBackupDialog extends React.PureComponent {
; } - return + return

{_t( "Warning: You should only set up key backup from a trusted computer.", {}, { b: sub => {sub} }, @@ -320,15 +325,14 @@ export default class CreateKeyBackupDialog extends React.PureComponent { primaryButton={_t('Next')} onPrimaryButtonClick={this._onPassPhraseNextClick} hasCancel={false} - primaryIsSubmit={true} disabled={!this._passPhraseIsValid()} />

{_t("Advanced")} -

+
; } @@ -362,7 +366,7 @@ export default class CreateKeyBackupDialog extends React.PureComponent {
; } const DialogButtons = sdk.getComponent('views.elements.DialogButtons'); - return
+ return

{_t( "Please enter your passphrase a second time to confirm.", )}

@@ -384,7 +388,6 @@ export default class CreateKeyBackupDialog extends React.PureComponent { primaryButton={_t('Next')} onPrimaryButtonClick={this._onPassPhraseConfirmNextClick} hasCancel={false} - primaryIsSubmit={true} disabled={this.state.passPhrase !== this.state.passPhraseConfirm} />
; diff --git a/src/async-components/views/dialogs/secretstorage/CreateSecretStorageDialog.js b/src/async-components/views/dialogs/secretstorage/CreateSecretStorageDialog.js index 12ca752421..99f91d4044 100644 --- a/src/async-components/views/dialogs/secretstorage/CreateSecretStorageDialog.js +++ b/src/async-components/views/dialogs/secretstorage/CreateSecretStorageDialog.js @@ -413,8 +413,8 @@ export default class CreateSecretStorageDialog extends React.PureComponent { "as trusted for other users.", )}

{authPrompt}
- @@ -458,7 +458,7 @@ export default class CreateSecretStorageDialog extends React.PureComponent {
; } - return
+ return

{_t( "Set up encryption on this session to allow it to verify other sessions, " + "granting them access to encrypted messages and marking them as trusted for other users.", @@ -495,7 +495,6 @@ export default class CreateSecretStorageDialog extends React.PureComponent { onPrimaryButtonClick={this._onPassPhraseNextClick} hasCancel={false} disabled={!this._passPhraseIsValid()} - primaryIsSubmit={true} >