From 657457c14bc1f49da33ddae8a51f9a7d5ac91cba Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Tue, 28 Jan 2020 11:13:09 +0000 Subject: [PATCH] Apply remainder of ux --- .../views/right_panel/_EncryptionInfo.scss | 2 + src/components/views/dialogs/ErrorDialog.js | 10 +- .../views/right_panel/EncryptionPanel.js | 52 ++++++-- .../views/right_panel/VerificationPanel.js | 112 ++++++++++++------ .../views/verification/VerificationShowSas.js | 1 - src/i18n/strings/en_EN.json | 12 +- 6 files changed, 139 insertions(+), 50 deletions(-) diff --git a/res/css/views/right_panel/_EncryptionInfo.scss b/res/css/views/right_panel/_EncryptionInfo.scss index 386eef8e7f..e13b1b6802 100644 --- a/res/css/views/right_panel/_EncryptionInfo.scss +++ b/res/css/views/right_panel/_EncryptionInfo.scss @@ -20,5 +20,7 @@ limitations under the License. margin-top: 25px; margin-bottom: 15px; } + + text-align: center; } } diff --git a/src/components/views/dialogs/ErrorDialog.js b/src/components/views/dialogs/ErrorDialog.js index 15c87990d0..fbc5509457 100644 --- a/src/components/views/dialogs/ErrorDialog.js +++ b/src/components/views/dialogs/ErrorDialog.js @@ -42,6 +42,7 @@ export default createReactClass({ button: PropTypes.string, focus: PropTypes.bool, onFinished: PropTypes.func.isRequired, + headerImage: PropTypes.string, }, getDefaultProps: function() { @@ -56,9 +57,12 @@ export default createReactClass({ render: function() { const BaseDialog = sdk.getComponent('views.dialogs.BaseDialog'); return ( -
{ this.props.description || _t('An error has occurred.') } diff --git a/src/components/views/right_panel/EncryptionPanel.js b/src/components/views/right_panel/EncryptionPanel.js index dfb145f61d..2c4a896624 100644 --- a/src/components/views/right_panel/EncryptionPanel.js +++ b/src/components/views/right_panel/EncryptionPanel.js @@ -21,19 +21,47 @@ import VerificationPanel from "./VerificationPanel"; import {MatrixClientPeg} from "../../../MatrixClientPeg"; import {ensureDMExists} from "../../../createRoom"; import {useEventEmitter} from "../../../hooks/useEventEmitter"; +import Modal from "../../../Modal"; +import {PHASE_REQUESTED} from "matrix-js-sdk/src/crypto/verification/request/VerificationRequest"; +import * as sdk from "../../../index"; +import {_t} from "../../../languageHandler"; -const EncryptionPanel = ({verificationRequest, member}) => { +// cancellation codes which constitute a key mismatch +const MISMATCHES = ["m.key_mismatch", "m.user_error", "m.mismatched_sas"]; + +const EncryptionPanel = ({verificationRequest, member, onClose}) => { const [request, setRequest] = useState(verificationRequest); useEffect(() => { setRequest(verificationRequest); }, [verificationRequest]); - const [pending, setPending] = useState(false); + const [phase, setPhase] = useState(false); const changeHandler = useCallback(() => { - setPending(request && request.requested); - }, [request]); + // handle transitions -> cancelled for mismatches which fire a modal instead of showing a card + if (request && request.cancelled && MISMATCHES.includes(request.cancellationCode)) { + const ErrorDialog = sdk.getComponent("dialogs.ErrorDialog"); + Modal.createTrackedDialog("Verification failed", "insecure", ErrorDialog, { + headerImage: require("../../../../res/img/e2e/warning.svg"), + title: _t("Your messages are not secure"), + description:
+ {_t("One of the following may be compromised:")} +
    +
  • {_t("Your homeserver")}
  • +
  • {_t("The homeserver the user you’re verifying is connected to")}
  • +
  • {_t("Yours, or the other users’ internet connection")}
  • +
  • {_t("Yours, or the other users’ device")}
  • +
+
, + onFinished: onClose, + }); + return; // don't update phase here as we will be transitioning away from this view shortly + } + + if (request) { + setPhase(request.phase); + } + }, [onClose, request]); useEventEmitter(request, "change", changeHandler); - useEffect(changeHandler, [changeHandler]); const onStartVerification = useCallback(async () => { const cli = MatrixClientPeg.get(); @@ -42,10 +70,18 @@ const EncryptionPanel = ({verificationRequest, member}) => { setRequest(verificationRequest); }, [member.userId]); - if (!request || pending) { - return ; + const requested = request && phase === PHASE_REQUESTED; + if (!request || requested) { + return ; } else { - return ; + return ( + + ); } }; EncryptionPanel.propTypes = { diff --git a/src/components/views/right_panel/VerificationPanel.js b/src/components/views/right_panel/VerificationPanel.js index f6a26665d8..6a5516927a 100644 --- a/src/components/views/right_panel/VerificationPanel.js +++ b/src/components/views/right_panel/VerificationPanel.js @@ -22,6 +22,13 @@ import VerificationQRCode from "../elements/crypto/VerificationQRCode"; import {MatrixClientPeg} from "../../../MatrixClientPeg"; import {_t} from "../../../languageHandler"; import E2EIcon from "../rooms/E2EIcon"; +import { + PHASE_READY, + PHASE_DONE, + PHASE_STARTED, + PHASE_CANCELLED, +} from "matrix-js-sdk/src/crypto/verification/request/VerificationRequest"; +import Spinner from "../elements/Spinner"; export default class VerificationPanel extends React.PureComponent { constructor(props) { @@ -30,11 +37,22 @@ export default class VerificationPanel extends React.PureComponent { this._hasVerifier = !!props.request.verifier; } - renderQRPhase() { + renderQRPhase(pending) { const {member, request} = this.props; // TODO change the button into a spinner when on click const AccessibleButton = sdk.getComponent('elements.AccessibleButton'); + let button; + if (pending) { + button = ; + } else { + button = ( + + {_t("Verify by emoji")} + + ); + } + const cli = MatrixClientPeg.get(); const crossSigningInfo = cli.getStoredCrossSigningForUser(request.otherUserId); if (!crossSigningInfo || !request.requestEvent || !request.requestEvent.getId()) { @@ -43,9 +61,7 @@ export default class VerificationPanel extends React.PureComponent {

Verify by emoji

{_t("Verify by comparing unique emoji.")}

- - {_t("Verify by emoji")} - + { button }
; } @@ -78,9 +94,7 @@ export default class VerificationPanel extends React.PureComponent {

Verify by emoji

{_t("If you can't scan the code above, verify by comparing unique emoji.")}

- - {_t("Verify by emoji")} - + { button } ; } @@ -97,7 +111,36 @@ export default class VerificationPanel extends React.PureComponent { })}

Verify all users in a room to ensure it's secure.

- + + + {_t("Got it")} + + + ); + } + + renderCancelledPhase() { + const {member, request} = this.props; + + const AccessibleButton = sdk.getComponent('elements.AccessibleButton'); + + let text; + if (request.cancellationCode === "m.timeout") { + text = _t("Verification timed out. Start verification again from their profile."); + } else if (request.cancellingUserId === request.otherUserId) { + text = _t("%(displayName)s cancelled verification. Start verification again from their profile.", { + displayName: member.displayName || member.name || member.userId, + }); + } else { + text = _t("You cancelled verification. Start verification again from their profile."); + } + + return ( +
+

Verification cancelled

+

{ text }

+ + {_t("Got it")}
@@ -105,34 +148,32 @@ export default class VerificationPanel extends React.PureComponent { } render() { - const {member, request} = this.props; + const {member} = this.props; const displayName = member.displayName || member.name || member.userId; - if (request.ready) { - return this.renderQRPhase(); - } else if (request.started) { - if (this.state.sasEvent) { - const VerificationShowSas = sdk.getComponent('views.verification.VerificationShowSas'); - // TODO implement "mismatch" vs "cancelled" - return
-

Compare emoji

- -
; - } else { - return (

Setting up SAS verification...

); - } - } else if (request.done) { - return this.renderVerifiedPhase(); - } else if (request.cancelled) { - // TODO check if this matches target - // TODO should this be a MODAL? - return

cancelled by {request.cancellingUserId}!

; + switch (this.props.phase) { + case PHASE_READY: + return this.renderQRPhase(); + case PHASE_STARTED: + if (this.state.sasEvent) { + const VerificationShowSas = sdk.getComponent('views.verification.VerificationShowSas'); + return
+

Compare emoji

+ +
; + } else { + return this.renderQRPhase(true); // keep showing same phase but with a spinner + } + case PHASE_DONE: + return this.renderVerifiedPhase(); + case PHASE_CANCELLED: + return this.renderCancelledPhase(); } return null; } @@ -143,8 +184,6 @@ export default class VerificationPanel extends React.PureComponent { await verifier.verify(); } catch (err) { console.error(err); - } finally { - this.setState({sasEvent: null}); } }; @@ -153,7 +192,7 @@ export default class VerificationPanel extends React.PureComponent { }; _onSasMismatchesClick = () => { - this.state.sasEvent.cancel(); + this.state.sasEvent.mismatch(); }; _onVerifierShowSas = (sasEvent) => { @@ -175,7 +214,6 @@ export default class VerificationPanel extends React.PureComponent { request.verifier.removeListener('show_sas', this._onVerifierShowSas); } this._hasVerifier = !!request.verifier; - this.forceUpdate(); // TODO fix this }; componentDidMount() { diff --git a/src/components/views/verification/VerificationShowSas.js b/src/components/views/verification/VerificationShowSas.js index 693a3769a0..08d0dd422d 100644 --- a/src/components/views/verification/VerificationShowSas.js +++ b/src/components/views/verification/VerificationShowSas.js @@ -16,7 +16,6 @@ limitations under the License. import React from 'react'; import PropTypes from 'prop-types'; -import * as sdk from '../../../index'; import { _t, _td } from '../../../languageHandler'; import {PendingActionSpinner} from "../right_panel/EncryptionInfo"; import AccessibleButton from "../elements/AccessibleButton"; diff --git a/src/i18n/strings/en_EN.json b/src/i18n/strings/en_EN.json index d4512d785d..e926d4ff91 100644 --- a/src/i18n/strings/en_EN.json +++ b/src/i18n/strings/en_EN.json @@ -1139,6 +1139,12 @@ "Verify User": "Verify User", "For extra security, verify this user by checking a one-time code on both of your devices.": "For extra security, verify this user by checking a one-time code on both of your devices.", "For maximum security, do this in person.": "For maximum security, do this in person.", + "Your messages are not secure": "Your messages are not secure", + "One of the following may be compromised:": "One of the following may be compromised:", + "Your homeserver": "Your homeserver", + "The homeserver the user you’re verifying is connected to": "The homeserver the user you’re verifying is connected to", + "Yours, or the other users’ internet connection": "Yours, or the other users’ internet connection", + "Yours, or the other users’ device": "Yours, or the other users’ device", "Members": "Members", "Files": "Files", "Trusted": "Trusted", @@ -1156,11 +1162,15 @@ "This client does not support end-to-end encryption.": "This client does not support end-to-end encryption.", "Messages in this room are not end-to-end encrypted.": "Messages in this room are not end-to-end encrypted.", "Security": "Security", + "Verify by emoji": "Verify by emoji", + "Verify by comparing unique emoji.": "Verify by comparing unique emoji.", "Ask %(displayName)s to scan your code:": "Ask %(displayName)s to scan your code:", "If you can't scan the code above, verify by comparing unique emoji.": "If you can't scan the code above, verify by comparing unique emoji.", - "Verify by emoji": "Verify by emoji", "You've successfully verified %(displayName)s!": "You've successfully verified %(displayName)s!", "Got it": "Got it", + "Verification timed out. Start verification again from their profile.": "Verification timed out. Start verification again from their profile.", + "%(displayName)s cancelled verification. Start verification again from their profile.": "%(displayName)s cancelled verification. Start verification again from their profile.", + "You cancelled verification. Start verification again from their profile.": "You cancelled verification. Start verification again from their profile.", "Sunday": "Sunday", "Monday": "Monday", "Tuesday": "Tuesday",