diff --git a/res/css/views/rooms/_EventTile.scss b/res/css/views/rooms/_EventTile.scss index fbac1e932a..81ba547ff0 100644 --- a/res/css/views/rooms/_EventTile.scss +++ b/res/css/views/rooms/_EventTile.scss @@ -367,6 +367,11 @@ div.mx_EventTile_notSent.mx_EventTile_redacted .mx_UnknownBody { opacity: 1; } +.mx_EventTile_e2eIcon_userVerified { + background-image: url('$(res)/img/e2e/normal.svg'); + opacity: 0.5; +} + .mx_EventTile_e2eIcon_unencrypted { background-image: url('$(res)/img/e2e/warning.svg'); opacity: 1; @@ -415,7 +420,8 @@ div.mx_EventTile_notSent.mx_EventTile_redacted .mx_UnknownBody { } .mx_EventTile:hover.mx_EventTile_verified .mx_EventTile_line, -.mx_EventTile:hover.mx_EventTile_unverified .mx_EventTile_line { +.mx_EventTile:hover.mx_EventTile_unverified .mx_EventTile_line, +.mx_EventTile:hover.mx_EventTile_userVerified .mx_EventTile_line { padding-left: 60px; } @@ -427,8 +433,13 @@ div.mx_EventTile_notSent.mx_EventTile_redacted .mx_UnknownBody { border-left: $e2e-unverified-color 5px solid; } +.mx_EventTile:hover.mx_EventTile_userVerified .mx_EventTile_line { + border-left: $e2e-userVerified-color 5px solid; +} + .mx_EventTile:hover.mx_EventTile_verified.mx_EventTile_info .mx_EventTile_line, -.mx_EventTile:hover.mx_EventTile_unverified.mx_EventTile_info .mx_EventTile_line { +.mx_EventTile:hover.mx_EventTile_unverified.mx_EventTile_info .mx_EventTile_line, +.mx_EventTile:hover.mx_EventTile_userVerified.mx_EventTile_info .mx_EventTile_line { padding-left: 78px; } @@ -439,14 +450,16 @@ div.mx_EventTile_notSent.mx_EventTile_redacted .mx_UnknownBody { // Explicit relationships so that it doesn't apply to nested EventTile components (e.g in Replies) .mx_EventTile:hover.mx_EventTile_verified .mx_EventTile_line > a > .mx_MessageTimestamp, -.mx_EventTile:hover.mx_EventTile_unverified .mx_EventTile_line > a > .mx_MessageTimestamp { +.mx_EventTile:hover.mx_EventTile_unverified .mx_EventTile_line > a > .mx_MessageTimestamp, +.mx_EventTile:hover.mx_EventTile_userVerified .mx_EventTile_line > a > .mx_MessageTimestamp { left: 3px; width: auto; } // Explicit relationships so that it doesn't apply to nested EventTile components (e.g in Replies) .mx_EventTile:hover.mx_EventTile_verified .mx_EventTile_line > .mx_EventTile_e2eIcon, -.mx_EventTile:hover.mx_EventTile_unverified .mx_EventTile_line > .mx_EventTile_e2eIcon { +.mx_EventTile:hover.mx_EventTile_unverified .mx_EventTile_line > .mx_EventTile_e2eIcon, +.mx_EventTile:hover.mx_EventTile_userVerified .mx_EventTile_line > .mx_EventTile_e2eIcon { display: block; left: 41px; } diff --git a/res/themes/light/css/_light.scss b/res/themes/light/css/_light.scss index 288fb3cadc..17b9a344ef 100644 --- a/res/themes/light/css/_light.scss +++ b/res/themes/light/css/_light.scss @@ -224,6 +224,7 @@ $copy-button-url: "$(res)/img/icon_copy_message.svg"; // e2e $e2e-verified-color: #76cfa5; // N.B. *NOT* the same as $accent-color +$e2e-userVerified-color: #e8bf37; $e2e-unverified-color: #e8bf37; $e2e-warning-color: #ba6363; diff --git a/src/components/views/rooms/EventTile.js b/src/components/views/rooms/EventTile.js index dce4dc8a93..4aefe6929b 100644 --- a/src/components/views/rooms/EventTile.js +++ b/src/components/views/rooms/EventTile.js @@ -235,6 +235,7 @@ export default createReactClass({ this._suppressReadReceiptAnimation = false; const client = this.context; client.on("deviceVerificationChanged", this.onDeviceVerificationChanged); + client.on("userTrustStatusChanged", this.onUserVerificationChanged); this.props.mxEvent.on("Event.decrypted", this._onDecrypted); if (this.props.showReactions) { this.props.mxEvent.on("Event.relationsCreated", this._onReactionsCreated); @@ -260,6 +261,7 @@ export default createReactClass({ componentWillUnmount: function() { const client = this.context; client.removeListener("deviceVerificationChanged", this.onDeviceVerificationChanged); + client.removeListener("userTrustStatusChanged", this.onUserVerificationChanged); this.props.mxEvent.removeListener("Event.decrypted", this._onDecrypted); if (this.props.showReactions) { this.props.mxEvent.removeListener("Event.relationsCreated", this._onReactionsCreated); @@ -282,18 +284,42 @@ export default createReactClass({ } }, + onUserVerificationChanged: function(userId, _trustStatus) { + if (userId === this.props.mxEvent.getSender()) { + this._verifyEvent(this.props.mxEvent); + } + }, + _verifyEvent: async function(mxEvent) { if (!mxEvent.isEncrypted()) { return; } + // If we directly trust the device, short-circuit here const verified = await this.context.isEventSenderVerified(mxEvent); + if (verified) { + this.setState({ + verified: "verified" + }, () => { + // Decryption may have caused a change in size + this.props.onHeightChanged(); + }); + return; + } + + const eventSenderTrust = await this.context.checkEventSenderTrust(mxEvent); + if (!eventSenderTrust) { + // We cannot find the device. Instead, we have to verify the user. + const userTrust = await this.context.checkUserTrust(mxEvent.getSender()); + this.setState({ + verified: userTrust.isVerified() ? "user-verified": "warning", + }, this.props.onHeightChanged); // Decryption may have cause a change in size + return; + } + this.setState({ - verified: verified, - }, () => { - // Decryption may have caused a change in size - this.props.onHeightChanged(); - }); + verified: eventSenderTrust.isVerified() ? "verified" : "warning", + }, this.props.onHeightChanged); // Decryption may have caused a change in size }, _propsEqual: function(objA, objB) { @@ -473,8 +499,10 @@ export default createReactClass({ // event is encrypted, display padlock corresponding to whether or not it is verified if (ev.isEncrypted()) { - if (this.state.verified) { + if (this.state.verified === "verified") { return; // no icon for verified + } else if (this.state.verified === "user-verified") { + return (); } else { return (); } @@ -604,8 +632,9 @@ export default createReactClass({ mx_EventTile_last: this.props.last, mx_EventTile_contextual: this.props.contextual, mx_EventTile_actionBarFocused: this.state.actionBarFocused, - mx_EventTile_verified: !isBubbleMessage && this.state.verified === true, - mx_EventTile_unverified: !isBubbleMessage && this.state.verified === false, + mx_EventTile_verified: !isBubbleMessage && this.state.verified === "verified", + mx_EventTile_unverified: !isBubbleMessage && this.state.verified === "warning", + mx_EventTile_userVerified: !isBubbleMessage && this.state.verified === "user-verified", mx_EventTile_bad: isEncryptionFailure, mx_EventTile_emote: msgtype === 'm.emote', mx_EventTile_redacted: isRedacted, @@ -901,6 +930,12 @@ function E2ePadlockUnencrypted(props) { ); } +function E2ePadlockUserVerified(props) { + return ( + + ); +} + class E2ePadlock extends React.Component { static propTypes = { icon: PropTypes.string.isRequired,