tests: Assert room key is rotated when a member leaves the room

Signed-off-by: Skye Elliot <actuallyori@gmail.com>
This commit is contained in:
Skye Elliot
2026-03-16 11:02:28 +00:00
parent 635fdd3f80
commit 7a26db66b5
@@ -1127,6 +1127,115 @@ async fn test_history_share_on_invite_respects_history_visibility() -> Result<()
Ok(())
}
/// Test that when a user leaves a room that uses history sharing, the room key
/// is rotated so they cannot decrypt future messages.
#[tokio::test(flavor = "multi_thread", worker_threads = 4)]
async fn test_history_share_on_invite_room_key_rotation() -> Result<()> {
let alice_span = tracing::info_span!("alice");
let bob_span = tracing::info_span!("bob");
let charlie_span = tracing::info_span!("charlie");
let alice =
create_encryption_enabled_client("alice", false).instrument(alice_span.clone()).await?;
let bob = create_encryption_enabled_client("bob", false).instrument(bob_span.clone()).await?;
let charlie = create_encryption_enabled_client("charlie", false)
.await
.expect("Failed to create Charlie's client");
// 1. Alice creates a room with `shared` history visibility and invites Bob.
let alice_room = alice
.create_room(assign!(CreateRoomRequest::new(), {
preset: Some(RoomPreset::PublicChat),
}))
.instrument(alice_span.clone())
.await?;
alice_room.enable_encryption().instrument(alice_span.clone()).await?;
alice_room.invite_user_by_id(bob.user_id().unwrap()).instrument(alice_span.clone()).await?;
bob.sync_once().instrument(bob_span.clone()).await?;
let bob_room = bob.join_room_by_id(alice_room.room_id()).instrument(bob_span.clone()).await?;
alice.sync_once().instrument(alice_span.clone()).await?;
// 2. Bob sends M1, which Charlie should be able to read later as Alice will
// send them a key bundle.
let event_id_a = bob_room
.send(RoomMessageEventContent::text_plain("Charlie is cool!"))
.into_future()
.instrument(bob_span.clone())
.await?
.response
.event_id;
// Store the session ID for later comparison.
let event_m1 = bob_room.event(&event_id_a, None).instrument(bob_span.clone()).await?;
let event_m1_session_id = event_m1
.encryption_info()
.and_then(|info| info.session_id())
.expect("Bob should be able to check the session ID of event M1");
// 3. Alice invites Charlie; Charlie joins and receives the keys for M1.
alice.sync_once().instrument(alice_span.clone()).await?;
alice_room.invite_user_by_id(charlie.user_id().unwrap()).instrument(alice_span.clone()).await?;
let sync_response = charlie.sync_once().instrument(charlie_span.clone()).await?;
assert_received_room_key_bundle(sync_response);
let charlie_room =
charlie.join_room_by_id(alice_room.room_id()).instrument(charlie_span.clone()).await?;
charlie.sync_once().instrument(charlie_span.clone()).await?;
// Sanity check: Charlie can decrypt message M1 via the bundle.
let event_a = charlie_room.event(&event_id_a, None).instrument(charlie_span.clone()).await?;
assert!(
event_a.encryption_info().is_some(),
"Charlie should be able to decrypt message M1 via the key bundle"
);
// 4. Charlie leaves the room.
charlie_room.leave().instrument(charlie_span.clone()).await?;
// Bob syncs to learn about Charlie's departure, which should trigger key
// rotation.
bob.sync_once().instrument(bob_span.clone()).await?;
// 5. Bob sends M2. Because key rotation should have been performed, this should
// be using a fresh session that hasn't been shared with Charlie.
let event_id_b = bob_room
.send(RoomMessageEventContent::text_plain("Charlie is mean!"))
.into_future()
.instrument(bob_span.clone())
.await?
.response
.event_id;
// Ensure the two session IDs of M1 and M2 are different
let event_b = bob_room.event(&event_id_b, None).instrument(bob_span.clone()).await?;
let event_m2_session_id = event_b
.encryption_info()
.and_then(|info| info.session_id())
.expect("Bob should be able to check the session ID of event M2");
assert_ne!(event_m1_session_id, event_m2_session_id, "Session was not rotated");
// 6. Charlie rejoins the room via ID.
charlie.sync_once().instrument(charlie_span.clone()).await?;
let charlie_room =
charlie.join_room_by_id(alice_room.room_id()).instrument(charlie_span.clone()).await?;
// 7. Charlie attempts to decrypt M2. He should not be able to, because the
// session was rotated after he left the room.
let event_b = charlie_room.event(&event_id_b, None).instrument(charlie_span.clone()).await?;
assert!(
event_b.encryption_info().is_none(),
"Charlie should not be able to decrypt message M2 after rejoining"
);
Ok(())
}
/// Creates a new encryption-enabled client with the given username and
/// settings.
///