From aa1454a28a974baf60fc4c802c50730fb756218a Mon Sep 17 00:00:00 2001 From: Ricardo Garim Date: Wed, 24 Sep 2025 12:56:08 -0300 Subject: [PATCH 1/2] run beforeCreateDirectRoom callback earlier to ensure federated user is created before room setup --- .../lib/server/functions/createDirectRoom.ts | 8 ++-- .../app/lib/server/functions/createRoom.ts | 9 +++- apps/meteor/lib/callbacks.ts | 3 +- .../room/hooks/BeforeFederationActions.ts | 2 +- .../federation-matrix/src/FederationMatrix.ts | 47 ++++--------------- .../src/types/IFederationMatrixService.ts | 2 +- 6 files changed, 26 insertions(+), 45 deletions(-) diff --git a/apps/meteor/app/lib/server/functions/createDirectRoom.ts b/apps/meteor/app/lib/server/functions/createDirectRoom.ts index c1867de9feb03..7fab669381e65 100644 --- a/apps/meteor/app/lib/server/functions/createDirectRoom.ts +++ b/apps/meteor/app/lib/server/functions/createDirectRoom.ts @@ -1,7 +1,7 @@ import { AppEvents, Apps } from '@rocket.chat/apps'; import { AppsEngineException } from '@rocket.chat/apps-engine/definition/exceptions'; import type { ISubscriptionExtraData } from '@rocket.chat/core-services'; -import type { ICreatedRoom, IRoom, ISubscription, IUser } from '@rocket.chat/core-typings'; +import type { ICreatedRoom, IRoom, IRoomNativeFederated, ISubscription, IUser } from '@rocket.chat/core-typings'; import { Rooms, Subscriptions, Users } from '@rocket.chat/models'; import { Random } from '@rocket.chat/random'; import { Meteor } from 'meteor/meteor'; @@ -42,7 +42,7 @@ const getName = (members: IUser[]): string => members.map(({ username }) => user export async function createDirectRoom( members: IUser[] | string[], - roomExtraData = {}, + roomExtraData: Partial = {}, options: { creator?: string; subscriptionExtra?: ISubscriptionExtraData; @@ -69,6 +69,8 @@ export async function createDirectRoom( }) .filter(isTruthy); + await callbacks.run('beforeCreateDirectRoom', membersUsernames, roomExtraData); + const roomMembers: IUser[] = await Users.findUsersByUsernames(membersUsernames, { projection: { _id: 1, name: 1, username: 1, settings: 1, customFields: 1 }, }).toArray(); @@ -97,8 +99,6 @@ export async function createDirectRoom( ...roomExtraData, }; - await callbacks.run('beforeCreateDirectRoom', members, roomInfo); - if (isNewRoom) { const tmpRoom: { _USERNAMES?: (string | undefined)[] } & typeof roomInfo = { ...roomInfo, diff --git a/apps/meteor/app/lib/server/functions/createRoom.ts b/apps/meteor/app/lib/server/functions/createRoom.ts index 3302425fbc58f..af078945ef0be 100644 --- a/apps/meteor/app/lib/server/functions/createRoom.ts +++ b/apps/meteor/app/lib/server/functions/createRoom.ts @@ -134,9 +134,16 @@ export const createRoom = async ( > => { const { teamId, ...optionalExtraData } = roomExtraData || ({} as IRoom); + const hasFederatedMembers = members.some((member) => { + if (typeof member === 'string') { + return member.includes(':') && member.includes('@'); + } + return member.username?.includes(':') && member.username?.includes('@'); + }); + const extraData = { ...optionalExtraData, - ...(optionalExtraData.federated && { + ...((hasFederatedMembers || optionalExtraData.federated) && { federated: true, federation: { version: 1, diff --git a/apps/meteor/lib/callbacks.ts b/apps/meteor/lib/callbacks.ts index e9c004e146ccd..8dd202f5d002e 100644 --- a/apps/meteor/lib/callbacks.ts +++ b/apps/meteor/lib/callbacks.ts @@ -21,6 +21,7 @@ import type { ILivechatDepartment, MessageMention, IOmnichannelInquiryExtraData, + IRoomNativeFederated, } from '@rocket.chat/core-typings'; import type { Updater } from '@rocket.chat/models'; import type { FilterOperators } from 'mongodb'; @@ -78,7 +79,7 @@ interface EventLikeCallbackSignatures { options?: ICreateRoomOptions; }, ) => void; - 'beforeCreateDirectRoom': (members: IUser[], room: IRoom) => void; + 'beforeCreateDirectRoom': (members: string[], room: Partial) => void; 'federation.beforeCreateDirectMessage': (members: IUser[]) => void; 'afterSetReaction': (message: IMessage, params: { user: IUser; reaction: string; shouldReact: boolean; room: IRoom }) => void; 'afterUnsetReaction': ( diff --git a/apps/meteor/server/services/room/hooks/BeforeFederationActions.ts b/apps/meteor/server/services/room/hooks/BeforeFederationActions.ts index 082edcb71781e..b8a7ae286c315 100644 --- a/apps/meteor/server/services/room/hooks/BeforeFederationActions.ts +++ b/apps/meteor/server/services/room/hooks/BeforeFederationActions.ts @@ -4,7 +4,7 @@ import type { IRoomNativeFederated, IRoom } from '@rocket.chat/core-typings'; import { throwIfFederationNotEnabled } from '../../federation/utils'; export class FederationActions { - public static shouldPerformFederationAction(room: IRoom): room is IRoomNativeFederated { + public static shouldPerformFederationAction(room: Partial): room is IRoomNativeFederated { if (!isRoomFederated(room)) { return false; } diff --git a/ee/packages/federation-matrix/src/FederationMatrix.ts b/ee/packages/federation-matrix/src/FederationMatrix.ts index 9cebe72368cc4..18e3455c5be37 100644 --- a/ee/packages/federation-matrix/src/FederationMatrix.ts +++ b/ee/packages/federation-matrix/src/FederationMatrix.ts @@ -256,47 +256,24 @@ export class FederationMatrix extends ServiceClass implements IFederationMatrixS } } - async ensureFederatedUsersExistLocally(members: (IUser | string)[]): Promise { + async ensureFederatedUsersExistLocally(usernames: string[]): Promise { try { - this.logger.debug('Ensuring federated users exist locally before DM creation', { memberCount: members.length }); + this.logger.debug('Ensuring federated users exist locally before DM creation', { memberCount: usernames.length }); - for await (const member of members) { - let username: string; - - if (typeof member === 'string') { - username = member; - } else if (typeof member.username === 'string') { - username = member.username; - } else { - continue; - } - - if (!username.includes(':') && !username.includes('@')) { + const federatedUsers = usernames.filter((username) => username?.includes(':') && username?.includes('@')); + for await (const username of federatedUsers) { + if (!username) { continue; } const existingUser = await Users.findOneByUsername(username); if (existingUser) { - // TODO review: DM - // const existingBridge = await MatrixBridgedUser.getExternalUserIdByLocalUserId(existingUser._id); // TODO review: DM - // if (!existingBridge) { - // const remoteDomain = externalUserId.split(':')[1] || this.serverName; - // await MatrixBridgedUser.createOrUpdateByLocalId(existingUser._id, externalUserId, true, remoteDomain); - // } continue; } - // TODO: there is not need to check if the username includes ':' or '@', we should just use the username as is - const externalUserId = username.includes(':') ? `@${username}` : `@${username}:${this.serverName}`; - this.logger.debug('Creating federated user locally', { externalUserId, username }); - - const remoteDomain = externalUserId.split(':')[1]; - - const localName = username.split(':')[0]?.replace('@', '') || username; - - const newUser = { + await Users.create({ username, - name: localName, + name: username, type: 'user' as const, status: UserStatus.OFFLINE, active: true, @@ -305,16 +282,12 @@ export class FederationMatrix extends ServiceClass implements IFederationMatrixS federated: true, federation: { version: 1, - mui: externalUserId, - origin: remoteDomain, + mui: username, + origin: username.split(':')[1], }, createdAt: new Date(), _updatedAt: new Date(), - }; - - const { insertedId } = await Users.insertOne(newUser); - - this.logger.debug('Successfully created federated user locally', { userId: insertedId, externalUserId }); + }); } } catch (error) { this.logger.error('Failed to ensure federated users exist locally:', error); diff --git a/packages/core-services/src/types/IFederationMatrixService.ts b/packages/core-services/src/types/IFederationMatrixService.ts index 28bf89012b7b5..893bca7be5f3c 100644 --- a/packages/core-services/src/types/IFederationMatrixService.ts +++ b/packages/core-services/src/types/IFederationMatrixService.ts @@ -7,7 +7,7 @@ export interface IFederationMatrixService { wellKnown: Router<'/.well-known'>; }; createRoom(room: IRoomFederated, owner: IUser, members: string[]): Promise<{ room_id: string; event_id: string }>; - ensureFederatedUsersExistLocally(members: (IUser | string)[]): Promise; + ensureFederatedUsersExistLocally(members: string[]): Promise; createDirectMessageRoom(room: IRoomFederated, members: IUser[], creatorId: IUser['_id']): Promise; sendMessage(message: IMessage, room: IRoomFederated, user: IUser): Promise; deleteMessage(matrixRoomId: string, message: IMessage, uid: string): Promise; From 225624a92e74e10a9cfd00993c3018775f6709ea Mon Sep 17 00:00:00 2001 From: Guilherme Gazzo Date: Wed, 24 Sep 2025 19:10:39 -0300 Subject: [PATCH 2/2] review --- apps/meteor/app/lib/server/functions/createDirectRoom.ts | 4 ++-- apps/meteor/lib/callbacks.ts | 3 +-- .../server/services/room/hooks/BeforeFederationActions.ts | 2 +- 3 files changed, 4 insertions(+), 5 deletions(-) diff --git a/apps/meteor/app/lib/server/functions/createDirectRoom.ts b/apps/meteor/app/lib/server/functions/createDirectRoom.ts index 7fab669381e65..2e0f34c0e1f31 100644 --- a/apps/meteor/app/lib/server/functions/createDirectRoom.ts +++ b/apps/meteor/app/lib/server/functions/createDirectRoom.ts @@ -1,7 +1,7 @@ import { AppEvents, Apps } from '@rocket.chat/apps'; import { AppsEngineException } from '@rocket.chat/apps-engine/definition/exceptions'; import type { ISubscriptionExtraData } from '@rocket.chat/core-services'; -import type { ICreatedRoom, IRoom, IRoomNativeFederated, ISubscription, IUser } from '@rocket.chat/core-typings'; +import type { ICreatedRoom, IRoom, ISubscription, IUser } from '@rocket.chat/core-typings'; import { Rooms, Subscriptions, Users } from '@rocket.chat/models'; import { Random } from '@rocket.chat/random'; import { Meteor } from 'meteor/meteor'; @@ -42,7 +42,7 @@ const getName = (members: IUser[]): string => members.map(({ username }) => user export async function createDirectRoom( members: IUser[] | string[], - roomExtraData: Partial = {}, + roomExtraData: Partial = {}, options: { creator?: string; subscriptionExtra?: ISubscriptionExtraData; diff --git a/apps/meteor/lib/callbacks.ts b/apps/meteor/lib/callbacks.ts index 8dd202f5d002e..7fe2240f2342e 100644 --- a/apps/meteor/lib/callbacks.ts +++ b/apps/meteor/lib/callbacks.ts @@ -21,7 +21,6 @@ import type { ILivechatDepartment, MessageMention, IOmnichannelInquiryExtraData, - IRoomNativeFederated, } from '@rocket.chat/core-typings'; import type { Updater } from '@rocket.chat/models'; import type { FilterOperators } from 'mongodb'; @@ -79,7 +78,7 @@ interface EventLikeCallbackSignatures { options?: ICreateRoomOptions; }, ) => void; - 'beforeCreateDirectRoom': (members: string[], room: Partial) => void; + 'beforeCreateDirectRoom': (members: string[], room: IRoom) => void; 'federation.beforeCreateDirectMessage': (members: IUser[]) => void; 'afterSetReaction': (message: IMessage, params: { user: IUser; reaction: string; shouldReact: boolean; room: IRoom }) => void; 'afterUnsetReaction': ( diff --git a/apps/meteor/server/services/room/hooks/BeforeFederationActions.ts b/apps/meteor/server/services/room/hooks/BeforeFederationActions.ts index b8a7ae286c315..082edcb71781e 100644 --- a/apps/meteor/server/services/room/hooks/BeforeFederationActions.ts +++ b/apps/meteor/server/services/room/hooks/BeforeFederationActions.ts @@ -4,7 +4,7 @@ import type { IRoomNativeFederated, IRoom } from '@rocket.chat/core-typings'; import { throwIfFederationNotEnabled } from '../../federation/utils'; export class FederationActions { - public static shouldPerformFederationAction(room: Partial): room is IRoomNativeFederated { + public static shouldPerformFederationAction(room: IRoom): room is IRoomNativeFederated { if (!isRoomFederated(room)) { return false; }