@@ -16,12 +16,14 @@ limitations under the License.
1616
1717import jsQR from "jsqr" ;
1818
19- import type { VerificationRequest , Verifier } from "matrix-js-sdk/src/crypto-api/verification" ;
19+ import type { MatrixClient } from "matrix-js-sdk/src/matrix" ;
20+ import type { VerificationRequest , Verifier } from "matrix-js-sdk/src/crypto-api" ;
2021import { CypressBot } from "../../support/bot" ;
2122import { HomeserverInstance } from "../../plugins/utils/homeserver" ;
2223import { emitPromise } from "../../support/util" ;
2324import { checkDeviceIsCrossSigned , doTwoWaySasVerification , logIntoElement , waitForVerificationRequest } from "./utils" ;
2425import { getToast } from "../../support/toasts" ;
26+ import { UserCredentials } from "../../support/login" ;
2527
2628/** Render a data URL and return the rendered image data */
2729async function renderQRCode ( dataUrl : string ) : Promise < ImageData > {
@@ -122,15 +124,9 @@ describe("Device verification", () => {
122124 /* the bot scans the QR code */
123125 cy . get < VerificationRequest > ( "@verificationRequest" )
124126 . then ( async ( request : VerificationRequest ) => {
125- // because I don't know how to scrape the imagedata from the cypress browser window,
126- // we extract the data url and render it to a new canvas.
127- const imageData = await renderQRCode ( qrCode . attr ( "src" ) ) ;
128-
129- // now we can decode the QR code...
130- const result = jsQR ( imageData . data , imageData . width , imageData . height ) ;
131-
132- // ... and feed it into the verification request.
133- return await request . scanQRCode ( new Uint8Array ( result . binaryData ) ) ;
127+ // feed the QR code into the verification request.
128+ const qrData = await readQrCode ( qrCode ) ;
129+ return await request . scanQRCode ( qrData ) ;
134130 } )
135131 . as ( "verifier" ) ;
136132 } ) ;
@@ -244,15 +240,7 @@ describe("Device verification", () => {
244240 cy . findByRole ( "button" , { name : "Start" } ) . click ( ) ;
245241
246242 /* on the bot side, wait for the verifier to exist ... */
247- async function awaitVerifier ( ) {
248- // wait for the verifier to exist
249- while ( ! botVerificationRequest . verifier ) {
250- await emitPromise ( botVerificationRequest , "change" ) ;
251- }
252- return botVerificationRequest . verifier ;
253- }
254-
255- cy . then ( ( ) => cy . wrap ( awaitVerifier ( ) ) ) . then ( ( verifier : Verifier ) => {
243+ cy . then ( ( ) => cy . wrap ( awaitVerifier ( botVerificationRequest ) ) ) . then ( ( verifier : Verifier ) => {
256244 // ... confirm ...
257245 botVerificationRequest . verifier . verify ( ) ;
258246
@@ -268,3 +256,145 @@ describe("Device verification", () => {
268256 } ) ;
269257 } ) ;
270258} ) ;
259+
260+ describe ( "User verification" , ( ) => {
261+ // note that there are other tests that check user verification works in `crypto.spec.ts`.
262+
263+ let aliceCredentials : UserCredentials ;
264+ let homeserver : HomeserverInstance ;
265+ let bob : CypressBot ;
266+
267+ beforeEach ( ( ) => {
268+ cy . startHomeserver ( "default" )
269+ . as ( "homeserver" )
270+ . then ( ( data ) => {
271+ homeserver = data ;
272+ cy . initTestUser ( homeserver , "Alice" , undefined , "alice_" ) . then ( ( credentials ) => {
273+ aliceCredentials = credentials ;
274+ } ) ;
275+ return cy . getBot ( homeserver , {
276+ displayName : "Bob" ,
277+ autoAcceptInvites : true ,
278+ userIdPrefix : "bob_" ,
279+ } ) ;
280+ } )
281+ . then ( ( data ) => {
282+ bob = data ;
283+ } ) ;
284+ } ) ;
285+
286+ afterEach ( ( ) => {
287+ cy . stopHomeserver ( homeserver ) ;
288+ } ) ;
289+
290+ it ( "can receive a verification request when there is no existing DM" , ( ) => {
291+ cy . bootstrapCrossSigning ( aliceCredentials ) ;
292+
293+ // the other user creates a DM
294+ let dmRoomId : string ;
295+ let bobVerificationRequest : VerificationRequest ;
296+ cy . wrap ( 0 ) . then ( async ( ) => {
297+ dmRoomId = await createDMRoom ( bob , aliceCredentials . userId ) ;
298+ } ) ;
299+
300+ // accept the DM
301+ cy . viewRoomByName ( "Bob" ) ;
302+ cy . findByRole ( "button" , { name : "Start chatting" } ) . click ( ) ;
303+
304+ // once Alice has joined, Bob starts the verification
305+ cy . wrap ( 0 ) . then ( async ( ) => {
306+ const room = bob . getRoom ( dmRoomId ) ! ;
307+ while ( room . getMember ( aliceCredentials . userId ) ?. membership !== "join" ) {
308+ await new Promise ( ( resolve ) => {
309+ // @ts -ignore can't access the enum here
310+ room . once ( "RoomState.members" , resolve ) ;
311+ } ) ;
312+ }
313+ bobVerificationRequest = await bob . getCrypto ( ) ! . requestVerificationDM ( aliceCredentials . userId , dmRoomId ) ;
314+ } ) ;
315+
316+ // there should also be a toast
317+ getToast ( "Verification requested" ) . within ( ( ) => {
318+ // it should contain the details of the requesting user
319+ cy . contains ( `Bob (${ bob . credentials . userId } )` ) ;
320+
321+ // Accept
322+ cy . findByRole ( "button" , { name : "Verify Session" } ) . click ( ) ;
323+ } ) ;
324+
325+ // request verification by emoji
326+ cy . get ( "#mx_RightPanel" ) . findByRole ( "button" , { name : "Verify by emoji" } ) . click ( ) ;
327+
328+ cy . wrap ( 0 )
329+ . then ( async ( ) => {
330+ /* on the bot side, wait for the verifier to exist ... */
331+ const verifier = await awaitVerifier ( bobVerificationRequest ) ;
332+ // ... confirm ...
333+ verifier . verify ( ) ;
334+ return verifier ;
335+ } )
336+ . then ( ( botVerifier ) => {
337+ // ... and then check the emoji match
338+ doTwoWaySasVerification ( botVerifier ) ;
339+ } ) ;
340+
341+ cy . findByRole ( "button" , { name : "They match" } ) . click ( ) ;
342+ cy . findByText ( "You've successfully verified Bob!" ) . should ( "exist" ) ;
343+ cy . findByRole ( "button" , { name : "Got it" } ) . click ( ) ;
344+ } ) ;
345+ } ) ;
346+
347+ /** Extract the qrcode out of an on-screen html element */
348+ async function readQrCode ( qrCode : JQuery < HTMLElement > ) {
349+ // because I don't know how to scrape the imagedata from the cypress browser window,
350+ // we extract the data url and render it to a new canvas.
351+ const imageData = await renderQRCode ( qrCode . attr ( "src" ) ) ;
352+
353+ // now we can decode the QR code.
354+ const result = jsQR ( imageData . data , imageData . width , imageData . height ) ;
355+ return new Uint8Array ( result . binaryData ) ;
356+ }
357+
358+ async function createDMRoom ( client : MatrixClient , userId : string ) : Promise < string > {
359+ const r = await client . createRoom ( {
360+ // @ts -ignore can't access the enum here
361+ preset : "trusted_private_chat" ,
362+ // @ts -ignore can't access the enum here
363+ visibility : "private" ,
364+ invite : [ userId ] ,
365+ is_direct : true ,
366+ initial_state : [
367+ {
368+ type : "m.room.encryption" ,
369+ state_key : "" ,
370+ content : {
371+ algorithm : "m.megolm.v1.aes-sha2" ,
372+ } ,
373+ } ,
374+ ] ,
375+ } ) ;
376+
377+ const roomId = r . room_id ;
378+
379+ // wait for the room to come down /sync
380+ while ( ! client . getRoom ( roomId ) ) {
381+ await new Promise ( ( resolve ) => {
382+ //@ts -ignore can't access the enum here
383+ client . once ( "Room" , resolve ) ;
384+ } ) ;
385+ }
386+
387+ return roomId ;
388+ }
389+
390+ /**
391+ * Wait for a verifier to exist for a VerificationRequest
392+ *
393+ * @param botVerificationRequest
394+ */
395+ async function awaitVerifier ( botVerificationRequest : VerificationRequest ) : Promise < Verifier > {
396+ while ( ! botVerificationRequest . verifier ) {
397+ await emitPromise ( botVerificationRequest , "change" ) ;
398+ }
399+ return botVerificationRequest . verifier ;
400+ }
0 commit comments