From 15ebaa14704c08c5a8875a33ffac96109b14efaf Mon Sep 17 00:00:00 2001 From: David Baker Date: Fri, 26 Jun 2020 15:22:04 +0100 Subject: [PATCH 1/9] Port recovery key upload button to new designs --- .../_AccessSecretStorageDialog.scss | 49 ++++- .../AccessSecretStorageDialog.js | 172 +++++++++++++++--- src/i18n/strings/en_EN.json | 10 +- 3 files changed, 198 insertions(+), 33 deletions(-) diff --git a/res/css/views/dialogs/secretstorage/_AccessSecretStorageDialog.scss b/res/css/views/dialogs/secretstorage/_AccessSecretStorageDialog.scss index f15d43b199..63d0ca555d 100644 --- a/res/css/views/dialogs/secretstorage/_AccessSecretStorageDialog.scss +++ b/res/css/views/dialogs/secretstorage/_AccessSecretStorageDialog.scss @@ -38,11 +38,56 @@ limitations under the License. height: 30px; } -.mx_AccessSecretStorageDialog_passPhraseInput, -.mx_AccessSecretStorageDialog_recoveryKeyInput { +.mx_AccessSecretStorageDialog_passPhraseInput { width: 300px; border: 1px solid $accent-color; border-radius: 5px; padding: 10px; } +.mx_AccessSecretStorageDialog_recoveryKeyEntry { + display: flex; + align-items: center; +} + +.mx_AccessSecretStorageDialog_recoveryKeyEntry_textInput { + flex-grow: 1; +} + +.mx_AccessSecretStorageDialog_recoveryKeyEntry_entryControlSeparatorText { + margin: 16px; +} + +.mx_AccessSecretStorageDialog_recoveryKeyFeedback { + &::before { + content: ""; + display: inline-block; + vertical-align: bottom; + width: 20px; + height: 20px; + mask-repeat: no-repeat; + mask-position: center; + mask-size: 20px; + margin-right: 5px; + } +} + +.mx_AccessSecretStorageDialog_recoveryKeyFeedback_valid { + color: $input-valid-border-color; + &::before { + mask-image: url('$(res)/img/feather-customised/check.svg'); + background-color: $input-valid-border-color; + } +} + +.mx_AccessSecretStorageDialog_recoveryKeyFeedback_invalid { + color: $input-invalid-border-color; + &::before { + mask-image: url('$(res)/img/feather-customised/x.svg'); + background-color: $input-invalid-border-color; + } +} + +.mx_AccessSecretStorageDialog_recoveryKeyEntry_fileInput { + display: none; +} diff --git a/src/components/views/dialogs/secretstorage/AccessSecretStorageDialog.js b/src/components/views/dialogs/secretstorage/AccessSecretStorageDialog.js index bb9937c429..7713f07115 100644 --- a/src/components/views/dialogs/secretstorage/AccessSecretStorageDialog.js +++ b/src/components/views/dialogs/secretstorage/AccessSecretStorageDialog.js @@ -15,13 +15,26 @@ See the License for the specific language governing permissions and limitations under the License. */ +import { debounce } from 'lodash'; +import classNames from 'classnames'; import React from 'react'; import PropTypes from "prop-types"; import * as sdk from '../../../../index'; import {MatrixClientPeg} from '../../../../MatrixClientPeg'; +import Field from '../../elements/Field'; +import AccessibleButton from '../../elements/AccessibleButton'; +import { decodeRecoveryKey } from 'matrix-js-sdk/src/crypto/recoverykey'; import { _t } from '../../../../languageHandler'; +// Maximum acceptable size of a key file. It's 59 characters including the spaces we encode, +// so this should be plenty and allow for people putting extra whitespace in the file because +// maybe that's a thing people would do? +const KEY_FILE_MAX_SIZE = 128; + +// Don't shout at the user that their key is invalid every time they type a key: wait a short time +const VALIDATION_THROTTLE_MS = 200; + /* * Access Secure Secret Storage by requesting the user's passphrase. */ @@ -35,9 +48,14 @@ export default class AccessSecretStorageDialog extends React.PureComponent { constructor(props) { super(props); + + this._fileUpload = React.createRef(); + this.state = { recoveryKey: "", - recoveryKeyValid: false, + recoveryKeyValid: null, + recoveryKeyCorrect: null, + recoveryKeyFileError: null, forceRecoveryKey: false, passPhrase: '', keyMatches: null, @@ -54,12 +72,89 @@ export default class AccessSecretStorageDialog extends React.PureComponent { }); } + _validateRecoveryKeyOnChange = debounce(() => { + this._validateRecoveryKey(); + }, VALIDATION_THROTTLE_MS); + + async _validateRecoveryKey() { + if (this.state.recoveryKey === '') { + this.setState({ + recoveryKeyValid: null, + recoveryKeyCorrect: null, + }); + return; + } + + try { + const decodedKey = decodeRecoveryKey(this.state.recoveryKey); + const correct = await MatrixClientPeg.get().checkSecretStorageKey( + decodedKey, this.props.keyInfo, + ); + this.setState({ + recoveryKeyValid: true, + recoveryKeyCorrect: correct, + }); + } catch (e) { + this.setState({ + recoveryKeyValid: false, + recoveryKeyCorrect: false, + }); + } + } + _onRecoveryKeyChange = (e) => { this.setState({ recoveryKey: e.target.value, - recoveryKeyValid: MatrixClientPeg.get().isValidRecoveryKey(e.target.value), - keyMatches: null, + recoveryKeyFileError: null, }); + + // also clear the file upload control so that the user can upload the same file + // the did before (otherwise the onchange wouldn't fire) + this._fileUpload.current.value = null; + + + // We don't use Field's validation here because a) we want it in a separate place rather + // than in a tooltip and b) we want it to display feedback based on the uploaded file + // as well as the text box. Ideally we would refactor Field's validation logic so we could + // re-use some of it. + this._validateRecoveryKeyOnChange(); + } + + _onRecoveryKeyFileChange = async e => { + if (e.target.files.length === 0) return; + + const f = e.target.files[0]; + + if (f.size > KEY_FILE_MAX_SIZE) { + this.setState({ + recoveryKeyFileError: true, + recoveryKeyCorrect: false, + recoveryKeyValid: false, + }); + } else { + const contents = await f.text(); + // test it's within the base58 alphabet. We could be more strict here, eg. require the + // right number of characters, but it's really just to make sure that what we're reading is + // text because we'll put it in the text field. + if (/^[123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz\s]+$/.test(contents)) { + this.setState({ + recoveryKeyFileError: null, + recoveryKey: contents.trim(), + }); + this._validateRecoveryKey(); + } else { + this.setState({ + recoveryKeyFileError: true, + recoveryKeyCorrect: false, + recoveryKeyValid: false, + recoveryKey: '', + }); + } + } + } + + _onRecoveryKeyFileUploadClick = () => { + this._fileUpload.current.click(); } _onPassPhraseNext = async (e) => { @@ -99,6 +194,20 @@ export default class AccessSecretStorageDialog extends React.PureComponent { }); } + getKeyValidationText() { + if (this.state.recoveryKeyFileError) { + return _t("Wrong file type"); + } else if (this.state.recoveryKeyCorrect) { + return _t("Looks good!"); + } else if (this.state.recoveryKeyValid) { + return _t("Wrong Recovery Key"); + } else if (this.state.recoveryKeyValid === null) { + return ''; + } else { + return _t("Invalid Recovery Key"); + } + } + render() { const BaseDialog = sdk.getComponent('views.dialogs.BaseDialog'); @@ -169,36 +278,43 @@ export default class AccessSecretStorageDialog extends React.PureComponent { titleClass = ['mx_AccessSecretStorageDialog_titleWithIcon mx_AccessSecretStorageDialog_secureBackupTitle']; const DialogButtons = sdk.getComponent('views.elements.DialogButtons'); - let keyStatus; - if (this.state.recoveryKey.length === 0) { - keyStatus =
; - } else if (this.state.keyMatches === false) { - keyStatus =
- {"\uD83D\uDC4E "}{_t( - "Unable to access secret storage. " + - "Please verify that you entered the correct recovery key.", - )} -
; - } else if (this.state.recoveryKeyValid) { - keyStatus =
- {"\uD83D\uDC4D "}{_t("This looks like a valid recovery key!")} -
; - } else { - keyStatus =
- {"\uD83D\uDC4E "}{_t("Not a valid recovery key")} -
; - } + const feedbackClasses = classNames({ + 'mx_AccessSecretStorageDialog_recoveryKeyFeedback': true, + 'mx_AccessSecretStorageDialog_recoveryKeyFeedback_valid': this.state.recoveryKeyCorrect === true, + 'mx_AccessSecretStorageDialog_recoveryKeyFeedback_invalid': this.state.recoveryKeyCorrect === false, + }); + const recoveryKeyFeedback =
+ {this.getKeyValidationText()} +
; content =

