From 68e555a0c6641b0061b55f0cf59044e1a2c6c8b2 Mon Sep 17 00:00:00 2001 From: David Baker Date: Fri, 5 Jun 2020 16:40:20 +0100 Subject: [PATCH 1/9] Support accounts with cross signing but no SSSS At least at the login stage. Fixes https://github.com/vector-im/riot-web/issues/13894 --- src/components/structures/MatrixChat.tsx | 10 ++++--- .../structures/auth/SetupEncryptionBody.js | 30 +++++++++++++------ src/i18n/strings/en_EN.json | 1 + src/stores/SetupEncryptionStore.js | 2 +- 4 files changed, 29 insertions(+), 14 deletions(-) diff --git a/src/components/structures/MatrixChat.tsx b/src/components/structures/MatrixChat.tsx index 7aaedcfb09..b5b77e3ae6 100644 --- a/src/components/structures/MatrixChat.tsx +++ b/src/components/structures/MatrixChat.tsx @@ -1900,10 +1900,12 @@ export default class MatrixChat extends React.PureComponent { return setLoggedInPromise; } - // Test for the master cross-signing key in SSSS as a quick proxy for - // whether cross-signing has been set up on the account. - const masterKeyInStorage = !!cli.getAccountData("m.cross_signing.master"); - if (masterKeyInStorage) { + // wait for the client to finish downloading cross-signing keys for us so we + // know whether or not we have keys set up on this account + await cli.downloadKeys([cli.getUserId()]); + + const crossSigningIsSetUp = cli.getStoredCrossSigningForUser(cli.getUserId()); + if (crossSigningIsSetUp) { this.setStateForNewView({ view: Views.COMPLETE_SECURITY }); } else if (await cli.doesServerSupportUnstableFeature("org.matrix.e2e_cross_signing")) { this.setStateForNewView({ view: Views.E2E_SETUP }); diff --git a/src/components/structures/auth/SetupEncryptionBody.js b/src/components/structures/auth/SetupEncryptionBody.js index 7886ed26dd..4cc5c5ef75 100644 --- a/src/components/structures/auth/SetupEncryptionBody.js +++ b/src/components/structures/auth/SetupEncryptionBody.js @@ -16,7 +16,7 @@ limitations under the License. import React from 'react'; import PropTypes from 'prop-types'; -import { _t } from '../../../languageHandler'; +import { _t, _td } from '../../../languageHandler'; import { MatrixClientPeg } from '../../../MatrixClientPeg'; import * as sdk from '../../../index'; import withValidation from '../../views/elements/Validation'; @@ -196,11 +196,27 @@ export default class SetupEncryptionBody extends React.Component { } else if (phase === PHASE_INTRO) { const store = SetupEncryptionStore.sharedInstance(); let recoveryKeyPrompt; - if (keyHasPassphrase(store.keyInfo)) { + if (store.keyInfo && keyHasPassphrase(store.keyInfo)) { recoveryKeyPrompt = _t("Use Recovery Key or Passphrase"); - } else { + } else if (store.keyInfo) { recoveryKeyPrompt = _t("Use Recovery Key"); } + + let useRecoveryKeyButton; + let resetKeysCaption; + if (recoveryKeyPrompt) { + useRecoveryKeyButton = + {recoveryKeyPrompt} + ; + resetKeysCaption = _td( + "If you've forgotten your recovery key you can ", + ); + } else { + resetKeysCaption = _td( + "If you have no other devices you can ", + ); + } + return (

