From 37ade6cde2468ecc1eb4eb0e14fd03961623f794 Mon Sep 17 00:00:00 2001 From: dhulke Date: Sun, 2 Nov 2025 13:12:49 -0300 Subject: [PATCH 1/5] fix: prioritizes federation not allowed error message over user not found --- .../app/lib/server/functions/createRoom.ts | 8 ++++ .../app/lib/server/methods/addUsersToRoom.ts | 16 +++++--- .../app/slashcommands-invite/server/server.ts | 38 +++++++++++++++++-- 3 files changed, 53 insertions(+), 9 deletions(-) diff --git a/apps/meteor/app/lib/server/functions/createRoom.ts b/apps/meteor/app/lib/server/functions/createRoom.ts index af078945ef0be..fc2e32cf2d33f 100644 --- a/apps/meteor/app/lib/server/functions/createRoom.ts +++ b/apps/meteor/app/lib/server/functions/createRoom.ts @@ -118,6 +118,7 @@ async function createUsersSubscriptions({ await Rooms.incUsersCountById(room._id, subs.length); } +// eslint-disable-next-line complexity export const createRoom = async ( type: T, name: T extends 'd' ? undefined : string, @@ -141,6 +142,13 @@ export const createRoom = async ( return member.username?.includes(':') && member.username?.includes('@'); }); + // Prevent adding federated users to rooms that are not marked as federated explicitly + if (hasFederatedMembers && optionalExtraData.federated !== true) { + throw new Meteor.Error('error-federated-users-in-non-federated-rooms', 'Cannot add federated users to non-federated rooms', { + method: 'createRoom', + }); + } + const extraData = { ...optionalExtraData, ...((hasFederatedMembers || optionalExtraData.federated) && { diff --git a/apps/meteor/app/lib/server/methods/addUsersToRoom.ts b/apps/meteor/app/lib/server/methods/addUsersToRoom.ts index 30830385b1dcd..c5fd718d6911f 100644 --- a/apps/meteor/app/lib/server/methods/addUsersToRoom.ts +++ b/apps/meteor/app/lib/server/methods/addUsersToRoom.ts @@ -1,7 +1,8 @@ import { api } from '@rocket.chat/core-services'; import type { IUser } from '@rocket.chat/core-typings'; -import { isRoomNativeFederated, isUserNativeFederated } from '@rocket.chat/core-typings'; +import { isRoomNativeFederated } from '@rocket.chat/core-typings'; import type { ServerMethods } from '@rocket.chat/ddp-client'; +import { validateFederatedUsername } from '@rocket.chat/federation-matrix'; import { Subscriptions, Users, Rooms } from '@rocket.chat/models'; import { Match } from 'meteor/check'; import { Meteor } from 'meteor/meteor'; @@ -88,15 +89,18 @@ export const addUsersToRoomMethod = async (userId: string, data: { rid: string; await Promise.all( data.users.map(async (username) => { - const newUser = await Users.findOneByUsernameIgnoringCase(sanitizeUsername(username)); - if (!newUser) { - throw new Meteor.Error('error-user-not-found', 'User not found', { + const sanitizedUsername = sanitizeUsername(username); + + // If it's a federated username format and the room is not federated, throw error immediately + if (validateFederatedUsername(sanitizedUsername) && !isRoomNativeFederated(room)) { + throw new Meteor.Error('error-federated-users-in-non-federated-rooms', 'Cannot add federated users to non-federated rooms', { method: 'addUsersToRoom', }); } - if (isUserNativeFederated(newUser) && !isRoomNativeFederated(room)) { - throw new Meteor.Error('error-federated-users-in-non-federated-rooms', 'Cannot add federated users to non-federated rooms', { + const newUser = await Users.findOneByUsernameIgnoringCase(sanitizedUsername); + if (!newUser) { + throw new Meteor.Error('error-user-not-found', 'User not found', { method: 'addUsersToRoom', }); } diff --git a/apps/meteor/app/slashcommands-invite/server/server.ts b/apps/meteor/app/slashcommands-invite/server/server.ts index 24ad0484fddcc..6740b4d6223c0 100644 --- a/apps/meteor/app/slashcommands-invite/server/server.ts +++ b/apps/meteor/app/slashcommands-invite/server/server.ts @@ -1,9 +1,11 @@ -import { api } from '@rocket.chat/core-services'; +import { api, FederationMatrix } from '@rocket.chat/core-services'; import type { IUser, SlashCommandCallbackParams } from '@rocket.chat/core-typings'; -import { Subscriptions, Users } from '@rocket.chat/models'; +import { validateFederatedUsername } from '@rocket.chat/federation-matrix'; +import { Subscriptions, Users, Rooms } from '@rocket.chat/models'; import { Meteor } from 'meteor/meteor'; import { i18n } from '../../../server/lib/i18n'; +import { FederationActions } from '../../../server/services/room/hooks/BeforeFederationActions'; import { addUsersToRoomMethod, sanitizeUsername } from '../../lib/server/methods/addUsersToRoom'; import { settings } from '../../settings/server'; import { slashCommands } from '../../utils/server/slashCommand'; @@ -15,13 +17,43 @@ import { slashCommands } from '../../utils/server/slashCommand'; slashCommands.add({ command: 'invite', callback: async ({ params, message, userId }: SlashCommandCallbackParams<'invite'>): Promise => { - const usernames = params + let usernames = params .split(/[\s,]/) .map((username) => sanitizeUsername(username)) .filter((a) => a !== ''); if (usernames.length === 0) { return; } + + // Get room information for federation check + const room = await Rooms.findOneById(message.rid); + if (!room) { + void api.broadcast('notify.ephemeralMessage', userId, message.rid, { + msg: i18n.t('error-invalid-room', { lng: settings.get('Language') || 'en' }), + }); + return; + } + + // Ensure federated users exist locally before looking them up + const federatedUsernames = usernames.filter((u) => validateFederatedUsername(u)) as string[]; + if (federatedUsernames.length > 0) { + if (FederationActions.shouldPerformFederationAction(room)) { + await FederationMatrix.ensureFederatedUsersExistLocally(federatedUsernames); + } else { + await Promise.all( + federatedUsernames.map(async () => { + return api.broadcast('notify.ephemeralMessage', userId, message.rid, { + msg: i18n.t('You_cannot_add_external_users_to_non_federated_room', { lng: settings.get('Language') || 'en' }), + }); + }), + ); + // These federated users shouldn't be invited and we already broadcasted the error message + usernames = usernames.filter((username) => { + return !federatedUsernames.includes(username); + }); + } + } + const users = await Users.findByUsernames(usernames).toArray(); if (users.length === 0) { void api.broadcast('notify.ephemeralMessage', userId, message.rid, { From f37e1ce91454f6ccfab9ef9a2a355515889f6073 Mon Sep 17 00:00:00 2001 From: dhulke Date: Sun, 2 Nov 2025 13:37:55 -0300 Subject: [PATCH 2/5] fix: export validateFederatedUsername from FederationMatrix --- ee/packages/federation-matrix/src/index.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ee/packages/federation-matrix/src/index.ts b/ee/packages/federation-matrix/src/index.ts index 46fda94431ca4..0a5f6225ba7e1 100644 --- a/ee/packages/federation-matrix/src/index.ts +++ b/ee/packages/federation-matrix/src/index.ts @@ -1,6 +1,6 @@ import 'reflect-metadata'; -export { FederationMatrix } from './FederationMatrix'; +export { FederationMatrix, validateFederatedUsername } from './FederationMatrix'; export { generateEd25519RandomSecretKey } from '@rocket.chat/federation-sdk'; From db0a51519a56f4bc1b082253c31f57ddfc202d3c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Danilo=20Mor=C3=A3es?= Date: Sun, 2 Nov 2025 13:42:37 -0300 Subject: [PATCH 3/5] Return early if all federated users were filtered out Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com> --- apps/meteor/app/slashcommands-invite/server/server.ts | 3 +++ 1 file changed, 3 insertions(+) diff --git a/apps/meteor/app/slashcommands-invite/server/server.ts b/apps/meteor/app/slashcommands-invite/server/server.ts index 6740b4d6223c0..933c48cd4fc6f 100644 --- a/apps/meteor/app/slashcommands-invite/server/server.ts +++ b/apps/meteor/app/slashcommands-invite/server/server.ts @@ -51,6 +51,9 @@ slashCommands.add({ usernames = usernames.filter((username) => { return !federatedUsernames.includes(username); }); + if (usernames.length === 0) { + return; + } } } From b9175bf2f72a646c562d711b8d91d95df9e81c17 Mon Sep 17 00:00:00 2001 From: dhulke Date: Sun, 2 Nov 2025 13:55:19 -0300 Subject: [PATCH 4/5] fix: broadcasting a single error instead of one per federated user --- apps/meteor/app/slashcommands-invite/server/server.ts | 10 +++------- 1 file changed, 3 insertions(+), 7 deletions(-) diff --git a/apps/meteor/app/slashcommands-invite/server/server.ts b/apps/meteor/app/slashcommands-invite/server/server.ts index 933c48cd4fc6f..cc4e116226665 100644 --- a/apps/meteor/app/slashcommands-invite/server/server.ts +++ b/apps/meteor/app/slashcommands-invite/server/server.ts @@ -40,13 +40,9 @@ slashCommands.add({ if (FederationActions.shouldPerformFederationAction(room)) { await FederationMatrix.ensureFederatedUsersExistLocally(federatedUsernames); } else { - await Promise.all( - federatedUsernames.map(async () => { - return api.broadcast('notify.ephemeralMessage', userId, message.rid, { - msg: i18n.t('You_cannot_add_external_users_to_non_federated_room', { lng: settings.get('Language') || 'en' }), - }); - }), - ); + void api.broadcast('notify.ephemeralMessage', userId, message.rid, { + msg: i18n.t('You_cannot_add_external_users_to_non_federated_room', { lng: settings.get('Language') || 'en' }), + }); // These federated users shouldn't be invited and we already broadcasted the error message usernames = usernames.filter((username) => { return !federatedUsernames.includes(username); From c49c3ee01d475c76dc55b010a2ec0082fab5e72a Mon Sep 17 00:00:00 2001 From: Guilherme Gazzo Date: Mon, 10 Nov 2025 17:31:34 -0300 Subject: [PATCH 5/5] Apply suggestion from @ggazzo --- apps/meteor/app/lib/server/functions/createRoom.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/apps/meteor/app/lib/server/functions/createRoom.ts b/apps/meteor/app/lib/server/functions/createRoom.ts index 0e9d6af4a2c46..b936d30145069 100644 --- a/apps/meteor/app/lib/server/functions/createRoom.ts +++ b/apps/meteor/app/lib/server/functions/createRoom.ts @@ -120,7 +120,6 @@ async function createUsersSubscriptions({ await Rooms.incUsersCountById(room._id, subs.length); } -// eslint-disable-next-line complexity export const createRoom = async ( type: T, name: T extends 'd' ? undefined : string,