Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 1 addition & 3 deletions apps/meteor/app/lib/server/functions/createDirectRoom.ts
Original file line number Diff line number Diff line change
Expand Up @@ -71,9 +71,7 @@ export async function createDirectRoom(

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();
const roomMembers = await Users.findUsersByUsernames(membersUsernames).toArray();
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion | 🟠 Major

🧩 Analysis chain

Verify necessity of fetching full user documents.

The selective projection was removed, causing all user fields to be fetched instead of only _id, name, username, settings, and customFields. While the PR context mentions federation fixes, the downstream code (lines 76-79, 135-174) only uses _id, name, username, and customFields.

Run the following script to check if federation-related fields (e.g., federated, isRemote, federatedId) are accessed elsewhere in the call chain:

Additionally, please search the web to confirm whether this aligns with Matrix Federation specification requirements for user data:


🏁 Script executed:

#!/bin/bash
# Description: Search for federation field usage in callbacks and related files

# Check afterCreateDirectRoom callback implementations for federation field usage
rg -n -C3 'afterCreateDirectRoom' --type=ts

# Check for federation-related field access patterns in room/subscription creation
rg -nP --type=ts '\b(federated|isRemote|federatedId|federation)\b' -g '*createDirectRoom*' -g '*callbacks*'

Length of output: 2124


🌐 Web query:

Matrix Federation user data requirements for direct messages

💡 Result:

Short answer — what gets shared over federation for direct/1:1 Matrix messages:

  • If it's a room-based 1:1 DM: room PDUs are sent to participant homeservers. That includes the event envelope (room_id, event type, sender user_id, event_id, origin/origin_server_ts, prev_events/auth_events, content). If E2EE is used the content is encrypted, but metadata (room membership, sender ID, room_id, timestamps, and any unencrypted state) is still shared. [1][2]

  • If it's a true send-to-device message (e.g., Olm/Device key/one-to-one keying): servers exchange m.direct_to_device EDUs containing sender user_id, event type, message_id and the encrypted payload — delivered to the target device(s) on the remote homeserver. These are not PDUs in a room graph but are federated EDUs. [1][2]

  • Other federated data related to DMs: profile/profile queries (displayname, avatar_url) are available via federation queries; presence or presence-invite EDUs may be exchanged; media referenced by mxc:// URIs requires homeserver-to-homeserver fetches. Note that redactions remove content but usernames/IDs and replicated copies on remote homeservers can remain. [1][2][3]

Notes: end-to-end encryption protects message content from remote servers only if the client used E2EE; servers still see routing/room/participant metadata. [1][2][3]

Sources: Matrix federation spec (send-to-device / PDUs) and Matrix privacy notice. [1][2][3]


Restore selective projection on Users.findUsersByUsernames
Reinstate { projection: { _id: 1, name: 1, username: 1, settings: 1, customFields: 1 } } on line 74 to avoid fetching full user documents when only those fields are used (callbacks and subscription creation here don’t require any additional properties). This also aligns with the explicit projection applied later at lines 153–156.

🤖 Prompt for AI Agents
In apps/meteor/app/lib/server/functions/createDirectRoom.ts around line 74, the
Users.findUsersByUsernames call currently fetches full user documents; restore
the selective projection by passing { projection: { _id: 1, name: 1, username:
1, settings: 1, customFields: 1 } } as the second argument so only those fields
are returned (matching the explicit projection used later at lines 153–156) to
avoid fetching unnecessary data.

// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
const sortedMembers = roomMembers.sort((u1, u2) => (u1.name! || u1.username!).localeCompare(u2.name! || u2.username!));

Expand Down
8 changes: 0 additions & 8 deletions apps/meteor/app/lib/server/methods/addUsersToRoom.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,10 @@
import { api } from '@rocket.chat/core-services';
import type { IUser } from '@rocket.chat/core-typings';
import { isRoomFederated } from '@rocket.chat/core-typings';
import type { ServerMethods } from '@rocket.chat/ddp-client';
import { Subscriptions, Users, Rooms } from '@rocket.chat/models';
import { Match } from 'meteor/check';
import { Meteor } from 'meteor/meteor';

import { callbacks } from '../../../../lib/callbacks';
import { i18n } from '../../../../server/lib/i18n';
import { hasPermissionAsync } from '../../../authorization/server/functions/hasPermission';
import { addUserToRoom } from '../functions/addUserToRoom';
Expand Down Expand Up @@ -79,12 +77,6 @@ export const addUsersToRoomMethod = async (userId: string, data: { rid: string;
});
}