{_t( @@ -224,16 +240,12 @@ export default class SetupEncryptionBody extends React.Component {

- - {recoveryKeyPrompt} - + {useRecoveryKeyButton} {_t("Skip")}
-
{_t( - "If you've forgotten your recovery key you can " + - "", {}, { +
{_t(resetKeysCaption, {}, { button: sub => diff --git a/src/i18n/strings/en_EN.json b/src/i18n/strings/en_EN.json index e54a4d8662..416d1debe7 100644 --- a/src/i18n/strings/en_EN.json +++ b/src/i18n/strings/en_EN.json @@ -2101,6 +2101,7 @@ "Looks good!": "Looks good!", "Use Recovery Key or Passphrase": "Use Recovery Key or Passphrase", "Use Recovery Key": "Use Recovery Key", + "If you have no other devices you can ": "If you have no other devices you can ", "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", diff --git a/src/stores/SetupEncryptionStore.js b/src/stores/SetupEncryptionStore.js index cc64e24a03..e155a5c29f 100644 --- a/src/stores/SetupEncryptionStore.js +++ b/src/stores/SetupEncryptionStore.js @@ -68,7 +68,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) { + if (!keys || Object.keys(keys).length === 0) { this.keyId = null; this.keyInfo = null; } else { From 7c59e397102310e5987ef1d0640f46658e2840cd Mon Sep 17 00:00:00 2001 From: David Baker Date: Fri, 5 Jun 2020 17:52:09 +0100 Subject: [PATCH 2/9] Sort out what we wait for after login We were waiting only for the client to become logged in rather than for setLoggedIn() to finish but then we were waiting for the first sync to complete which is far longer. We need setLoggedIn to have finished for crypto to be set up so we can query cross-signing keys, so just wait for that anyway, the logic becomes a lot simpler and we're waiting the same amount of time because we have to wait for the first sync to finish. We can also download keys in parallel. --- src/components/structures/MatrixChat.tsx | 43 +++++++++--------------- 1 file changed, 15 insertions(+), 28 deletions(-) diff --git a/src/components/structures/MatrixChat.tsx b/src/components/structures/MatrixChat.tsx index b5b77e3ae6..4d85108aaf 100644 --- a/src/components/structures/MatrixChat.tsx +++ b/src/components/structures/MatrixChat.tsx @@ -1868,42 +1868,31 @@ export default class MatrixChat extends React.PureComponent { this.accountPasswordTimer = null; }, 60 * 5 * 1000); - // Wait for the client to be logged in (but not started) - // which is enough to ask the server about account data. - const loggedIn = new Promise(resolve => { - const actionHandlerRef = dis.register(payload => { - if (payload.action !== "on_logged_in") { - return; - } - dis.unregister(actionHandlerRef); - resolve(); - }); - }); - - // Create and start the client in the background - const setLoggedInPromise = Lifecycle.setLoggedIn(credentials); - await loggedIn; + // Create and start the client + await Lifecycle.setLoggedIn(credentials); const cli = MatrixClientPeg.get(); - // We're checking `isCryptoAvailable` here instead of `isCryptoEnabled` - // because the client hasn't been started yet. - const cryptoAvailable = isCryptoAvailable(); - if (!cryptoAvailable) { + const cryptoEnabled = cli.isCryptoEnabled(); + if (!cryptoEnabled) { this.onLoggedIn(); } - this.setState({ pendingInitialSync: true }); - await this.firstSyncPromise.promise; + const promisesList = [this.firstSyncPromise.promise]; + if (cryptoEnabled) { + // wait for the client to finish downloading cross-signing keys for us so we + // know whether or not we have keys set up on this account + promisesList.push(cli.downloadKeys([cli.getUserId()])); + } - if (!cryptoAvailable) { + this.setState({ pendingInitialSync: true }); + + await Promise.all(promisesList); + + if (!cryptoEnabled) { this.setState({ pendingInitialSync: false }); return setLoggedInPromise; } - // wait for the client to finish downloading cross-signing keys for us so we - // know whether or not we have keys set up on this account - await cli.downloadKeys([cli.getUserId()]); - const crossSigningIsSetUp = cli.getStoredCrossSigningForUser(cli.getUserId()); if (crossSigningIsSetUp) { this.setStateForNewView({ view: Views.COMPLETE_SECURITY }); @@ -1913,8 +1902,6 @@ export default class MatrixChat extends React.PureComponent { this.onLoggedIn(); } this.setState({ pendingInitialSync: false }); - - return setLoggedInPromise; }; // complete security / e2e setup has finished From ed7f0fd95f0b72a7dc2c46e037ec84981ea01687 Mon Sep 17 00:00:00 2001 From: David Baker Date: Fri, 5 Jun 2020 18:08:25 +0100 Subject: [PATCH 3/9] This promise doesn't exist anymore --- src/components/structures/MatrixChat.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/structures/MatrixChat.tsx b/src/components/structures/MatrixChat.tsx index 4d85108aaf..ec65fd6957 100644 --- a/src/components/structures/MatrixChat.tsx +++ b/src/components/structures/MatrixChat.tsx @@ -1890,7 +1890,7 @@ export default class MatrixChat extends React.PureComponent { if (!cryptoEnabled) { this.setState({ pendingInitialSync: false }); - return setLoggedInPromise; + return; } const crossSigningIsSetUp = cli.getStoredCrossSigningForUser(cli.getUserId()); From a75dfca73eb4d1435107ea72a63976a987d82590 Mon Sep 17 00:00:00 2001 From: David Baker Date: Mon, 15 Jun 2020 12:11:45 +0100 Subject: [PATCH 4/9] Comment on when we start waiting for the first sync --- src/components/structures/MatrixChat.tsx | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/components/structures/MatrixChat.tsx b/src/components/structures/MatrixChat.tsx index 4ce29ea772..f581ec9114 100644 --- a/src/components/structures/MatrixChat.tsx +++ b/src/components/structures/MatrixChat.tsx @@ -1886,6 +1886,8 @@ export default class MatrixChat extends React.PureComponent { promisesList.push(cli.downloadKeys([cli.getUserId()])); } + // Now update the state to sya we're waiting for the first sync to complete rather + // than for the login to finish. this.setState({ pendingInitialSync: true }); await Promise.all(promisesList); From 3d1ec9effb5e3708d2c1c5f1753cdf60ae580a51 Mon Sep 17 00:00:00 2001 From: David Baker Date: Mon, 15 Jun 2020 12:59:38 +0100 Subject: [PATCH 5/9] typo Co-authored-by: Michael Telatynski <7t3chguy@gmail.com> --- src/components/structures/MatrixChat.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/structures/MatrixChat.tsx b/src/components/structures/MatrixChat.tsx index f581ec9114..9cb6ed078c 100644 --- a/src/components/structures/MatrixChat.tsx +++ b/src/components/structures/MatrixChat.tsx @@ -1886,7 +1886,7 @@ export default class MatrixChat extends React.PureComponent { promisesList.push(cli.downloadKeys([cli.getUserId()])); } - // Now update the state to sya we're waiting for the first sync to complete rather + // Now update the state to say we're waiting for the first sync to complete rather // than for the login to finish. this.setState({ pendingInitialSync: true }); From b02e439b8bca7bc4bb8139a49af30ce124f30caf Mon Sep 17 00:00:00 2001 From: David Baker Date: Mon, 15 Jun 2020 15:18:57 +0100 Subject: [PATCH 6/9] Matrix client is no longer returned by onLoggedIn It seems non-obvious that it should do, and the doc saying it should do seems to have disappeared. --- src/components/structures/auth/Registration.js | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/components/structures/auth/Registration.js b/src/components/structures/auth/Registration.js index 6349614d72..3b5f5676dc 100644 --- a/src/components/structures/auth/Registration.js +++ b/src/components/structures/auth/Registration.js @@ -378,7 +378,7 @@ export default createReactClass({ } if (response.access_token) { - const cli = await this.props.onLoggedIn({ + await this.props.onLoggedIn({ userId: response.user_id, deviceId: response.device_id, homeserverUrl: this.state.matrixClient.getHomeserverUrl(), @@ -386,7 +386,7 @@ export default createReactClass({ accessToken: response.access_token, }, this.state.formVals.password); - this._setupPushers(cli); + this._setupPushers(); // we're still busy until we get unmounted: don't show the registration form again newState.busy = true; } else { @@ -397,10 +397,11 @@ export default createReactClass({ this.setState(newState); }, - _setupPushers: function(matrixClient) { + _setupPushers: function() { if (!this.props.brand) { return Promise.resolve(); } + const matrixClient = MatrixClientPeg.get(); return matrixClient.getPushers().then((resp)=>{ const pushers = resp.pushers; for (let i = 0; i < pushers.length; ++i) { From a1592704a252959c2ef2d1a97409684111011d2e Mon Sep 17 00:00:00 2001 From: David Baker Date: Fri, 19 Jun 2020 17:17:04 +0100 Subject: [PATCH 7/9] Unused code & import --- src/components/structures/auth/SetupEncryptionBody.js | 10 +--------- 1 file changed, 1 insertion(+), 9 deletions(-) diff --git a/src/components/structures/auth/SetupEncryptionBody.js b/src/components/structures/auth/SetupEncryptionBody.js index edb4a7689d..f2e702c8cb 100644 --- a/src/components/structures/auth/SetupEncryptionBody.js +++ b/src/components/structures/auth/SetupEncryptionBody.js @@ -16,7 +16,7 @@ limitations under the License. import React from 'react'; import PropTypes from 'prop-types'; -import { _t, _td } from '../../../languageHandler'; +import { _t } from '../../../languageHandler'; import { MatrixClientPeg } from '../../../MatrixClientPeg'; import * as sdk from '../../../index'; import { @@ -125,18 +125,10 @@ export default class SetupEncryptionBody extends React.Component { } let useRecoveryKeyButton; - let resetKeysCaption; if (recoveryKeyPrompt) { useRecoveryKeyButton = {recoveryKeyPrompt} ; - resetKeysCaption = _td( - "If you've forgotten your recovery key you can ", - ); - } else { - resetKeysCaption = _td( - "If you have no other devices you can ", - ); } return ( From 6e4a2b7efe0131e5db99aeb160d5bd6ba839b3f2 Mon Sep 17 00:00:00 2001 From: David Baker Date: Fri, 19 Jun 2020 17:22:53 +0100 Subject: [PATCH 8/9] i18n --- src/i18n/strings/en_EN.json | 1 - 1 file changed, 1 deletion(-) diff --git a/src/i18n/strings/en_EN.json b/src/i18n/strings/en_EN.json index 945aee6382..50de4e102f 100644 --- a/src/i18n/strings/en_EN.json +++ b/src/i18n/strings/en_EN.json @@ -2121,7 +2121,6 @@ "Create your account": "Create your account", "Use Recovery Key or Passphrase": "Use Recovery Key or Passphrase", "Use Recovery Key": "Use Recovery Key", - "If you have no other devices you can ": "If you have no other devices you can ", "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", From d1caadec9f523d392e728367216841df8bc373f5 Mon Sep 17 00:00:00 2001 From: David Baker Date: Fri, 19 Jun 2020 20:07:20 +0100 Subject: [PATCH 9/9] Add null check --- src/stores/SetupEncryptionStore.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/stores/SetupEncryptionStore.js b/src/stores/SetupEncryptionStore.js index 5427eeb88c..63b8c428eb 100644 --- a/src/stores/SetupEncryptionStore.js +++ b/src/stores/SetupEncryptionStore.js @@ -76,7 +76,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) { + if (keys === null || Object.keys(keys).length === 0) { this.keyId = null; this.keyInfo = null; } else {