diff --git a/apps/meteor/app/channel-settings/server/functions/saveRoomName.ts b/apps/meteor/app/channel-settings/server/functions/saveRoomName.ts index a86f16180954a..a092ce877ba10 100644 --- a/apps/meteor/app/channel-settings/server/functions/saveRoomName.ts +++ b/apps/meteor/app/channel-settings/server/functions/saveRoomName.ts @@ -96,6 +96,6 @@ export async function saveRoomName( await Message.saveSystemMessage('r', rid, displayName, user); } - await callbacks.run('afterRoomNameChange', { room, name: displayName, oldName: room.name, userId: user._id }); + await callbacks.run('afterRoomNameChange', { room, name: displayName, oldName: room.name, user }); return displayName; } diff --git a/apps/meteor/app/channel-settings/server/functions/saveRoomTopic.ts b/apps/meteor/app/channel-settings/server/functions/saveRoomTopic.ts index 947f38e6df444..0a8a83be49c1e 100644 --- a/apps/meteor/app/channel-settings/server/functions/saveRoomTopic.ts +++ b/apps/meteor/app/channel-settings/server/functions/saveRoomTopic.ts @@ -28,6 +28,6 @@ export const saveRoomTopic = async ( if (update && sendMessage) { await Message.saveSystemMessage('room_changed_topic', rid, roomTopic || '', user); } - await callbacks.run('afterRoomTopicChange', undefined, { room, topic: roomTopic, userId: user._id }); + await callbacks.run('afterRoomTopicChange', undefined, { room, topic: roomTopic, user }); return update; }; diff --git a/apps/meteor/app/discussion/server/hooks/propagateDiscussionMetadata.ts b/apps/meteor/app/discussion/server/hooks/propagateDiscussionMetadata.ts index 4e2f9a8667361..86b2b30b699df 100644 --- a/apps/meteor/app/discussion/server/hooks/propagateDiscussionMetadata.ts +++ b/apps/meteor/app/discussion/server/hooks/propagateDiscussionMetadata.ts @@ -48,7 +48,7 @@ callbacks.add( callbacks.add( 'afterDeleteMessage', - async (message, { _id, prid }) => { + async (message, { room: { _id, prid } }) => { if (prid) { const room = await Rooms.findOneById(_id, { projection: { diff --git a/apps/meteor/app/lib/server/functions/createDirectRoom.ts b/apps/meteor/app/lib/server/functions/createDirectRoom.ts index b5207256c3a7a..c1867de9feb03 100644 --- a/apps/meteor/app/lib/server/functions/createDirectRoom.ts +++ b/apps/meteor/app/lib/server/functions/createDirectRoom.ts @@ -46,6 +46,7 @@ export async function createDirectRoom( options: { creator?: string; subscriptionExtra?: ISubscriptionExtraData; + federatedRoomId?: string; }, ): Promise { const maxUsers = settings.get('DirectMesssage_maxUsers') || 1; @@ -179,7 +180,11 @@ export async function createDirectRoom( if (isNewRoom) { const insertedRoom = await Rooms.findOneById(rid); - await callbacks.run('afterCreateDirectRoom', insertedRoom, { members: roomMembers, creatorId: options?.creator }); + await callbacks.run('afterCreateDirectRoom', insertedRoom, { + members: roomMembers, + creatorId: options?.creator, + mrid: options?.federatedRoomId, + }); void Apps.self?.triggerEvent(AppEvents.IPostRoomCreate, insertedRoom); } diff --git a/apps/meteor/app/lib/server/functions/createRoom.ts b/apps/meteor/app/lib/server/functions/createRoom.ts index 747d8704eda73..2910001f6e370 100644 --- a/apps/meteor/app/lib/server/functions/createRoom.ts +++ b/apps/meteor/app/lib/server/functions/createRoom.ts @@ -131,7 +131,18 @@ export const createRoom = async ( rid: string; } > => { - const { teamId, ...extraData } = roomExtraData || ({} as IRoom); + const { teamId, ...optionalExtraData } = roomExtraData || ({} as IRoom); + + const extraData = { + ...optionalExtraData, + ...(optionalExtraData.federated && { + federated: true, + federation: { + version: 1, + // TODO we should be able to provide all values from here, currently we update on callback afterCreateRoom + }, + }), + }; await prepareCreateRoomCallback.run({ type, @@ -190,12 +201,6 @@ export const createRoom = async ( fname: name, _updatedAt: now, ...extraData, - ...(extraData.federated && { - federated: true, - federation: { - version: 1, - }, - }), name: isDiscussion ? name : await getValidRoomName(name.trim(), undefined), t: type, msgs: 0, diff --git a/apps/meteor/app/lib/server/functions/deleteMessage.ts b/apps/meteor/app/lib/server/functions/deleteMessage.ts index 391ba936241f5..7e1dcddd921af 100644 --- a/apps/meteor/app/lib/server/functions/deleteMessage.ts +++ b/apps/meteor/app/lib/server/functions/deleteMessage.ts @@ -93,7 +93,7 @@ export async function deleteMessage(message: IMessage, user: IUser): Promise { - if (FederationActions.shouldPerformFederationAction(room)) { - const shouldBeHandledByFederation = room.federated === true || user.username?.includes(':'); - - if (shouldBeHandledByFederation) { - try { - // TODO: Check if message already exists in the database, if it does, don't send it to the federation to avoid loops - // If message is federated, it will save external_message_id like into the message object - // if this prop exists here it should not be sent to the federation to avoid loops - if (!message.federation?.eventId) { - await FederationMatrix.sendMessage(message, room, user); - } - } catch (error) { - // Log the error but don't prevent the message from being sent locally - console.error('[sendMessage] Failed to send message to Native Federation:', error); - } + if (!FederationActions.shouldPerformFederationAction(room)) { + return; + } + + try { + // TODO: Check if message already exists in the database, if it does, don't send it to the federation to avoid loops + // If message is federated, it will save external_message_id like into the message object + // if this prop exists here it should not be sent to the federation to avoid loops + if (!message.federation?.eventId) { + await FederationMatrix.sendMessage(message, room, user); } + } catch (error) { + // Log the error but don't prevent the message from being sent locally + console.error('[sendMessage] Failed to send message to Native Federation:', error); } }, callbacks.priority.HIGH, 'federation-v2-after-room-message-sent', ); + callbacks.add( 'afterDeleteMessage', - async (message: IMessage, room) => { + async (message: IMessage, { room, user }) => { if (!message.federation?.eventId) { return; } - const isFromExternalUser = message.u?.username?.includes(':'); - if (isFromExternalUser) { + // removing messages from external users is not allowed + // TODO should we make it work for external users? + if (user.federated) { return; } + if (!isUserNativeFederated(user)) { + return; + } if (FederationActions.shouldPerformFederationAction(room)) { - await FederationMatrix.deleteMessage(message); + await FederationMatrix.deleteMessage(room.federation.mrid, message, user.federation.mui); } }, callbacks.priority.MEDIUM, @@ -146,7 +150,7 @@ afterLeaveRoomCallback.add( afterRemoveFromRoomCallback.add( async (data: { removedUser: IUser; userWhoRemoved: IUser }, room: IRoom): Promise => { if (FederationActions.shouldPerformFederationAction(room)) { - await FederationMatrix.kickUser(room._id, data.removedUser, data.userWhoRemoved); + await FederationMatrix.kickUser(room, data.removedUser, data.userWhoRemoved); } }, callbacks.priority.HIGH, @@ -155,9 +159,9 @@ afterRemoveFromRoomCallback.add( callbacks.add( 'afterRoomNameChange', - async ({ room, name, userId }) => { + async ({ room, name, user }) => { if (FederationActions.shouldPerformFederationAction(room)) { - await FederationMatrix.updateRoomName(room._id, name, userId); + await FederationMatrix.updateRoomName(room._id, name, user); } }, callbacks.priority.HIGH, @@ -166,9 +170,9 @@ callbacks.add( callbacks.add( 'afterRoomTopicChange', - async (_, { room, topic, userId }) => { + async (_, { room, topic, user }) => { if (FederationActions.shouldPerformFederationAction(room)) { - await FederationMatrix.updateRoomTopic(room._id, topic, userId); + await FederationMatrix.updateRoomTopic(room, topic, user); } }, callbacks.priority.HIGH, @@ -182,9 +186,8 @@ callbacks.add( if (!isEditedMessage(message)) { return; } - FederationActions.shouldPerformFederationAction(room); - await FederationMatrix.updateMessage(message._id, message.msg, message.u); + await FederationMatrix.updateMessage(room, message); } }, callbacks.priority.HIGH, @@ -194,7 +197,7 @@ callbacks.add( beforeChangeRoomRole.add( async (params: { fromUserId: string; userId: string; room: IRoom; role: 'moderator' | 'owner' | 'leader' | 'user' }) => { if (FederationActions.shouldPerformFederationAction(params.room)) { - await FederationMatrix.addUserRoleRoomScoped(params.room._id, params.fromUserId, params.userId, params.role); + await FederationMatrix.addUserRoleRoomScoped(params.room, params.fromUserId, params.userId, params.role); } }, callbacks.priority.HIGH, @@ -214,7 +217,14 @@ callbacks.add( callbacks.add( 'afterCreateDirectRoom', - async (room: IRoom, params: { members: IUser[]; creatorId: IUser['_id'] }): Promise => { + async (room: IRoom, params: { members: IUser[]; creatorId: IUser['_id']; mrid?: string }): Promise => { + if (params.mrid) { + await Rooms.setAsFederated(room._id, { + mrid: params.mrid, + origin: params.mrid.split(':').pop()!, + }); + return; + } if (FederationActions.shouldPerformFederationAction(room)) { await FederationMatrix.createDirectMessageRoom(room, params.members, params.creatorId); } diff --git a/apps/meteor/ee/server/lib/engagementDashboard/messages.ts b/apps/meteor/ee/server/lib/engagementDashboard/messages.ts index e3d99ac3039a5..55bcae0da3aeb 100644 --- a/apps/meteor/ee/server/lib/engagementDashboard/messages.ts +++ b/apps/meteor/ee/server/lib/engagementDashboard/messages.ts @@ -19,7 +19,7 @@ export const handleMessagesSent = async (message: IMessage, { room }: { room?: I return message; }; -export const handleMessagesDeleted = async (message: IMessage, room?: IRoom): Promise => { +export const handleMessagesDeleted = async (message: IMessage, { room }: { room: IRoom }): Promise => { const roomTypesToShow = roomCoordinator.getTypesToShowOnDashboard(); if (!room || !roomTypesToShow.includes(room.t)) { return message; diff --git a/apps/meteor/lib/callbacks.ts b/apps/meteor/lib/callbacks.ts index 8aca3095551f0..bade59f363c41 100644 --- a/apps/meteor/lib/callbacks.ts +++ b/apps/meteor/lib/callbacks.ts @@ -41,14 +41,14 @@ interface EventLikeCallbackSignatures { 'afterCreateChannel': (owner: IUser, room: IRoom) => void; 'afterCreatePrivateGroup': (owner: IUser, room: IRoom) => void; 'afterDeactivateUser': (user: IUser) => void; - 'afterDeleteMessage': (message: IMessage, room: IRoom) => void; + 'afterDeleteMessage': (message: IMessage, params: { room: IRoom; user: IUser }) => void; 'workspaceLicenseChanged': (license: string) => void; 'workspaceLicenseRemoved': () => void; 'afterReadMessages': (rid: IRoom['_id'], params: { uid: IUser['_id']; lastSeen?: Date; tmid?: IMessage['_id'] }) => void; 'beforeReadMessages': (rid: IRoom['_id'], uid: IUser['_id']) => void; 'afterDeleteUser': (user: IUser) => void; 'afterFileUpload': (params: { user: IUser; room: IRoom; message: IMessage }) => void; - 'afterRoomNameChange': (params: { room: IRoom; name: string; oldName: string; userId: IUser['_id'] }) => void; + 'afterRoomNameChange': (params: { room: IRoom; name: string; oldName: string; user: IUser }) => void; 'afterSaveMessage': (message: IMessage, params: { room: IRoom; user: IUser; roomUpdater?: Updater }) => void; 'afterOmnichannelSaveMessage': (message: IMessage, constant: { room: IOmnichannelRoom; roomUpdater: Updater }) => void; 'livechat.removeAgentDepartment': (params: { departmentId: ILivechatDepartmentRecord['_id']; agentsId: ILivechatAgent['_id'][] }) => void; @@ -64,7 +64,7 @@ interface EventLikeCallbackSignatures { user: AtLeast; inviter: AtLeast; }) => void; - 'afterCreateDirectRoom': (params: IRoom, second: { members: IUser[]; creatorId: IUser['_id'] }) => void; + 'afterCreateDirectRoom': (params: IRoom, second: { members: IUser[]; creatorId: IUser['_id']; mrid?: string }) => void; 'beforeDeleteRoom': (params: IRoom) => void; 'beforeJoinDefaultChannels': (user: IUser) => void; 'beforeCreateChannel': (owner: IUser, room: IRoom) => void; @@ -205,7 +205,7 @@ type ChainedCallbackSignatures = { 'roomAvatarChanged': (room: IRoom) => void; 'beforeGetMentions': (mentionIds: string[], teamMentions: MessageMention[]) => Promise; 'livechat.manageDepartmentUnit': (params: { userId: string; departmentId: string; unitId?: string }) => void; - 'afterRoomTopicChange': (params: undefined, { room, topic, userId }: { room: IRoom; topic: string; userId: IUser['_id'] }) => void; + 'afterRoomTopicChange': (params: undefined, { room, topic, user }: { room: IRoom; topic: string; user: IUser }) => void; }; export type Hook = diff --git a/apps/meteor/server/models.ts b/apps/meteor/server/models.ts index abe9f5d5bb273..f00bb4e0143d1 100644 --- a/apps/meteor/server/models.ts +++ b/apps/meteor/server/models.ts @@ -41,8 +41,6 @@ import { LivechatTriggerRaw, LivechatVisitorsRaw, LoginServiceConfigurationRaw, - MatrixBridgedRoomRaw, - MatrixBridgedUserRaw, MessageReadsRaw, MessagesRaw, MigrationsRaw, @@ -132,8 +130,6 @@ registerModel('ILivechatPriorityModel', new LivechatPriorityRaw(db)); registerModel('ILivechatTriggerModel', new LivechatTriggerRaw(db)); registerModel('ILivechatVisitorsModel', new LivechatVisitorsRaw(db)); registerModel('ILoginServiceConfigurationModel', new LoginServiceConfigurationRaw(db)); -registerModel('IMatrixBridgedRoomModel', new MatrixBridgedRoomRaw(db)); -registerModel('IMatrixBridgedUserModel', new MatrixBridgedUserRaw(db)); registerModel('IMessageReadsModel', new MessageReadsRaw(db)); registerModel('IMessagesModel', new MessagesRaw(db, trashCollection)); registerModel('IMigrationsModel', new MigrationsRaw(db)); diff --git a/apps/meteor/server/services/federation/infrastructure/rocket-chat/adapters/Statistics.ts b/apps/meteor/server/services/federation/infrastructure/rocket-chat/adapters/Statistics.ts index b88b5f2755d6d..81a645329f8ee 100644 --- a/apps/meteor/server/services/federation/infrastructure/rocket-chat/adapters/Statistics.ts +++ b/apps/meteor/server/services/federation/infrastructure/rocket-chat/adapters/Statistics.ts @@ -1,5 +1,5 @@ import type { IMatrixFederationStatistics } from '@rocket.chat/core-typings'; -import { MatrixBridgedRoom, Rooms, Users } from '@rocket.chat/models'; +import { Rooms, Users } from '@rocket.chat/models'; import { settings } from '../../../../../../app/settings/server'; @@ -45,9 +45,7 @@ class RocketChatStatisticsAdapter { } async getAmountOfConnectedExternalServers(): Promise<{ quantity: number; servers: string[] }> { - const externalServers = await MatrixBridgedRoom.getExternalServerConnectedExcluding( - settings.get('Federation_Matrix_homeserver_domain'), - ); + const externalServers = await Rooms.countDistinctFederationRoomsExcluding(settings.get('Federation_Matrix_homeserver_domain')); return { quantity: externalServers.length, diff --git a/ee/packages/federation-matrix/src/FederationMatrix.ts b/ee/packages/federation-matrix/src/FederationMatrix.ts index b404f5e010bc9..94dda8c04bf7e 100644 --- a/ee/packages/federation-matrix/src/FederationMatrix.ts +++ b/ee/packages/federation-matrix/src/FederationMatrix.ts @@ -5,12 +5,19 @@ import { ConfigService, createFederationContainer, getAllServices } from '@hs/fe import type { HomeserverEventSignatures, HomeserverServices, FederationContainerOptions } from '@hs/federation-sdk'; import type { EventID } from '@hs/room'; import { type IFederationMatrixService, ServiceClass, Settings } from '@rocket.chat/core-services'; -import { isDeletedMessage, isMessageFromMatrixFederation, isQuoteAttachment, UserStatus } from '@rocket.chat/core-typings'; -import type { MessageQuoteAttachment, IMessage, IRoom, IUser } from '@rocket.chat/core-typings'; +import { + isDeletedMessage, + isMessageFromMatrixFederation, + isQuoteAttachment, + isRoomNativeFederated, + isUserNativeFederated, + UserStatus, +} from '@rocket.chat/core-typings'; +import type { MessageQuoteAttachment, IMessage, IRoom, IUser, IRoomNativeFederated } from '@rocket.chat/core-typings'; import { Emitter } from '@rocket.chat/emitter'; import { Router } from '@rocket.chat/http-router'; import { Logger } from '@rocket.chat/logger'; -import { MatrixBridgedUser, MatrixBridgedRoom, Users, Subscriptions, Messages, Rooms } from '@rocket.chat/models'; +import { Users, Subscriptions, Messages, Rooms } from '@rocket.chat/models'; import emojione from 'emojione'; import { getWellKnownRoutes } from './api/.well-known/server'; @@ -26,7 +33,6 @@ import { isFederationDomainAllowedMiddleware } from './api/middlewares/isFederat import { isFederationEnabledMiddleware } from './api/middlewares/isFederationEnabled'; import { isLicenseEnabledMiddleware } from './api/middlewares/isLicenseEnabled'; import { registerEvents } from './events'; -import { saveExternalUserIdForLocalUser } from './helpers/identifiers'; import { toExternalMessageFormat, toExternalQuoteMessageFormat } from './helpers/message.parsers'; import { MatrixMediaService } from './services/MatrixMediaService'; @@ -121,12 +127,12 @@ export class FederationMatrix extends ServiceClass implements IFederationMatrixS if (!user.username || !user.status) { return; } - const localUser = await Users.findOneByUsername(user.username, { projection: { _id: 1 } }); + const localUser = await Users.findOneByUsername(user.username, { projection: { _id: 1, federated: 1, federation: 1 } }); if (!localUser) { return; } - const externalUserId = await MatrixBridgedUser.getExternalUserIdByLocalUserId(localUser._id); - if (!externalUserId) { + + if (!isUserNativeFederated(localUser)) { return; } @@ -141,7 +147,7 @@ export class FederationMatrix extends ServiceClass implements IFederationMatrixS void instance.homeserverServices.edu.sendPresenceUpdateToRooms( [ { - user_id: externalUserId, + user_id: localUser.federation.mui, presence: statusMap[user.status] || 'offline', }, ], @@ -187,13 +193,13 @@ export class FederationMatrix extends ServiceClass implements IFederationMatrixS return this.httpRoutes; } - async createRoom(room: IRoom, owner: IUser, members: string[]): Promise { + async createRoom(room: IRoom, owner: IUser, members: string[]): Promise<{ room_id: string; event_id: string }> { if (!this.homeserverServices) { this.logger.warn('Homeserver services not available, skipping room creation'); - return; + throw new Error('Homeserver services not available'); } - if (!(room.t === 'c' || room.t === 'p')) { + if (room.t !== 'c' && room.t !== 'p') { throw new Error('Room is not a public or private room'); } @@ -206,25 +212,13 @@ export class FederationMatrix extends ServiceClass implements IFederationMatrixS this.logger.debug('Matrix room created:', matrixRoomResult); - await MatrixBridgedRoom.createOrUpdateByLocalRoomId(room._id, matrixRoomResult.room_id, this.serverName); - - await saveExternalUserIdForLocalUser(owner, matrixUserId); + await Rooms.setAsFederated(room._id, { mrid: matrixRoomResult.room_id, origin: this.serverName }); for await (const member of members) { if (member === owner.username) { continue; } - try { - // TODO: Check if it is external user - split domain etc - const localUserId = await Users.findOneByUsername(member); - if (localUserId) { - await MatrixBridgedUser.createOrUpdateByLocalId(localUserId._id, member, false, this.serverName); - // continue; - } - } catch (error) { - this.logger.error('Error creating or updating bridged user:', error); - } // We are not generating bridged users for members outside of the current workspace // They will be created when the invite is accepted @@ -232,8 +226,9 @@ export class FederationMatrix extends ServiceClass implements IFederationMatrixS } this.logger.debug('Room creation completed successfully', room._id); + + return matrixRoomResult; } catch (error) { - console.log(error); this.logger.error('Failed to create room:', error); throw error; } @@ -256,21 +251,23 @@ export class FederationMatrix extends ServiceClass implements IFederationMatrixS continue; } - const externalUserId = username.includes(':') ? `@${username}` : `@${username}:${this.serverName}`; - const existingUser = await Users.findOneByUsername(username); if (existingUser) { - const existingBridge = await MatrixBridgedUser.getExternalUserIdByLocalUserId(existingUser._id); - if (!existingBridge) { - const remoteDomain = externalUserId.split(':')[1] || this.serverName; - await MatrixBridgedUser.createOrUpdateByLocalId(existingUser._id, externalUserId, true, remoteDomain); - } + // 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] || this.serverName; + const remoteDomain = externalUserId.split(':')[1]; + const localName = username.split(':')[0]?.replace('@', '') || username; const newUser = { @@ -284,13 +281,14 @@ export class FederationMatrix extends ServiceClass implements IFederationMatrixS federated: true, federation: { version: 1, + mui: externalUserId, + origin: remoteDomain, }, createdAt: new Date(), _updatedAt: new Date(), }; const { insertedId } = await Users.insertOne(newUser); - await MatrixBridgedUser.createOrUpdateByLocalId(insertedId, externalUserId, true, remoteDomain); this.logger.debug('Successfully created federated user locally', { userId: insertedId, externalUserId }); } @@ -313,13 +311,7 @@ export class FederationMatrix extends ServiceClass implements IFederationMatrixS throw new Error('Creator not found in members list'); } - const matrixUserId = `@${creator.username}:${this.serverName}`; - const existingMatrixUserId = await MatrixBridgedUser.getExternalUserIdByLocalUserId(creator._id); - if (!existingMatrixUserId) { - await MatrixBridgedUser.createOrUpdateByLocalId(creator._id, matrixUserId, true, this.serverName); - } - - const actualMatrixUserId = existingMatrixUserId || matrixUserId; + const actualMatrixUserId = `@${creator.username}:${this.serverName}`; let matrixRoomResult: { room_id: string; event_id?: string }; if (members.length === 2) { @@ -348,13 +340,16 @@ export class FederationMatrix extends ServiceClass implements IFederationMatrixS matrixRoomResult = await this.homeserverServices.room.createRoom(actualMatrixUserId, roomName, 'invite'); } - const mapping = await MatrixBridgedRoom.getLocalRoomId(matrixRoomResult.room_id); - if (!mapping) { - await MatrixBridgedRoom.createOrUpdateByLocalRoomId(room._id, matrixRoomResult.room_id, this.serverName); - } + // TODO is this needed? + // const mapping = await MatrixBridgedRoom.getLocalRoomId(matrixRoomResult.room_id); + // if (!mapping) { + // await MatrixBridgedRoom.createOrUpdateByLocalRoomId(room._id, matrixRoomResult.room_id, this.serverName); + // } for await (const member of members) { - if (typeof member !== 'string' && member._id === creatorId) continue; + if (typeof member !== 'string' && member._id === creatorId) { + continue; + } try { let memberMatrixUserId: string; @@ -367,15 +362,31 @@ export class FederationMatrix extends ServiceClass implements IFederationMatrixS memberMatrixUserId = member.username.startsWith('@') ? member.username : `@${member.username}`; memberId = member._id; } else { - memberMatrixUserId = `@${member.username}:${this.serverName}`; - memberId = member._id; + continue; } if (memberId) { - const existingMemberMatrixUserId = await MatrixBridgedUser.getExternalUserIdByLocalUserId(memberId); - + const existingMemberMatrixUserId = await Users.findOne({ 'federation.mui': memberId }); if (!existingMemberMatrixUserId) { - await MatrixBridgedUser.createOrUpdateByLocalId(memberId, memberMatrixUserId, true, this.serverName); + const newUser = { + username: memberId, + name: memberId, + type: 'user' as const, + status: UserStatus.OFFLINE, + active: true, + roles: ['user'], + requirePasswordChange: false, + federated: true, + federation: { + version: 1, + mui: memberId, + origin: memberMatrixUserId.split(':').pop(), + }, + createdAt: new Date(), + _updatedAt: new Date(), + }; + + await Users.insertOne(newUser); } } @@ -386,7 +397,10 @@ export class FederationMatrix extends ServiceClass implements IFederationMatrixS this.logger.error('Error creating or updating bridged user for DM:', error); } } - await Rooms.setAsFederated(room._id); + await Rooms.setAsFederated(room._id, { + mrid: matrixRoomResult.room_id, + origin: this.serverName, + }); this.logger.debug('Direct message room creation completed successfully', room._id); } catch (error) { this.logger.error('Failed to create direct message room:', error); @@ -531,31 +545,20 @@ export class FederationMatrix extends ServiceClass implements IFederationMatrixS ); } - async sendMessage(message: IMessage, room: IRoom, user: IUser): Promise { + async sendMessage(message: IMessage, room: IRoomNativeFederated, user: IUser): Promise { try { - const matrixRoomId = await MatrixBridgedRoom.getExternalRoomId(room._id); - if (!matrixRoomId) { - throw new Error(`No Matrix room mapping found for room ${room._id}`); - } - - const matrixUserId = `@${user.username}:${this.serverName}`; - const existingMatrixUserId = await MatrixBridgedUser.getExternalUserIdByLocalUserId(user._id); - if (!existingMatrixUserId) { - await MatrixBridgedUser.createOrUpdateByLocalId(user._id, matrixUserId, true, this.serverName); - } - if (!this.homeserverServices) { this.logger.warn('Homeserver services not available, skipping message send'); return; } - const actualMatrixUserId = existingMatrixUserId || matrixUserId; + const userMui = isUserNativeFederated(user) ? user.federation.mui : `@${user.username}:${this.serverName}`; let result; if (message.files && message.files.length > 0) { - result = await this.handleFileMessage(message, matrixRoomId, actualMatrixUserId, this.serverName); + result = await this.handleFileMessage(message, room.federation.mrid, userMui, this.serverName); } else { - result = await this.handleTextMessage(message, matrixRoomId, actualMatrixUserId, this.serverName); + result = await this.handleTextMessage(message, room.federation.mrid, userMui, this.serverName); } if (!result) { @@ -611,30 +614,25 @@ export class FederationMatrix extends ServiceClass implements IFederationMatrixS }; } - async deleteMessage(message: IMessage): Promise { + async deleteMessage(matrixRoomId: string, message: IMessage, uid: string): Promise { try { if (!isMessageFromMatrixFederation(message) || isDeletedMessage(message)) { return; } - const matrixRoomId = await MatrixBridgedRoom.getExternalRoomId(message.rid); - if (!matrixRoomId) { - throw new Error(`No Matrix room mapping found for room ${message.rid}`); - } - const matrixUserId = `@${message.u.username}:${this.serverName}`; - const existingMatrixUserId = await MatrixBridgedUser.getExternalUserIdByLocalUserId(message.u._id); - if (!existingMatrixUserId) { - await MatrixBridgedUser.createOrUpdateByLocalId(message.u._id, matrixUserId, true, this.serverName); - } if (!this.homeserverServices) { this.logger.warn('Homeserver services not available, skipping message redaction'); return; } + const matrixEventId = message.federation?.eventId; if (!matrixEventId) { throw new Error(`No Matrix event ID mapping found for message ${message._id}`); } - const eventId = await this.homeserverServices.message.redactMessage(matrixRoomId, matrixEventId as EventID, matrixUserId); + + // TODO fix branded EventID and remove type casting + // TODO message.u?.username is not the user who removed the message + const eventId = await this.homeserverServices.message.redactMessage(matrixRoomId, matrixEventId as EventID, uid); this.logger.debug('Message Redaction sent to Matrix successfully:', eventId); } catch (error) { @@ -643,13 +641,8 @@ export class FederationMatrix extends ServiceClass implements IFederationMatrixS } } - async inviteUsersToRoom(room: IRoom, usersUserName: string[], inviter: IUser): Promise { + async inviteUsersToRoom(room: IRoomNativeFederated, usersUserName: string[], inviter: IUser): Promise { try { - const matrixRoomId = await MatrixBridgedRoom.getExternalRoomId(room._id); - if (!matrixRoomId) { - throw new Error(`No Matrix room mapping found for room ${room._id}`); - } - const inviterUserId = `@${inviter.username}:${this.serverName}`; await Promise.all( @@ -664,11 +657,11 @@ export class FederationMatrix extends ServiceClass implements IFederationMatrixS return; } - await this.homeserverServices.invite.inviteUserToRoom(username, matrixRoomId, inviterUserId); + await this.homeserverServices.invite.inviteUserToRoom(username, room.federation.mrid, inviterUserId); }), ); } catch (error) { - this.logger.error('Failed to invite an user to Matrix:', error); + this.logger.error({ msg: 'Failed to invite an user to Matrix:', err: error }); throw error; } } @@ -680,8 +673,8 @@ export class FederationMatrix extends ServiceClass implements IFederationMatrixS throw new Error(`Message ${messageId} not found`); } - const matrixRoomId = await MatrixBridgedRoom.getExternalRoomId(message.rid); - if (!matrixRoomId) { + const room = await Rooms.findOneById(message.rid); + if (!room || !isRoomNativeFederated(room)) { throw new Error(`No Matrix room mapping found for room ${message.rid}`); } @@ -692,13 +685,9 @@ export class FederationMatrix extends ServiceClass implements IFederationMatrixS const reactionKey = emojione.shortnameToUnicode(reaction); - const existingMatrixUserId = await MatrixBridgedUser.getExternalUserIdByLocalUserId(user._id); - if (!existingMatrixUserId) { - this.logger.error(`No Matrix user ID mapping found for user ${user._id}`); - return; - } + const userMui = isUserNativeFederated(user) ? user.federation.mui : `@${user.username}:${this.serverName}`; - const eventId = await this.homeserverServices.message.sendReaction(matrixRoomId, matrixEventId, reactionKey, existingMatrixUserId); + const eventId = await this.homeserverServices.message.sendReaction(room.federation.mrid, matrixEventId, reactionKey, userMui); await Messages.setFederationReactionEventId(user.username || '', messageId, reaction, eventId); @@ -723,18 +712,15 @@ export class FederationMatrix extends ServiceClass implements IFederationMatrixS return; } - const matrixRoomId = await MatrixBridgedRoom.getExternalRoomId(message.rid); - if (!matrixRoomId) { + const room = await Rooms.findOneById(message.rid); + if (!room || !isRoomNativeFederated(room)) { this.logger.error(`No Matrix room mapping found for room ${message.rid}`); return; } const reactionKey = emojione.shortnameToUnicode(reaction); - const existingMatrixUserId = await MatrixBridgedUser.getExternalUserIdByLocalUserId(user._id); - if (!existingMatrixUserId) { - this.logger.error(`No Matrix user ID mapping found for user ${user._id}`); - return; - } + + const userMui = isUserNativeFederated(user) ? user.federation.mui : `@${user.username}:${this.serverName}`; const reactionData = oldMessage.reactions?.[reaction]; if (!reactionData?.federationReactionEventIds) { @@ -747,10 +733,10 @@ export class FederationMatrix extends ServiceClass implements IFederationMatrixS } const redactionEventId = await this.homeserverServices.message.unsetReaction( - matrixRoomId, + room.federation.mrid, eventId as EventID, reactionKey, - existingMatrixUserId, + userMui, ); if (!redactionEventId) { this.logger.warn('No reaction event found to remove in Matrix'); @@ -783,121 +769,81 @@ export class FederationMatrix extends ServiceClass implements IFederationMatrixS async leaveRoom(roomId: string, user: IUser): Promise { try { const room = await Rooms.findOneById(roomId); - if (!room?.federated) { + if (!room || !isRoomNativeFederated(room)) { this.logger.debug(`Room ${roomId} is not federated, skipping leave operation`); return; } - const matrixRoomId = await MatrixBridgedRoom.getExternalRoomId(roomId); - if (!matrixRoomId) { - this.logger.warn(`No Matrix room mapping found for federated room ${roomId}, skipping leave`); - return; - } - - const matrixUserId = `@${user.username}:${this.serverName}`; - const existingMatrixUserId = await MatrixBridgedUser.getExternalUserIdByLocalUserId(user._id); - - if (!existingMatrixUserId) { - // User might not have been bridged yet if they never sent a message - await MatrixBridgedUser.createOrUpdateByLocalId(user._id, matrixUserId, true, this.serverName); - } - if (!this.homeserverServices) { this.logger.warn('Homeserver services not available, skipping room leave'); return; } - const actualMatrixUserId = existingMatrixUserId || matrixUserId; + const actualMatrixUserId = isUserNativeFederated(user) ? user.federation.mui : `@${user.username}:${this.serverName}`; - await this.homeserverServices.room.leaveRoom(matrixRoomId, actualMatrixUserId); + await this.homeserverServices.room.leaveRoom(room.federation.mrid, actualMatrixUserId); - this.logger.info(`User ${user.username} left Matrix room ${matrixRoomId} successfully`); + this.logger.info(`User ${user.username} left Matrix room ${room.federation.mrid} successfully`); } catch (error) { this.logger.error('Failed to leave room in Matrix:', error); throw error; } } - async kickUser(roomId: string, removedUser: IUser, userWhoRemoved: IUser): Promise { - try { - const room = await Rooms.findOneById(roomId); - if (!room?.federated) { - this.logger.debug(`Room ${roomId} is not federated, skipping kick operation`); - return; - } - - const matrixRoomId = await MatrixBridgedRoom.getExternalRoomId(roomId); - if (!matrixRoomId) { - this.logger.warn(`No Matrix room mapping found for federated room ${roomId}, skipping kick`); - return; - } - - const kickedMatrixUserId = `@${removedUser.username}:${this.serverName}`; - const existingKickedMatrixUserId = await MatrixBridgedUser.getExternalUserIdByLocalUserId(removedUser._id); - if (!existingKickedMatrixUserId) { - await MatrixBridgedUser.createOrUpdateByLocalId(removedUser._id, kickedMatrixUserId, true, this.serverName); - } - const actualKickedMatrixUserId = existingKickedMatrixUserId || kickedMatrixUserId; + async kickUser(room: IRoomNativeFederated, removedUser: IUser, userWhoRemoved: IUser): Promise { + if (!this.homeserverServices) { + this.logger.warn('Homeserver services not available, skipping user kick'); + return; + } - const senderMatrixUserId = `@${userWhoRemoved.username}:${this.serverName}`; - const existingSenderMatrixUserId = await MatrixBridgedUser.getExternalUserIdByLocalUserId(userWhoRemoved._id); - if (!existingSenderMatrixUserId) { - await MatrixBridgedUser.createOrUpdateByLocalId(userWhoRemoved._id, senderMatrixUserId, true, this.serverName); - } - const actualSenderMatrixUserId = existingSenderMatrixUserId || senderMatrixUserId; + try { + const actualKickedMatrixUserId = isUserNativeFederated(removedUser) + ? removedUser.federation.mui + : `@${removedUser.username}:${this.serverName}`; - if (!this.homeserverServices) { - this.logger.warn('Homeserver services not available, skipping user kick'); - return; - } + const actualSenderMatrixUserId = isUserNativeFederated(userWhoRemoved) + ? userWhoRemoved.federation.mui + : `@${userWhoRemoved.username}:${this.serverName}`; await this.homeserverServices.room.kickUser( - matrixRoomId, + room.federation.mrid, actualKickedMatrixUserId, actualSenderMatrixUserId, `Kicked by ${userWhoRemoved.username}`, ); - this.logger.info(`User ${removedUser.username} was kicked from Matrix room ${matrixRoomId} by ${userWhoRemoved.username}`); + this.logger.info(`User ${removedUser.username} was kicked from Matrix room ${room.federation.mrid} by ${userWhoRemoved.username}`); } catch (error) { this.logger.error('Failed to kick user from Matrix room:', error); throw error; } } - async updateMessage(messageId: string, newContent: string, sender: IUser): Promise { + async updateMessage(room: IRoomNativeFederated, message: IMessage): Promise { try { - const message = await Messages.findOneById(messageId); - if (!message) { - throw new Error(`Message ${messageId} not found`); - } - - const matrixRoomId = await MatrixBridgedRoom.getExternalRoomId(message.rid); - if (!matrixRoomId) { - throw new Error(`No Matrix room mapping found for room ${message.rid}`); - } - const matrixEventId = message.federation?.eventId; if (!matrixEventId) { - throw new Error(`No Matrix event ID mapping found for message ${messageId}`); + throw new Error(`No Matrix event ID mapping found for message ${message._id}`); } - const existingMatrixUserId = await MatrixBridgedUser.getExternalUserIdByLocalUserId(sender._id); - if (!existingMatrixUserId) { - this.logger.error(`No Matrix user ID mapping found for user ${sender._id}`); + const user = await Users.findOneById(message.u._id, { projection: { _id: 1, username: 1, federation: 1, federated: 1 } }); + if (!user) { + this.logger.error(`No user found for ID ${message.u._id}`); return; } + const userMui = isUserNativeFederated(user) ? user.federation.mui : `@${user.username}:${this.serverName}`; + const parsedMessage = await toExternalMessageFormat({ - message: newContent, - externalRoomId: matrixRoomId, + message: message.msg, + externalRoomId: room.federation.mrid, homeServerDomain: this.serverName, }); const eventId = await this.homeserverServices.message.updateMessage( - matrixRoomId, - newContent, + room.federation.mrid, + message.msg, parsedMessage, - existingMatrixUserId, + userMui, matrixEventId, ); @@ -908,47 +854,36 @@ export class FederationMatrix extends ServiceClass implements IFederationMatrixS } } - async updateRoomName(rid: string, displayName: string, senderId: string): Promise { + async updateRoomName(rid: string, displayName: string, user: IUser): Promise { if (!this.homeserverServices) { this.logger.warn('Homeserver services not available, skipping room name update'); return; } - const matrixRoomId = await MatrixBridgedRoom.getExternalRoomId(rid); - if (!matrixRoomId) { + const room = await Rooms.findOneById(rid); + if (!room || !isRoomNativeFederated(room)) { throw new Error(`No Matrix room mapping found for room ${rid}`); } - const userId = await MatrixBridgedUser.getExternalUserIdByLocalUserId(senderId); - if (!userId) { - throw new Error(`No Matrix user ID mapping found for user ${senderId}`); - } + const userMui = isUserNativeFederated(user) ? user.federation.mui : `@${user.username}:${this.serverName}`; - await this.homeserverServices.room.updateRoomName(matrixRoomId, displayName, userId); + await this.homeserverServices.room.updateRoomName(room.federation.mrid, displayName, userMui); } - async updateRoomTopic(rid: string, topic: string, senderId: string): Promise { + async updateRoomTopic(room: IRoomNativeFederated, topic: string, user: IUser): Promise { if (!this.homeserverServices) { this.logger.warn('Homeserver services not available, skipping room topic update'); return; } - const matrixRoomId = await MatrixBridgedRoom.getExternalRoomId(rid); - if (!matrixRoomId) { - throw new Error(`No Matrix room mapping found for room ${rid}`); - } + const userMui = isUserNativeFederated(user) ? user.federation.mui : `@${user.username}:${this.serverName}`; - const userId = await MatrixBridgedUser.getExternalUserIdByLocalUserId(senderId); - if (!userId) { - throw new Error(`No Matrix user ID mapping found for user ${senderId}`); - } - - await this.homeserverServices.room.setRoomTopic(matrixRoomId, userId, topic); + await this.homeserverServices.room.setRoomTopic(room.federation.mrid, userMui, topic); } async addUserRoleRoomScoped( - rid: string, + room: IRoomNativeFederated, senderId: string, userId: string, role: 'moderator' | 'owner' | 'leader' | 'user', @@ -962,20 +897,17 @@ export class FederationMatrix extends ServiceClass implements IFederationMatrixS throw new Error('Leader role is not supported'); } - const matrixRoomId = await MatrixBridgedRoom.getExternalRoomId(rid); - if (!matrixRoomId) { - throw new Error(`No Matrix room mapping found for room ${rid}`); - } - - const matrixUserId = await MatrixBridgedUser.getExternalUserIdByLocalUserId(userId); - if (!matrixUserId) { - throw new Error(`No Matrix user ID mapping found for user ${userId}`); + const user = await Users.findOneById(userId); + if (!user) { + throw new Error(`No user found for ID ${userId}`); } + const userMui = isUserNativeFederated(user) ? user.federation.mui : `@${user.username}:${this.serverName}`; - const senderMatrixUserId = await MatrixBridgedUser.getExternalUserIdByLocalUserId(senderId); - if (!senderMatrixUserId) { - throw new Error(`No Matrix user ID mapping found for user ${senderId}`); + const userSender = await Users.findOneById(senderId); + if (!userSender) { + throw new Error(`No user found for ID ${senderId}`); } + const senderMui = isUserNativeFederated(userSender) ? userSender.federation.mui : `@${userSender.username}:${this.serverName}`; let powerLevel = 0; if (role === 'owner') { @@ -983,26 +915,27 @@ export class FederationMatrix extends ServiceClass implements IFederationMatrixS } else if (role === 'moderator') { powerLevel = 50; } - await this.homeserverServices.room.setPowerLevelForUser(matrixRoomId, senderMatrixUserId, matrixUserId, powerLevel); + await this.homeserverServices.room.setPowerLevelForUser(room.federation.mrid, senderMui, userMui, powerLevel); } async notifyUserTyping(rid: string, user: string, isTyping: boolean) { if (!rid || !user) { return; } - const externalRoomId = await MatrixBridgedRoom.getExternalRoomId(rid); - if (!externalRoomId) { + const room = await Rooms.findOneById(rid); + if (!room || !isRoomNativeFederated(room)) { return; } - const localUser = await Users.findOneByUsername(user, { projection: { _id: 1 } }); + const localUser = await Users.findOneByUsername>(user, { + projection: { _id: 1, username: 1, federation: 1, federated: 1 }, + }); + if (!localUser) { return; } - const externalUserId = await MatrixBridgedUser.getExternalUserIdByLocalUserId(localUser._id); - if (!externalUserId) { - return; - } - void this.homeserverServices.edu.sendTypingNotification(externalRoomId, externalUserId, isTyping); + const userMui = isUserNativeFederated(localUser) ? localUser.federation.mui : `@${localUser.username}:${this.serverName}`; + + void this.homeserverServices.edu.sendTypingNotification(room.federation.mrid, userMui, isTyping); } } diff --git a/ee/packages/federation-matrix/src/api/_matrix/invite.ts b/ee/packages/federation-matrix/src/api/_matrix/invite.ts index 3768317d2019f..180197361e1db 100644 --- a/ee/packages/federation-matrix/src/api/_matrix/invite.ts +++ b/ee/packages/federation-matrix/src/api/_matrix/invite.ts @@ -3,7 +3,7 @@ import type { PduMembershipEventContent, PersistentEventBase, RoomVersion } from import { Room } from '@rocket.chat/core-services'; import type { IUser, UserStatus } from '@rocket.chat/core-typings'; import { Router } from '@rocket.chat/http-router'; -import { MatrixBridgedRoom, MatrixBridgedUser, Rooms, Users } from '@rocket.chat/models'; +import { Rooms, Users } from '@rocket.chat/models'; import { ajv } from '@rocket.chat/rest-typings/dist/v1/Ajv'; const EventBaseSchema = { @@ -176,32 +176,14 @@ async function joinRoom({ } // need both the sender and the participating user to exist in the room - const internalSenderUserId = await MatrixBridgedUser.getLocalUserIdByExternalId(inviteEvent.sender); - - let senderUserId: string; - - if (!internalSenderUserId) { - // create locally - // what we were using previously - /* - public getStorageRepresentation(): Readonly { - return { - _id: this.internalId, - username: this.internalReference.username || '', - type: this.internalReference.type, - status: this.internalReference.status, - active: this.internalReference.active, - roles: this.internalReference.roles, - name: this.internalReference.name, - requirePasswordChange: this.internalReference.requirePasswordChange, - createdAt: new Date(), - _updatedAt: new Date(), - federated: this.isRemote(), - }; - } - */ + // TODO implement on model + const senderUser = await Users.findOne({ 'federation.mui': inviteEvent.sender }, { projection: { _id: 1 } }); + + let senderUserId = senderUser?._id; - const user = { + // create locally + if (!senderUser) { + const createdUser = await Users.insertOne({ // let the _id auto generate we deal with usernames username: inviteEvent.sender, type: 'user', @@ -213,31 +195,25 @@ async function joinRoom({ federated: true, federation: { version: 1, + mui: inviteEvent.sender, + origin: matrixRoom.origin, }, createdAt: new Date(), _updatedAt: new Date(), - }; - - const createdUser = await Users.insertOne(user); + }); senderUserId = createdUser.insertedId; + } - await MatrixBridgedUser.createOrUpdateByLocalId(senderUserId, inviteEvent.sender, true, matrixRoom.origin); - } else { - // already got the mapped sender - const user = await Users.findOneById(internalSenderUserId); - if (!user) { - throw new Error('user not found although should have as it is in mapping not processing invite'); - } - - senderUserId = user._id; + if (!senderUserId) { + throw new Error('Sender user ID not found'); } let internalRoomId: string; - const internalMappedRoomId = await MatrixBridgedRoom.getLocalRoomId(inviteEvent.roomId); + const internalMappedRoom = await Rooms.findOne({ 'federation.mrid': inviteEvent.roomId }); - if (!internalMappedRoomId) { + if (!internalMappedRoom) { let roomType: 'c' | 'p' | 'd'; if (isDM) { @@ -301,19 +277,15 @@ async function joinRoom({ internalRoomId = ourRoom._id; } else { - const room = await Rooms.findOneById(internalMappedRoomId); - if (!room) { - throw new Error('room not found although should have as it is in mapping not processing invite'); - } - - internalRoomId = room._id; + internalRoomId = internalMappedRoom._id; } await Room.addUserToRoom(internalRoomId, { _id: user._id }, { _id: senderUserId, username: inviteEvent.sender }); - if (isDM) { - await MatrixBridgedRoom.createOrUpdateByLocalRoomId(internalRoomId, inviteEvent.roomId, matrixRoom.origin); - } + // TODO is this needed? + // if (isDM) { + // await MatrixBridgedRoom.createOrUpdateByLocalRoomId(internalRoomId, inviteEvent.roomId, matrixRoom.origin); + // } } async function startJoiningRoom(...opts: Parameters) { diff --git a/ee/packages/federation-matrix/src/events/edu.ts b/ee/packages/federation-matrix/src/events/edu.ts index 47ab6ae31e7db..b03ae9937a096 100644 --- a/ee/packages/federation-matrix/src/events/edu.ts +++ b/ee/packages/federation-matrix/src/events/edu.ts @@ -3,35 +3,29 @@ import { api } from '@rocket.chat/core-services'; import { UserStatus } from '@rocket.chat/core-typings'; import type { Emitter } from '@rocket.chat/emitter'; import { Logger } from '@rocket.chat/logger'; -import { MatrixBridgedUser, MatrixBridgedRoom, Users } from '@rocket.chat/models'; +import { Rooms, Users } from '@rocket.chat/models'; const logger = new Logger('federation-matrix:edu'); export const edus = async (emitter: Emitter) => { emitter.on('homeserver.matrix.typing', async (data) => { try { - const matrixRoom = await MatrixBridgedRoom.getLocalRoomId(data.room_id); + const matrixRoom = await Rooms.findOne({ 'federation.mrid': data.room_id }, { projection: { _id: 1 } }); if (!matrixRoom) { logger.debug(`No bridged room found for Matrix room_id: ${data.room_id}`); return; } - const matrixUser = await MatrixBridgedUser.findOne({ mui: data.user_id }); - if (!matrixUser) { + const matrixUser = await Users.findOne({ 'federation.mui': data.user_id }); + if (!matrixUser?.username) { logger.debug(`No bridged user found for Matrix user_id: ${data.user_id}`); return; } - const user = await Users.findOneById(matrixUser.uid, { projection: { _id: 1, username: 1 } }); - if (!user || !user.username) { - logger.debug(`User not found for uid: ${matrixUser.uid}`); - return; - } - void api.broadcast('user.activity', { - user: user.username, + user: matrixUser.username, isTyping: data.typing, - roomId: matrixRoom, + roomId: matrixRoom._id, }); } catch (error) { logger.error('Error handling Matrix typing event:', error); @@ -40,18 +34,11 @@ export const edus = async (emitter: Emitter) => { emitter.on('homeserver.matrix.presence', async (data) => { try { - const matrixUser = await MatrixBridgedUser.findOne({ mui: data.user_id }); + const matrixUser = await Users.findOne({ 'federation.mui': data.user_id }); if (!matrixUser) { logger.debug(`No bridged user found for Matrix user_id: ${data.user_id}`); return; } - const user = await Users.findOneById(matrixUser.uid, { - projection: { _id: 1, username: 1, statusText: 1, roles: 1, name: 1, status: 1 }, - }); - if (!user) { - logger.debug(`User not found for uid: ${matrixUser.uid}`); - return; - } const statusMap = { online: UserStatus.ONLINE, @@ -61,7 +48,7 @@ export const edus = async (emitter: Emitter) => { const status = statusMap[data.presence] || UserStatus.OFFLINE; await Users.updateOne( - { _id: user._id }, + { _id: matrixUser._id }, { $set: { status, @@ -70,12 +57,12 @@ export const edus = async (emitter: Emitter) => { }, ); - const { _id, username, statusText, roles, name } = user; + const { _id, username, statusText, roles, name } = matrixUser; void api.broadcast('presence.status', { user: { status, _id, username, statusText, roles, name }, previousStatus: undefined, }); - logger.debug(`Updated presence for user ${matrixUser.uid} to ${status} from Matrix federation`); + logger.debug(`Updated presence for user ${matrixUser._id} to ${status} from Matrix federation`); } catch (error) { logger.error('Error handling Matrix presence event:', error); } diff --git a/ee/packages/federation-matrix/src/events/invite.ts b/ee/packages/federation-matrix/src/events/invite.ts index 225b475f9bbf6..7629ba9a510dd 100644 --- a/ee/packages/federation-matrix/src/events/invite.ts +++ b/ee/packages/federation-matrix/src/events/invite.ts @@ -2,11 +2,11 @@ import type { HomeserverEventSignatures } from '@hs/federation-sdk'; import { Room } from '@rocket.chat/core-services'; import { UserStatus } from '@rocket.chat/core-typings'; import type { Emitter } from '@rocket.chat/emitter'; -import { MatrixBridgedRoom, MatrixBridgedUser, Users } from '@rocket.chat/models'; +import { Rooms, Users } from '@rocket.chat/models'; export function invite(emitter: Emitter) { emitter.on('homeserver.matrix.accept-invite', async (data) => { - const room = await MatrixBridgedRoom.findOne({ mri: data.room_id }); + const room = await Rooms.findOne({ 'federation.mrid': data.room_id }); if (!room) { console.warn(`No bridged room found for room_id: ${data.room_id}`); return; @@ -15,10 +15,15 @@ export function invite(emitter: Emitter) { const internalUsername = data.sender; const localUser = await Users.findOneByUsername(internalUsername); if (localUser) { - await Room.addUserToRoom(room.rid, localUser); + await Room.addUserToRoom(room._id, localUser); return; } + const [, serverName] = data.sender.split(':'); + if (!serverName) { + throw new Error('Invalid sender format, missing server name'); + } + const { insertedId } = await Users.insertOne({ username: internalUsername, type: 'user', @@ -32,19 +37,16 @@ export function invite(emitter: Emitter) { federated: true, federation: { version: 1, + mui: data.sender, + origin: serverName, }, }); - const serverName = data.sender.split(':')[1] || 'unknown'; - const bridgedUser = await MatrixBridgedUser.findOne({ mui: data.sender }); - if (!bridgedUser) { - await MatrixBridgedUser.createOrUpdateByLocalId(insertedId, data.sender, true, serverName); - } const user = await Users.findOneById(insertedId); if (!user) { console.warn(`User with ID ${insertedId} not found after insertion`); return; } - await Room.addUserToRoom(room.rid, user); + await Room.addUserToRoom(room._id, user); }); } diff --git a/ee/packages/federation-matrix/src/events/member.ts b/ee/packages/federation-matrix/src/events/member.ts index 91f930dd77ab0..e5ce0e557b5c5 100644 --- a/ee/packages/federation-matrix/src/events/member.ts +++ b/ee/packages/federation-matrix/src/events/member.ts @@ -2,7 +2,7 @@ import type { HomeserverEventSignatures } from '@hs/federation-sdk'; import { Room } from '@rocket.chat/core-services'; import type { Emitter } from '@rocket.chat/emitter'; import { Logger } from '@rocket.chat/logger'; -import { MatrixBridgedRoom, MatrixBridgedUser, Users } from '@rocket.chat/models'; +import { Rooms, Users } from '@rocket.chat/models'; const logger = new Logger('federation-matrix:member'); @@ -15,44 +15,34 @@ export function member(emitter: Emitter) { return; } - const room = await MatrixBridgedRoom.findOne({ mri: data.room_id }); + const room = await Rooms.findOne({ 'federation.mrid': data.room_id }, { projection: { _id: 1 } }); if (!room) { logger.warn(`No bridged room found for Matrix room_id: ${data.room_id}`); return; } // state_key is the user affected by the membership change - const affectedMatrixUser = await MatrixBridgedUser.findOne({ mui: data.state_key }); - if (!affectedMatrixUser) { - logger.warn(`No bridged user found for Matrix user_id: ${data.state_key}`); - return; - } - - const affectedUser = await Users.findOneById(affectedMatrixUser.uid); + const affectedUser = await Users.findOne({ 'federation.mui': data.state_key }); if (!affectedUser) { - logger.error(`No Rocket.Chat user found for bridged user: ${affectedMatrixUser.uid}`); + logger.error(`No Rocket.Chat user found for bridged user: ${data.state_key}`); return; } // Check if this is a kick (sender != state_key) or voluntary leave (sender == state_key) if (data.sender === data.state_key) { // Voluntary leave - await Room.removeUserFromRoom(room.rid, affectedUser); - logger.info(`User ${affectedUser.username} left room ${room.rid} via Matrix federation`); + await Room.removeUserFromRoom(room._id, affectedUser); + logger.info(`User ${affectedUser.username} left room ${room._id} via Matrix federation`); } else { // Kick - find who kicked - const kickerMatrixUser = await MatrixBridgedUser.findOne({ mui: data.sender }); - let kickerUser = null; - if (kickerMatrixUser) { - kickerUser = await Users.findOneById(kickerMatrixUser.uid); - } + const kickerUser = await Users.findOne({ 'federation.mui': data.sender }); - await Room.removeUserFromRoom(room.rid, affectedUser, { + await Room.removeUserFromRoom(room._id, affectedUser, { byUser: kickerUser || { _id: 'matrix.federation', username: 'Matrix User' }, }); const reasonText = data.content.reason ? ` Reason: ${data.content.reason}` : ''; - logger.info(`User ${affectedUser.username} was kicked from room ${room.rid} by ${data.sender} via Matrix federation.${reasonText}`); + logger.info(`User ${affectedUser.username} was kicked from room ${room._id} by ${data.sender} via Matrix federation.${reasonText}`); } } catch (error) { logger.error('Failed to process Matrix membership event:', error); diff --git a/ee/packages/federation-matrix/src/events/message.ts b/ee/packages/federation-matrix/src/events/message.ts index 3f3d61a226c0d..243680d566310 100644 --- a/ee/packages/federation-matrix/src/events/message.ts +++ b/ee/packages/federation-matrix/src/events/message.ts @@ -2,11 +2,10 @@ import type { FileMessageType, MessageType } from '@hs/core'; import type { HomeserverEventSignatures } from '@hs/federation-sdk'; import type { EventID } from '@hs/room'; import { FederationMatrix, Message, MeteorService } from '@rocket.chat/core-services'; -import { UserStatus } from '@rocket.chat/core-typings'; import type { IUser, IRoom } from '@rocket.chat/core-typings'; import type { Emitter } from '@rocket.chat/emitter'; import { Logger } from '@rocket.chat/logger'; -import { Users, MatrixBridgedUser, MatrixBridgedRoom, Rooms, Subscriptions, Messages } from '@rocket.chat/models'; +import { Users, Rooms, Messages } from '@rocket.chat/models'; import { fileTypes } from '../FederationMatrix'; import { toInternalMessageFormat, toInternalQuoteMessageFormat } from '../helpers/message.parsers'; @@ -14,102 +13,6 @@ import { MatrixMediaService } from '../services/MatrixMediaService'; const logger = new Logger('federation-matrix:message'); -async function getOrCreateFederatedUser(matrixUserId: string): Promise { - const [userPart, domain] = matrixUserId.split(':'); - if (!userPart || !domain) { - logger.error('Invalid Matrix sender ID format:', matrixUserId); - return null; - } - const username = userPart.substring(1); - - const user = await Users.findOneByUsername(matrixUserId); - if (user) { - await MatrixBridgedUser.createOrUpdateByLocalId(user._id, matrixUserId, false, domain); - return user; - } - - logger.info('Creating new federated user:', { username: matrixUserId, externalId: matrixUserId }); - - const userData = { - username: matrixUserId, - name: username, // TODO: Fetch display name from Matrix profile - type: 'user', - status: UserStatus.ONLINE, - active: true, - roles: ['user'], - requirePasswordChange: false, - federated: true, - federation: { - version: 1, - }, - createdAt: new Date(), - _updatedAt: new Date(), - }; - - const { insertedId } = await Users.insertOne(userData); - - await MatrixBridgedUser.createOrUpdateByLocalId( - insertedId, - matrixUserId, - true, // isRemote = true for external Matrix users - domain, - ); - - const newUser = await Users.findOneById(insertedId); - if (!newUser) { - logger.error('Failed to create user:', matrixUserId); - return null; - } - - logger.info('Successfully created federated user:', { userId: newUser._id, username }); - - return newUser; -} - -async function getRoomAndEnsureSubscription(matrixRoomId: string, user: IUser): Promise { - const internalRoomId = await MatrixBridgedRoom.getLocalRoomId(matrixRoomId); - if (!internalRoomId) { - logger.error('Room not found in bridge mapping:', matrixRoomId); - // TODO: Handle room creation for unknown federated rooms - return null; - } - - const room = await Rooms.findOneById(internalRoomId); - if (!room) { - logger.error('Room not found:', internalRoomId); - return null; - } - - if (!room.federated) { - logger.error('Room is not marked as federated:', { roomId: room._id, matrixRoomId }); - // TODO: Should we update the room to be federated? - } - - const existingSubscription = await Subscriptions.findOneByRoomIdAndUserId(room._id, user._id); - - if (existingSubscription) { - return room; - } - - logger.info('Creating subscription for federated user in room:', { userId: user._id, roomId: room._id }); - - const { insertedId } = await Subscriptions.createWithRoomAndUser(room, user, { - ts: new Date(), - open: false, - alert: false, - unread: 0, - userMentions: 0, - groupMentions: 0, - }); - - if (insertedId) { - logger.debug('Successfully created subscription:', insertedId); - // TODO: Import and use notifyOnSubscriptionChangedById if needed - } - - return room; -} - async function getThreadMessageId(threadRootEventId: EventID): Promise<{ tmid: string; tshow: boolean } | undefined> { const threadRootMessage = await Messages.findOneByFederationId(threadRootEventId); if (!threadRootMessage) { @@ -229,14 +132,15 @@ export function message(emitter: Emitter, serverName: return; } - const user = await getOrCreateFederatedUser(data.sender); + // at this point we know for sure the user already exists + const user = await Users.findOne({ 'federation.mui': data.sender }); if (!user) { - return; + throw new Error(`User not found for sender: ${data.sender}`); } - const room = await getRoomAndEnsureSubscription(data.room_id, user); + const room = await Rooms.findOne({ 'federation.mrid': data.room_id }); if (!room) { - return; + throw new Error(`No mapped room found for room_id: ${data.room_id}`); } const relation = content['m.relates_to']; diff --git a/ee/packages/federation-matrix/src/events/room.ts b/ee/packages/federation-matrix/src/events/room.ts index a083485f99751..7e09c60053df4 100644 --- a/ee/packages/federation-matrix/src/events/room.ts +++ b/ee/packages/federation-matrix/src/events/room.ts @@ -1,64 +1,59 @@ import type { HomeserverEventSignatures } from '@hs/federation-sdk'; import { Room } from '@rocket.chat/core-services'; import type { Emitter } from '@rocket.chat/emitter'; -import { MatrixBridgedRoom, MatrixBridgedUser, Rooms } from '@rocket.chat/models'; +import { Rooms, Users } from '@rocket.chat/models'; export function room(emitter: Emitter) { emitter.on('homeserver.matrix.room.name', async (data) => { const { room_id: roomId, name, user_id: userId } = data; - const localRoomId = await MatrixBridgedRoom.getLocalRoomId(roomId); + const localRoomId = await Rooms.findOne({ 'federation.mrid': roomId }, { projection: { _id: 1 } }); if (!localRoomId) { throw new Error('mapped room not found'); } - const localUserId = await MatrixBridgedUser.getLocalUserIdByExternalId(userId); + const localUserId = await Users.findOne({ 'federation.mui': userId }, { projection: { _id: 1 } }); if (!localUserId) { throw new Error('mapped user not found'); } - await Room.saveRoomName(localRoomId, localUserId, name); + await Room.saveRoomName(localRoomId._id, localUserId._id, name); }); emitter.on('homeserver.matrix.room.topic', async (data) => { const { room_id: roomId, topic, user_id: userId } = data; - const localRoomId = await MatrixBridgedRoom.getLocalRoomId(roomId); + const localRoomId = await Rooms.findOne({ 'federation.mrid': roomId }, { projection: { _id: 1 } }); if (!localRoomId) { throw new Error('mapped room not found'); } - const localUserId = await MatrixBridgedUser.getLocalUserIdByExternalId(userId); + const localUserId = await Users.findOne({ 'federation.mui': userId }, { projection: { _id: 1 } }); if (!localUserId) { throw new Error('mapped user not found'); } - await Room.saveRoomTopic(localRoomId, topic, { _id: localUserId, username: userId }); + await Room.saveRoomTopic(localRoomId._id, topic, { _id: localUserId._id, username: userId }); }); emitter.on('homeserver.matrix.room.role', async (data) => { const { room_id: roomId, user_id: userId, sender_id: senderId, role } = data; - const localRoomId = await MatrixBridgedRoom.getLocalRoomId(roomId); + const localRoomId = await Rooms.findOne({ 'federation.mrid': roomId }, { projection: { _id: 1 } }); if (!localRoomId) { throw new Error('mapped room not found'); } - const localRoom = await Rooms.findOneById(localRoomId); - if (!localRoom) { - throw new Error('mapped room object not found'); - } - - const localUserId = await MatrixBridgedUser.getLocalUserIdByExternalId(userId); + const localUserId = await Users.findOne({ 'federation.mui': userId }, { projection: { _id: 1 } }); if (!localUserId) { throw new Error('mapped user not found'); } - const localSenderId = await MatrixBridgedUser.getLocalUserIdByExternalId(senderId); + const localSenderId = await Users.findOne({ 'federation.mui': senderId }, { projection: { _id: 1 } }); if (!localSenderId) { throw new Error('mapped user not found'); } - await Room.addUserRoleRoomScoped(localSenderId, localUserId, localRoomId, role); + await Room.addUserRoleRoomScoped(localSenderId._id, localUserId._id, localRoomId._id, role); }); } diff --git a/ee/packages/federation-matrix/src/helpers/identifiers.ts b/ee/packages/federation-matrix/src/helpers/identifiers.ts deleted file mode 100644 index 781d3831194cc..0000000000000 --- a/ee/packages/federation-matrix/src/helpers/identifiers.ts +++ /dev/null @@ -1,81 +0,0 @@ -import type { IUser, UserStatus } from '@rocket.chat/core-typings'; -import { MatrixBridgedUser, Users } from '@rocket.chat/models'; - -export const convertExternalUserIdToInternalUsername = (externalUserId: string): string => externalUserId.replace(/@/g, ''); - -export const getLocalUsernameForMatrixUserIdToSave = (matrixUserId: string): string => matrixUserId; // TODO: decide on whether to keep @ or not - -export const getLocalNameForMatrixUserIdToSave = (matrixUserId: string): string => - matrixUserId.split(':').shift()?.replace(/@/g, '') as string; - -// can have none if in case of local user -export const getExternalUserIdForLocalUserToSave = (user: IUser): string | undefined => - user.federated ? /* remote user, already has @ according to the function above */ (user.username as string) : undefined; - -export async function getLocalUserForExternalUserId(externalUserId: string): Promise { - const localUserId = await MatrixBridgedUser.getExternalUserIdByLocalUserId(externalUserId); - if (!localUserId) { - return null; - } - - const user = await Users.findOneById(localUserId); - if (!user) { - throw new Error('user not found although should have as it is in mapping not processing invite'); - } - - return user; -} - -export async function getExternalUserIdForLocalUser(user: IUser): Promise { - const externalUserId = await MatrixBridgedUser.getExternalUserIdByLocalUserId(user._id); - return externalUserId; -} - -export async function saveExternalUserIdForLocalUser(user: IUser, externalUserId: string): Promise { - const matrixDomain = externalUserId.split(':')[1]; - await MatrixBridgedUser.createOrUpdateByLocalId(user._id, externalUserId, true, matrixDomain); -} - -export async function saveLocalUserForExternalUserId(externalUserId: string, origin: string): Promise { - /* - * using from ------- - public getStorageRepresentation(): Readonly { - return { - _id: this.internalId, - username: this.internalReference.username || '', - type: this.internalReference.type, - status: this.internalReference.status, - active: this.internalReference.active, - roles: this.internalReference.roles, - name: this.internalReference.name, - requirePasswordChange: this.internalReference.requirePasswordChange, - createdAt: new Date(), - _updatedAt: new Date(), - federated: this.isRemote(), - }; - } - */ - - const user = { - // let the _id auto generate we deal with usernames - username: getLocalUsernameForMatrixUserIdToSave(externalUserId), - type: 'user', - status: 'online' as UserStatus, - active: true, - roles: ['user'], - name: getLocalNameForMatrixUserIdToSave(externalUserId), - requirePasswordChange: false, - federated: true, - federation: { - version: 1, - }, - createdAt: new Date(), - _updatedAt: new Date(), - }; - - const { insertedId } = await Users.insertOne(user); - - await MatrixBridgedUser.createOrUpdateByLocalId(insertedId, externalUserId, true, origin); - - return insertedId; -} diff --git a/packages/core-services/src/types/IFederationMatrixService.ts b/packages/core-services/src/types/IFederationMatrixService.ts index 9be6df23fb8bb..be59a924d09df 100644 --- a/packages/core-services/src/types/IFederationMatrixService.ts +++ b/packages/core-services/src/types/IFederationMatrixService.ts @@ -1,4 +1,4 @@ -import type { AtLeast, IMessage, IRoomFederated, IUser } from '@rocket.chat/core-typings'; +import type { IMessage, IRoomFederated, IRoomNativeFederated, IUser } from '@rocket.chat/core-typings'; import type { Router } from '@rocket.chat/http-router'; export interface IRouteContext { @@ -15,21 +15,21 @@ export interface IFederationMatrixService { matrix: Router<'/_matrix'>; wellKnown: Router<'/.well-known'>; }; - createRoom(room: IRoomFederated, owner: IUser, members: string[]): Promise; + createRoom(room: IRoomFederated, owner: IUser, members: string[]): Promise<{ room_id: string; event_id: string }>; ensureFederatedUsersExistLocally(members: (IUser | string)[]): Promise; createDirectMessageRoom(room: IRoomFederated, members: IUser[], creatorId: IUser['_id']): Promise; sendMessage(message: IMessage, room: IRoomFederated, user: IUser): Promise; - deleteMessage(message: IMessage): Promise; + deleteMessage(matrixRoomId: string, message: IMessage, uid: string): Promise; sendReaction(messageId: string, reaction: string, user: IUser): Promise; removeReaction(messageId: string, reaction: string, user: IUser, oldMessage: IMessage): Promise; getEventById(eventId: string): Promise; leaveRoom(rid: IRoomFederated['_id'], user: IUser): Promise; - kickUser(rid: IRoomFederated['_id'], removedUser: IUser, userWhoRemoved: IUser): Promise; - updateMessage(messageId: string, newContent: string, sender: AtLeast): Promise; - updateRoomName(rid: IRoomFederated['_id'], name: string, sender: string): Promise; - updateRoomTopic(rid: IRoomFederated['_id'], topic: string, sender: string): Promise; + kickUser(room: IRoomNativeFederated, removedUser: IUser, userWhoRemoved: IUser): Promise; + updateMessage(room: IRoomNativeFederated, message: IMessage): Promise; + updateRoomName(rid: string, displayName: string, user: IUser): Promise; + updateRoomTopic(room: IRoomNativeFederated, topic: string, user: IUser): Promise; addUserRoleRoomScoped( - rid: IRoomFederated['_id'], + room: IRoomNativeFederated, senderId: string, userId: string, role: 'moderator' | 'owner' | 'leader' | 'user', diff --git a/packages/core-typings/src/IRoom.ts b/packages/core-typings/src/IRoom.ts index 7f3ca6348c233..629ac5ee1e124 100644 --- a/packages/core-typings/src/IRoom.ts +++ b/packages/core-typings/src/IRoom.ts @@ -114,6 +114,8 @@ export interface IRoomFederated extends IRoom { export interface IRoomNativeFederated extends IRoomFederated { federation: { version: number; + // Matrix's room ID. Example: !XqJXqZxXqJXq:matrix.org + mrid: string; }; } diff --git a/packages/core-typings/src/IUser.ts b/packages/core-typings/src/IUser.ts index bab6a3e40b461..bd76e342b7a5d 100644 --- a/packages/core-typings/src/IUser.ts +++ b/packages/core-typings/src/IUser.ts @@ -228,6 +228,8 @@ export interface IUser extends IRocketChatRecord { // @deprecated federation?: { version?: number; + mui?: string; + origin?: string; avatarUrl?: string; searchedServerNames?: string[]; }; @@ -258,17 +260,20 @@ export interface IRegisterUser extends IUser { } export const isRegisterUser = (user: IUser): user is IRegisterUser => user.username !== undefined && user.name !== undefined; + export const isUserFederated = (user: Partial | Partial>) => 'federated' in user && user.federated === true; export interface IUserNativeFederated extends IUser { federated: true; federation: { version: number; + mui: string; + origin: string; }; } export const isUserNativeFederated = (user: Partial): user is IUserNativeFederated => - isUserFederated(user) && 'federation' in user && typeof user.federation?.version === 'string'; + isUserFederated(user) && 'federation' in user && typeof user.federation?.version === 'number'; export type IUserDataEvent = { id: unknown; diff --git a/packages/core-typings/src/federation/IMatrixBridgedRoom.ts b/packages/core-typings/src/federation/IMatrixBridgedRoom.ts deleted file mode 100644 index 635ea94090dbf..0000000000000 --- a/packages/core-typings/src/federation/IMatrixBridgedRoom.ts +++ /dev/null @@ -1,6 +0,0 @@ -import type { IRocketChatRecord } from '../IRocketChatRecord'; - -export interface IMatrixBridgedRoom extends IRocketChatRecord { - rid: string; - mri: string; -} diff --git a/packages/core-typings/src/federation/IMatrixBridgedUser.ts b/packages/core-typings/src/federation/IMatrixBridgedUser.ts deleted file mode 100644 index dfc805158c86c..0000000000000 --- a/packages/core-typings/src/federation/IMatrixBridgedUser.ts +++ /dev/null @@ -1,8 +0,0 @@ -import type { IRocketChatRecord } from '../IRocketChatRecord'; - -export interface IMatrixBridgedUser extends IRocketChatRecord { - uid: string; - mui: string; - remote: boolean; - fromServer?: string; -} diff --git a/packages/core-typings/src/federation/index.ts b/packages/core-typings/src/federation/index.ts index f3dbfe7778c66..5b98253d935b4 100644 --- a/packages/core-typings/src/federation/index.ts +++ b/packages/core-typings/src/federation/index.ts @@ -1,4 +1 @@ -export * from './IMatrixBridgedRoom'; -export * from './IMatrixBridgedUser'; - export * from './v1'; diff --git a/packages/model-typings/src/index.ts b/packages/model-typings/src/index.ts index 75c51b19ecb1a..61c2876df0498 100644 --- a/packages/model-typings/src/index.ts +++ b/packages/model-typings/src/index.ts @@ -69,8 +69,6 @@ export * from './models/IUsersSessionsModel'; export * from './models/IVideoConferenceModel'; export * from './models/IVoipRoomModel'; export * from './models/IWebdavAccountsModel'; -export * from './models/IMatrixBridgedRoomModel'; -export * from './models/IMatrixBridgedUserModel'; export * from './models/ICalendarEventModel'; export * from './models/IOmnichannelServiceLevelAgreementsModel'; export * from './models/IAppLogsModel'; diff --git a/packages/model-typings/src/models/IMatrixBridgedRoomModel.ts b/packages/model-typings/src/models/IMatrixBridgedRoomModel.ts deleted file mode 100644 index 538e05336a1ad..0000000000000 --- a/packages/model-typings/src/models/IMatrixBridgedRoomModel.ts +++ /dev/null @@ -1,11 +0,0 @@ -import type { IMatrixBridgedRoom } from '@rocket.chat/core-typings'; - -import type { IBaseModel } from './IBaseModel'; - -export interface IMatrixBridgedRoomModel extends IBaseModel { - getExternalRoomId(localRoomId: string): Promise; - getLocalRoomId(externalRoomId: string): Promise; - removeByLocalRoomId(localRoomId: string): Promise; - createOrUpdateByLocalRoomId(localRoomId: string, externalRoomId: string, fromServer: string): Promise; - getExternalServerConnectedExcluding(exclude: string): Promise; -} diff --git a/packages/model-typings/src/models/IMatrixBridgedUserModel.ts b/packages/model-typings/src/models/IMatrixBridgedUserModel.ts deleted file mode 100644 index 02ad87604fee0..0000000000000 --- a/packages/model-typings/src/models/IMatrixBridgedUserModel.ts +++ /dev/null @@ -1,12 +0,0 @@ -import type { IMatrixBridgedUser } from '@rocket.chat/core-typings'; - -import type { IBaseModel } from './IBaseModel'; - -export interface IMatrixBridgedUserModel extends IBaseModel { - getExternalUserIdByLocalUserId(localUserId: string): Promise; - getBridgedUserByExternalUserId(externalUserId: string): Promise; - getLocalUserIdByExternalId(externalUserId: string): Promise; - getLocalUsersByExternalIds(externalUserIds: string[]): Promise; - getBridgedUserByLocalId(localUserId: string): Promise; - createOrUpdateByLocalId(localUserId: string, externalUserId: string, remote: boolean, fromServer: string): Promise; -} diff --git a/packages/model-typings/src/models/IRoomsModel.ts b/packages/model-typings/src/models/IRoomsModel.ts index 86e6e5d45f4c3..f652657b5543f 100644 --- a/packages/model-typings/src/models/IRoomsModel.ts +++ b/packages/model-typings/src/models/IRoomsModel.ts @@ -124,7 +124,7 @@ export interface IRoomsModel extends IBaseModel { findByBroadcast(options?: FindOptions): FindCursor; - setAsFederated(roomId: IRoom['_id']): Promise; + setAsFederated(roomId: IRoom['_id'], { mrid, origin }: { mrid: string; origin: string }): Promise; setRoomTypeById(roomId: IRoom['_id'], roomType: IRoom['t']): Promise; @@ -313,4 +313,5 @@ export interface IRoomsModel extends IBaseModel { countByE2E(options?: CountDocumentsOptions): Promise; markRolePrioritesCreatedForRoom(rid: IRoom['_id'], version: number): Promise; hasCreatedRolePrioritiesForRoom(rid: IRoom['_id'], syncVersion: number): Promise; + countDistinctFederationRoomsExcluding(serverNames?: string[]): Promise; } diff --git a/packages/models/src/index.ts b/packages/models/src/index.ts index 1afd7264e54e1..247105b4a6430 100644 --- a/packages/models/src/index.ts +++ b/packages/models/src/index.ts @@ -74,8 +74,6 @@ import type { IVideoConferenceModel, IVoipRoomModel, IWebdavAccountsModel, - IMatrixBridgedRoomModel, - IMatrixBridgedUserModel, ICalendarEventModel, IOmnichannelServiceLevelAgreementsModel, IAppsModel, @@ -116,8 +114,6 @@ import { TeamRaw, UsersRaw, UsersSessionsRaw, - MatrixBridgedUserRaw, - MatrixBridgedRoomRaw, } from './modelClasses'; import { proxify, registerModel } from './proxify'; @@ -213,8 +209,6 @@ export const UsersSessions = proxify('IUsersSessionsModel') export const VideoConference = proxify('IVideoConferenceModel'); export const VoipRoom = proxify('IVoipRoomModel'); export const WebdavAccounts = proxify('IWebdavAccountsModel'); -export const MatrixBridgedRoom = proxify('IMatrixBridgedRoomModel'); -export const MatrixBridgedUser = proxify('IMatrixBridgedUserModel'); export const CalendarEvent = proxify('ICalendarEventModel'); export const OmnichannelServiceLevelAgreements = proxify( 'IOmnichannelServiceLevelAgreementsModel', @@ -258,8 +252,6 @@ export function registerServiceModels(db: Db, trash?: Collection new LivechatRoomsRaw(db)); registerModel('IUploadsModel', () => new UploadsRaw(db)); registerModel('ILivechatVisitorsModel', () => new LivechatVisitorsRaw(db)); - registerModel('IMatrixBridgedUserModel', () => new MatrixBridgedUserRaw(db)); - registerModel('IMatrixBridgedRoomModel', () => new MatrixBridgedRoomRaw(db)); } if (!dbWatchersDisabled) { diff --git a/packages/models/src/modelClasses.ts b/packages/models/src/modelClasses.ts index 429be82fd64a7..8ec0945bb4e49 100644 --- a/packages/models/src/modelClasses.ts +++ b/packages/models/src/modelClasses.ts @@ -68,8 +68,6 @@ export * from './models/UsersSessions'; export * from './models/VideoConference'; export * from './models/VoipRoom'; export * from './models/WebdavAccounts'; -export * from './models/MatrixBridgedRoom'; -export * from './models/MatrixBridgedUser'; export * from './models/CredentialTokens'; export * from './models/MessageReads'; export * from './models/CronHistoryModel'; diff --git a/packages/models/src/models/MatrixBridgedRoom.ts b/packages/models/src/models/MatrixBridgedRoom.ts deleted file mode 100644 index bca6150a096d4..0000000000000 --- a/packages/models/src/models/MatrixBridgedRoom.ts +++ /dev/null @@ -1,45 +0,0 @@ -import type { IMatrixBridgedRoom, RocketChatRecordDeleted } from '@rocket.chat/core-typings'; -import type { IMatrixBridgedRoomModel } from '@rocket.chat/model-typings'; -import type { Collection, Db, IndexDescription } from 'mongodb'; - -import { BaseRaw } from './BaseRaw'; - -export class MatrixBridgedRoomRaw extends BaseRaw implements IMatrixBridgedRoomModel { - constructor(db: Db, trash?: Collection>) { - super(db, 'matrix_bridged_rooms', trash); - } - - protected modelIndexes(): IndexDescription[] { - return [ - { key: { rid: 1 }, unique: true, sparse: true }, - { key: { mri: 1 }, unique: true, sparse: true }, - { key: { fromServer: 1 }, sparse: true }, - ]; - } - - async getExternalRoomId(localRoomId: string): Promise { - const bridgedRoom = await this.findOne({ rid: localRoomId }); - - return bridgedRoom ? bridgedRoom.mri : null; - } - - async getLocalRoomId(externalRoomId: string): Promise { - const bridgedRoom = await this.findOne({ mri: externalRoomId }); - - return bridgedRoom ? bridgedRoom.rid : null; - } - - async removeByLocalRoomId(localRoomId: string): Promise { - await this.deleteOne({ rid: localRoomId }); - } - - async createOrUpdateByLocalRoomId(localRoomId: string, externalRoomId: string, fromServer: string): Promise { - await this.updateOne({ rid: localRoomId }, { $set: { rid: localRoomId, mri: externalRoomId, fromServer } }, { upsert: true }); - } - - async getExternalServerConnectedExcluding(exclude: string): Promise { - const externalServers = await this.col.distinct('fromServer'); - - return externalServers.filter((serverName) => serverName !== exclude); - } -} diff --git a/packages/models/src/models/MatrixBridgedUser.ts b/packages/models/src/models/MatrixBridgedUser.ts deleted file mode 100644 index 37ee374f65cf1..0000000000000 --- a/packages/models/src/models/MatrixBridgedUser.ts +++ /dev/null @@ -1,60 +0,0 @@ -import type { IMatrixBridgedUser, RocketChatRecordDeleted } from '@rocket.chat/core-typings'; -import type { IMatrixBridgedUserModel } from '@rocket.chat/model-typings'; -import type { Collection, Db, IndexDescription } from 'mongodb'; - -import { BaseRaw } from './BaseRaw'; - -export class MatrixBridgedUserRaw extends BaseRaw implements IMatrixBridgedUserModel { - constructor(db: Db, trash?: Collection>) { - super(db, 'matrix_bridged_users', trash); - } - - protected modelIndexes(): IndexDescription[] { - return [ - { key: { uid: 1 }, unique: true, sparse: true }, - { key: { mui: 1 }, unique: true, sparse: true }, - { key: { fromServer: 1 }, sparse: true }, - ]; - } - - async getExternalUserIdByLocalUserId(localUserId: string): Promise { - const bridgedUser = await this.findOne({ uid: localUserId }); - - return bridgedUser ? bridgedUser.mui : null; - } - - async getBridgedUserByExternalUserId(externalUserId: string): Promise { - return this.findOne({ mui: externalUserId }); - } - - async getLocalUserIdByExternalId(externalUserId: string): Promise { - const bridgedUser = await this.findOne({ mui: externalUserId }); - - return bridgedUser ? bridgedUser.uid : null; - } - - async getLocalUsersByExternalIds(externalUserIds: string[]): Promise { - const bridgedUsers = await this.find({ mui: { $in: externalUserIds } }).toArray(); - - return bridgedUsers; - } - - async getBridgedUserByLocalId(localUserId: string): Promise { - return this.findOne({ uid: localUserId }); - } - - async createOrUpdateByLocalId(localUserId: string, externalUserId: string, remote: boolean, fromServer: string): Promise { - await this.updateOne( - { uid: localUserId }, - { - $set: { - uid: localUserId, - mui: externalUserId, - remote, - fromServer, - }, - }, - { upsert: true }, - ); - } -} diff --git a/packages/models/src/models/Rooms.ts b/packages/models/src/models/Rooms.ts index 8529cf674d8c1..43c01e46a057c 100644 --- a/packages/models/src/models/Rooms.ts +++ b/packages/models/src/models/Rooms.ts @@ -667,8 +667,8 @@ export class RoomsRaw extends BaseRaw implements IRoomsModel { ); } - setAsFederated(roomId: IRoom['_id']): Promise { - return this.updateOne({ _id: roomId }, { $set: { federated: true } }); + setAsFederated(roomId: IRoom['_id'], { mrid, origin }: { mrid: string; origin: string }): Promise { + return this.updateOne({ _id: roomId }, { $set: { 'federated': true, 'federation.mrid': mrid, 'federation.origin': origin } }); } setRoomTypeById(roomId: IRoom['_id'], roomType: IRoom['t']): Promise { @@ -1969,12 +1969,6 @@ export class RoomsRaw extends BaseRaw implements IRoomsModel { _id: (await this.insertOne(room)).insertedId, _updatedAt: new Date(), ...room, - ...(room.federated && { - federated: true, - federation: { - version: 1, - }, - }), }; return newRoom; @@ -2219,4 +2213,9 @@ export class RoomsRaw extends BaseRaw implements IRoomsModel { async hasCreatedRolePrioritiesForRoom(rid: IRoom['_id'], syncVersion: number) { return this.countDocuments({ _id: rid, rolePrioritiesCreated: syncVersion }); } + + async countDistinctFederationRoomsExcluding(_serverNames: string[] = []): Promise { + // TODO implement + return []; + } } diff --git a/packages/models/src/models/Subscriptions.ts b/packages/models/src/models/Subscriptions.ts index dba1abc564daa..dbd72d644cf11 100644 --- a/packages/models/src/models/Subscriptions.ts +++ b/packages/models/src/models/Subscriptions.ts @@ -2077,18 +2077,10 @@ export class SubscriptionsRaw extends BaseRaw implements ISubscri 'room.federated': true, }, }, - { - $lookup: { - from: 'rocketchat_matrix_bridged_rooms', - localField: 'rid', - foreignField: 'rid', - as: 'matrixRoom', - }, - }, { $project: { _id: '$rid', - externalRoomId: { $arrayElemAt: ['$matrixRoom.mri', 0] }, + externalRoomId: { $arrayElemAt: ['$room.federation.mrid', 0] }, }, }, ]);