From 82398a82738a73895966c06805b02573fa3c26f3 Mon Sep 17 00:00:00 2001 From: Diego Sampaio Date: Mon, 30 Mar 2026 19:19:27 -0300 Subject: [PATCH 1/3] test(federation): add tests for re-inviting RC users after leaving or being kicked --- .../tests/end-to-end/room.spec.ts | 140 ++++++++++++++++++ 1 file changed, 140 insertions(+) diff --git a/ee/packages/federation-matrix/tests/end-to-end/room.spec.ts b/ee/packages/federation-matrix/tests/end-to-end/room.spec.ts index 8dfc338560c57..347f7bd4b8ebe 100644 --- a/ee/packages/federation-matrix/tests/end-to-end/room.spec.ts +++ b/ee/packages/federation-matrix/tests/end-to-end/room.spec.ts @@ -1809,6 +1809,146 @@ import { SynapseClient } from '../helper/synapse-client'; }); }); + describe('Re-inviting a RC user from Synapse after leaving or being kicked', () => { + describe('Kick a RC user from Synapse, send a message, then re-invite the same user', () => { + let matrixRoomId: string; + let channelName: string; + + beforeAll(async () => { + channelName = `federated-channel-reinvite-kicked-${Date.now()}`; + + // Step 1: Create a room on Synapse + matrixRoomId = await hs1AdminApp.createRoom(channelName); + + // Step 2: Invite RC user from Synapse + await hs1AdminApp.matrixClient.invite(matrixRoomId, federationConfig.rc1.adminMatrixUserId); + + // Step 3: RC user accepts the invite + const subscriptions = await getSubscriptions(rc1AdminRequestConfig); + const pendingInvitation = subscriptions.update.find( + (subscription) => subscription.status === 'INVITED' && subscription.fname?.includes(channelName), + ); + expect(pendingInvitation).not.toBeUndefined(); + + const rid = pendingInvitation!.rid!; + await acceptRoomInvite(rid, rc1AdminRequestConfig); + + // Wait for join to propagate to Synapse + await retry( + 'waiting for RC user join to propagate to Synapse', + async () => { + const member = await hs1AdminApp.findRoomMember(channelName, federationConfig.rc1.adminMatrixUserId); + expect(member?.membership).toBe('join'); + }, + { delayMs: 500 }, + ); + + // Step 4: Kick RC user from Synapse + await hs1AdminApp.matrixClient.kick(matrixRoomId, federationConfig.rc1.adminMatrixUserId, 'Kicked for re-invite test'); + + // Wait for kick to propagate to Synapse + await retry( + 'waiting for RC user kick to propagate to Synapse', + async () => { + const member = await hs1AdminApp.findRoomMember(channelName, federationConfig.rc1.adminMatrixUserId); + expect(member?.membership).toBe('leave'); + }, + { delayMs: 500 }, + ); + + // Step 5: Send a message from Synapse (while RC user is kicked) + await hs1AdminApp.matrixClient.sendTextMessage(matrixRoomId, 'Message sent after kicking RC user'); + }, 30000); + + it('should allow re-inviting the same RC user after being kicked', async () => { + // Step 6: Re-invite the same RC user from Synapse + await hs1AdminApp.matrixClient.invite(matrixRoomId, federationConfig.rc1.adminMatrixUserId); + + // Step 7: Validate the invite was created successfully on the RC side + const subscriptions = await getSubscriptions(rc1AdminRequestConfig); + const pendingInvitation = subscriptions.update.find( + (subscription) => subscription.status === 'INVITED' && subscription.fname?.includes(channelName), + ); + + expect(pendingInvitation).not.toBeUndefined(); + expect(pendingInvitation).toHaveProperty('rid'); + expect(pendingInvitation).toHaveProperty('fname'); + expect(pendingInvitation!.fname).toContain(channelName); + }); + }); + + describe('RC user leaves the room, Synapse sends a message, then re-invites the same user', () => { + let matrixRoomId: string; + let channelName: string; + + beforeAll(async () => { + channelName = `federated-channel-reinvite-left-${Date.now()}`; + + // Step 1: Create a room on Synapse + matrixRoomId = await hs1AdminApp.createRoom(channelName); + + // Step 2: Invite RC user from Synapse + await hs1AdminApp.matrixClient.invite(matrixRoomId, federationConfig.rc1.adminMatrixUserId); + + // Step 3: RC user accepts the invite + const subscriptions = await getSubscriptions(rc1AdminRequestConfig); + const pendingInvitation = subscriptions.update.find( + (subscription) => subscription.status === 'INVITED' && subscription.fname?.includes(channelName), + ); + expect(pendingInvitation).not.toBeUndefined(); + + const rid = pendingInvitation!.rid!; + await acceptRoomInvite(rid, rc1AdminRequestConfig); + + // Wait for join to propagate to Synapse + await retry( + 'waiting for RC user join to propagate to Synapse', + async () => { + const member = await hs1AdminApp.findRoomMember(channelName, federationConfig.rc1.adminMatrixUserId); + expect(member?.membership).toBe('join'); + }, + { delayMs: 500 }, + ); + + // Step 4: RC user leaves the room + await rc1AdminRequestConfig.request + .post(api('rooms.leave')) + .set(rc1AdminRequestConfig.credentials) + .send({ roomId: rid }) + .expect(200); + + // Wait for leave to propagate to Synapse + await retry( + 'waiting for RC user leave to propagate to Synapse', + async () => { + const member = await hs1AdminApp.findRoomMember(channelName, federationConfig.rc1.adminMatrixUserId); + expect(member?.membership).toBe('leave'); + }, + { delayMs: 500 }, + ); + + // Step 5: Send a message from Synapse (while RC user has left) + await hs1AdminApp.matrixClient.sendTextMessage(matrixRoomId, 'Message sent after RC user left'); + }, 30000); + + it('should allow re-inviting the same RC user after they left', async () => { + // Step 6: Re-invite the same RC user from Synapse + await hs1AdminApp.matrixClient.invite(matrixRoomId, federationConfig.rc1.adminMatrixUserId); + + // Step 7: Validate the invite was created successfully on the RC side + const subscriptions = await getSubscriptions(rc1AdminRequestConfig); + const pendingInvitation = subscriptions.update.find( + (subscription) => subscription.status === 'INVITED' && subscription.fname?.includes(channelName), + ); + + expect(pendingInvitation).not.toBeUndefined(); + expect(pendingInvitation).toHaveProperty('rid'); + expect(pendingInvitation).toHaveProperty('fname'); + expect(pendingInvitation!.fname).toContain(channelName); + }); + }); + }); + describe.skip('Synchronizing user names across federated servers', () => { const ts = Date.now(); From 4649c0deea1771d4464518e90bbee247629e46ab Mon Sep 17 00:00:00 2001 From: Diego Sampaio Date: Tue, 31 Mar 2026 14:15:57 -0300 Subject: [PATCH 2/3] bump: update federation-sdk to version 0.6.3 --- apps/meteor/package.json | 2 +- ee/packages/federation-matrix/package.json | 2 +- packages/core-services/package.json | 2 +- yarn.lock | 16 ++++++++-------- 4 files changed, 11 insertions(+), 11 deletions(-) diff --git a/apps/meteor/package.json b/apps/meteor/package.json index 7ccc0456809b1..f5bf7e07efd52 100644 --- a/apps/meteor/package.json +++ b/apps/meteor/package.json @@ -103,7 +103,7 @@ "@rocket.chat/emitter": "^0.32.0", "@rocket.chat/favicon": "workspace:^", "@rocket.chat/federation-matrix": "workspace:^", - "@rocket.chat/federation-sdk": "0.6.2", + "@rocket.chat/federation-sdk": "0.6.3", "@rocket.chat/fuselage": "^0.73.0", "@rocket.chat/fuselage-forms": "^1.0.0", "@rocket.chat/fuselage-hooks": "^0.40.0", diff --git a/ee/packages/federation-matrix/package.json b/ee/packages/federation-matrix/package.json index ab9ac1a8d8c7d..b2cef8f916234 100644 --- a/ee/packages/federation-matrix/package.json +++ b/ee/packages/federation-matrix/package.json @@ -22,7 +22,7 @@ "@rocket.chat/core-services": "workspace:^", "@rocket.chat/core-typings": "workspace:^", "@rocket.chat/emitter": "^0.32.0", - "@rocket.chat/federation-sdk": "0.6.2", + "@rocket.chat/federation-sdk": "0.6.3", "@rocket.chat/http-router": "workspace:^", "@rocket.chat/license": "workspace:^", "@rocket.chat/models": "workspace:^", diff --git a/packages/core-services/package.json b/packages/core-services/package.json index 94457ae56c103..97aca0f9ad1a5 100644 --- a/packages/core-services/package.json +++ b/packages/core-services/package.json @@ -18,7 +18,7 @@ }, "dependencies": { "@rocket.chat/core-typings": "workspace:^", - "@rocket.chat/federation-sdk": "0.6.2", + "@rocket.chat/federation-sdk": "0.6.3", "@rocket.chat/http-router": "workspace:^", "@rocket.chat/icons": "~0.47.0", "@rocket.chat/media-signaling": "workspace:^", diff --git a/yarn.lock b/yarn.lock index 63aa64a700fe6..f24e6b61ad2ba 100644 --- a/yarn.lock +++ b/yarn.lock @@ -9140,7 +9140,7 @@ __metadata: dependencies: "@rocket.chat/apps-engine": "workspace:^" "@rocket.chat/core-typings": "workspace:^" - "@rocket.chat/federation-sdk": "npm:0.6.2" + "@rocket.chat/federation-sdk": "npm:0.6.3" "@rocket.chat/http-router": "workspace:^" "@rocket.chat/icons": "npm:~0.47.0" "@rocket.chat/jest-presets": "workspace:~" @@ -9350,7 +9350,7 @@ __metadata: "@rocket.chat/core-typings": "workspace:^" "@rocket.chat/ddp-client": "workspace:^" "@rocket.chat/emitter": "npm:^0.32.0" - "@rocket.chat/federation-sdk": "npm:0.6.2" + "@rocket.chat/federation-sdk": "npm:0.6.3" "@rocket.chat/http-router": "workspace:^" "@rocket.chat/license": "workspace:^" "@rocket.chat/models": "workspace:^" @@ -9379,9 +9379,9 @@ __metadata: languageName: unknown linkType: soft -"@rocket.chat/federation-sdk@npm:0.6.2": - version: 0.6.2 - resolution: "@rocket.chat/federation-sdk@npm:0.6.2" +"@rocket.chat/federation-sdk@npm:0.6.3": + version: 0.6.3 + resolution: "@rocket.chat/federation-sdk@npm:0.6.3" dependencies: "@datastructures-js/priority-queue": "npm:^6.3.5" "@noble/ed25519": "npm:^3.0.0" @@ -9394,7 +9394,7 @@ __metadata: zod: "npm:~4.3.6" peerDependencies: typescript: ~5.9.2 - checksum: 10/dd21576de694e4f18dcde46d3357f5ca3b817e3f9f20d2b66b526a35c716faee870f7a72356c86c1b59c27beddd53225725e126c460cd5f52eaba964bb807bdf + checksum: 10/71c8667f3d63e0b4ef0d82ee2b7c7c707494c286cfadf0c6be7e0feed7abc8817b0dfd44bb249861f8411c7747fdcfcde996a1c63d9c2396bba19d7ecb5689ac languageName: node linkType: hard @@ -10001,7 +10001,7 @@ __metadata: "@rocket.chat/emitter": "npm:^0.32.0" "@rocket.chat/favicon": "workspace:^" "@rocket.chat/federation-matrix": "workspace:^" - "@rocket.chat/federation-sdk": "npm:0.6.2" + "@rocket.chat/federation-sdk": "npm:0.6.3" "@rocket.chat/fuselage": "npm:^0.73.0" "@rocket.chat/fuselage-forms": "npm:^1.0.0" "@rocket.chat/fuselage-hooks": "npm:^0.40.0" @@ -11366,7 +11366,7 @@ __metadata: peerDependencies: "@rocket.chat/layout": "*" "@rocket.chat/tools": 0.2.5-rc.0 - "@rocket.chat/ui-contexts": 29.0.0-rc.2 + "@rocket.chat/ui-contexts": 29.0.0-rc.3 "@tanstack/react-query": "*" react: "*" react-hook-form: "*" From 9070fa9ace84f62b289c55385654bbbc4d41e29d Mon Sep 17 00:00:00 2001 From: Diego Sampaio Date: Tue, 31 Mar 2026 15:39:03 -0300 Subject: [PATCH 3/3] test: increase synapse rate limiting for room creation --- .../federation-matrix/docker-compose/hs1/homeserver.yaml | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/ee/packages/federation-matrix/docker-compose/hs1/homeserver.yaml b/ee/packages/federation-matrix/docker-compose/hs1/homeserver.yaml index decb4ee98cd65..6db872a0559bf 100644 --- a/ee/packages/federation-matrix/docker-compose/hs1/homeserver.yaml +++ b/ee/packages/federation-matrix/docker-compose/hs1/homeserver.yaml @@ -72,6 +72,11 @@ rc_registration_token_validity: per_second: 10000 burst_count: 10000 +# Room creation +rc_room_creation: + per_second: 10000 + burst_count: 10000 + # Login (IP, account, and failed-attempt buckets) rc_login: address: