diff --git a/src/components/views/dialogs/ManualDeviceKeyVerificationDialog.js b/src/components/views/dialogs/ManualDeviceKeyVerificationDialog.js new file mode 100644 index 0000000000..4b9d7239e6 --- /dev/null +++ b/src/components/views/dialogs/ManualDeviceKeyVerificationDialog.js @@ -0,0 +1,86 @@ +/* +Copyright 2016 OpenMarket Ltd +Copyright 2017 Vector Creations Ltd +Copyright 2019 New Vector Ltd +Copyright 2019 Michael Telatynski <7t3chguy@gmail.com> +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. +*/ + +import React from 'react'; +import PropTypes from 'prop-types'; +import {MatrixClientPeg} from '../../../MatrixClientPeg'; +import * as sdk from '../../../index'; +import * as FormattingUtils from '../../../utils/FormattingUtils'; +import { _t } from '../../../languageHandler'; + +export default class ManualDeviceKeyVerificationDialog extends React.Component { + static propTypes = { + userId: PropTypes.string.isRequired, + device: PropTypes.object.isRequired, + onFinished: PropTypes.func.isRequired, + }; + + _onCancelClick = () => { + this.props.onFinished(false); + } + + _onLegacyFinished = (confirm) => { + if (confirm) { + MatrixClientPeg.get().setDeviceVerified( + this.props.userId, this.props.device.deviceId, true, + ); + } + this.props.onFinished(confirm); + } + + render() { + const QuestionDialog = sdk.getComponent("dialogs.QuestionDialog"); + + let text; + if (MatrixClientPeg.get().getUserId() === this.props.userId) { + text = _t("Confirm by comparing the following with the User Settings in your other session:"); + } else { + text = _t("Confirm this user's session by comparing the following with their User Settings:"); + } + + const key = FormattingUtils.formatCryptoKey(this.props.device.getFingerprint()); + const body = ( +
+

+ { text } +

+
+ +
+

+ { _t("If they don't match, the security of your communication may be compromised.") } +

+
+ ); + + return ( + + ); + } +} diff --git a/src/i18n/strings/en_EN.json b/src/i18n/strings/en_EN.json index c3befa2298..4ff7ebc9c1 100644 --- a/src/i18n/strings/en_EN.json +++ b/src/i18n/strings/en_EN.json @@ -294,8 +294,9 @@ "Not Trusted": "Not Trusted", "%(name)s (%(userId)s) signed in to a new session without verifying it:": "%(name)s (%(userId)s) signed in to a new session without verifying it:", "Ask this user to verify their session, or manually verify it below.": "Ask this user to verify their session, or manually verify it below.", + "Manually Verify by Text": "Manually Verify by Text", + "Interactively verify by Emoji": "Interactively verify by Emoji", "Done": "Done", - "Manually Verify": "Manually Verify", "%(displayName)s is typing …": "%(displayName)s is typing …", "%(names)s and %(count)s others are typing …|other": "%(names)s and %(count)s others are typing …", "%(names)s and %(count)s others are typing …|one": "%(names)s and one other is typing …", @@ -1613,6 +1614,9 @@ "Manually export keys": "Manually export keys", "You'll lose access to your encrypted messages": "You'll lose access to your encrypted messages", "Are you sure you want to sign out?": "Are you sure you want to sign out?", + "Confirm by comparing the following with the User Settings in your other session:": "Confirm by comparing the following with the User Settings in your other session:", + "Confirm this user's session by comparing the following with their User Settings:": "Confirm this user's session by comparing the following with their User Settings:", + "If they don't match, the security of your communication may be compromised.": "If they don't match, the security of your communication may be compromised.", "Your homeserver doesn't seem to support this feature.": "Your homeserver doesn't seem to support this feature.", "Message edits": "Message edits", "Your account is not secure": "Your account is not secure", diff --git a/src/verification.js b/src/verification.js index 4443079b95..d0f6fd7806 100644 --- a/src/verification.js +++ b/src/verification.js @@ -39,38 +39,58 @@ async function enable4SIfNeeded() { return true; } +function UntrustedDeviceDialog(props) { + const {device, user, onFinished} = props; + const BaseDialog = sdk.getComponent("dialogs.BaseDialog"); + const AccessibleButton = sdk.getComponent("elements.AccessibleButton"); + return +
+

{_t("%(name)s (%(userId)s) signed in to a new session without verifying it:", {name: user.displayName, userId: user.userId})}

+

{device.getDisplayName()} ({device.deviceId})

+

{_t("Ask this user to verify their session, or manually verify it below.")}

+
+
+ onFinished("legacy")}>{_t("Manually Verify by Text")} + onFinished("sas")}>{_t("Interactively verify by Emoji")} + onFinished()}>{_t("Done")} +
+
; +} + export async function verifyDevice(user, device) { if (!await enable4SIfNeeded()) { return; } - const QuestionDialog = sdk.getComponent("dialogs.QuestionDialog"); - Modal.createTrackedDialog("Verification warning", "unverified session", QuestionDialog, { - headerImage: require("../res/img/e2e/warning.svg"), - title: _t("Not Trusted"), - description:
-

{_t("%(name)s (%(userId)s) signed in to a new session without verifying it:", {name: user.displayName, userId: user.userId})}

-

{device.getDisplayName()} ({device.deviceId})

-

{_t("Ask this user to verify their session, or manually verify it below.")}

-
, - onFinished: async (doneClicked) => { - const manuallyVerifyClicked = !doneClicked; - if (!manuallyVerifyClicked) { - return; + Modal.createTrackedDialog("Verification warning", "unverified session", UntrustedDeviceDialog, { + user, + device, + onFinished: async (action) => { + if (action === "sas") { + const cli = MatrixClientPeg.get(); + const verificationRequestPromise = cli.legacyDeviceVerification( + user.userId, + device.deviceId, + verificationMethods.SAS, + ); + dis.dispatch({ + action: "set_right_panel_phase", + phase: RIGHT_PANEL_PHASES.EncryptionPanel, + refireParams: {member: user, verificationRequestPromise}, + }); + } else if (action === "legacy") { + const ManualDeviceKeyVerificationDialog = sdk.getComponent("dialogs.ManualDeviceKeyVerificationDialog"); + Modal.createTrackedDialog("Legacy verify session", "legacy verify session", + ManualDeviceKeyVerificationDialog, + { + userId: user.userId, + device, + }, + ); } - const cli = MatrixClientPeg.get(); - const verificationRequestPromise = cli.legacyDeviceVerification( - user.userId, - device.deviceId, - verificationMethods.SAS, - ); - dis.dispatch({ - action: "set_right_panel_phase", - phase: RIGHT_PANEL_PHASES.EncryptionPanel, - refireParams: {member: user, verificationRequestPromise}, - }); }, - primaryButton: _t("Done"), - cancelButton: _t("Manually Verify"), }); }