From dd23a50a3cccb50bfe4c90d0009f2a5951bbccdb Mon Sep 17 00:00:00 2001 From: RiotRobot Date: Wed, 17 Jun 2020 21:40:30 +0100 Subject: [PATCH 1/8] Upgrade matrix-js-sdk to 7.0.0-rc.1 --- package.json | 2 +- yarn.lock | 7 ++++--- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/package.json b/package.json index 5f9b7dde1f..98d7f7a6fa 100644 --- a/package.json +++ b/package.json @@ -80,7 +80,7 @@ "is-ip": "^2.0.0", "linkifyjs": "^2.1.6", "lodash": "^4.17.14", - "matrix-js-sdk": "github:matrix-org/matrix-js-sdk#develop", + "matrix-js-sdk": "7.0.0-rc.1", "minimist": "^1.2.0", "pako": "^1.0.5", "parse5": "^5.1.1", diff --git a/yarn.lock b/yarn.lock index d2d53692b5..2bb99f4602 100644 --- a/yarn.lock +++ b/yarn.lock @@ -5820,9 +5820,10 @@ mathml-tag-names@^2.0.1: resolved "https://registry.yarnpkg.com/mathml-tag-names/-/mathml-tag-names-2.1.3.tgz#4ddadd67308e780cf16a47685878ee27b736a0a3" integrity sha512-APMBEanjybaPzUrfqU0IMU5I0AswKMH7k8OTLs0vvV4KZpExkTkY87nR/zpbuTPj+gARop7aGUbl11pnDfW6xg== -"matrix-js-sdk@github:matrix-org/matrix-js-sdk#develop": - version "6.2.2" - resolved "https://codeload.github.com/matrix-org/matrix-js-sdk/tar.gz/1c194e81637fb07fe6ad67cda33be0d5d4c10115" +matrix-js-sdk@7.0.0-rc.1: + version "7.0.0-rc.1" + resolved "https://registry.yarnpkg.com/matrix-js-sdk/-/matrix-js-sdk-7.0.0-rc.1.tgz#95a258218f5c5ec73ec4be510b28768c35809a0b" + integrity sha512-1znl0d2UxU6Mmimy+pMSQP1lQfsmDb9jxiKV5sfMvTBsLtUE2cTqEBVDNVoOHL4UJ9U4oMLsrBgu3sELkgSJLQ== dependencies: "@babel/runtime" "^7.8.3" another-json "^0.2.0" From 2533c33c54c870689960f4850e91e8b4be2c5e29 Mon Sep 17 00:00:00 2001 From: RiotRobot Date: Wed, 17 Jun 2020 21:46:33 +0100 Subject: [PATCH 2/8] Prepare changelog for v2.8.0-rc.1 --- CHANGELOG.md | 180 +++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 180 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 089bfa73e0..9d7a73b264 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,183 @@ +Changes in [2.8.0-rc.1](https://github.com/matrix-org/matrix-react-sdk/releases/tag/v2.8.0-rc.1) (2020-06-17) +============================================================================================================= +[Full Changelog](https://github.com/matrix-org/matrix-react-sdk/compare/v2.7.2...v2.8.0-rc.1) + + * Upgrade to JS SDK 7.0.0-rc.1 + * Fix Styled Checkbox and Radio Button disabled state + [\#4778](https://github.com/matrix-org/matrix-react-sdk/pull/4778) + * clean up and fix the isMasterRuleEnabled logic + [\#4782](https://github.com/matrix-org/matrix-react-sdk/pull/4782) + * Fix case-sensitivity of /me to match rest of slash commands + [\#4763](https://github.com/matrix-org/matrix-react-sdk/pull/4763) + * Add a 'show less' button to the new room list + [\#4765](https://github.com/matrix-org/matrix-react-sdk/pull/4765) + * Update from Weblate + [\#4781](https://github.com/matrix-org/matrix-react-sdk/pull/4781) + * Sticky and collapsing headers for new room list + [\#4758](https://github.com/matrix-org/matrix-react-sdk/pull/4758) + * Make the room list labs setting reload on change + [\#4780](https://github.com/matrix-org/matrix-react-sdk/pull/4780) + * Handle/hide old rooms in the room list + [\#4767](https://github.com/matrix-org/matrix-react-sdk/pull/4767) + * Add some media queries to improve UI on mobile (#3991) + [\#4656](https://github.com/matrix-org/matrix-react-sdk/pull/4656) + * Match fuzzy filtering a bit more reliably in the new room list + [\#4769](https://github.com/matrix-org/matrix-react-sdk/pull/4769) + * Improve Field ts definitions some more + [\#4777](https://github.com/matrix-org/matrix-react-sdk/pull/4777) + * Fix alignment of checkboxes in new room list's context menu + [\#4776](https://github.com/matrix-org/matrix-react-sdk/pull/4776) + * Fix Field ts def, fix LocalEchoWrapper and NotificationsEnabledController + [\#4775](https://github.com/matrix-org/matrix-react-sdk/pull/4775) + * Add presence indicators and globes to new room list + [\#4774](https://github.com/matrix-org/matrix-react-sdk/pull/4774) + * Include the sticky room when filtering in the new room list + [\#4772](https://github.com/matrix-org/matrix-react-sdk/pull/4772) + * Add a home button to the new room list menu when available + [\#4771](https://github.com/matrix-org/matrix-react-sdk/pull/4771) + * use group layout for search results + [\#4764](https://github.com/matrix-org/matrix-react-sdk/pull/4764) + * Fix m.id.phone spec compliance + [\#4757](https://github.com/matrix-org/matrix-react-sdk/pull/4757) + * User Info default power levels for ban/kick/redact to 50 as per spec + [\#4759](https://github.com/matrix-org/matrix-react-sdk/pull/4759) + * Match new room list's text search to old room list + [\#4768](https://github.com/matrix-org/matrix-react-sdk/pull/4768) + * Fix ordering of recent rooms in the new room list + [\#4766](https://github.com/matrix-org/matrix-react-sdk/pull/4766) + * Change theme selector to use new styled radio buttons + [\#4731](https://github.com/matrix-org/matrix-react-sdk/pull/4731) + * Use recovery keys over passphrases + [\#4686](https://github.com/matrix-org/matrix-react-sdk/pull/4686) + * Update from Weblate + [\#4760](https://github.com/matrix-org/matrix-react-sdk/pull/4760) + * Initial dark theme support for new room list + [\#4756](https://github.com/matrix-org/matrix-react-sdk/pull/4756) + * Support per-list options and algorithms on the new room list + [\#4754](https://github.com/matrix-org/matrix-react-sdk/pull/4754) + * Send read marker updates immediately after moving visually + [\#4755](https://github.com/matrix-org/matrix-react-sdk/pull/4755) + * Add a minimized view to the new room list + [\#4753](https://github.com/matrix-org/matrix-react-sdk/pull/4753) + * Fix e2e icon alignment in irc-layout + [\#4752](https://github.com/matrix-org/matrix-react-sdk/pull/4752) + * Add some resource leak protection to new room list badges + [\#4750](https://github.com/matrix-org/matrix-react-sdk/pull/4750) + * Fix read-receipt alignment + [\#4747](https://github.com/matrix-org/matrix-react-sdk/pull/4747) + * Show message previews on the new room list tiles + [\#4751](https://github.com/matrix-org/matrix-react-sdk/pull/4751) + * Fix various layout concerns with the new room list + [\#4749](https://github.com/matrix-org/matrix-react-sdk/pull/4749) + * Prioritize text on the clipboard over file + [\#4748](https://github.com/matrix-org/matrix-react-sdk/pull/4748) + * Move Settings flag to ts + [\#4729](https://github.com/matrix-org/matrix-react-sdk/pull/4729) + * Add a context menu to rooms in the new room list + [\#4743](https://github.com/matrix-org/matrix-react-sdk/pull/4743) + * Add hover states and basic context menu to new room list + [\#4742](https://github.com/matrix-org/matrix-react-sdk/pull/4742) + * Update resize handle for new designs in new room list + [\#4741](https://github.com/matrix-org/matrix-react-sdk/pull/4741) + * Improve general stability in the new room list + [\#4740](https://github.com/matrix-org/matrix-react-sdk/pull/4740) + * Reimplement breadcrumbs for new room list + [\#4735](https://github.com/matrix-org/matrix-react-sdk/pull/4735) + * Add styled radio buttons + [\#4744](https://github.com/matrix-org/matrix-react-sdk/pull/4744) + * Hide checkbox tick on dark backgrounds + [\#4730](https://github.com/matrix-org/matrix-react-sdk/pull/4730) + * Make checkboxes a11y friendly + [\#4746](https://github.com/matrix-org/matrix-react-sdk/pull/4746) + * EventIndex: Store and restore the encryption info for encrypted events. + [\#4738](https://github.com/matrix-org/matrix-react-sdk/pull/4738) + * Use IDestroyable instead of IDisposable + [\#4739](https://github.com/matrix-org/matrix-react-sdk/pull/4739) + * Add/improve badge counts in new room list + [\#4734](https://github.com/matrix-org/matrix-react-sdk/pull/4734) + * Convert FormattingUtils to TypeScript and add badge utility function + [\#4732](https://github.com/matrix-org/matrix-react-sdk/pull/4732) + * Add filtering and exploring to the new room list + [\#4736](https://github.com/matrix-org/matrix-react-sdk/pull/4736) + * Support prioritized room list filters + [\#4737](https://github.com/matrix-org/matrix-react-sdk/pull/4737) + * Clean up font scaling appearance + [\#4733](https://github.com/matrix-org/matrix-react-sdk/pull/4733) + * Add user menu to new room list + [\#4722](https://github.com/matrix-org/matrix-react-sdk/pull/4722) + * New room list basic styling and layout + [\#4711](https://github.com/matrix-org/matrix-react-sdk/pull/4711) + * Fix read receipt overlap + [\#4727](https://github.com/matrix-org/matrix-react-sdk/pull/4727) + * Load correct default font size + [\#4726](https://github.com/matrix-org/matrix-react-sdk/pull/4726) + * send state of lowBandwidth in rageshakes + [\#4724](https://github.com/matrix-org/matrix-react-sdk/pull/4724) + * Change internal font size from from 15 to 10 + [\#4725](https://github.com/matrix-org/matrix-react-sdk/pull/4725) + * Upgrade deps + [\#4723](https://github.com/matrix-org/matrix-react-sdk/pull/4723) + * Ensure active Jitsi conference is closed on widget pop-out + [\#4444](https://github.com/matrix-org/matrix-react-sdk/pull/4444) + * Introduce sticky rooms to the new room list + [\#4720](https://github.com/matrix-org/matrix-react-sdk/pull/4720) + * Handle remaining cases for room updates in new room list + [\#4721](https://github.com/matrix-org/matrix-react-sdk/pull/4721) + * Allow searching the emoji picker using other emoji + [\#4719](https://github.com/matrix-org/matrix-react-sdk/pull/4719) + * New room list scrolling and resizing + [\#4697](https://github.com/matrix-org/matrix-react-sdk/pull/4697) + * Don't show FormatBar if composer is empty + [\#4696](https://github.com/matrix-org/matrix-react-sdk/pull/4696) + * Split the left panel into new and old for new room list designs + [\#4687](https://github.com/matrix-org/matrix-react-sdk/pull/4687) + * Fix compact layout regression + [\#4712](https://github.com/matrix-org/matrix-react-sdk/pull/4712) + * fix emoji in safari + [\#4710](https://github.com/matrix-org/matrix-react-sdk/pull/4710) + * Fix not being able to dismiss new login toasts + [\#4709](https://github.com/matrix-org/matrix-react-sdk/pull/4709) + * Fix exceptions from Tooltip + [\#4708](https://github.com/matrix-org/matrix-react-sdk/pull/4708) + * Stop removing variation selector from quick reactions + [\#4707](https://github.com/matrix-org/matrix-react-sdk/pull/4707) + * Tidy up continuation algorithm and make it work for hidden profile changes + [\#4704](https://github.com/matrix-org/matrix-react-sdk/pull/4704) + * Profile settings should never show a disambiguated display name + [\#4699](https://github.com/matrix-org/matrix-react-sdk/pull/4699) + * Prevent (double) 4S bootstrap from RestoreKeyBackupDialog + [\#4701](https://github.com/matrix-org/matrix-react-sdk/pull/4701) + * Stop checkbox styling bleeding through room address selector + [\#4691](https://github.com/matrix-org/matrix-react-sdk/pull/4691) + * Center HeaderButtons + [\#4695](https://github.com/matrix-org/matrix-react-sdk/pull/4695) + * Add .well-known option to control default e2ee behaviour + [\#4605](https://github.com/matrix-org/matrix-react-sdk/pull/4605) + * Add max-width to right and left panels + [\#4692](https://github.com/matrix-org/matrix-react-sdk/pull/4692) + * Fix login loop where the sso flow returns to `#/login` + [\#4685](https://github.com/matrix-org/matrix-react-sdk/pull/4685) + * Don't clear MAU toasts when a successful sync comes in + [\#4690](https://github.com/matrix-org/matrix-react-sdk/pull/4690) + * Add initial filtering support to new room list + [\#4681](https://github.com/matrix-org/matrix-react-sdk/pull/4681) + * Bubble up a decline-to-render of verification events to outside wrapper + [\#4664](https://github.com/matrix-org/matrix-react-sdk/pull/4664) + * upgrade to twemoji 13.0.0 + [\#4672](https://github.com/matrix-org/matrix-react-sdk/pull/4672) + * Apply FocusLock to ImageView to capture Escape handling + [\#4666](https://github.com/matrix-org/matrix-react-sdk/pull/4666) + * Fix the 'complete security' screen + [\#4689](https://github.com/matrix-org/matrix-react-sdk/pull/4689) + * add null-guard for Autocomplete containerRef + [\#4688](https://github.com/matrix-org/matrix-react-sdk/pull/4688) + * Remove legacy codepaths for Unknown Device Error (UDE/UDD) handling + [\#4660](https://github.com/matrix-org/matrix-react-sdk/pull/4660) + * Remove feature_cross_signing + [\#4655](https://github.com/matrix-org/matrix-react-sdk/pull/4655) + * Autocomplete: use scrollIntoView for auto-scroll to fix it + [\#4670](https://github.com/matrix-org/matrix-react-sdk/pull/4670) + Changes in [2.7.2](https://github.com/matrix-org/matrix-react-sdk/releases/tag/v2.7.2) (2020-06-16) =================================================================================================== [Full Changelog](https://github.com/matrix-org/matrix-react-sdk/compare/v2.7.1...v2.7.2) From b5aa66015c38748f1bc59a386cff8a7979903e36 Mon Sep 17 00:00:00 2001 From: RiotRobot Date: Wed, 17 Jun 2020 21:46:34 +0100 Subject: [PATCH 3/8] v2.8.0-rc.1 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 98d7f7a6fa..06c4c43622 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "matrix-react-sdk", - "version": "2.7.2", + "version": "2.8.0-rc.1", "description": "SDK for matrix.org using React", "author": "matrix.org", "repository": { From e4d824839ac782966fe61343707f45c1797f92b2 Mon Sep 17 00:00:00 2001 From: David Baker Date: Thu, 18 Jun 2020 09:35:11 +0100 Subject: [PATCH 4/8] Revert "Use recovery keys over passphrases" --- .../structures/auth/_CompleteSecurity.scss | 4 - .../_CreateSecretStorageDialog.scss | 35 +- src/CrossSigningManager.js | 20 +- .../CreateSecretStorageDialog.js | 464 +++++++++++++----- .../structures/auth/CompleteSecurity.js | 4 - .../structures/auth/SetupEncryptionBody.js | 153 +----- .../keybackup/RestoreKeyBackupDialog.js | 2 +- .../AccessSecretStorageDialog.js | 9 +- .../views/settings/CrossSigningPanel.js | 2 +- src/i18n/strings/en_EN.json | 47 +- src/stores/SetupEncryptionStore.js | 64 +-- test/end-to-end-tests/src/usecases/signup.js | 23 +- 12 files changed, 400 insertions(+), 427 deletions(-) diff --git a/res/css/structures/auth/_CompleteSecurity.scss b/res/css/structures/auth/_CompleteSecurity.scss index b0462db477..f742be70e4 100644 --- a/res/css/structures/auth/_CompleteSecurity.scss +++ b/res/css/structures/auth/_CompleteSecurity.scss @@ -98,7 +98,3 @@ limitations under the License. } } } - -.mx_CompleteSecurity_resetText { - padding-top: 20px; -} diff --git a/res/css/views/dialogs/secretstorage/_CreateSecretStorageDialog.scss b/res/css/views/dialogs/secretstorage/_CreateSecretStorageDialog.scss index 9f1d0f4998..63e5a3de09 100644 --- a/res/css/views/dialogs/secretstorage/_CreateSecretStorageDialog.scss +++ b/res/css/views/dialogs/secretstorage/_CreateSecretStorageDialog.scss @@ -73,42 +73,33 @@ limitations under the License. margin-left: 20px; } +.mx_CreateSecretStorageDialog_recoveryKeyHeader { + margin-bottom: 1em; +} + .mx_CreateSecretStorageDialog_recoveryKeyContainer { - width: 380px; - margin-left: auto; - margin-right: auto; + display: flex; } .mx_CreateSecretStorageDialog_recoveryKey { - font-weight: bold; - text-align: center; + width: 262px; padding: 20px; color: $info-plinth-fg-color; background-color: $info-plinth-bg-color; - border-radius: 6px; - word-spacing: 1em; - margin-bottom: 20px; + margin-right: 12px; } .mx_CreateSecretStorageDialog_recoveryKeyButtons { + flex: 1; display: flex; - justify-content: space-between; align-items: center; } .mx_CreateSecretStorageDialog_recoveryKeyButtons .mx_AccessibleButton { - width: 160px; - padding-left: 0px; - padding-right: 0px; + margin-right: 10px; +} + +.mx_CreateSecretStorageDialog_recoveryKeyButtons button { + flex: 1; white-space: nowrap; } - -.mx_CreateSecretStorageDialog_continueSpinner { - margin-top: 33px; - text-align: right; -} - -.mx_CreateSecretStorageDialog_continueSpinner img { - width: 20px; - height: 20px; -} diff --git a/src/CrossSigningManager.js b/src/CrossSigningManager.js index d40f820ac0..c37d0f8bf5 100644 --- a/src/CrossSigningManager.js +++ b/src/CrossSigningManager.js @@ -30,8 +30,6 @@ import {encodeBase64} from "matrix-js-sdk/src/crypto/olmlib"; // operation ends. let secretStorageKeys = {}; let secretStorageBeingAccessed = false; -// Stores the 'passphraseOnly' option for the active storage access operation -let passphraseOnlyOption = null; function isCachingAllowed() { return ( @@ -101,7 +99,6 @@ async function getSecretStorageKey({ keys: keyInfos }, ssssItemName) { const key = await inputToKey(input); return await MatrixClientPeg.get().checkSecretStorageKey(key, info); }, - passphraseOnly: passphraseOnlyOption, }, /* className= */ null, /* isPriorityModal= */ false, @@ -216,27 +213,19 @@ export async function promptForBackupPassphrase() { * * @param {Function} [func] An operation to perform once secret storage has been * bootstrapped. Optional. - * @param {object} [opts] Named options - * @param {bool} [opts.forceReset] Reset secret storage even if it's already set up - * @param {object} [opts.withKeys] Map of key ID to key for SSSS keys that the client - * already has available. If a key is not supplied here, the user will be prompted. - * @param {bool} [opts.passphraseOnly] If true, do not prompt for recovery key or to reset keys + * @param {bool} [forceReset] Reset secret storage even if it's already set up */ -export async function accessSecretStorage( - func = async () => { }, opts = {}, -) { +export async function accessSecretStorage(func = async () => { }, forceReset = false) { const cli = MatrixClientPeg.get(); secretStorageBeingAccessed = true; - passphraseOnlyOption = opts.passphraseOnly; - secretStorageKeys = Object.assign({}, opts.withKeys || {}); try { - if (!await cli.hasSecretStorageKey() || opts.forceReset) { + if (!await cli.hasSecretStorageKey() || forceReset) { // This dialog calls bootstrap itself after guiding the user through // passphrase creation. const { finished } = Modal.createTrackedDialogAsync('Create Secret Storage dialog', '', import("./async-components/views/dialogs/secretstorage/CreateSecretStorageDialog"), { - force: opts.forceReset, + force: forceReset, }, null, /* priority = */ false, /* static = */ true, ); @@ -274,6 +263,5 @@ export async function accessSecretStorage( if (!isCachingAllowed()) { secretStorageKeys = {}; } - passphraseOnlyOption = null; } } diff --git a/src/async-components/views/dialogs/secretstorage/CreateSecretStorageDialog.js b/src/async-components/views/dialogs/secretstorage/CreateSecretStorageDialog.js index 192427d384..d7b79c2cfa 100644 --- a/src/async-components/views/dialogs/secretstorage/CreateSecretStorageDialog.js +++ b/src/async-components/views/dialogs/secretstorage/CreateSecretStorageDialog.js @@ -20,23 +20,25 @@ import PropTypes from 'prop-types'; import * as sdk from '../../../../index'; import {MatrixClientPeg} from '../../../../MatrixClientPeg'; 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 AccessibleButton from "../../../../components/views/elements/AccessibleButton"; -import DialogButtons from "../../../../components/views/elements/DialogButtons"; -import InlineSpinner from "../../../../components/views/elements/InlineSpinner"; - +import PassphraseField from "../../../../components/views/auth/PassphraseField"; const PHASE_LOADING = 0; const PHASE_LOADERROR = 1; const PHASE_MIGRATE = 2; -const PHASE_INTRO = 3; -const PHASE_SHOWKEY = 4; -const PHASE_STORING = 5; -const PHASE_CONFIRM_SKIP = 6; +const PHASE_PASSPHRASE = 3; +const PHASE_PASSPHRASE_CONFIRM = 4; +const PHASE_SHOWKEY = 5; +const PHASE_KEEPITSAFE = 6; +const PHASE_STORING = 7; +const PHASE_DONE = 8; +const PHASE_CONFIRM_SKIP = 9; + +const PASSWORD_MIN_SCORE = 4; // So secure, many characters, much complex, wow, etc, etc. /* * Walks the user through the process of creating a passphrase to guard Secure @@ -63,32 +65,34 @@ export default class CreateSecretStorageDialog extends React.PureComponent { this.state = { phase: PHASE_LOADING, - downloaded: false, + passPhrase: '', + passPhraseValid: false, + passPhraseConfirm: '', copied: false, + downloaded: false, backupInfo: null, - backupInfoFetched: false, - backupInfoFetchError: null, backupSigStatus: null, // does the server offer a UI auth flow with just m.login.password - // for /keys/device_signing/upload? (If we have an account password, we - // assume that it can) + // for /keys/device_signing/upload? canUploadKeysWithPasswordOnly: null, - canUploadKeyCheckInProgress: false, accountPassword: props.accountPassword || "", accountPasswordCorrect: null, - // No toggle for this: if we really don't want one, remove it & just hard code true + // status of the key backup toggle switch useKeyBackup: true, }; - if (props.accountPassword) { - // If we have an account password, we assume we can upload keys with - // just a password (otherwise leave it as null so we poll to check) - this.state.canUploadKeysWithPasswordOnly = true; - } - this._passphraseField = createRef(); - this.loadData(); + this._fetchBackupInfo(); + if (this.state.accountPassword) { + // If we have an account password in memory, let's simplify and + // assume it means password auth is also supported for device + // signing key upload as well. This avoids hitting the server to + // test auth flows, which may be slow under high load. + this.state.canUploadKeysWithPasswordOnly = true; + } else { + this._queryKeyUploadAuth(); + } MatrixClientPeg.get().on('crypto.keyBackupStatus', this._onKeyBackupStatusChange); } @@ -105,11 +109,13 @@ export default class CreateSecretStorageDialog extends React.PureComponent { MatrixClientPeg.get().isCryptoEnabled() && await MatrixClientPeg.get().isKeyBackupTrusted(backupInfo) ); + const { force } = this.props; + const phase = (backupInfo && !force) ? PHASE_MIGRATE : PHASE_PASSPHRASE; + this.setState({ - backupInfoFetched: true, + phase, backupInfo, backupSigStatus, - backupInfoFetchError: null, }); return { @@ -117,25 +123,20 @@ export default class CreateSecretStorageDialog extends React.PureComponent { backupSigStatus, }; } catch (e) { - this.setState({backupInfoFetchError: e}); + this.setState({phase: PHASE_LOADERROR}); } } async _queryKeyUploadAuth() { try { - this.setState({canUploadKeyCheckInProgress: true}); await MatrixClientPeg.get().uploadDeviceSigningKeys(null, {}); // We should never get here: the server should always require // UI auth to upload device signing keys. If we do, we upload // no keys which would be a no-op. console.log("uploadDeviceSigningKeys unexpectedly succeeded without UI auth!"); - this.setState({canUploadKeyCheckInProgress: false}); } catch (error) { if (!error.data || !error.data.flows) { console.log("uploadDeviceSigningKeys advertised no flows!"); - this.setState({ - canUploadKeyCheckInProgress: false, - }); return; } const canUploadKeysWithPasswordOnly = error.data.flows.some(f => { @@ -143,18 +144,10 @@ export default class CreateSecretStorageDialog extends React.PureComponent { }); this.setState({ canUploadKeysWithPasswordOnly, - canUploadKeyCheckInProgress: false, }); } } - async _createRecoveryKey() { - this._recoveryKey = await MatrixClientPeg.get().createRecoveryKeyFromPassphrase(); - this.setState({ - phase: PHASE_SHOWKEY, - }); - } - _onKeyBackupStatusChange = () => { if (this.state.phase === PHASE_MIGRATE) this._fetchBackupInfo(); } @@ -163,6 +156,12 @@ export default class CreateSecretStorageDialog extends React.PureComponent { this._recoveryKeyNode = n; } + _onUseKeyBackupChange = (enabled) => { + this.setState({ + useKeyBackup: enabled, + }); + } + _onMigrateFormSubmit = (e) => { e.preventDefault(); if (this.state.backupSigStatus.usable) { @@ -172,15 +171,12 @@ export default class CreateSecretStorageDialog extends React.PureComponent { } } - _onIntroContinueClick = () => { - this._createRecoveryKey(); - } - _onCopyClick = () => { const successful = copyNode(this._recoveryKeyNode); if (successful) { this.setState({ copied: true, + phase: PHASE_KEEPITSAFE, }); } } @@ -190,8 +186,10 @@ export default class CreateSecretStorageDialog extends React.PureComponent { type: 'text/plain;charset=us-ascii', }); FileSaver.saveAs(blob, 'recovery-key.txt'); + this.setState({ downloaded: true, + phase: PHASE_KEEPITSAFE, }); } @@ -247,9 +245,7 @@ export default class CreateSecretStorageDialog extends React.PureComponent { _bootstrapSecretStorage = async () => { this.setState({ - // we use LOADING here rather than STORING as STORING still shows the 'show key' - // screen which is not relevant: LOADING is just a generic spinner. - phase: PHASE_LOADING, + phase: PHASE_STORING, error: null, }); @@ -290,7 +286,9 @@ export default class CreateSecretStorageDialog extends React.PureComponent { }, }); } - this.props.onFinished(true); + this.setState({ + phase: PHASE_DONE, + }); } catch (e) { if (this.state.canUploadKeysWithPasswordOnly && e.httpStatus === 401 && e.data.flows) { this.setState({ @@ -309,6 +307,10 @@ export default class CreateSecretStorageDialog extends React.PureComponent { this.props.onFinished(false); } + _onDone = () => { + this.props.onFinished(true); + } + _restoreBackup = async () => { // It's possible we'll need the backup key later on for bootstrapping, // so let's stash it here, rather than prompting for it twice. @@ -335,41 +337,88 @@ export default class CreateSecretStorageDialog extends React.PureComponent { } } - _onShowKeyContinueClick = () => { - this._bootstrapSecretStorage(); - } - _onLoadRetryClick = () => { - this.loadData(); - } - - async loadData() { this.setState({phase: PHASE_LOADING}); - const proms = []; - - if (!this.state.backupInfoFetched) proms.push(this._fetchBackupInfo()); - if (this.state.canUploadKeysWithPasswordOnly === null) proms.push(this._queryKeyUploadAuth()); - - await Promise.all(proms); - if (this.state.canUploadKeysWithPasswordOnly === null || this.state.backupInfoFetchError) { - this.setState({phase: PHASE_LOADERROR}); - } else if (this.state.backupInfo && !this.props.force) { - this.setState({phase: PHASE_MIGRATE}); - } else { - this.setState({phase: PHASE_INTRO}); - } + this._fetchBackupInfo(); } _onSkipSetupClick = () => { this.setState({phase: PHASE_CONFIRM_SKIP}); } - _onGoBackClick = () => { - if (this.state.backupInfo && !this.props.force) { - this.setState({phase: PHASE_MIGRATE}); - } else { - this.setState({phase: PHASE_INTRO}); + _onSetUpClick = () => { + this.setState({phase: PHASE_PASSPHRASE}); + } + + _onSkipPassPhraseClick = async () => { + this._recoveryKey = + await MatrixClientPeg.get().createRecoveryKeyFromPassphrase(); + this.setState({ + copied: false, + downloaded: false, + phase: PHASE_SHOWKEY, + }); + } + + _onPassPhraseNextClick = async (e) => { + e.preventDefault(); + if (!this._passphraseField.current) return; // unmounting + + 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) => { + e.preventDefault(); + + if (this.state.passPhrase !== this.state.passPhraseConfirm) return; + + this._recoveryKey = + await MatrixClientPeg.get().createRecoveryKeyFromPassphrase(this.state.passPhrase); + this.setState({ + copied: false, + downloaded: false, + phase: PHASE_SHOWKEY, + }); + } + + _onSetAgainClick = () => { + this.setState({ + passPhrase: '', + passPhraseValid: false, + passPhraseConfirm: '', + phase: PHASE_PASSPHRASE, + }); + } + + _onKeepItSafeBackClick = () => { + this.setState({ + phase: PHASE_SHOWKEY, + }); + } + + _onPassPhraseValidate = (result) => { + this.setState({ + passPhraseValid: result.valid, + }); + }; + + _onPassPhraseChange = (e) => { + this.setState({ + passPhrase: e.target.value, + }); + } + + _onPassPhraseConfirmChange = (e) => { + this.setState({ + passPhraseConfirm: e.target.value, + }); } _onAccountPasswordChange = (e) => { @@ -384,14 +433,12 @@ export default class CreateSecretStorageDialog extends React.PureComponent { // Once we're confident enough in this (and it's supported enough) we can do // it automatically. // https://github.com/vector-im/riot-web/issues/11696 + const DialogButtons = sdk.getComponent('views.elements.DialogButtons'); const Field = sdk.getComponent('views.elements.Field'); let authPrompt; let nextCaption = _t("Next"); - if (!this.state.backupSigStatus.usable) { - authPrompt = null; - nextCaption = _t("Upload"); - } else if (this.state.canUploadKeysWithPasswordOnly && !this.props.accountPassword) { + if (this.state.canUploadKeysWithPasswordOnly) { authPrompt =
{_t("Enter your account password to confirm the upgrade:")}
; + } else if (!this.state.backupSigStatus.usable) { + authPrompt =
+
{_t("Restore your key backup to upgrade your encryption")}
+
; + nextCaption = _t("Restore"); } else { authPrompt =

{_t("You'll need to authenticate with the server to confirm the upgrade.")} @@ -411,9 +463,9 @@ export default class CreateSecretStorageDialog extends React.PureComponent { return

{_t( - "Upgrade your Recovery Key to store encryption keys & secrets " + - "with your account data. If you lose access to this login you'll " + - "need it to unlock your data.", + "Upgrade this session to allow it to verify other sessions, " + + "granting them access to encrypted messages and marking them " + + "as trusted for other users.", )}

{authPrompt}
; } - _renderPhaseShowKey() { - let continueButton; - if (this.state.phase === PHASE_SHOWKEY) { - continueButton = +

{_t( + "Set a recovery passphrase to secure encrypted information and recover it if you log out. " + + "This should be different to your account password:", + )}

+ +
+ +
+ + + + ; - } else { - continueButton =
- -
; + disabled={!this.state.passPhraseValid} + > + +
+ +
+ {_t("Advanced")} + + {_t("Set up with a recovery key")} + +
+ ; + } + + _renderPhasePassPhraseConfirm() { + const AccessibleButton = sdk.getComponent('elements.AccessibleButton'); + const Field = sdk.getComponent('views.elements.Field'); + + let matchText; + let changeText; + if (this.state.passPhraseConfirm === this.state.passPhrase) { + matchText = _t("That matches!"); + changeText = _t("Use a different passphrase?"); + } else if (!this.state.passPhrase.startsWith(this.state.passPhraseConfirm)) { + // only tell them they're wrong if they've actually gone wrong. + // Security concious readers will note that if you left riot-web unattended + // on this screen, this would make it easy for a malicious person to guess + // your passphrase one letter at a time, but they could get this faster by + // just opening the browser's developer tools and reading it. + // Note that not having typed anything at all will not hit this clause and + // fall through so empty box === no hint. + matchText = _t("That doesn't match."); + changeText = _t("Go back to set it again."); } + let passPhraseMatch = null; + if (matchText) { + passPhraseMatch =
+
{matchText}
+
+ + {changeText} + +
+
; + } + const DialogButtons = sdk.getComponent('views.elements.DialogButtons'); + return
+

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

+
+ +
+ {passPhraseMatch} +
+
+ + + +
; + } + + _renderPhaseShowKey() { + const AccessibleButton = sdk.getComponent('elements.AccessibleButton'); return

{_t( - "Store your Recovery Key somewhere safe, it can be used to unlock your encrypted messages & data.", + "Your recovery key is a safety net - you can use it to restore " + + "access to your encrypted messages if you forget your recovery passphrase.", + )}

+

{_t( + "Keep a copy of it somewhere secure, like a password manager or even a safe.", )}

+
+ {_t("Your recovery key")} +
{this._recoveryKey.encodedPrivateKey}
- - {_t("Download")} - - {_t("or")} - {this.state.copied ? _t("Copied!") : _t("Copy")} + {_t("Copy")} + + + {_t("Download")}
- {continueButton} +
; + } + + _renderPhaseKeepItSafe() { + let introText; + if (this.state.copied) { + introText = _t( + "Your recovery key has been copied to your clipboard, paste it to:", + {}, {b: s => {s}}, + ); + } else if (this.state.downloaded) { + introText = _t( + "Your recovery key is in your Downloads folder.", + {}, {b: s => {s}}, + ); + } + const DialogButtons = sdk.getComponent('views.elements.DialogButtons'); + return
+ {introText} +
    +
  • {_t("Print it and store it somewhere safe", {}, {b: s => {s}})}
  • +
  • {_t("Save it on a USB key or backup drive", {}, {b: s => {s}})}
  • +
  • {_t("Copy it to your personal cloud storage", {}, {b: s => {s}})}
  • +
+ + +
; } @@ -483,6 +671,7 @@ export default class CreateSecretStorageDialog extends React.PureComponent { } _renderPhaseLoadError() { + const DialogButtons = sdk.getComponent('views.elements.DialogButtons'); return

{_t("Unable to query secret storage status")}

@@ -495,44 +684,29 @@ export default class CreateSecretStorageDialog extends React.PureComponent {
; } - _renderPhaseIntro() { - let cancelButton; - if (this.props.force) { - // if this is a forced key reset then aborting will just leave the old keys - // in place, and is thereforece just 'cancel' - cancelButton = ; - } else { - // if it's setting up from scratch then aborting leaves the user without - // crypto set up, so they skipping the setup. - cancelButton = ; - } - + _renderPhaseDone() { + const DialogButtons = sdk.getComponent('views.elements.DialogButtons'); return

{_t( - "Create a Recovery Key to store encryption keys & secrets with your account data. " + - "If you lose access to this login you’ll need it to unlock your data.", + "You can now verify your other devices, " + + "and other users to keep your chats safe.", )}

-
- - {cancelButton} - -
+
; } _renderPhaseSkipConfirm() { + const DialogButtons = sdk.getComponent('views.elements.DialogButtons'); return
{_t( "Without completing security on this session, it won’t have " + "access to encrypted messages.", )} @@ -542,15 +716,21 @@ export default class CreateSecretStorageDialog extends React.PureComponent { _titleForPhase(phase) { switch (phase) { - case PHASE_INTRO: - return _t('Create a Recovery Key'); case PHASE_MIGRATE: - return _t('Upgrade your Recovery Key'); + return _t('Upgrade your encryption'); + case PHASE_PASSPHRASE: + return _t('Set up encryption'); + case PHASE_PASSPHRASE_CONFIRM: + return _t('Confirm recovery passphrase'); case PHASE_CONFIRM_SKIP: return _t('Are you sure?'); case PHASE_SHOWKEY: + case PHASE_KEEPITSAFE: + return _t('Make a copy of your recovery key'); case PHASE_STORING: - return _t('Store your Recovery Key'); + return _t('Setting up keys'); + case PHASE_DONE: + return _t("You're done!"); default: return ''; } @@ -561,6 +741,7 @@ export default class CreateSecretStorageDialog extends React.PureComponent { let content; if (this.state.error) { + const DialogButtons = sdk.getComponent('views.elements.DialogButtons'); content =

{_t("Unable to set up secret storage")}

@@ -579,16 +760,27 @@ export default class CreateSecretStorageDialog extends React.PureComponent { case PHASE_LOADERROR: content = this._renderPhaseLoadError(); break; - case PHASE_INTRO: - content = this._renderPhaseIntro(); - break; case PHASE_MIGRATE: content = this._renderPhaseMigrate(); break; + case PHASE_PASSPHRASE: + content = this._renderPhasePassPhrase(); + break; + case PHASE_PASSPHRASE_CONFIRM: + content = this._renderPhasePassPhraseConfirm(); + break; case PHASE_SHOWKEY: - case PHASE_STORING: content = this._renderPhaseShowKey(); break; + case PHASE_KEEPITSAFE: + content = this._renderPhaseKeepItSafe(); + break; + case PHASE_STORING: + content = this._renderBusyPhase(); + break; + case PHASE_DONE: + content = this._renderPhaseDone(); + break; case PHASE_CONFIRM_SKIP: content = this._renderPhaseSkipConfirm(); break; @@ -605,7 +797,7 @@ export default class CreateSecretStorageDialog extends React.PureComponent { onFinished={this.props.onFinished} title={this._titleForPhase(this.state.phase)} headerImage={headerImage} - hasCancel={this.props.hasCancel} + hasCancel={this.props.hasCancel && [PHASE_PASSPHRASE].includes(this.state.phase)} fixedWidth={false} >
diff --git a/src/components/structures/auth/CompleteSecurity.js b/src/components/structures/auth/CompleteSecurity.js index e38ecd3eac..c73691611d 100644 --- a/src/components/structures/auth/CompleteSecurity.js +++ b/src/components/structures/auth/CompleteSecurity.js @@ -21,7 +21,6 @@ import * as sdk from '../../../index'; import { SetupEncryptionStore, PHASE_INTRO, - PHASE_RECOVERY_KEY, PHASE_BUSY, PHASE_DONE, PHASE_CONFIRM_SKIP, @@ -62,9 +61,6 @@ export default class CompleteSecurity extends React.Component { if (phase === PHASE_INTRO) { icon = ; title = _t("Verify this login"); - } else if (phase === PHASE_RECOVERY_KEY) { - icon = ; - title = _t("Recovery Key"); } else if (phase === PHASE_DONE) { icon = ; title = _t("Session verified"); diff --git a/src/components/structures/auth/SetupEncryptionBody.js b/src/components/structures/auth/SetupEncryptionBody.js index 7886ed26dd..26534c6e02 100644 --- a/src/components/structures/auth/SetupEncryptionBody.js +++ b/src/components/structures/auth/SetupEncryptionBody.js @@ -19,26 +19,15 @@ import PropTypes from 'prop-types'; import { _t } from '../../../languageHandler'; import { MatrixClientPeg } from '../../../MatrixClientPeg'; import * as sdk from '../../../index'; -import withValidation from '../../views/elements/Validation'; -import { decodeRecoveryKey } from 'matrix-js-sdk/src/crypto/recoverykey'; import { SetupEncryptionStore, PHASE_INTRO, - PHASE_RECOVERY_KEY, PHASE_BUSY, PHASE_DONE, PHASE_CONFIRM_SKIP, PHASE_FINISHED, } from '../../../stores/SetupEncryptionStore'; -function keyHasPassphrase(keyInfo) { - return ( - keyInfo.passphrase && - keyInfo.passphrase.salt && - keyInfo.passphrase.iterations - ); -} - export default class SetupEncryptionBody extends React.Component { static propTypes = { onFinished: PropTypes.func.isRequired, @@ -56,11 +45,6 @@ export default class SetupEncryptionBody extends React.Component { // Because of the latter, it lives in the state. verificationRequest: store.verificationRequest, backupInfo: store.backupInfo, - recoveryKey: '', - // whether the recovery key is a valid recovery key - recoveryKeyValid: null, - // whether the recovery key is the correct key or not - recoveryKeyCorrect: null, }; } @@ -83,19 +67,9 @@ export default class SetupEncryptionBody extends React.Component { store.stop(); } - _onResetClick = () => { + _onUsePassphraseClick = async () => { const store = SetupEncryptionStore.sharedInstance(); - store.startKeyReset(); - } - - _onUseRecoveryKeyClick = async () => { - const store = SetupEncryptionStore.sharedInstance(); - store.useRecoveryKey(); - } - - _onRecoveryKeyCancelClick() { - const store = SetupEncryptionStore.sharedInstance(); - store.cancelUseRecoveryKey(); + store.usePassPhrase(); } onSkipClick = () => { @@ -118,66 +92,6 @@ export default class SetupEncryptionBody extends React.Component { store.done(); } - _onUsePassphraseClick = () => { - const store = SetupEncryptionStore.sharedInstance(); - store.usePassPhrase(); - } - - _onRecoveryKeyChange = (e) => { - this.setState({recoveryKey: e.target.value}); - } - - _onRecoveryKeyValidate = async (fieldState) => { - const result = await this._validateRecoveryKey(fieldState); - this.setState({recoveryKeyValid: result.valid}); - return result; - } - - _validateRecoveryKey = withValidation({ - rules: [ - { - key: "required", - test: async (state) => { - try { - const decodedKey = decodeRecoveryKey(state.value); - const correct = await MatrixClientPeg.get().checkSecretStorageKey( - decodedKey, SetupEncryptionStore.sharedInstance().keyInfo, - ); - this.setState({ - recoveryKeyValid: true, - recoveryKeyCorrect: correct, - }); - return correct; - } catch (e) { - this.setState({ - recoveryKeyValid: false, - recoveryKeyCorrect: false, - }); - return false; - } - }, - invalid: function() { - if (this.state.recoveryKeyValid) { - return _t("This isn't the recovery key for your account"); - } else { - return _t("This isn't a valid recovery key"); - } - }, - valid: function() { - return _t("Looks good!"); - }, - }, - ], - }) - - _onRecoveryKeyFormSubmit = (e) => { - e.preventDefault(); - if (!this.state.recoveryKeyCorrect) return; - - const store = SetupEncryptionStore.sharedInstance(); - store.setupWithRecoveryKey(decodeRecoveryKey(this.state.recoveryKey)); - } - render() { const AccessibleButton = sdk.getComponent("elements.AccessibleButton"); @@ -194,13 +108,6 @@ export default class SetupEncryptionBody extends React.Component { member={MatrixClientPeg.get().getUser(this.state.verificationRequest.otherUserId)} />; } else if (phase === PHASE_INTRO) { - const store = SetupEncryptionStore.sharedInstance(); - let recoveryKeyPrompt; - if (keyHasPassphrase(store.keyInfo)) { - recoveryKeyPrompt = _t("Use Recovery Key or Passphrase"); - } else { - recoveryKeyPrompt = _t("Use Recovery Key"); - } return (

{_t( @@ -224,67 +131,15 @@ export default class SetupEncryptionBody extends React.Component {

- - {recoveryKeyPrompt} + + {_t("Use Recovery Passphrase or Key")} {_t("Skip")}
-
{_t( - "If you've forgotten your recovery key you can " + - "", {}, { - button: sub => - {sub} - , - }, - )}
); - } else if (phase === PHASE_RECOVERY_KEY) { - const store = SetupEncryptionStore.sharedInstance(); - let keyPrompt; - if (keyHasPassphrase(store.keyInfo)) { - keyPrompt = _t( - "Enter your Recovery Key or enter a Recovery Passphrase to continue.", {}, - { - a: sub => {sub}, - }, - ); - } else { - keyPrompt = _t("Enter your Recovery Key to continue."); - } - - const Field = sdk.getComponent('elements.Field'); - return
-

{keyPrompt}

-
- -
-
- - {_t("Cancel")} - - - {_t("Continue")} - -
-
; } else if (phase === PHASE_DONE) { let message; if (this.state.backupInfo) { diff --git a/src/components/views/dialogs/keybackup/RestoreKeyBackupDialog.js b/src/components/views/dialogs/keybackup/RestoreKeyBackupDialog.js index 87ba6f7396..dd34dfbbf0 100644 --- a/src/components/views/dialogs/keybackup/RestoreKeyBackupDialog.js +++ b/src/components/views/dialogs/keybackup/RestoreKeyBackupDialog.js @@ -88,7 +88,7 @@ export default class RestoreKeyBackupDialog extends React.PureComponent { _onResetRecoveryClick = () => { this.props.onFinished(false); - accessSecretStorage(() => {}, {forceReset: true}); + accessSecretStorage(() => {}, /* forceReset = */ true); } _onRecoveryKeyChange = (e) => { diff --git a/src/components/views/dialogs/secretstorage/AccessSecretStorageDialog.js b/src/components/views/dialogs/secretstorage/AccessSecretStorageDialog.js index 43697f8ee7..e2ceadfbb9 100644 --- a/src/components/views/dialogs/secretstorage/AccessSecretStorageDialog.js +++ b/src/components/views/dialogs/secretstorage/AccessSecretStorageDialog.js @@ -32,9 +32,6 @@ export default class AccessSecretStorageDialog extends React.PureComponent { keyInfo: PropTypes.object.isRequired, // Function from one of { passphrase, recoveryKey } -> boolean checkPrivateKey: PropTypes.func.isRequired, - // If true, only prompt for a passphrase and do not offer to restore with - // a recovery key or reset keys. - passphraseOnly: PropTypes.bool, } constructor(props) { @@ -61,7 +58,7 @@ export default class AccessSecretStorageDialog extends React.PureComponent { _onResetRecoveryClick = () => { // Re-enter the access flow, but resetting storage this time around. this.props.onFinished(false); - accessSecretStorage(() => {}, {forceReset: true}); + accessSecretStorage(() => {}, /* forceReset = */ true); } _onRecoveryKeyChange = (e) => { @@ -167,7 +164,7 @@ export default class AccessSecretStorageDialog extends React.PureComponent { primaryDisabled={this.state.passPhrase.length === 0} /> - {this.props.passphraseOnly ? null : _t( + {_t( "If you've forgotten your recovery passphrase you can "+ "use your recovery key or " + "set up new recovery options." @@ -237,7 +234,7 @@ export default class AccessSecretStorageDialog extends React.PureComponent { primaryDisabled={!this.state.recoveryKeyValid} /> - {this.props.passphraseOnly ? null : _t( + {_t( "If you've forgotten your recovery key you can "+ "." , {}, { diff --git a/src/components/views/settings/CrossSigningPanel.js b/src/components/views/settings/CrossSigningPanel.js index f48ee3cd0d..7eb239cbca 100644 --- a/src/components/views/settings/CrossSigningPanel.js +++ b/src/components/views/settings/CrossSigningPanel.js @@ -113,7 +113,7 @@ export default class CrossSigningPanel extends React.PureComponent { _bootstrapSecureSecretStorage = async (forceReset=false) => { this.setState({ error: null }); try { - await accessSecretStorage(() => undefined, {forceReset}); + await accessSecretStorage(() => undefined, forceReset); } catch (e) { this.setState({ error: e }); console.error("Error bootstrapping secret storage", e); diff --git a/src/i18n/strings/en_EN.json b/src/i18n/strings/en_EN.json index 5de33ada55..9a41517664 100644 --- a/src/i18n/strings/en_EN.json +++ b/src/i18n/strings/en_EN.json @@ -2068,7 +2068,6 @@ "Account settings": "Account settings", "Could not load user profile": "Could not load user profile", "Verify this login": "Verify this login", - "Recovery Key": "Recovery Key", "Session verified": "Session verified", "Failed to send email": "Failed to send email", "The email address linked to your account must be entered.": "The email address linked to your account must be entered.", @@ -2122,16 +2121,10 @@ "You can now close this window or log in to your new account.": "You can now close this window or log in to your new account.", "Registration Successful": "Registration Successful", "Create your account": "Create your account", - "This isn't the recovery key for your account": "This isn't the recovery key for your account", - "This isn't a valid recovery key": "This isn't a valid recovery key", - "Looks good!": "Looks good!", - "Use Recovery Key or Passphrase": "Use Recovery Key or Passphrase", - "Use Recovery Key": "Use Recovery Key", "Confirm your identity by verifying this login from one of your other sessions, granting it access to encrypted messages.": "Confirm your identity by verifying this login from one of your other sessions, granting it access to encrypted messages.", "This requires the latest Riot on your other devices:": "This requires the latest Riot on your other devices:", "or another cross-signing capable Matrix client": "or another cross-signing capable Matrix client", - "Enter your Recovery Key or enter a Recovery Passphrase to continue.": "Enter your Recovery Key or enter a Recovery Passphrase to continue.", - "Enter your Recovery Key to continue.": "Enter your Recovery Key to continue.", + "Use Recovery Passphrase or Key": "Use Recovery Passphrase or Key", "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.", @@ -2175,43 +2168,47 @@ "Confirm encryption setup": "Confirm encryption setup", "Click the button below to confirm setting up encryption.": "Click the button below to confirm setting up encryption.", "Enter your account password to confirm the upgrade:": "Enter your account password to confirm the upgrade:", + "Restore your key backup to upgrade your encryption": "Restore your key backup to upgrade your encryption", + "Restore": "Restore", "You'll need to authenticate with the server to confirm the upgrade.": "You'll need to authenticate with the server to confirm the upgrade.", - "Upgrade your Recovery Key to store encryption keys & secrets with your account data. If you lose access to this login you'll need it to unlock your data.": "Upgrade your Recovery Key to store encryption keys & secrets with your account data. If you lose access to this login you'll need it to unlock your data.", - "Store your Recovery Key somewhere safe, it can be used to unlock your encrypted messages & data.": "Store your Recovery Key somewhere safe, it can be used to unlock your encrypted messages & data.", - "Download": "Download", - "Copy": "Copy", - "Unable to query secret storage status": "Unable to query secret storage status", - "Retry": "Retry", - "Create a Recovery Key to store encryption keys & secrets with your account data. If you lose access to this login you’ll need it to unlock your data.": "Create a Recovery Key to store encryption keys & secrets with your account data. If you lose access to this login you’ll need it to unlock your data.", - "Create a Recovery Key": "Create a Recovery Key", - "Upgrade your Recovery Key": "Upgrade your Recovery Key", - "Store your Recovery Key": "Store your Recovery Key", - "Unable to set up secret storage": "Unable to set up secret storage", - "We'll store an encrypted copy of your keys on our server. Secure your backup with a recovery passphrase.": "We'll store an encrypted copy of your keys on our server. Secure your backup with a recovery passphrase.", - "For maximum security, this should be different from your account password.": "For maximum security, this should be different from your account password.", + "Upgrade this session to allow it to verify other sessions, granting them access to encrypted messages and marking them as trusted for other users.": "Upgrade this session to allow it to verify other sessions, granting them access to encrypted messages and marking them as trusted for other users.", + "Set a recovery passphrase to secure encrypted information and recover it if you log out. This should be different to your account password:": "Set a recovery passphrase to secure encrypted information and recover it if you log out. This should be different to your account password:", "Enter a recovery passphrase": "Enter a recovery passphrase", "Great! This recovery passphrase looks strong enough.": "Great! This recovery passphrase looks strong enough.", + "Back up encrypted message keys": "Back up encrypted message keys", "Set up with a recovery key": "Set up with a recovery key", "That matches!": "That matches!", "Use a different passphrase?": "Use a different passphrase?", "That doesn't match.": "That doesn't match.", "Go back to set it again.": "Go back to set it again.", - "Please enter your recovery passphrase a second time to confirm.": "Please enter your recovery passphrase a second time to confirm.", - "Repeat your recovery passphrase...": "Repeat your recovery passphrase...", + "Enter your recovery passphrase a second time to confirm it.": "Enter your recovery passphrase a second time to confirm it.", + "Confirm your recovery passphrase": "Confirm your recovery passphrase", "Your recovery key is a safety net - you can use it to restore access to your encrypted messages if you forget your recovery passphrase.": "Your recovery key is a safety net - you can use it to restore access to your encrypted messages if you forget your recovery passphrase.", "Keep a copy of it somewhere secure, like a password manager or even a safe.": "Keep a copy of it somewhere secure, like a password manager or even a safe.", "Your recovery key": "Your recovery key", + "Copy": "Copy", + "Download": "Download", "Your recovery key has been copied to your clipboard, paste it to:": "Your recovery key has been copied to your clipboard, paste it to:", "Your recovery key is in your Downloads folder.": "Your recovery key is in your Downloads folder.", "Print it and store it somewhere safe": "Print it and store it somewhere safe", "Save it on a USB key or backup drive": "Save it on a USB key or backup drive", "Copy it to your personal cloud storage": "Copy it to your personal cloud storage", + "Unable to query secret storage status": "Unable to query secret storage status", + "Retry": "Retry", + "You can now verify your other devices, and other users to keep your chats safe.": "You can now verify your other devices, and other users to keep your chats safe.", + "Upgrade your encryption": "Upgrade your encryption", + "Confirm recovery passphrase": "Confirm recovery passphrase", + "Make a copy of your recovery key": "Make a copy of your recovery key", + "You're done!": "You're done!", + "Unable to set up secret storage": "Unable to set up secret storage", + "We'll store an encrypted copy of your keys on our server. Secure your backup with a recovery passphrase.": "We'll store an encrypted copy of your keys on our server. Secure your backup with a recovery passphrase.", + "For maximum security, this should be different from your account password.": "For maximum security, this should be different from your account password.", + "Please enter your recovery passphrase a second time to confirm.": "Please enter your recovery passphrase a second time to confirm.", + "Repeat your recovery passphrase...": "Repeat your recovery passphrase...", "Your keys are being backed up (the first backup could take a few minutes).": "Your keys are being backed up (the first backup could take a few minutes).", "Without setting up Secure Message Recovery, you won't be able to restore your encrypted message history if you log out or use another session.": "Without setting up Secure Message Recovery, you won't be able to restore your encrypted message history if you log out or use another session.", "Set up Secure Message Recovery": "Set up Secure Message Recovery", "Secure your backup with a recovery passphrase": "Secure your backup with a recovery passphrase", - "Confirm your recovery passphrase": "Confirm your recovery passphrase", - "Make a copy of your recovery key": "Make a copy of your recovery key", "Starting backup...": "Starting backup...", "Success!": "Success!", "Create key backup": "Create key backup", diff --git a/src/stores/SetupEncryptionStore.js b/src/stores/SetupEncryptionStore.js index cc64e24a03..ae1f998b02 100644 --- a/src/stores/SetupEncryptionStore.js +++ b/src/stores/SetupEncryptionStore.js @@ -20,11 +20,10 @@ import { accessSecretStorage, AccessCancelledError } from '../CrossSigningManage import { PHASE_DONE as VERIF_PHASE_DONE } from "matrix-js-sdk/src/crypto/verification/request/VerificationRequest"; export const PHASE_INTRO = 0; -export const PHASE_RECOVERY_KEY = 1; -export const PHASE_BUSY = 2; -export const PHASE_DONE = 3; //final done stage, but still showing UX -export const PHASE_CONFIRM_SKIP = 4; -export const PHASE_FINISHED = 5; //UX can be closed +export const PHASE_BUSY = 1; +export const PHASE_DONE = 2; //final done stage, but still showing UX +export const PHASE_CONFIRM_SKIP = 3; +export const PHASE_FINISHED = 4; //UX can be closed export class SetupEncryptionStore extends EventEmitter { static sharedInstance() { @@ -37,19 +36,11 @@ export class SetupEncryptionStore extends EventEmitter { return; } this._started = true; - this.phase = PHASE_BUSY; + this.phase = PHASE_INTRO; this.verificationRequest = null; this.backupInfo = null; - - // ID of the key that the secrets we want are encrypted with - this.keyId = null; - // Descriptor of the key that the secrets we want are encrypted with - this.keyInfo = null; - MatrixClientPeg.get().on("crypto.verification.request", this.onVerificationRequest); MatrixClientPeg.get().on('userTrustStatusChanged', this._onUserTrustStatusChanged); - - this.fetchKeyInfo(); } stop() { @@ -66,49 +57,7 @@ export class SetupEncryptionStore extends EventEmitter { } } - async fetchKeyInfo() { - const keys = await MatrixClientPeg.get().isSecretStored('m.cross_signing.master', false); - if (Object.keys(keys).length === 0) { - this.keyId = null; - this.keyInfo = null; - } else { - // If the secret is stored under more than one key, we just pick an arbitrary one - this.keyId = Object.keys(keys)[0]; - this.keyInfo = keys[this.keyId]; - } - - this.phase = PHASE_INTRO; - this.emit("update"); - } - - async startKeyReset() { - try { - await accessSecretStorage(() => {}, {forceReset: true}); - // If the keys are reset, the trust status event will fire and we'll change state - } catch (e) { - // dialog was cancelled - stay on the current screen - } - } - - async useRecoveryKey() { - this.phase = PHASE_RECOVERY_KEY; - this.emit("update"); - } - - cancelUseRecoveryKey() { - this.phase = PHASE_INTRO; - this.emit("update"); - } - - async setupWithRecoveryKey(recoveryKey) { - this.startTrustCheck({[this.keyId]: recoveryKey}); - } - async usePassPhrase() { - this.startTrustCheck(); - } - - async startTrustCheck(withKeys) { this.phase = PHASE_BUSY; this.emit("update"); const cli = MatrixClientPeg.get(); @@ -135,9 +84,6 @@ export class SetupEncryptionStore extends EventEmitter { // to advance before this. await cli.restoreKeyBackupWithSecretStorage(backupInfo); } - }, { - withKeys, - passphraseOnly: true, }).catch(reject); } catch (e) { console.error(e); diff --git a/test/end-to-end-tests/src/usecases/signup.js b/test/end-to-end-tests/src/usecases/signup.js index 2859aadbda..aa9f6b7efa 100644 --- a/test/end-to-end-tests/src/usecases/signup.js +++ b/test/end-to-end-tests/src/usecases/signup.js @@ -79,7 +79,20 @@ module.exports = async function signup(session, username, password, homeserver) const acceptButton = await session.query('.mx_InteractiveAuthEntryComponents_termsSubmit'); await acceptButton.click(); - const xsignContButton = await session.query('.mx_CreateSecretStorageDialog .mx_Dialog_buttons .mx_Dialog_primary'); + //plow through cross-signing setup by entering arbitrary details + //TODO: It's probably important for the tests to know the passphrase + const xsigningPassphrase = 'a7eaXcjpa9!Yl7#V^h$B^%dovHUVX'; // https://xkcd.com/221/ + let passphraseField = await session.query('.mx_CreateSecretStorageDialog_passPhraseField input'); + await session.replaceInputText(passphraseField, xsigningPassphrase); + await session.delay(1000); // give it a second to analyze our passphrase for security + let xsignContButton = await session.query('.mx_CreateSecretStorageDialog .mx_Dialog_buttons .mx_Dialog_primary'); + await xsignContButton.click(); + + //repeat passphrase entry + passphraseField = await session.query('.mx_CreateSecretStorageDialog_passPhraseField input'); + await session.replaceInputText(passphraseField, xsigningPassphrase); + await session.delay(1000); // give it a second to analyze our passphrase for security + xsignContButton = await session.query('.mx_CreateSecretStorageDialog .mx_Dialog_buttons .mx_Dialog_primary'); await xsignContButton.click(); //ignore the recovery key @@ -88,11 +101,13 @@ module.exports = async function signup(session, username, password, homeserver) await copyButton.click(); //acknowledge that we copied the recovery key to a safe place - const copyContinueButton = await session.query( - '.mx_CreateSecretStorageDialog .mx_Dialog_buttons .mx_Dialog_primary', - ); + const copyContinueButton = await session.query('.mx_CreateSecretStorageDialog .mx_Dialog_primary'); await copyContinueButton.click(); + //acknowledge that we're done cross-signing setup and our keys are safe + const doneOkButton = await session.query('.mx_CreateSecretStorageDialog .mx_Dialog_primary'); + await doneOkButton.click(); + //wait for registration to finish so the hash gets set //onhashchange better? From 38bf1680a242ea8d50176791354a58326db54adc Mon Sep 17 00:00:00 2001 From: "J. Ryan Stinnett" Date: Mon, 22 Jun 2020 19:02:03 +0100 Subject: [PATCH 5/8] Update read receipt remainder for internal font size change In https://github.com/matrix-org/matrix-react-sdk/pull/4725, we changed the internal font size from 15 to 10, but the `toRem` function (currently only used for read receipts remainders curiously) was not updated. This updates the function, which restores the remainders. Fixes https://github.com/vector-im/riot-web/issues/14127 --- src/utils/units.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/utils/units.ts b/src/utils/units.ts index 54dd6b0523..03775f4c21 100644 --- a/src/utils/units.ts +++ b/src/utils/units.ts @@ -19,7 +19,7 @@ limitations under the License. // converts a pixel value to rem. export function toRem(pixelValue: number): string { - return pixelValue / 15 + "rem"; + return pixelValue / 10 + "rem"; } export function toPx(pixelValue: number): string { From 54ba75afd7e8ebc6809c7dcb2c678ee43bbece7b Mon Sep 17 00:00:00 2001 From: RiotRobot Date: Tue, 23 Jun 2020 14:48:23 +0100 Subject: [PATCH 6/8] Upgrade matrix-js-sdk to 7.0.0 --- package.json | 2 +- yarn.lock | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/package.json b/package.json index 06c4c43622..07cb11d45b 100644 --- a/package.json +++ b/package.json @@ -80,7 +80,7 @@ "is-ip": "^2.0.0", "linkifyjs": "^2.1.6", "lodash": "^4.17.14", - "matrix-js-sdk": "7.0.0-rc.1", + "matrix-js-sdk": "7.0.0", "minimist": "^1.2.0", "pako": "^1.0.5", "parse5": "^5.1.1", diff --git a/yarn.lock b/yarn.lock index 2bb99f4602..2aa39a257f 100644 --- a/yarn.lock +++ b/yarn.lock @@ -5820,10 +5820,10 @@ mathml-tag-names@^2.0.1: resolved "https://registry.yarnpkg.com/mathml-tag-names/-/mathml-tag-names-2.1.3.tgz#4ddadd67308e780cf16a47685878ee27b736a0a3" integrity sha512-APMBEanjybaPzUrfqU0IMU5I0AswKMH7k8OTLs0vvV4KZpExkTkY87nR/zpbuTPj+gARop7aGUbl11pnDfW6xg== -matrix-js-sdk@7.0.0-rc.1: - version "7.0.0-rc.1" - resolved "https://registry.yarnpkg.com/matrix-js-sdk/-/matrix-js-sdk-7.0.0-rc.1.tgz#95a258218f5c5ec73ec4be510b28768c35809a0b" - integrity sha512-1znl0d2UxU6Mmimy+pMSQP1lQfsmDb9jxiKV5sfMvTBsLtUE2cTqEBVDNVoOHL4UJ9U4oMLsrBgu3sELkgSJLQ== +matrix-js-sdk@7.0.0: + version "7.0.0" + resolved "https://registry.yarnpkg.com/matrix-js-sdk/-/matrix-js-sdk-7.0.0.tgz#da2b24e57574379c3d8f7065eb68ea6c479d9806" + integrity sha512-0i1NmfwS5HzEPPjqUAXpw81o+8DImBS67QQBemJiM6D/imU3KFBacdhkmgjMLKXnAfTy6a+aCGfYBfVolfmNQw== dependencies: "@babel/runtime" "^7.8.3" another-json "^0.2.0" From 059d5f927d549a6ebb22e6adbbbd3144814a3b31 Mon Sep 17 00:00:00 2001 From: RiotRobot Date: Tue, 23 Jun 2020 15:06:52 +0100 Subject: [PATCH 7/8] Prepare changelog for v2.8.0 --- CHANGELOG.md | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 9d7a73b264..21bfbf0a93 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,12 @@ +Changes in [2.8.0](https://github.com/matrix-org/matrix-react-sdk/releases/tag/v2.8.0) (2020-06-23) +=================================================================================================== +[Full Changelog](https://github.com/matrix-org/matrix-react-sdk/compare/v2.8.0-rc.1...v2.8.0) + + * Update read receipt remainder for internal font size change + [\#4807](https://github.com/matrix-org/matrix-react-sdk/pull/4807) + * Revert "Use recovery keys over passphrases" + [\#4793](https://github.com/matrix-org/matrix-react-sdk/pull/4793) + Changes in [2.8.0-rc.1](https://github.com/matrix-org/matrix-react-sdk/releases/tag/v2.8.0-rc.1) (2020-06-17) ============================================================================================================= [Full Changelog](https://github.com/matrix-org/matrix-react-sdk/compare/v2.7.2...v2.8.0-rc.1) From 5256a86545d601a4579024f5650ce16ddc2d2f87 Mon Sep 17 00:00:00 2001 From: RiotRobot Date: Tue, 23 Jun 2020 15:06:53 +0100 Subject: [PATCH 8/8] v2.8.0 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 07cb11d45b..591922498f 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "matrix-react-sdk", - "version": "2.8.0-rc.1", + "version": "2.8.0", "description": "SDK for matrix.org using React", "author": "matrix.org", "repository": {