// Validate each user, then add to room
if (isRoomFederated(room)) {
await callbacks.run('federation.onAddUsersToRoom', { invitees: data.users, inviter: user }, room);
return true;
}

await Promise.all(
data.users.map(async (username) => {
const newUser = await Users.findOneByUsernameIgnoringCase(username);
Expand Down
15 changes: 0 additions & 15 deletions apps/meteor/ee/server/hooks/federation/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -70,21 +70,6 @@ callbacks.add(
'native-federation-after-delete-message',
);

callbacks.add(
'federation.onAddUsersToRoom',
async ({ invitees, inviter }, room) => {
if (FederationActions.shouldPerformFederationAction(room)) {
await FederationMatrix.inviteUsersToRoom(
room,
invitees.map((invitee) => (typeof invitee === 'string' ? invitee : invitee.username)).filter((v) => v != null),
inviter,
);
}
},
callbacks.priority.MEDIUM,
'native-federation-on-add-users-to-room ',
);

beforeAddUserToRoom.add(
async ({ user, inviter }, room) => {
if (!user.username || !inviter) {
Expand Down
2 changes: 0 additions & 2 deletions apps/meteor/lib/callbacks.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,6 @@ import type {
VideoConference,
OEmbedMeta,
OEmbedUrlContent,
Username,
IOmnichannelRoom,
ILivechatTag,
ILivechatTagRecord,
Expand Down Expand Up @@ -85,7 +84,6 @@ interface EventLikeCallbackSignatures {
message: IMessage,
params: { user: IUser; reaction: string; shouldReact: boolean; oldMessage: IMessage; room: IRoom },
) => void;
'federation.onAddUsersToRoom': (params: { invitees: IUser[] | Username[]; inviter: IUser }, room: IRoom) => void;
'onJoinVideoConference': (callId: VideoConference['_id'], userId?: IUser['_id']) => Promise<void>;
'usernameSet': () => void;
'beforeJoinRoom': (user: IUser, room: IRoom) => void;
Expand Down
2 changes: 1 addition & 1 deletion apps/meteor/server/methods/addRoomModerator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ export const addRoomModerator = async (fromUserId: IUser['_id'], rid: IRoom['_id
check(rid, String);
check(userId, String);

const room = await Rooms.findOneById(rid, { projection: { t: 1, federated: 1 } });
const room = await Rooms.findOneById(rid, { projection: { t: 1, federated: 1, federation: 1 } });
if (!room) {
throw new Meteor.Error('error-invalid-room', 'Invalid room', {
method: 'addRoomModerator',
Expand Down
2 changes: 1 addition & 1 deletion apps/meteor/server/methods/addRoomOwner.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ export const addRoomOwner = async (fromUserId: IUser['_id'], rid: IRoom['_id'],
check(rid, String);
check(userId, String);

const room = await Rooms.findOneById(rid, { projection: { t: 1, federated: 1 } });
const room = await Rooms.findOneById(rid, { projection: { t: 1, federated: 1, federation: 1 } });
if (!room) {
throw new Meteor.Error('error-invalid-room', 'Invalid room', {
method: 'addRoomOwner',
Expand Down
2 changes: 1 addition & 1 deletion apps/meteor/server/methods/removeRoomModerator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ export const removeRoomModerator = async (fromUserId: IUser['_id'], rid: IRoom['
check(rid, String);
check(userId, String);

const room = await Rooms.findOneById(rid, { projection: { t: 1, federated: 1 } });
const room = await Rooms.findOneById(rid, { projection: { t: 1, federated: 1, federation: 1 } });
if (!room) {
throw new Meteor.Error('error-invalid-room', 'Invalid room', {
method: 'removeRoomModerator',
Expand Down
2 changes: 1 addition & 1 deletion apps/meteor/server/methods/removeRoomOwner.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ export const removeRoomOwner = async (fromUserId: string, rid: string, userId: s
check(rid, String);
check(userId, String);

const room = await Rooms.findOneById(rid, { projection: { t: 1, federated: 1 } });
const room = await Rooms.findOneById(rid, { projection: { t: 1, federated: 1, federation: 1 } });
if (!room) {
throw new Meteor.Error('error-invalid-room', 'Invalid room', {
method: 'removeRoomOwner',
Expand Down
Loading
Loading