From fe2dfcadac004d27fb9b33581482fdb1bb992628 Mon Sep 17 00:00:00 2001 From: dodaa08 Date: Mon, 19 Jan 2026 11:36:08 +0530 Subject: [PATCH 1/5] fix: filter non-members from thread followers Filter followers and mentionIds to only include room members before adding to thread replies. Prevents non-members from appearing as thread followers in private channels. --- apps/meteor/app/threads/server/functions.ts | 74 +++++++++++++++++---- 1 file changed, 60 insertions(+), 14 deletions(-) diff --git a/apps/meteor/app/threads/server/functions.ts b/apps/meteor/app/threads/server/functions.ts index b747a8d5d9555..b473e7fce876a 100644 --- a/apps/meteor/app/threads/server/functions.ts +++ b/apps/meteor/app/threads/server/functions.ts @@ -1,6 +1,6 @@ -import type { IMessage } from '@rocket.chat/core-typings'; +import type { IMessage, IRoom } from '@rocket.chat/core-typings'; import { isEditedMessage } from '@rocket.chat/core-typings'; -import { Messages, Subscriptions, ReadReceipts, NotificationQueue } from '@rocket.chat/models'; +import { Messages, Subscriptions, ReadReceipts, NotificationQueue, Rooms } from '@rocket.chat/models'; import { notifyOnSubscriptionChangedByRoomIdAndUserIds, @@ -16,29 +16,75 @@ export async function reply({ tmid }: { tmid?: string }, message: IMessage, pare const { rid, ts, u } = message; const { toAll, toHere, mentionIds } = await getMentions(message); + + const filterUsersInRoom = async ({ + roomId, + userIds, + room + }: { + roomId: string; + userIds: string[]; + room: IRoom | null + }) => { + try { + + if (!userIds.length || !room) { + return []; + } + + if (room.t === 'd') { + return userIds.filter((userId) => room.uids?.includes(userId)); + } + + if (room.t === 'p' || room.t === 'c') { + + const subscriptions = await Subscriptions.findByRoomIdAndUserIds(roomId, userIds, { + projection: { u: 1 }, + }).toArray(); + + return subscriptions.map((sub) => sub.u._id); + } + + return []; + + } catch (error) { + console.error('Error filtering users in room:', { roomId, error }); + return []; + } + } + + const room = await Rooms.findOneById(rid); + const [highlightsUids, threadFollowers] = await Promise.all([ + getUserIdsFromHighlights(rid, message), + Messages.getThreadFollowsByThreadId(tmid), + ]); + + + const [followersInRoom, mentionIdsInRoom, highlightsUidsInRoom] = await Promise.all([ + filterUsersInRoom({ roomId: rid, userIds: followers, room }), + filterUsersInRoom({ roomId: rid, userIds: mentionIds, room }), + filterUsersInRoom({ roomId: rid, userIds: highlightsUids, room }), // ← Add here + ]); const addToReplies = [ ...new Set([ - ...followers, - ...mentionIds, + ...followersInRoom, + ...mentionIdsInRoom, ...(Array.isArray(parentMessage.replies) && parentMessage.replies.length ? [u._id] : [parentMessage.u._id, u._id]), ]), ]; await Messages.updateRepliesByThreadId(tmid, addToReplies, ts); - const [highlightsUids, threadFollowers] = await Promise.all([ - getUserIdsFromHighlights(rid, message), - Messages.getThreadFollowsByThreadId(tmid), - ]); + - const threadFollowersUids = threadFollowers?.filter((userId) => userId !== u._id && !mentionIds.includes(userId)) || []; + const threadFollowersUids = threadFollowers?.filter((userId) => userId !== u._id && !mentionIdsInRoom.includes(userId)) || []; // Notify everyone involved in the thread const notifyOptions = toAll || toHere ? { groupMention: true } : {}; // Notify message mentioned users and highlights - const mentionedUsers = [...new Set([...mentionIds, ...highlightsUids])]; + const mentionedUsers = [...new Set([...mentionIdsInRoom, ...highlightsUidsInRoom])]; const promises = [ ReadReceipts.setAsThreadById(tmid), @@ -49,16 +95,16 @@ export async function reply({ tmid }: { tmid?: string }, message: IMessage, pare promises.push(Subscriptions.addUnreadThreadByRoomIdAndUserIds(rid, mentionedUsers, tmid, { userMention: true })); } - if (highlightsUids.length) { + if (highlightsUidsInRoom.length) { promises.push( - Subscriptions.setAlertForRoomIdAndUserIds(rid, highlightsUids), - Subscriptions.setOpenForRoomIdAndUserIds(rid, highlightsUids), + Subscriptions.setAlertForRoomIdAndUserIds(rid, highlightsUidsInRoom), + Subscriptions.setOpenForRoomIdAndUserIds(rid, highlightsUidsInRoom), ); } await Promise.allSettled(promises); - void notifyOnSubscriptionChangedByRoomIdAndUserIds(rid, [...threadFollowersUids, ...mentionedUsers, ...highlightsUids]); + void notifyOnSubscriptionChangedByRoomIdAndUserIds(rid, [...threadFollowersUids, ...mentionedUsers, ...highlightsUidsInRoom]); } export async function follow({ tmid, uid }: { tmid: string; uid: string }) { From a23e4087c05bdbb46d74c0d27bc4f5789a132c5f Mon Sep 17 00:00:00 2001 From: dodaa08 Date: Mon, 19 Jan 2026 22:34:14 +0530 Subject: [PATCH 2/5] style: fix linting issues --- apps/meteor/app/threads/server/functions.ts | 40 +++++++++------------ 1 file changed, 17 insertions(+), 23 deletions(-) diff --git a/apps/meteor/app/threads/server/functions.ts b/apps/meteor/app/threads/server/functions.ts index b473e7fce876a..c57c6e7845fe9 100644 --- a/apps/meteor/app/threads/server/functions.ts +++ b/apps/meteor/app/threads/server/functions.ts @@ -16,18 +16,17 @@ export async function reply({ tmid }: { tmid?: string }, message: IMessage, pare const { rid, ts, u } = message; const { toAll, toHere, mentionIds } = await getMentions(message); - - const filterUsersInRoom = async ({ - roomId, - userIds, - room - }: { - roomId: string; - userIds: string[]; - room: IRoom | null + + const filterUsersInRoom = async ({ + roomId, + userIds, + room, + }: { + roomId: string; + userIds: string[]; + room: IRoom | null; }) => { try { - if (!userIds.length || !room) { return []; } @@ -37,34 +36,31 @@ export async function reply({ tmid }: { tmid?: string }, message: IMessage, pare } if (room.t === 'p' || room.t === 'c') { - const subscriptions = await Subscriptions.findByRoomIdAndUserIds(roomId, userIds, { projection: { u: 1 }, }).toArray(); - + return subscriptions.map((sub) => sub.u._id); } return []; - - } catch (error) { + } catch (error) { console.error('Error filtering users in room:', { roomId, error }); return []; } - } - + }; + const room = await Rooms.findOneById(rid); const [highlightsUids, threadFollowers] = await Promise.all([ getUserIdsFromHighlights(rid, message), Messages.getThreadFollowsByThreadId(tmid), ]); - - + const [followersInRoom, mentionIdsInRoom, highlightsUidsInRoom] = await Promise.all([ filterUsersInRoom({ roomId: rid, userIds: followers, room }), filterUsersInRoom({ roomId: rid, userIds: mentionIds, room }), - filterUsersInRoom({ roomId: rid, userIds: highlightsUids, room }), // ← Add here - ]); + filterUsersInRoom({ roomId: rid, userIds: highlightsUids, room }), + ]); const addToReplies = [ ...new Set([ @@ -76,8 +72,6 @@ export async function reply({ tmid }: { tmid?: string }, message: IMessage, pare await Messages.updateRepliesByThreadId(tmid, addToReplies, ts); - - const threadFollowersUids = threadFollowers?.filter((userId) => userId !== u._id && !mentionIdsInRoom.includes(userId)) || []; // Notify everyone involved in the thread @@ -143,4 +137,4 @@ export const readThread = async ({ userId, rid, tmid }: { userId: string; rid: s } await NotificationQueue.clearQueueByUserId(userId); -}; +}; \ No newline at end of file From 8fa5df6fc0235da027cf87318dc568699dc78744 Mon Sep 17 00:00:00 2001 From: dodaa08 Date: Mon, 19 Jan 2026 22:58:14 +0530 Subject: [PATCH 3/5] style: fix prettier formatting errors --- apps/meteor/app/threads/server/functions.ts | 12 ++---------- 1 file changed, 2 insertions(+), 10 deletions(-) diff --git a/apps/meteor/app/threads/server/functions.ts b/apps/meteor/app/threads/server/functions.ts index c57c6e7845fe9..71edd55a887e7 100644 --- a/apps/meteor/app/threads/server/functions.ts +++ b/apps/meteor/app/threads/server/functions.ts @@ -17,15 +17,7 @@ export async function reply({ tmid }: { tmid?: string }, message: IMessage, pare const { toAll, toHere, mentionIds } = await getMentions(message); - const filterUsersInRoom = async ({ - roomId, - userIds, - room, - }: { - roomId: string; - userIds: string[]; - room: IRoom | null; - }) => { + const filterUsersInRoom = async ({ roomId, userIds, room }: { roomId: string; userIds: string[]; room: IRoom | null }) => { try { if (!userIds.length || !room) { return []; @@ -137,4 +129,4 @@ export const readThread = async ({ userId, rid, tmid }: { userId: string; rid: s } await NotificationQueue.clearQueueByUserId(userId); -}; \ No newline at end of file +}; From 22d3d8980b82a6f9fed62f70e168111efadc4ad0 Mon Sep 17 00:00:00 2001 From: dodaa08 Date: Tue, 20 Jan 2026 00:09:10 +0530 Subject: [PATCH 4/5] style: fix linting issues --- apps/meteor/app/threads/server/functions.ts | 129 ++++++++++++++------ 1 file changed, 91 insertions(+), 38 deletions(-) diff --git a/apps/meteor/app/threads/server/functions.ts b/apps/meteor/app/threads/server/functions.ts index 71edd55a887e7..4096edccbcb5b 100644 --- a/apps/meteor/app/threads/server/functions.ts +++ b/apps/meteor/app/threads/server/functions.ts @@ -8,77 +8,112 @@ import { } from '../../lib/server/lib/notifyListener'; import { getMentions, getUserIdsFromHighlights } from '../../lib/server/lib/notifyUsersOnMessage'; -export async function reply({ tmid }: { tmid?: string }, message: IMessage, parentMessage: IMessage, followers: string[]) { - if (!tmid || isEditedMessage(message)) { - return false; +const filterUsersInRoom = async ({ + roomId, + userIds, + room, +}: { + roomId: string; + userIds: string[]; + room: IRoom | null; +}) => { + if (!userIds.length || !room) { + return []; } - const { rid, ts, u } = message; + if (room.t === 'd') { + return userIds.filter((userId) => room.uids?.includes(userId)); + } - const { toAll, toHere, mentionIds } = await getMentions(message); + if (room.t === 'p' || room.t === 'c') { + const subscriptions = await Subscriptions.findByRoomIdAndUserIds(roomId, userIds, { + projection: { u: 1 }, + }).toArray(); - const filterUsersInRoom = async ({ roomId, userIds, room }: { roomId: string; userIds: string[]; room: IRoom | null }) => { - try { - if (!userIds.length || !room) { - return []; - } + return subscriptions.map((sub) => sub.u._id); + } - if (room.t === 'd') { - return userIds.filter((userId) => room.uids?.includes(userId)); - } + return []; +}; - if (room.t === 'p' || room.t === 'c') { - const subscriptions = await Subscriptions.findByRoomIdAndUserIds(roomId, userIds, { - projection: { u: 1 }, - }).toArray(); +export async function reply( + { tmid }: { tmid?: string }, + message: IMessage, + parentMessage: IMessage, + followers: string[], +) { + if (!tmid || isEditedMessage(message)) { + return false; + } - return subscriptions.map((sub) => sub.u._id); - } + const { rid, ts, u } = message; + const room = await Rooms.findOneById(rid); - return []; - } catch (error) { - console.error('Error filtering users in room:', { roomId, error }); - return []; - } - }; + const { toAll, toHere, mentionIds } = await getMentions(message); - const room = await Rooms.findOneById(rid); const [highlightsUids, threadFollowers] = await Promise.all([ getUserIdsFromHighlights(rid, message), Messages.getThreadFollowsByThreadId(tmid), ]); - const [followersInRoom, mentionIdsInRoom, highlightsUidsInRoom] = await Promise.all([ + const [ + followersInRoom, + mentionIdsInRoom, + highlightsUidsInRoom, + threadFollowersInRoom, + ] = await Promise.all([ filterUsersInRoom({ roomId: rid, userIds: followers, room }), filterUsersInRoom({ roomId: rid, userIds: mentionIds, room }), filterUsersInRoom({ roomId: rid, userIds: highlightsUids, room }), + filterUsersInRoom({ roomId: rid, userIds: threadFollowers || [], room }), ]); + const baseReplyUsers = + Array.isArray(parentMessage.replies) && parentMessage.replies.length + ? [u._id] + : [parentMessage.u._id, u._id]; + + const baseReplyUsersInRoom = await filterUsersInRoom({ + roomId: rid, + userIds: baseReplyUsers, + room, + }); + const addToReplies = [ ...new Set([ ...followersInRoom, ...mentionIdsInRoom, - ...(Array.isArray(parentMessage.replies) && parentMessage.replies.length ? [u._id] : [parentMessage.u._id, u._id]), + ...baseReplyUsersInRoom, ]), ]; await Messages.updateRepliesByThreadId(tmid, addToReplies, ts); - const threadFollowersUids = threadFollowers?.filter((userId) => userId !== u._id && !mentionIdsInRoom.includes(userId)) || []; + const threadFollowersUids = threadFollowersInRoom.filter( + (userId) => userId !== u._id && !mentionIdsInRoom.includes(userId), + ); - // Notify everyone involved in the thread const notifyOptions = toAll || toHere ? { groupMention: true } : {}; - // Notify message mentioned users and highlights + // Notify everyone involved in the thread const mentionedUsers = [...new Set([...mentionIdsInRoom, ...highlightsUidsInRoom])]; - const promises = [ + const promises: Promise[] = [ ReadReceipts.setAsThreadById(tmid), - Subscriptions.addUnreadThreadByRoomIdAndUserIds(rid, threadFollowersUids, tmid, notifyOptions), + Subscriptions.addUnreadThreadByRoomIdAndUserIds( + rid, + threadFollowersUids, + tmid, + notifyOptions, + ), ]; if (mentionedUsers.length) { - promises.push(Subscriptions.addUnreadThreadByRoomIdAndUserIds(rid, mentionedUsers, tmid, { userMention: true })); + promises.push( + Subscriptions.addUnreadThreadByRoomIdAndUserIds(rid, mentionedUsers, tmid, { + userMention: true, + }), + ); } if (highlightsUidsInRoom.length) { @@ -90,7 +125,11 @@ export async function reply({ tmid }: { tmid?: string }, message: IMessage, pare await Promise.allSettled(promises); - void notifyOnSubscriptionChangedByRoomIdAndUserIds(rid, [...threadFollowersUids, ...mentionedUsers, ...highlightsUidsInRoom]); + void notifyOnSubscriptionChangedByRoomIdAndUserIds(rid, [ + ...threadFollowersUids, + ...mentionedUsers, + ...highlightsUidsInRoom, + ]); } export async function follow({ tmid, uid }: { tmid: string; uid: string }) { @@ -106,7 +145,8 @@ export async function unfollow({ tmid, rid, uid }: { tmid: string; rid: string; return false; } - const removeUnreadThreadResponse = await Subscriptions.removeUnreadThreadByRoomIdAndUserId(rid, uid, tmid); + const removeUnreadThreadResponse = + await Subscriptions.removeUnreadThreadByRoomIdAndUserId(rid, uid, tmid); if (removeUnreadThreadResponse.modifiedCount) { void notifyOnSubscriptionChangedByRoomIdAndUserId(rid, uid); } @@ -114,7 +154,15 @@ export async function unfollow({ tmid, rid, uid }: { tmid: string; rid: string; await Messages.removeThreadFollowerByThreadId(tmid, uid); } -export const readThread = async ({ userId, rid, tmid }: { userId: string; rid: string; tmid: string }) => { +export const readThread = async ({ + userId, + rid, + tmid, +}: { + userId: string; + rid: string; + tmid: string; +}) => { const sub = await Subscriptions.findOneByRoomIdAndUserId(rid, userId, { projection: { tunread: 1 } }); if (!sub) { return; @@ -123,7 +171,12 @@ export const readThread = async ({ userId, rid, tmid }: { userId: string; rid: s // if the thread being marked as read is the last one unread also clear the unread subscription flag const clearAlert = sub.tunread && sub.tunread?.length <= 1 && sub.tunread.includes(tmid); - const removeUnreadThreadResponse = await Subscriptions.removeUnreadThreadByRoomIdAndUserId(rid, userId, tmid, clearAlert); + const removeUnreadThreadResponse = await Subscriptions.removeUnreadThreadByRoomIdAndUserId( + rid, + userId, + tmid, + clearAlert, + ); if (removeUnreadThreadResponse.modifiedCount) { void notifyOnSubscriptionChangedByRoomIdAndUserId(rid, userId); } From 347a1d6d09db94d931c5f8019e6d83261ba89ca3 Mon Sep 17 00:00:00 2001 From: dodaa08 Date: Sat, 24 Jan 2026 12:42:34 +0530 Subject: [PATCH 5/5] style: fix prettier formatting in threads server functions --- apps/meteor/app/threads/server/functions.ts | 74 +++------------------ 1 file changed, 11 insertions(+), 63 deletions(-) diff --git a/apps/meteor/app/threads/server/functions.ts b/apps/meteor/app/threads/server/functions.ts index 4096edccbcb5b..7906f33ba77b0 100644 --- a/apps/meteor/app/threads/server/functions.ts +++ b/apps/meteor/app/threads/server/functions.ts @@ -8,15 +8,7 @@ import { } from '../../lib/server/lib/notifyListener'; import { getMentions, getUserIdsFromHighlights } from '../../lib/server/lib/notifyUsersOnMessage'; -const filterUsersInRoom = async ({ - roomId, - userIds, - room, -}: { - roomId: string; - userIds: string[]; - room: IRoom | null; -}) => { +const filterUsersInRoom = async ({ roomId, userIds, room }: { roomId: string; userIds: string[]; room: IRoom | null }) => { if (!userIds.length || !room) { return []; } @@ -36,12 +28,7 @@ const filterUsersInRoom = async ({ return []; }; -export async function reply( - { tmid }: { tmid?: string }, - message: IMessage, - parentMessage: IMessage, - followers: string[], -) { +export async function reply({ tmid }: { tmid?: string }, message: IMessage, parentMessage: IMessage, followers: string[]) { if (!tmid || isEditedMessage(message)) { return false; } @@ -56,22 +43,14 @@ export async function reply( Messages.getThreadFollowsByThreadId(tmid), ]); - const [ - followersInRoom, - mentionIdsInRoom, - highlightsUidsInRoom, - threadFollowersInRoom, - ] = await Promise.all([ + const [followersInRoom, mentionIdsInRoom, highlightsUidsInRoom, threadFollowersInRoom] = await Promise.all([ filterUsersInRoom({ roomId: rid, userIds: followers, room }), filterUsersInRoom({ roomId: rid, userIds: mentionIds, room }), filterUsersInRoom({ roomId: rid, userIds: highlightsUids, room }), filterUsersInRoom({ roomId: rid, userIds: threadFollowers || [], room }), ]); - const baseReplyUsers = - Array.isArray(parentMessage.replies) && parentMessage.replies.length - ? [u._id] - : [parentMessage.u._id, u._id]; + const baseReplyUsers = Array.isArray(parentMessage.replies) && parentMessage.replies.length ? [u._id] : [parentMessage.u._id, u._id]; const baseReplyUsersInRoom = await filterUsersInRoom({ roomId: rid, @@ -79,19 +58,11 @@ export async function reply( room, }); - const addToReplies = [ - ...new Set([ - ...followersInRoom, - ...mentionIdsInRoom, - ...baseReplyUsersInRoom, - ]), - ]; + const addToReplies = [...new Set([...followersInRoom, ...mentionIdsInRoom, ...baseReplyUsersInRoom])]; await Messages.updateRepliesByThreadId(tmid, addToReplies, ts); - const threadFollowersUids = threadFollowersInRoom.filter( - (userId) => userId !== u._id && !mentionIdsInRoom.includes(userId), - ); + const threadFollowersUids = threadFollowersInRoom.filter((userId) => userId !== u._id && !mentionIdsInRoom.includes(userId)); const notifyOptions = toAll || toHere ? { groupMention: true } : {}; @@ -100,12 +71,7 @@ export async function reply( const promises: Promise[] = [ ReadReceipts.setAsThreadById(tmid), - Subscriptions.addUnreadThreadByRoomIdAndUserIds( - rid, - threadFollowersUids, - tmid, - notifyOptions, - ), + Subscriptions.addUnreadThreadByRoomIdAndUserIds(rid, threadFollowersUids, tmid, notifyOptions), ]; if (mentionedUsers.length) { @@ -125,11 +91,7 @@ export async function reply( await Promise.allSettled(promises); - void notifyOnSubscriptionChangedByRoomIdAndUserIds(rid, [ - ...threadFollowersUids, - ...mentionedUsers, - ...highlightsUidsInRoom, - ]); + void notifyOnSubscriptionChangedByRoomIdAndUserIds(rid, [...threadFollowersUids, ...mentionedUsers, ...highlightsUidsInRoom]); } export async function follow({ tmid, uid }: { tmid: string; uid: string }) { @@ -145,8 +107,7 @@ export async function unfollow({ tmid, rid, uid }: { tmid: string; rid: string; return false; } - const removeUnreadThreadResponse = - await Subscriptions.removeUnreadThreadByRoomIdAndUserId(rid, uid, tmid); + const removeUnreadThreadResponse = await Subscriptions.removeUnreadThreadByRoomIdAndUserId(rid, uid, tmid); if (removeUnreadThreadResponse.modifiedCount) { void notifyOnSubscriptionChangedByRoomIdAndUserId(rid, uid); } @@ -154,15 +115,7 @@ export async function unfollow({ tmid, rid, uid }: { tmid: string; rid: string; await Messages.removeThreadFollowerByThreadId(tmid, uid); } -export const readThread = async ({ - userId, - rid, - tmid, -}: { - userId: string; - rid: string; - tmid: string; -}) => { +export const readThread = async ({ userId, rid, tmid }: { userId: string; rid: string; tmid: string }) => { const sub = await Subscriptions.findOneByRoomIdAndUserId(rid, userId, { projection: { tunread: 1 } }); if (!sub) { return; @@ -171,12 +124,7 @@ export const readThread = async ({ // if the thread being marked as read is the last one unread also clear the unread subscription flag const clearAlert = sub.tunread && sub.tunread?.length <= 1 && sub.tunread.includes(tmid); - const removeUnreadThreadResponse = await Subscriptions.removeUnreadThreadByRoomIdAndUserId( - rid, - userId, - tmid, - clearAlert, - ); + const removeUnreadThreadResponse = await Subscriptions.removeUnreadThreadByRoomIdAndUserId(rid, userId, tmid, clearAlert); if (removeUnreadThreadResponse.modifiedCount) { void notifyOnSubscriptionChangedByRoomIdAndUserId(rid, userId); }