2019-12-16 20:16:22 +03:00
/ *
2020-01-24 19:16:46 +03:00
Copyright 2019 , 2020 The Matrix . org Foundation C . I . C .
2019-12-16 20:16:22 +03:00
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 .
* /
2020-01-29 10:53:45 +03:00
import React from "react" ;
import PropTypes from "prop-types" ;
2020-01-24 19:16:46 +03:00
2020-05-12 13:05:30 +03:00
import { MatrixClientPeg } from "../../../MatrixClientPeg" ;
2020-01-17 17:50:27 +03:00
import * as sdk from '../../../index' ;
2020-01-16 23:23:32 +03:00
import { verificationMethods } from 'matrix-js-sdk/src/crypto' ;
2020-02-14 15:48:18 +03:00
import { SCAN _QR _CODE _METHOD } from "matrix-js-sdk/src/crypto/verification/QRCode" ;
2020-01-18 05:53:33 +03:00
import VerificationQRCode from "../elements/crypto/VerificationQRCode" ;
2020-01-24 19:16:46 +03:00
import { _t } from "../../../languageHandler" ;
import E2EIcon from "../rooms/E2EIcon" ;
2020-01-28 14:13:09 +03:00
import {
2020-01-29 10:53:45 +03:00
PHASE _UNSENT ,
PHASE _REQUESTED ,
2020-01-28 14:13:09 +03:00
PHASE _READY ,
PHASE _DONE ,
PHASE _STARTED ,
2020-03-31 16:46:11 +03:00
PHASE _CANCELLED ,
2020-01-28 14:13:09 +03:00
} from "matrix-js-sdk/src/crypto/verification/request/VerificationRequest" ;
import Spinner from "../elements/Spinner" ;
2019-12-16 20:16:22 +03:00
export default class VerificationPanel extends React . PureComponent {
2020-01-29 10:53:45 +03:00
static propTypes = {
2020-01-31 18:04:44 +03:00
layout : PropTypes . string ,
2020-01-29 10:53:45 +03:00
request : PropTypes . object . isRequired ,
member : PropTypes . object . isRequired ,
phase : PropTypes . oneOf ( [
PHASE _UNSENT ,
PHASE _REQUESTED ,
PHASE _READY ,
PHASE _STARTED ,
PHASE _CANCELLED ,
PHASE _DONE ,
] ) . isRequired ,
onClose : PropTypes . func . isRequired ,
2020-03-26 01:38:11 +03:00
isRoomEncrypted : PropTypes . bool ,
2020-01-29 10:53:45 +03:00
} ;
2019-12-16 20:16:22 +03:00
constructor ( props ) {
super ( props ) ;
2020-03-31 16:46:11 +03:00
this . state = { } ;
2020-01-29 19:58:23 +03:00
this . _hasVerifier = false ;
2019-12-16 20:16:22 +03:00
}
2020-03-31 16:46:41 +03:00
renderQRPhase ( ) {
2020-02-14 15:48:18 +03:00
const { member , request } = this . props ;
2020-03-20 15:37:00 +03:00
const showSAS = request . otherPartySupportsMethod ( verificationMethods . SAS ) ;
const showQR = request . otherPartySupportsMethod ( SCAN _QR _CODE _METHOD ) ;
2020-01-24 19:16:46 +03:00
const AccessibleButton = sdk . getComponent ( 'elements.AccessibleButton' ) ;
2020-01-24 19:41:43 +03:00
2020-02-14 15:48:18 +03:00
const noCommonMethodError = ! showSAS && ! showQR ?
< p > { _t ( "The session you are trying to verify doesn't support scanning a QR code or emoji verification, which is what Riot supports. Try with a different client." ) } < / p > :
null ;
2020-01-31 18:04:44 +03:00
if ( this . props . layout === 'dialog' ) {
// HACK: This is a terrible idea.
2020-02-14 15:48:18 +03:00
let qrBlock ;
let sasBlock ;
if ( showQR ) {
qrBlock =
< div className = 'mx_VerificationPanel_QRPhase_startOption' >
< p > { _t ( "Scan this unique code" ) } < / p >
2020-03-31 16:46:11 +03:00
< VerificationQRCode qrCodeData = { request . qrCodeData } / >
2020-02-14 15:48:18 +03:00
< / d i v > ;
2020-01-31 18:04:44 +03:00
}
2020-02-14 15:48:18 +03:00
if ( showSAS ) {
sasBlock =
< div className = 'mx_VerificationPanel_QRPhase_startOption' >
< p > { _t ( "Compare unique emoji" ) } < / p >
< span className = 'mx_VerificationPanel_QRPhase_helpText' > { _t ( "Compare a unique set of emoji if you don't have a camera on either device" ) } < / s p a n >
< AccessibleButton disabled = { this . state . emojiButtonClicked } onClick = { this . _startSAS } kind = 'primary' >
{ _t ( "Start" ) }
< / A c c e s s i b l e B u t t o n >
< / d i v > ;
}
const or = qrBlock && sasBlock ?
< div className = 'mx_VerificationPanel_QRPhase_betweenText' > { _t ( "or" ) } < / d i v > : n u l l ;
2020-01-31 18:04:44 +03:00
return (
< div >
{ _t ( "Verify this session by completing one of the following:" ) }
2020-02-10 16:20:54 +03:00
< div className = 'mx_VerificationPanel_QRPhase_startOptions' >
2020-02-14 15:48:18 +03:00
{ qrBlock }
{ or }
{ sasBlock }
{ noCommonMethodError }
2020-01-31 18:04:44 +03:00
< / d i v >
< / d i v >
) ;
}
2020-02-14 15:48:18 +03:00
let qrBlock ;
2020-03-31 16:46:11 +03:00
if ( showQR ) {
2020-02-14 15:48:18 +03:00
qrBlock = < div className = "mx_UserInfo_container" >
2020-02-10 16:44:20 +03:00
< h3 > { _t ( "Verify by scanning" ) } < / h 3 >
2020-01-27 20:17:05 +03:00
< p > { _t ( "Ask %(displayName)s to scan your code:" , {
2020-01-24 19:16:46 +03:00
displayName : member . displayName || member . name || member . userId ,
} ) } < / p >
2020-01-24 19:41:43 +03:00
2020-01-27 20:17:05 +03:00
< div className = "mx_VerificationPanel_qrCode" >
2020-03-31 16:46:11 +03:00
< VerificationQRCode qrCodeData = { request . qrCodeData } / >
2020-01-27 20:17:05 +03:00
< / d i v >
2020-02-14 15:48:18 +03:00
< / d i v > ;
}
2020-01-24 19:16:46 +03:00
2020-02-14 15:48:18 +03:00
let sasBlock ;
if ( showSAS ) {
2020-03-31 16:46:41 +03:00
const disabled = this . state . emojiButtonClicked ;
2020-03-31 16:46:11 +03:00
const sasLabel = showQR ?
2020-02-14 15:48:18 +03:00
_t ( "If you can't scan the code above, verify by comparing unique emoji." ) :
_t ( "Verify by comparing unique emoji." ) ;
2020-04-17 23:31:33 +03:00
// Note: mx_VerificationPanel_verifyByEmojiButton is for the end-to-end tests
2020-02-14 15:48:18 +03:00
sasBlock = < div className = "mx_UserInfo_container" >
2020-02-10 16:44:20 +03:00
< h3 > { _t ( "Verify by emoji" ) } < / h 3 >
2020-02-14 15:48:18 +03:00
< p > { sasLabel } < / p >
2020-04-17 23:31:33 +03:00
< AccessibleButton
disabled = { disabled }
kind = "primary"
className = "mx_UserInfo_wideButton mx_VerificationPanel_verifyByEmojiButton"
onClick = { this . _startSAS }
>
2020-03-31 16:46:41 +03:00
{ _t ( "Verify by emoji" ) }
< / A c c e s s i b l e B u t t o n >
2020-02-14 15:48:18 +03:00
< / d i v > ;
}
const noCommonMethodBlock = noCommonMethodError ?
< div className = "mx_UserInfo_container" > { noCommonMethodError } < / d i v > :
null ;
// TODO: add way to open camera to scan a QR code
return < React . Fragment >
{ qrBlock }
{ sasBlock }
{ noCommonMethodBlock }
2020-01-24 19:16:46 +03:00
< / R e a c t . F r a g m e n t > ;
2019-12-20 23:30:47 +03:00
}
2020-04-02 14:00:45 +03:00
_onReciprocateYesClick = ( ) => {
this . setState ( { reciprocateButtonClicked : true } ) ;
this . state . reciprocateQREvent . confirm ( ) ;
} ;
_onReciprocateNoClick = ( ) => {
this . setState ( { reciprocateButtonClicked : true } ) ;
this . state . reciprocateQREvent . cancel ( ) ;
} ;
2020-05-12 13:05:30 +03:00
_getDevice ( ) {
const deviceId = this . props . request && this . props . request . channel . deviceId ;
return MatrixClientPeg . get ( ) . getStoredDevice ( MatrixClientPeg . get ( ) . getUserId ( ) , deviceId ) ;
}
2020-03-31 16:46:41 +03:00
renderQRReciprocatePhase ( ) {
2020-04-02 18:12:10 +03:00
const { member , request } = this . props ;
2020-04-02 17:42:39 +03:00
let Button ;
2020-04-02 17:44:42 +03:00
// a bit of a hack, but the FormButton should only be used in the right panel
// they should probably just be the same component with a css class applied to it?
2020-04-02 17:42:39 +03:00
if ( this . props . inDialog ) {
Button = sdk . getComponent ( "elements.AccessibleButton" ) ;
} else {
Button = sdk . getComponent ( "elements.FormButton" ) ;
}
2020-04-02 18:12:10 +03:00
const description = request . isSelfVerification ?
2020-04-02 17:42:39 +03:00
_t ( "Almost there! Is your other session showing the same shield?" ) :
_t ( "Almost there! Is %(displayName)s showing the same shield?" , {
displayName : member . displayName || member . name || member . userId ,
} ) ;
2020-03-31 16:46:41 +03:00
let body ;
if ( this . state . reciprocateQREvent ) {
// riot web doesn't support scanning yet, so assume here we're the client being scanned.
2020-04-02 17:44:42 +03:00
//
// we're passing both a label and a child string to Button as
// FormButton and AccessibleButton expect this differently
2020-03-31 16:46:41 +03:00
body = < React . Fragment >
2020-04-02 17:42:39 +03:00
< p > { description } < / p >
2020-03-31 16:46:41 +03:00
< E2EIcon isUser = { true } status = "verified" size = { 128 } hideTooltip = { true } / >
2020-04-02 14:00:45 +03:00
< div className = "mx_VerificationPanel_reciprocateButtons" >
2020-04-02 17:42:39 +03:00
< Button
2020-04-02 14:00:45 +03:00
label = { _t ( "No" ) } kind = "danger"
disabled = { this . state . reciprocateButtonClicked }
2020-04-02 17:42:39 +03:00
onClick = { this . _onReciprocateNoClick } > { _t ( "No" ) } < / B u t t o n >
< Button
label = { _t ( "Yes" ) } kind = "primary"
2020-04-02 14:00:45 +03:00
disabled = { this . state . reciprocateButtonClicked }
2020-04-02 17:42:39 +03:00
onClick = { this . _onReciprocateYesClick } > { _t ( "Yes" ) } < / B u t t o n >
2020-04-02 14:00:45 +03:00
< / d i v >
2020-03-31 16:46:41 +03:00
< / R e a c t . F r a g m e n t > ;
} else {
2020-04-02 14:00:45 +03:00
body = < p > < Spinner / > < / p > ;
2020-03-31 16:46:41 +03:00
}
2020-04-02 13:54:14 +03:00
return < div className = "mx_UserInfo_container mx_VerificationPanel_reciprocate_section" >
2020-03-31 16:46:41 +03:00
< h3 > { _t ( "Verify by scanning" ) } < / h 3 >
{ body }
< / d i v > ;
}
2020-01-24 19:16:46 +03:00
renderVerifiedPhase ( ) {
2020-04-03 18:04:29 +03:00
const { member , request } = this . props ;
2020-01-24 19:16:46 +03:00
2020-03-26 01:38:11 +03:00
let text ;
2020-04-03 18:04:29 +03:00
if ( ! request . isSelfVerification ) {
if ( this . props . isRoomEncrypted ) {
text = _t ( "Verify all users in a room to ensure it's secure." ) ;
} else {
text = _t ( "In encrypted rooms, verify all users to ensure it’ s secure." ) ;
}
2020-03-26 01:38:11 +03:00
}
2020-05-12 13:05:30 +03:00
let description ;
if ( request . isSelfVerification ) {
const device = this . _getDevice ( ) ;
if ( ! device ) {
2020-05-12 13:14:05 +03:00
// This can happen if the device is logged out while we're still showing verification
// UI for it.
2020-05-12 13:05:30 +03:00
console . warn ( "Verified device we don't know about: " + this . props . request . channel . deviceId ) ;
2020-05-12 14:42:16 +03:00
description = _t ( "You've successfully verified your device!" ) ;
} else {
description = _t ( "You've successfully verified %(deviceName)s (%(deviceId)s)!" , {
deviceName : device ? device . getDisplayName ( ) : '' ,
deviceId : this . props . request . channel . deviceId ,
} ) ;
2020-05-12 13:05:30 +03:00
}
} else {
description = _t ( "You've successfully verified %(displayName)s!" , {
2020-04-03 18:03:37 +03:00
displayName : member . displayName || member . name || member . userId ,
} ) ;
2020-05-12 13:05:30 +03:00
}
2020-04-03 18:03:37 +03:00
2020-05-12 13:05:30 +03:00
const AccessibleButton = sdk . getComponent ( 'elements.AccessibleButton' ) ;
2020-01-24 19:16:46 +03:00
return (
< div className = "mx_UserInfo_container mx_VerificationPanel_verified_section" >
2020-03-26 01:38:11 +03:00
< h3 > { _t ( "Verified" ) } < / h 3 >
2020-04-03 18:03:37 +03:00
< p > { description } < / p >
2020-02-13 17:15:08 +03:00
< E2EIcon isUser = { true } status = "verified" size = { 128 } hideTooltip = { true } / >
2020-04-03 18:04:29 +03:00
{ text ? < p > { text } < / p > : n u l l }
2020-01-29 17:11:50 +03:00
< AccessibleButton kind = "primary" className = "mx_UserInfo_wideButton" onClick = { this . props . onClose } >
2020-01-24 19:16:46 +03:00
{ _t ( "Got it" ) }
< / A c c e s s i b l e B u t t o n >
< / d i v >
) ;
}
2020-01-28 14:13:09 +03:00
renderCancelledPhase ( ) {
2020-01-24 19:16:46 +03:00
const { member , request } = this . props ;
2020-01-28 14:13:09 +03:00
const AccessibleButton = sdk . getComponent ( 'elements.AccessibleButton' ) ;
2020-04-03 18:04:29 +03:00
let startAgainInstruction ;
if ( request . isSelfVerification ) {
startAgainInstruction = _t ( "Start verification again from the notification." ) ;
} else {
startAgainInstruction = _t ( "Start verification again from their profile." ) ;
}
2020-01-28 14:13:09 +03:00
let text ;
if ( request . cancellationCode === "m.timeout" ) {
2020-04-03 18:04:29 +03:00
text = _t ( "Verification timed out." ) + ` ${ startAgainInstruction } ` ;
2020-01-28 14:13:09 +03:00
} else if ( request . cancellingUserId === request . otherUserId ) {
2020-04-03 18:04:29 +03:00
if ( request . isSelfVerification ) {
text = _t ( "You cancelled verification on your other session." ) ;
} else {
text = _t ( "%(displayName)s cancelled verification." , {
displayName : member . displayName || member . name || member . userId ,
} ) ;
}
text = ` ${ text } ${ startAgainInstruction } ` ;
2020-01-28 14:13:09 +03:00
} else {
2020-04-03 18:04:29 +03:00
text = _t ( "You cancelled verification." ) + ` ${ startAgainInstruction } ` ;
2020-01-28 14:13:09 +03:00
}
return (
< div className = "mx_UserInfo_container" >
2020-03-26 01:38:11 +03:00
< h3 > { _t ( "Verification cancelled" ) } < / h 3 >
2020-01-28 14:13:09 +03:00
< p > { text } < / p >
2020-01-29 17:11:50 +03:00
< AccessibleButton kind = "primary" className = "mx_UserInfo_wideButton" onClick = { this . props . onClose } >
2020-01-28 14:13:09 +03:00
{ _t ( "Got it" ) }
< / A c c e s s i b l e B u t t o n >
< / d i v >
) ;
}
render ( ) {
2020-03-31 16:46:41 +03:00
const { member , phase , request } = this . props ;
2020-01-28 14:13:09 +03:00
2020-01-24 19:16:46 +03:00
const displayName = member . displayName || member . name || member . userId ;
2019-12-16 20:16:22 +03:00
2020-01-29 11:00:32 +03:00
switch ( phase ) {
2020-01-28 14:13:09 +03:00
case PHASE _READY :
return this . renderQRPhase ( ) ;
case PHASE _STARTED :
2020-03-31 16:46:41 +03:00
switch ( request . chosenMethod ) {
case verificationMethods . RECIPROCATE _QR _CODE :
return this . renderQRReciprocatePhase ( ) ;
case verificationMethods . SAS : {
const VerificationShowSas = sdk . getComponent ( 'views.verification.VerificationShowSas' ) ;
const emojis = this . state . sasEvent ?
< VerificationShowSas
displayName = { displayName }
2020-05-12 13:05:30 +03:00
device = { this . _getDevice ( ) }
2020-03-31 16:46:41 +03:00
sas = { this . state . sasEvent . sas }
onCancel = { this . _onSasMismatchesClick }
onDone = { this . _onSasMatchesClick }
inDialog = { this . props . inDialog }
2020-04-02 19:28:14 +03:00
isSelf = { request . isSelfVerification }
2020-03-31 16:46:41 +03:00
/> : <Spinner / > ;
return < div className = "mx_UserInfo_container" >
< h3 > { _t ( "Compare emoji" ) } < / h 3 >
{ emojis }
< / d i v > ;
}
default :
return null ;
2020-01-28 14:13:09 +03:00
}
case PHASE _DONE :
return this . renderVerifiedPhase ( ) ;
case PHASE _CANCELLED :
return this . renderCancelledPhase ( ) ;
2019-12-16 20:16:22 +03:00
}
2020-01-29 11:08:52 +03:00
console . error ( "VerificationPanel unhandled phase:" , phase ) ;
2020-01-24 19:16:46 +03:00
return null ;
2019-12-16 20:16:22 +03:00
}
_startSAS = async ( ) => {
2020-02-02 12:48:02 +03:00
this . setState ( { emojiButtonClicked : true } ) ;
2019-12-16 20:16:22 +03:00
const verifier = this . props . request . beginKeyVerification ( verificationMethods . SAS ) ;
try {
2019-12-18 20:26:54 +03:00
await verifier . verify ( ) ;
2019-12-19 19:08:53 +03:00
} catch ( err ) {
console . error ( err ) ;
2019-12-16 20:16:22 +03:00
}
} ;
_onSasMatchesClick = ( ) => {
this . state . sasEvent . confirm ( ) ;
} ;
_onSasMismatchesClick = ( ) => {
2020-01-28 14:13:09 +03:00
this . state . sasEvent . mismatch ( ) ;
2019-12-16 20:16:22 +03:00
} ;
2020-03-31 16:46:41 +03:00
_updateVerifierState = ( ) => {
2020-02-24 15:48:41 +03:00
const { request } = this . props ;
2020-04-02 12:27:00 +03:00
const { sasEvent , reciprocateQREvent } = request . verifier ;
2020-03-31 16:46:41 +03:00
request . verifier . off ( 'show_sas' , this . _updateVerifierState ) ;
request . verifier . off ( 'show_reciprocate_qr' , this . _updateVerifierState ) ;
this . setState ( { sasEvent , reciprocateQREvent } ) ;
2019-12-16 20:16:22 +03:00
} ;
2019-12-19 19:28:23 +03:00
_onRequestChange = async ( ) => {
2019-12-18 20:26:54 +03:00
const { request } = this . props ;
2020-01-29 17:23:13 +03:00
const hadVerifier = this . _hasVerifier ;
this . _hasVerifier = ! ! request . verifier ;
if ( ! hadVerifier && this . _hasVerifier ) {
2020-03-31 16:46:41 +03:00
request . verifier . on ( 'show_sas' , this . _updateVerifierState ) ;
request . verifier . on ( 'show_reciprocate_qr' , this . _updateVerifierState ) ;
2019-12-18 20:26:54 +03:00
try {
2019-12-19 19:28:23 +03:00
// on the requester side, this is also awaited in _startSAS,
// but that's ok as verify should return the same promise.
await request . verifier . verify ( ) ;
2019-12-18 20:26:54 +03:00
} catch ( err ) {
console . error ( "error verify" , err ) ;
}
}
2019-12-16 20:16:22 +03:00
} ;
componentDidMount ( ) {
2020-02-13 16:32:33 +03:00
const { request } = this . props ;
request . on ( "change" , this . _onRequestChange ) ;
if ( request . verifier ) {
2020-03-31 16:46:41 +03:00
const { request } = this . props ;
2020-04-02 12:27:00 +03:00
const { sasEvent , reciprocateQREvent } = request . verifier ;
2020-03-31 16:46:41 +03:00
this . setState ( { sasEvent , reciprocateQREvent } ) ;
2020-02-13 16:32:33 +03:00
}
2020-01-29 19:58:23 +03:00
this . _onRequestChange ( ) ;
2019-12-16 20:16:22 +03:00
}
componentWillUnmount ( ) {
2020-02-24 15:48:41 +03:00
const { request } = this . props ;
if ( request . verifier ) {
2020-03-31 16:46:41 +03:00
request . verifier . off ( 'show_sas' , this . _updateVerifierState ) ;
request . verifier . off ( 'show_reciprocate_qr' , this . _updateVerifierState ) ;
2020-02-24 15:48:41 +03:00
}
request . off ( "change" , this . _onRequestChange ) ;
2019-12-16 20:16:22 +03:00
}
}