From becd5c8ac8bc08c9671e32a0efb15aa2cd76e5c5 Mon Sep 17 00:00:00 2001 From: Jannis Braun <151788261+TheZwiss@users.noreply.github.com> Date: Fri, 27 Mar 2026 02:48:26 +0100 Subject: [PATCH] fix: exempt existing DM members from friend check in createGroup MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit When converting a 1-on-1 DM to a group, the existing DM partner was incorrectly required to be your friend. DMs don't require friendship, so this check was over-strict. Added fromDmChannelId parameter to createGroup — existing members of the source DM are exempt from the friendship validation. --- packages/server/src/routes/dm.ts | 25 +++++++++++++++++-- packages/shared/src/types.ts | 1 + .../components/modals/AddDmMemberModal.tsx | 2 +- 3 files changed, 25 insertions(+), 3 deletions(-) diff --git a/packages/server/src/routes/dm.ts b/packages/server/src/routes/dm.ts index 0b18107e..bd18cdfe 100644 --- a/packages/server/src/routes/dm.ts +++ b/packages/server/src/routes/dm.ts @@ -487,7 +487,7 @@ export async function dmRoutes(app: FastifyInstance): Promise { app.post<{ Body: CreateGroupDmRequest }>('/api/dm/group', { preHandler: authenticate, }, async (request, reply) => { - const { users: userIdentities } = request.body; + const { users: userIdentities, fromDmChannelId } = request.body; // Validate input is a non-empty array of at least 2 identity objects if (!Array.isArray(userIdentities) || userIdentities.length < 2) { @@ -547,8 +547,29 @@ export async function dmRoutes(app: FastifyInstance): Promise { return reply.code(400).send({ error: 'Do not include yourself — you are added automatically', statusCode: 400 }); } - // Validate all target users are friends with the caller + // When converting a 1-on-1 DM to a group, existing DM members are exempt + // from the friendship check (DMs don't require friendship). + const exemptUserIds = new Set(); + if (fromDmChannelId) { + const sourceDm = db.select().from(schema.dmChannels).where( + and(eq(schema.dmChannels.id, fromDmChannelId), isNull(schema.dmChannels.deletedAt)), + ).get(); + if (sourceDm && !sourceDm.ownerId) { + // Only exempt members from 1-on-1 DMs (ownerId is null) + if (isDmMember(fromDmChannelId, request.userId)) { + const members = db.select().from(schema.dmMembers) + .where(eq(schema.dmMembers.dmChannelId, fromDmChannelId)).all(); + for (const m of members) { + exemptUserIds.add(m.userId); + } + } + } + } + + // Validate all target users are friends with the caller (exempt existing DM members) for (const targetUser of targetUsers) { + if (exemptUserIds.has(targetUser.id)) continue; + const friendship = db.select().from(schema.friends).where( or( and(eq(schema.friends.userId, request.userId), eq(schema.friends.friendId, targetUser.id)), diff --git a/packages/shared/src/types.ts b/packages/shared/src/types.ts index 67ca9279..dee85c72 100644 --- a/packages/shared/src/types.ts +++ b/packages/shared/src/types.ts @@ -524,6 +524,7 @@ export interface GroupDmUserIdentity { export interface CreateGroupDmRequest { users: GroupDmUserIdentity[]; + fromDmChannelId?: string; } export interface CreateDmMessageRequest { diff --git a/packages/web/src/components/modals/AddDmMemberModal.tsx b/packages/web/src/components/modals/AddDmMemberModal.tsx index 5ceb3a8a..43a65067 100644 --- a/packages/web/src/components/modals/AddDmMemberModal.tsx +++ b/packages/web/src/components/modals/AddDmMemberModal.tsx @@ -107,7 +107,7 @@ export function AddDmMemberModal() { homeInstance: f.homeInstance, })), ]; - const newChannel = await api.dm.createGroup({ users }); + const newChannel = await api.dm.createGroup({ users, fromDmChannelId: dmChannelId }); addDmChannel(newChannel); closeModal(); navigate(`/channels/@me/${newChannel.id}`);