{_t("Use your Security Key to continue.")}

- - {keyStatus} +
+
+ +
+ + {_t("or")} + +
+ + + {_t("Upload")} + +
+
+ {recoveryKeyFeedback} Use your Security Key to continue.": "Enter your Security Phrase or to continue.", "Security Key": "Security Key", - "Unable to access secret storage. Please verify that you entered the correct recovery key.": "Unable to access secret storage. Please verify that you entered the correct recovery key.", - "This looks like a valid recovery key!": "This looks like a valid recovery key!", - "Not a valid recovery key": "Not a valid recovery key", "Use your Security Key to continue.": "Use your Security Key to continue.", + "Recovery Key": "Recovery Key", "Restoring keys from backup": "Restoring keys from backup", "Fetching keys from server...": "Fetching keys from server...", "%(completed)s of %(total)s keys restored": "%(completed)s of %(total)s keys restored", @@ -1813,6 +1815,8 @@ "Access your secure message history and set up secure messaging by entering your recovery passphrase.": "Access your secure message history and set up secure messaging by entering your recovery passphrase.", "If you've forgotten your recovery passphrase you can use your recovery key or set up new recovery options": "If you've forgotten your recovery passphrase you can use your recovery key or set up new recovery options", "Enter recovery key": "Enter recovery key", + "This looks like a valid recovery key!": "This looks like a valid recovery key!", + "Not a valid recovery key": "Not a valid recovery key", "Warning: You should only set up key backup from a trusted computer.": "Warning: You should only set up key backup from a trusted computer.", "Access your secure message history and set up secure messaging by entering your recovery key.": "Access your secure message history and set up secure messaging by entering your recovery key.", "If you've forgotten your recovery key you can ": "If you've forgotten your recovery key you can ", From b74674ced8b9a947fdb089fb471a5fb2c865cf06 Mon Sep 17 00:00:00 2001 From: David Baker Date: Fri, 26 Jun 2020 18:04:06 +0100 Subject: [PATCH 2/9] Right name for security key and fix cancel button --- .../views/dialogs/secretstorage/AccessSecretStorageDialog.js | 4 +++- src/i18n/strings/en_EN.json | 1 - 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/src/components/views/dialogs/secretstorage/AccessSecretStorageDialog.js b/src/components/views/dialogs/secretstorage/AccessSecretStorageDialog.js index 7713f07115..3141cfb33b 100644 --- a/src/components/views/dialogs/secretstorage/AccessSecretStorageDialog.js +++ b/src/components/views/dialogs/secretstorage/AccessSecretStorageDialog.js @@ -295,7 +295,7 @@ export default class AccessSecretStorageDialog extends React.PureComponent {
@@ -319,6 +319,8 @@ export default class AccessSecretStorageDialog extends React.PureComponent { primaryButton={_t('Continue')} onPrimaryButtonClick={this._onRecoveryKeyNext} hasCancel={true} + cancelButton={_t("Go Back")} + cancelButtonClass='danger' onCancel={this._onCancel} focus={false} primaryDisabled={!this.state.recoveryKeyValid} diff --git a/src/i18n/strings/en_EN.json b/src/i18n/strings/en_EN.json index 7a2fb12867..9160d48f48 100644 --- a/src/i18n/strings/en_EN.json +++ b/src/i18n/strings/en_EN.json @@ -1796,7 +1796,6 @@ "Enter your Security Phrase or to continue.": "Enter your Security Phrase or to continue.", "Security Key": "Security Key", "Use your Security Key to continue.": "Use your Security Key to continue.", - "Recovery Key": "Recovery Key", "Restoring keys from backup": "Restoring keys from backup", "Fetching keys from server...": "Fetching keys from server...", "%(completed)s of %(total)s keys restored": "%(completed)s of %(total)s keys restored", From 24baf19d6528cae719cedde35d3588b178f15421 Mon Sep 17 00:00:00 2001 From: David Baker Date: Fri, 26 Jun 2020 18:50:05 +0100 Subject: [PATCH 3/9] Set field validity (ie. border colour) correctly Changes flagInvalid to forceValidity which can force valid as well as invalid. --- .../dialogs/secretstorage/CreateSecretStorageDialog.js | 2 +- .../dialogs/secretstorage/AccessSecretStorageDialog.js | 1 + src/components/views/elements/Field.tsx | 10 +++++----- src/components/views/settings/SetIdServer.js | 2 +- 4 files changed, 8 insertions(+), 7 deletions(-) diff --git a/src/async-components/views/dialogs/secretstorage/CreateSecretStorageDialog.js b/src/async-components/views/dialogs/secretstorage/CreateSecretStorageDialog.js index 58b5b57354..984158c7a2 100644 --- a/src/async-components/views/dialogs/secretstorage/CreateSecretStorageDialog.js +++ b/src/async-components/views/dialogs/secretstorage/CreateSecretStorageDialog.js @@ -491,7 +491,7 @@ export default class CreateSecretStorageDialog extends React.PureComponent { label={_t("Password")} value={this.state.accountPassword} onChange={this._onAccountPasswordChange} - flagInvalid={this.state.accountPasswordCorrect === false} + forceValidity={this.state.accountPasswordCorrect === false ? false : null} autoFocus={true} />
; diff --git a/src/components/views/dialogs/secretstorage/AccessSecretStorageDialog.js b/src/components/views/dialogs/secretstorage/AccessSecretStorageDialog.js index 3141cfb33b..5029856f26 100644 --- a/src/components/views/dialogs/secretstorage/AccessSecretStorageDialog.js +++ b/src/components/views/dialogs/secretstorage/AccessSecretStorageDialog.js @@ -298,6 +298,7 @@ export default class AccessSecretStorageDialog extends React.PureComponent { label={_t('Security Key')} value={this.state.recoveryKey} onChange={this._onRecoveryKeyChange} + forceValidity={this.state.recoveryKeyCorrect} />
diff --git a/src/components/views/elements/Field.tsx b/src/components/views/elements/Field.tsx index 834edff7df..9a889a0351 100644 --- a/src/components/views/elements/Field.tsx +++ b/src/components/views/elements/Field.tsx @@ -50,7 +50,7 @@ interface IProps { // to the user. onValidate?: (input: IFieldState) => Promise; // If specified, overrides the value returned by onValidate. - flagInvalid?: boolean; + forceValidity?: boolean; // If specified, contents will appear as a tooltip on the element and // validation feedback tooltips will be suppressed. tooltipContent?: React.ReactNode; @@ -203,7 +203,7 @@ export default class Field extends React.PureComponent { public render() { const { element, prefixComponent, postfixComponent, className, onValidate, children, - tooltipContent, flagInvalid, tooltipClassName, list, ...inputProps} = this.props; + tooltipContent, forceValidity, tooltipClassName, list, ...inputProps} = this.props; // Set some defaults for the element const ref = input => this.input = input; @@ -228,15 +228,15 @@ export default class Field extends React.PureComponent { postfixContainer = {postfixComponent}; } - const hasValidationFlag = flagInvalid !== null && flagInvalid !== undefined; + const hasValidationFlag = forceValidity !== null && forceValidity !== undefined; const fieldClasses = classNames("mx_Field", `mx_Field_${this.props.element}`, className, { // If we have a prefix element, leave the label always at the top left and // don't animate it, as it looks a bit clunky and would add complexity to do // properly. mx_Field_labelAlwaysTopLeft: prefixComponent, - mx_Field_valid: onValidate && this.state.valid === true, + mx_Field_valid: hasValidationFlag ? forceValidity : onValidate && this.state.valid === true, mx_Field_invalid: hasValidationFlag - ? flagInvalid + ? !forceValidity : onValidate && this.state.valid === false, }); diff --git a/src/components/views/settings/SetIdServer.js b/src/components/views/settings/SetIdServer.js index 23e72e2352..e05fe4f1c3 100644 --- a/src/components/views/settings/SetIdServer.js +++ b/src/components/views/settings/SetIdServer.js @@ -413,7 +413,7 @@ export default class SetIdServer extends React.Component { tooltipContent={this._getTooltip()} tooltipClassName="mx_SetIdServer_tooltip" disabled={this.state.busy} - flagInvalid={!!this.state.error} + forceValidity={this.state.error ? false : null} /> Date: Fri, 26 Jun 2020 18:55:23 +0100 Subject: [PATCH 4/9] Disable spellcheck on the recovery key entry --- .../views/dialogs/secretstorage/AccessSecretStorageDialog.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/views/dialogs/secretstorage/AccessSecretStorageDialog.js b/src/components/views/dialogs/secretstorage/AccessSecretStorageDialog.js index 5029856f26..8b61af8886 100644 --- a/src/components/views/dialogs/secretstorage/AccessSecretStorageDialog.js +++ b/src/components/views/dialogs/secretstorage/AccessSecretStorageDialog.js @@ -290,7 +290,7 @@ export default class AccessSecretStorageDialog extends React.PureComponent { content =

{_t("Use your Security Key to continue.")}

- +
Date: Fri, 26 Jun 2020 18:58:12 +0100 Subject: [PATCH 5/9] i18n --- src/i18n/strings/en_EN.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/i18n/strings/en_EN.json b/src/i18n/strings/en_EN.json index 9160d48f48..4e5fd63d6c 100644 --- a/src/i18n/strings/en_EN.json +++ b/src/i18n/strings/en_EN.json @@ -1796,6 +1796,7 @@ "Enter your Security Phrase or to continue.": "Enter your Security Phrase or to continue.", "Security Key": "Security Key", "Use your Security Key to continue.": "Use your Security Key to continue.", + "Go Back": "Go Back", "Restoring keys from backup": "Restoring keys from backup", "Fetching keys from server...": "Fetching keys from server...", "%(completed)s of %(total)s keys restored": "%(completed)s of %(total)s keys restored", @@ -2138,7 +2139,6 @@ "Your new session is now verified. It has access to your encrypted messages, and other users will see it as trusted.": "Your new session is now verified. It has access to your encrypted messages, and other users will see it as trusted.", "Your new session is now verified. Other users will see it as trusted.": "Your new session is now verified. Other users will see it as trusted.", "Without completing security on this session, it won’t have access to encrypted messages.": "Without completing security on this session, it won’t have access to encrypted messages.", - "Go Back": "Go Back", "Failed to re-authenticate due to a homeserver problem": "Failed to re-authenticate due to a homeserver problem", "Incorrect password": "Incorrect password", "Failed to re-authenticate": "Failed to re-authenticate", From 916f60687298e75f2a7e51043b9bead20cc542a7 Mon Sep 17 00:00:00 2001 From: David Baker Date: Fri, 26 Jun 2020 19:07:39 +0100 Subject: [PATCH 6/9] Apparently we need to null check here --- .../views/dialogs/secretstorage/AccessSecretStorageDialog.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/views/dialogs/secretstorage/AccessSecretStorageDialog.js b/src/components/views/dialogs/secretstorage/AccessSecretStorageDialog.js index 8b61af8886..868eeb2218 100644 --- a/src/components/views/dialogs/secretstorage/AccessSecretStorageDialog.js +++ b/src/components/views/dialogs/secretstorage/AccessSecretStorageDialog.js @@ -110,7 +110,7 @@ export default class AccessSecretStorageDialog extends React.PureComponent { // also clear the file upload control so that the user can upload the same file // the did before (otherwise the onchange wouldn't fire) - this._fileUpload.current.value = null; + if (this._fileUpload.current) this._fileUpload.current.value = null; // We don't use Field's validation here because a) we want it in a separate place rather From 0579c9f748b052c4e7ad547457d8d46946da8c31 Mon Sep 17 00:00:00 2001 From: David Baker Date: Fri, 26 Jun 2020 20:25:38 +0100 Subject: [PATCH 7/9] Fix tests --- .../AccessSecretStorageDialog.js | 6 ++-- .../dialogs/AccessSecretStorageDialog-test.js | 30 +++++++++++-------- 2 files changed, 20 insertions(+), 16 deletions(-) diff --git a/src/components/views/dialogs/secretstorage/AccessSecretStorageDialog.js b/src/components/views/dialogs/secretstorage/AccessSecretStorageDialog.js index 868eeb2218..e5b75f4dfc 100644 --- a/src/components/views/dialogs/secretstorage/AccessSecretStorageDialog.js +++ b/src/components/views/dialogs/secretstorage/AccessSecretStorageDialog.js @@ -23,7 +23,6 @@ import * as sdk from '../../../../index'; import {MatrixClientPeg} from '../../../../MatrixClientPeg'; import Field from '../../elements/Field'; import AccessibleButton from '../../elements/AccessibleButton'; -import { decodeRecoveryKey } from 'matrix-js-sdk/src/crypto/recoverykey'; import { _t } from '../../../../languageHandler'; @@ -86,8 +85,9 @@ export default class AccessSecretStorageDialog extends React.PureComponent { } try { - const decodedKey = decodeRecoveryKey(this.state.recoveryKey); - const correct = await MatrixClientPeg.get().checkSecretStorageKey( + const cli = MatrixClientPeg.get(); + const decodedKey = cli.keyBackupKeyFromRecoveryKey(this.state.recoveryKey); + const correct = await cli.checkSecretStorageKey( decodedKey, this.props.keyInfo, ); this.setState({ diff --git a/test/components/views/dialogs/AccessSecretStorageDialog-test.js b/test/components/views/dialogs/AccessSecretStorageDialog-test.js index c754a4b607..71413a2978 100644 --- a/test/components/views/dialogs/AccessSecretStorageDialog-test.js +++ b/test/components/views/dialogs/AccessSecretStorageDialog-test.js @@ -40,19 +40,20 @@ describe("AccessSecretStorageDialog", function() { testInstance.getInstance()._onRecoveryKeyNext(e); }); - it("Considers a valid key to be valid", function() { + it("Considers a valid key to be valid", async function() { const testInstance = TestRenderer.create( true} />, ); - const v = "asfd"; + const v = "asdf"; const e = { target: { value: v } }; stubClient(); - MatrixClientPeg.get().isValidRecoveryKey = function(k) { - return k == v; - }; + MatrixClientPeg.get().keyBackupKeyFromRecoveryKey = () => 'a raw key'; + MatrixClientPeg.get().checkSecretStorageKey = () => true; testInstance.getInstance()._onRecoveryKeyChange(e); + // force a validation now because it debounces + await testInstance.getInstance()._validateRecoveryKey(); const { recoveryKeyValid } = testInstance.getInstance().state; expect(recoveryKeyValid).toBe(true); }); @@ -65,17 +66,20 @@ describe("AccessSecretStorageDialog", function() { ); const e = { target: { value: "a" } }; stubClient(); - MatrixClientPeg.get().isValidRecoveryKey = () => true; + MatrixClientPeg.get().keyBackupKeyFromRecoveryKey = () => { + throw new Error("that's no key"); + }; testInstance.getInstance()._onRecoveryKeyChange(e); - await testInstance.getInstance()._onRecoveryKeyNext({ preventDefault: () => {} }); - const { keyMatches } = testInstance.getInstance().state; - expect(keyMatches).toBe(false); + // force a validation now because it debounces + await testInstance.getInstance()._validateRecoveryKey(); + + const { recoveryKeyValid, recoveryKeyCorrect } = testInstance.getInstance().state; + expect(recoveryKeyValid).toBe(false); + expect(recoveryKeyCorrect).toBe(false); const notification = testInstance.root.findByProps({ - className: "mx_AccessSecretStorageDialog_keyStatus", + className: "mx_AccessSecretStorageDialog_recoveryKeyFeedback mx_AccessSecretStorageDialog_recoveryKeyFeedback_invalid", }); - expect(notification.props.children).toEqual( - ["\uD83D\uDC4E ", "Unable to access secret storage. Please verify that you " + - "entered the correct recovery key."]); + expect(notification.props.children).toEqual("Invalid Recovery Key"); done(); }); From 2969820371b60976a995d3a159f3f740610d1955 Mon Sep 17 00:00:00 2001 From: David Baker Date: Fri, 26 Jun 2020 20:31:22 +0100 Subject: [PATCH 8/9] LINT --- .../components/views/dialogs/AccessSecretStorageDialog-test.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/test/components/views/dialogs/AccessSecretStorageDialog-test.js b/test/components/views/dialogs/AccessSecretStorageDialog-test.js index 71413a2978..5a8dcbf763 100644 --- a/test/components/views/dialogs/AccessSecretStorageDialog-test.js +++ b/test/components/views/dialogs/AccessSecretStorageDialog-test.js @@ -77,7 +77,8 @@ describe("AccessSecretStorageDialog", function() { expect(recoveryKeyValid).toBe(false); expect(recoveryKeyCorrect).toBe(false); const notification = testInstance.root.findByProps({ - className: "mx_AccessSecretStorageDialog_recoveryKeyFeedback mx_AccessSecretStorageDialog_recoveryKeyFeedback_invalid", + className: "mx_AccessSecretStorageDialog_recoveryKeyFeedback " + + "mx_AccessSecretStorageDialog_recoveryKeyFeedback_invalid", }); expect(notification.props.children).toEqual("Invalid Recovery Key"); done(); From 7caf2d5459cf472a470dea7cda48bee7d21be827 Mon Sep 17 00:00:00 2001 From: David Baker Date: Tue, 30 Jun 2020 17:56:50 +0100 Subject: [PATCH 9/9] remove rogue blank line --- .../views/dialogs/secretstorage/AccessSecretStorageDialog.js | 1 - 1 file changed, 1 deletion(-) diff --git a/src/components/views/dialogs/secretstorage/AccessSecretStorageDialog.js b/src/components/views/dialogs/secretstorage/AccessSecretStorageDialog.js index e5b75f4dfc..5c01a6907f 100644 --- a/src/components/views/dialogs/secretstorage/AccessSecretStorageDialog.js +++ b/src/components/views/dialogs/secretstorage/AccessSecretStorageDialog.js @@ -112,7 +112,6 @@ export default class AccessSecretStorageDialog extends React.PureComponent { // the did before (otherwise the onchange wouldn't fire) if (this._fileUpload.current) this._fileUpload.current.value = null; - // We don't use Field's validation here because a) we want it in a separate place rather // than in a tooltip and b) we want it to display feedback based on the uploaded file // as well as the text box. Ideally we would refactor Field's validation logic so we could