diff --git a/res/css/_components.scss b/res/css/_components.scss index 7a9ebfdf26b..a9a114a4cf5 100644 --- a/res/css/_components.scss +++ b/res/css/_components.scss @@ -28,6 +28,7 @@ @import "./structures/_TopLeftMenuButton.scss"; @import "./structures/_UploadBar.scss"; @import "./structures/_ViewSource.scss"; +@import "./structures/auth/_CompleteSecurity.scss"; @import "./structures/auth/_Login.scss"; @import "./views/auth/_AuthBody.scss"; @import "./views/auth/_AuthButtons.scss"; diff --git a/res/css/structures/auth/_CompleteSecurity.scss b/res/css/structures/auth/_CompleteSecurity.scss new file mode 100644 index 00000000000..c258ce4ec7e --- /dev/null +++ b/res/css/structures/auth/_CompleteSecurity.scss @@ -0,0 +1,51 @@ +/* +Copyright 2020 The Matrix.org Foundation C.I.C. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +.mx_CompleteSecurity_header { + display: flex; + align-items: center; +} + +.mx_CompleteSecurity_headerIcon { + width: 24px; + height: 24px; + margin: 0 4px; + position: relative; +} + +.mx_CompleteSecurity_heroIcon { + width: 128px; + height: 128px; + position: relative; + margin: 0 auto; +} + +.mx_CompleteSecurity_body { + font-size: 15px; +} + +.mx_CompleteSecurity_actionRow { + display: flex; + justify-content: flex-end; + + .mx_AccessibleButton { + margin-inline-start: 18px; + + &.warning { + color: $warning-color; + } + } +} diff --git a/src/MatrixClientPeg.js b/src/MatrixClientPeg.js index 9c939f2fd37..dbc570c8729 100644 --- a/src/MatrixClientPeg.js +++ b/src/MatrixClientPeg.js @@ -2,7 +2,7 @@ Copyright 2015, 2016 OpenMarket Ltd Copyright 2017 Vector Creations Ltd. Copyright 2017, 2018, 2019 New Vector Ltd -Copyright 2019 The Matrix.org Foundation C.I.C. +Copyright 2019, 2020 The Matrix.org Foundation C.I.C. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. @@ -223,9 +223,10 @@ class _MatrixClientPeg { }; opts.cryptoCallbacks = {}; - if (SettingsStore.isFeatureEnabled("feature_cross_signing")) { - Object.assign(opts.cryptoCallbacks, crossSigningCallbacks); - } + // These are always installed regardless of the labs flag so that + // cross-signing features can toggle on without reloading and also be + // accessed immediately after login. + Object.assign(opts.cryptoCallbacks, crossSigningCallbacks); this.matrixClient = createMatrixClient(opts); diff --git a/src/async-components/views/dialogs/secretstorage/CreateSecretStorageDialog.js b/src/async-components/views/dialogs/secretstorage/CreateSecretStorageDialog.js index 628214a2bbc..01b9c9c7c8d 100644 --- a/src/async-components/views/dialogs/secretstorage/CreateSecretStorageDialog.js +++ b/src/async-components/views/dialogs/secretstorage/CreateSecretStorageDialog.js @@ -313,7 +313,7 @@ export default class CreateSecretStorageDialog extends React.PureComponent {

{_t( "Secret Storage will be set up using your existing key backup details. " + "Your secret storage passphrase and recovery key will be the same as " + - " they were for your key backup", + "they were for your key backup.", )}

{ + const actionHandlerRef = dis.register(payload => { + if (payload.action !== "on_logged_in") { + return; + } + dis.unregister(actionHandlerRef); + resolve(); + }); + }); + + // Create and start the client in the background + Lifecycle.setLoggedIn(credentials); + await loggedIn; + + const cli = MatrixClientPeg.get(); + // We're checking `isCryptoAvailable` here instead of `isCryptoEnabled` + // because the client hasn't been started yet. + if (!isCryptoAvailable()) { + this._onLoggedIn(); + } + + // Test for the master cross-signing key in SSSS as a quick proxy for + // whether cross-signing has been set up on the account. + let masterKeyInStorage = false; + try { + masterKeyInStorage = !!await cli.getAccountDataFromServer("m.cross_signing.master"); + } catch (e) { + if (e.errcode !== "M_NOT_FOUND") throw e; + } + + if (masterKeyInStorage) { + this.setStateForNewView({ view: VIEWS.COMPLETE_SECURITY }); + } else { + this._onLoggedIn(); + } + }, + + onCompleteSecurityFinished() { + this._onLoggedIn(); + }, + render: function() { // console.log(`Rendering MatrixChat with view ${this.state.view}`); let view; - if ( - this.state.view === VIEWS.LOADING || - this.state.view === VIEWS.LOGGING_IN - ) { + if (this.state.view === VIEWS.LOADING) { const Spinner = sdk.getComponent('elements.Spinner'); view = (
); + } else if (this.state.view === VIEWS.COMPLETE_SECURITY) { + const CompleteSecurity = sdk.getComponent('structures.auth.CompleteSecurity'); + view = ( + + ); } else if (this.state.view === VIEWS.POST_REGISTRATION) { // needs to be before normal PageTypes as you are logged in technically const PostRegistration = sdk.getComponent('structures.auth.PostRegistration'); @@ -1921,7 +1966,7 @@ export default createReactClass({ const Login = sdk.getComponent('structures.auth.Login'); view = ( { + const cli = MatrixClientPeg.get(); + await accessSecretStorage(async () => { + await cli.checkOwnCrossSigningTrust(); + }); + this.setState({ + phase: PHASE_DONE, + }); + } + + onSkipClick = () => { + this.setState({ + phase: PHASE_CONFIRM_SKIP, + }); + } + + onSkipConfirmClick = () => { + this.props.onFinished(); + } + + onSkipBackClick = () => { + this.setState({ + phase: PHASE_INTRO, + }); + } + + onDoneClick = () => { + this.props.onFinished(); + } + + render() { + const AuthPage = sdk.getComponent("auth.AuthPage"); + const AuthHeader = sdk.getComponent("auth.AuthHeader"); + const AuthBody = sdk.getComponent("auth.AuthBody"); + const AccessibleButton = sdk.getComponent("elements.AccessibleButton"); + + const { + phase, + } = this.state; + + let icon; + let title; + let body; + if (phase === PHASE_INTRO) { + icon = ; + title = _t("Complete security"); + body = ( +
+

{_t( + "Verify this session to grant it access to encrypted messages.", + )}

+
+ + {_t("Skip")} + + + {_t("Start")} + +
+
+ ); + } else if (phase === PHASE_DONE) { + icon = ; + title = _t("Session verified"); + body = ( +
+
+

{_t( + "Your new session is now verified. It has access to your " + + "encrypted messages, and other users will see it as trusted.", + )}

+
+ + {_t("Done")} + +
+
+ ); + } else if (phase === PHASE_CONFIRM_SKIP) { + icon = ; + title = _t("Are you sure?"); + body = ( +
+

{_t( + "Without completing security on this device, it won’t have " + + "access to encrypted messages.", + )}

+
+ + {_t("Skip")} + + + {_t("Go Back")} + +
+
+ ); + } else { + throw new Error(`Unknown phase ${phase}`); + } + + return ( + + + +

+ {icon} + {title} +

+
+ {body} +
+
+
+ ); + } +} diff --git a/src/components/structures/auth/SoftLogout.js b/src/components/structures/auth/SoftLogout.js index 63f590da2ed..40800ad907f 100644 --- a/src/components/structures/auth/SoftLogout.js +++ b/src/components/structures/auth/SoftLogout.js @@ -66,7 +66,7 @@ export default class SoftLogout extends React.Component { componentDidMount(): void { // We've ended up here when we don't need to - navigate to login if (!Lifecycle.isSoftLogout()) { - dis.dispatch({action: "on_logged_in"}); + dis.dispatch({action: "start_login"}); return; } diff --git a/src/i18n/strings/en_EN.json b/src/i18n/strings/en_EN.json index 42c87172b82..3756b4c60b3 100644 --- a/src/i18n/strings/en_EN.json +++ b/src/i18n/strings/en_EN.json @@ -1848,6 +1848,14 @@ "Uploading %(filename)s and %(count)s others|zero": "Uploading %(filename)s", "Uploading %(filename)s and %(count)s others|one": "Uploading %(filename)s and %(count)s other", "Could not load user profile": "Could not load user profile", + "Complete security": "Complete security", + "Verify this session to grant it access to encrypted messages.": "Verify this session to grant it access to encrypted messages.", + "Start": "Start", + "Session verified": "Session verified", + "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.", + "Done": "Done", + "Without completing security on this device, it won’t have access to encrypted messages.": "Without completing security on this device, it won’t have access to encrypted messages.", + "Go Back": "Go Back", "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.", "A new password must be entered.": "A new password must be entered.", @@ -1952,7 +1960,7 @@ "Import": "Import", "Key Backup is enabled on your account but has not been set up from this session. To set up secret storage, restore your key backup.": "Key Backup is enabled on your account but has not been set up from this session. To set up secret storage, restore your key backup.", "Restore": "Restore", - "Secret Storage will be set up using your existing key backup details. Your secret storage passphrase and recovery key will be the same as they were for your key backup": "Secret Storage will be set up using your existing key backup details. Your secret storage passphrase and recovery key will be the same as they were for your key backup", + "Secret Storage will be set up using your existing key backup details. Your secret storage passphrase and recovery key will be the same as they were for your key backup.": "Secret Storage will be set up using your existing key backup details. Your secret storage passphrase and recovery key will be the same as they were for your key backup.", "Great! This passphrase looks strong enough.": "Great! This passphrase looks strong enough.", "Warning: You should only set up secret storage from a trusted computer.": "Warning: You should only set up secret storage from a trusted computer.", "We'll use secret storage to optionally store an encrypted copy of your cross-signing identity for verifying other devices and message keys on our server. Protect your access to encrypted messages with a passphrase to keep it secure.": "We'll use secret storage to optionally store an encrypted copy of your cross-signing identity for verifying other devices and message keys on our server. Protect your access to encrypted messages with a passphrase to keep it secure.", diff --git a/src/settings/Settings.js b/src/settings/Settings.js index 8ee8d898904..2b8c0aef89f 100644 --- a/src/settings/Settings.js +++ b/src/settings/Settings.js @@ -1,7 +1,7 @@ /* Copyright 2017 Travis Ralston Copyright 2018, 2019 New Vector Ltd. -Copyright 2019 The Matrix.org Foundation C.I.C. +Copyright 2019, 2020 The Matrix.org Foundation C.I.C. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. @@ -153,7 +153,6 @@ export const SETTINGS = { displayName: _td("Enable cross-signing to verify per-user instead of per-device (in development)"), supportedLevels: LEVELS_FEATURE, default: false, - controller: new ReloadOnChangeController(), }, "feature_event_indexing": { isFeature: true,