@@ -310,3 +310,75 @@ func TestToDeviceMessagesAreBatched(t *testing.T) {
310
310
311
311
})
312
312
}
313
+
314
+ // Regression test for https://github.com/element-hq/element-web/issues/24682
315
+ //
316
+ // When a to-device msg is received, the SDK may need to check that the device belongs
317
+ // to the user in question. To do this, it needs an up-to-date device list. To get this,
318
+ // it does a /keys/query request. If this request fails, the entire processing of the
319
+ // to-device msg could fail, dropping the msg and the room key it contains.
320
+ //
321
+ // This test reproduces this by having an existing E2EE room between Alice and Bob, then:
322
+ // - Block /keys/query requests.
323
+ // - Alice logs in on a new device.
324
+ // - Alice sends a message on the new device.
325
+ // - Bob should get that message but may refuse to decrypt it as it cannot verify that the sender_key
326
+ // belongs to Alice.
327
+ // - Unblock /keys/query requests.
328
+ // - Bob should eventually retry and be able to decrypt the event.
329
+ func TestToDeviceMessagesArentLostWhenKeysQueryFails (t * testing.T ) {
330
+ ForEachClientType (t , func (t * testing.T , clientType api.ClientType ) {
331
+ tc := CreateTestContext (t , clientType , clientType )
332
+ // get a normal E2EE room set up
333
+ roomID := tc .CreateNewEncryptedRoom (t , tc .Alice , EncRoomOptions .Invite ([]string {tc .Bob .UserID }))
334
+ tc .Bob .MustJoinRoom (t , roomID , []string {clientType .HS })
335
+ tc .WithAliceAndBobSyncing (t , func (alice , bob api.Client ) {
336
+ msg := "hello world"
337
+ msg2 := "new device message from alice"
338
+ alice .SendMessage (t , roomID , msg )
339
+ bob .WaitUntilEventInRoom (t , roomID , api .CheckEventHasBody (msg )).Waitf (t , 5 * time .Second , "bob failed to see message from alice" )
340
+ // Block /keys/query requests
341
+ waiter := helpers .NewWaiter ()
342
+ callbackURL , closeCallbackServer := deploy .NewCallbackServer (t , tc .Deployment , func (cd deploy.CallbackData ) {
343
+ t .Logf ("%+v" , cd )
344
+ waiter .Finish ()
345
+ })
346
+ defer closeCallbackServer ()
347
+ var eventID string
348
+ bobAccessToken := bob .CurrentAccessToken (t )
349
+ t .Logf ("Bob's token => %s" , bobAccessToken )
350
+ tc .Deployment .WithMITMOptions (t , map [string ]interface {}{
351
+ "statuscode" : map [string ]interface {}{
352
+ "return_status" : http .StatusGatewayTimeout ,
353
+ "block_request" : true ,
354
+ "count" : 3 ,
355
+ "filter" : "~u .*/keys/query.* ~hq " + bobAccessToken ,
356
+ },
357
+ "callback" : map [string ]interface {}{
358
+ "callback_url" : callbackURL ,
359
+ "filter" : "~u .*/keys/query.*" ,
360
+ },
361
+ }, func () {
362
+ // Alice logs in on a new device.
363
+ csapiAlice2 := tc .MustRegisterNewDevice (t , tc .Alice , clientType .HS , "OTHER_DEVICE" )
364
+ alice2 := tc .MustLoginClient (t , csapiAlice2 , clientType )
365
+ defer alice2 .Close (t )
366
+ alice2StopSyncing := alice2 .MustStartSyncing (t )
367
+ defer alice2StopSyncing ()
368
+ // we don't know how long it will take for the device list update to be processed, so wait 1s
369
+ time .Sleep (time .Second )
370
+
371
+ // Alice sends a message on the new device.
372
+ eventID = alice2 .SendMessage (t , roomID , msg2 )
373
+
374
+ waiter .Waitf (t , 3 * time .Second , "did not see /keys/query" )
375
+ time .Sleep (3 * time .Second ) // let Bob retry /keys/query
376
+ })
377
+ // now we aren't blocking /keys/query anymore.
378
+ // Bob should be able to decrypt this message.
379
+ ev := bob .MustGetEvent (t , roomID , eventID )
380
+ must .Equal (t , ev .Text , msg2 , "bob failed to decrypt " + eventID )
381
+ })
382
+
383
+ })
384
+ }
0 commit comments