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
6 changes: 3 additions & 3 deletions apps/meteor/app/lib/server/functions/createDirectRoom.ts
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ const getName = (members: IUser[]): string => members.map(({ username }) => user

export async function createDirectRoom(
members: IUser[] | string[],
roomExtraData = {},
roomExtraData: Partial<IRoom> = {},
options: {
creator?: string;
subscriptionExtra?: ISubscriptionExtraData;
Expand All @@ -69,6 +69,8 @@ export async function createDirectRoom(
})
.filter(isTruthy);

await callbacks.run('beforeCreateDirectRoom', membersUsernames, roomExtraData);

const roomMembers: IUser[] = await Users.findUsersByUsernames(membersUsernames, {
projection: { _id: 1, name: 1, username: 1, settings: 1, customFields: 1 },
}).toArray();
Expand Down Expand Up @@ -97,8 +99,6 @@ export async function createDirectRoom(
...roomExtraData,
};

await callbacks.run('beforeCreateDirectRoom', members, roomInfo);

if (isNewRoom) {
const tmpRoom: { _USERNAMES?: (string | undefined)[] } & typeof roomInfo = {
...roomInfo,
Expand Down
9 changes: 8 additions & 1 deletion apps/meteor/app/lib/server/functions/createRoom.ts
Original file line number Diff line number Diff line change
Expand Up @@ -134,9 +134,16 @@ export const createRoom = async <T extends RoomType>(
> => {
const { teamId, ...optionalExtraData } = roomExtraData || ({} as IRoom);

const hasFederatedMembers = members.some((member) => {
if (typeof member === 'string') {
return member.includes(':') && member.includes('@');
}
return member.username?.includes(':') && member.username?.includes('@');
});

Comment on lines +137 to +143
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

Broaden federated member detection (don’t require '@')

Requiring both “@” and “:” misses valid inputs like user:domain. This can prevent extraData.federated from being set and skip federation handling.

Apply this diff:

-	const hasFederatedMembers = members.some((member) => {
-		if (typeof member === 'string') {
-			return member.includes(':') && member.includes('@');
-		}
-		return member.username?.includes(':') && member.username?.includes('@');
-	});
+	const hasFederatedMembers = members.some((member) => {
+		const username = typeof member === 'string' ? member : member.username;
+		return typeof username === 'string' && username.includes(':');
+	});
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
const hasFederatedMembers = members.some((member) => {
if (typeof member === 'string') {
return member.includes(':') && member.includes('@');
}
return member.username?.includes(':') && member.username?.includes('@');
});
const hasFederatedMembers = members.some((member) => {
const username = typeof member === 'string' ? member : member.username;
return typeof username === 'string' && username.includes(':');
});
🤖 Prompt for AI Agents
In apps/meteor/app/lib/server/functions/createRoom.ts around lines 137 to 143,
the federated-member detection currently requires both ':' and '@', which misses
valid forms like "user:domain"; change the predicate to detect federation if the
string (or member.username) contains ':' OR contains '@' (use || instead of &&)
so hasFederatedMembers returns true for inputs like "user:domain" as well as
"@user:domain" or "user@domain".

const extraData = {
...optionalExtraData,
...(optionalExtraData.federated && {
...((hasFederatedMembers || optionalExtraData.federated) && {
federated: true,
federation: {
version: 1,
Expand Down
2 changes: 1 addition & 1 deletion apps/meteor/lib/callbacks.ts
Original file line number Diff line number Diff line change
Expand Up @@ -78,7 +78,7 @@ interface EventLikeCallbackSignatures {
options?: ICreateRoomOptions;
},
) => void;
'beforeCreateDirectRoom': (members: IUser[], room: IRoom) => void;
'beforeCreateDirectRoom': (members: string[], room: IRoom) => void;
'federation.beforeCreateDirectMessage': (members: IUser[]) => void;
'afterSetReaction': (message: IMessage, params: { user: IUser; reaction: string; shouldReact: boolean; room: IRoom }) => void;
'afterUnsetReaction': (
Expand Down
47 changes: 10 additions & 37 deletions ee/packages/federation-matrix/src/FederationMatrix.ts
Original file line number Diff line number Diff line change
Expand Up @@ -256,47 +256,24 @@ export class FederationMatrix extends ServiceClass implements IFederationMatrixS
}
}

async ensureFederatedUsersExistLocally(members: (IUser | string)[]): Promise<void> {
async ensureFederatedUsersExistLocally(usernames: string[]): Promise<void> {
try {
this.logger.debug('Ensuring federated users exist locally before DM creation', { memberCount: members.length });
this.logger.debug('Ensuring federated users exist locally before DM creation', { memberCount: usernames.length });

for await (const member of members) {
let username: string;

if (typeof member === 'string') {
username = member;
} else if (typeof member.username === 'string') {
username = member.username;
} else {
continue;
}

if (!username.includes(':') && !username.includes('@')) {
const federatedUsers = usernames.filter((username) => username?.includes(':') && username?.includes('@'));
for await (const username of federatedUsers) {
if (!username) {
continue;
}

const existingUser = await Users.findOneByUsername(username);
if (existingUser) {
// TODO review: DM
// const existingBridge = await MatrixBridgedUser.getExternalUserIdByLocalUserId(existingUser._id); // TODO review: DM
// if (!existingBridge) {
// const remoteDomain = externalUserId.split(':')[1] || this.serverName;
// await MatrixBridgedUser.createOrUpdateByLocalId(existingUser._id, externalUserId, true, remoteDomain);
// }
continue;
}

// TODO: there is not need to check if the username includes ':' or '@', we should just use the username as is
const externalUserId = username.includes(':') ? `@${username}` : `@${username}:${this.serverName}`;
this.logger.debug('Creating federated user locally', { externalUserId, username });

const remoteDomain = externalUserId.split(':')[1];

const localName = username.split(':')[0]?.replace('@', '') || username;

const newUser = {
await Users.create({
username,
name: localName,
name: username,
type: 'user' as const,
status: UserStatus.OFFLINE,
active: true,
Expand All @@ -305,16 +282,12 @@ export class FederationMatrix extends ServiceClass implements IFederationMatrixS
federated: true,
federation: {
version: 1,
mui: externalUserId,
origin: remoteDomain,
mui: username,
origin: username.split(':')[1],
},
createdAt: new Date(),
_updatedAt: new Date(),
};

const { insertedId } = await Users.insertOne(newUser);

this.logger.debug('Successfully created federated user locally', { userId: insertedId, externalUserId });
});
}
} catch (error) {
this.logger.error('Failed to ensure federated users exist locally:', error);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ export interface IFederationMatrixService {
wellKnown: Router<'/.well-known'>;
};
createRoom(room: IRoomFederated, owner: IUser, members: string[]): Promise<{ room_id: string; event_id: string }>;
ensureFederatedUsersExistLocally(members: (IUser | string)[]): Promise<void>;
ensureFederatedUsersExistLocally(members: string[]): Promise<void>;
createDirectMessageRoom(room: IRoomFederated, members: IUser[], creatorId: IUser['_id']): Promise<void>;
sendMessage(message: IMessage, room: IRoomFederated, user: IUser): Promise<void>;
deleteMessage(matrixRoomId: string, message: IMessage, uid: string): Promise<void>;
Expand Down
Loading