@@ -16,12 +16,14 @@ limitations under the License.
16
16
17
17
import jsQR from "jsqr" ;
18
18
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" ;
20
21
import { CypressBot } from "../../support/bot" ;
21
22
import { HomeserverInstance } from "../../plugins/utils/homeserver" ;
22
23
import { emitPromise } from "../../support/util" ;
23
24
import { checkDeviceIsCrossSigned , doTwoWaySasVerification , logIntoElement , waitForVerificationRequest } from "./utils" ;
24
25
import { getToast } from "../../support/toasts" ;
26
+ import { UserCredentials } from "../../support/login" ;
25
27
26
28
/** Render a data URL and return the rendered image data */
27
29
async function renderQRCode ( dataUrl : string ) : Promise < ImageData > {
@@ -122,15 +124,9 @@ describe("Device verification", () => {
122
124
/* the bot scans the QR code */
123
125
cy . get < VerificationRequest > ( "@verificationRequest" )
124
126
. 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 ) ;
134
130
} )
135
131
. as ( "verifier" ) ;
136
132
} ) ;
@@ -244,15 +240,7 @@ describe("Device verification", () => {
244
240
cy . findByRole ( "button" , { name : "Start" } ) . click ( ) ;
245
241
246
242
/* 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 ) => {
256
244
// ... confirm ...
257
245
botVerificationRequest . verifier . verify ( ) ;
258
246
@@ -268,3 +256,145 @@ describe("Device verification", () => {
268
256
} ) ;
269
257
} ) ;
270
258
} ) ;
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