fix: exempt existing DM members from friend check in createGroup

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.
This commit is contained in:
Jannis Braun
2026-03-27 02:48:26 +01:00
parent 1607a8569c
commit becd5c8ac8
3 changed files with 25 additions and 3 deletions
+23 -2
View File
@@ -487,7 +487,7 @@ export async function dmRoutes(app: FastifyInstance): Promise<void> {
app.post<{ Body: CreateGroupDmRequest }>('/api/dm/group', { app.post<{ Body: CreateGroupDmRequest }>('/api/dm/group', {
preHandler: authenticate, preHandler: authenticate,
}, async (request, reply) => { }, 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 // Validate input is a non-empty array of at least 2 identity objects
if (!Array.isArray(userIdentities) || userIdentities.length < 2) { if (!Array.isArray(userIdentities) || userIdentities.length < 2) {
@@ -547,8 +547,29 @@ export async function dmRoutes(app: FastifyInstance): Promise<void> {
return reply.code(400).send({ error: 'Do not include yourself — you are added automatically', statusCode: 400 }); 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<string>();
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) { for (const targetUser of targetUsers) {
if (exemptUserIds.has(targetUser.id)) continue;
const friendship = db.select().from(schema.friends).where( const friendship = db.select().from(schema.friends).where(
or( or(
and(eq(schema.friends.userId, request.userId), eq(schema.friends.friendId, targetUser.id)), and(eq(schema.friends.userId, request.userId), eq(schema.friends.friendId, targetUser.id)),
+1
View File
@@ -524,6 +524,7 @@ export interface GroupDmUserIdentity {
export interface CreateGroupDmRequest { export interface CreateGroupDmRequest {
users: GroupDmUserIdentity[]; users: GroupDmUserIdentity[];
fromDmChannelId?: string;
} }
export interface CreateDmMessageRequest { export interface CreateDmMessageRequest {
@@ -107,7 +107,7 @@ export function AddDmMemberModal() {
homeInstance: f.homeInstance, homeInstance: f.homeInstance,
})), })),
]; ];
const newChannel = await api.dm.createGroup({ users }); const newChannel = await api.dm.createGroup({ users, fromDmChannelId: dmChannelId });
addDmChannel(newChannel); addDmChannel(newChannel);
closeModal(); closeModal();
navigate(`/channels/@me/${newChannel.id}`); navigate(`/channels/@me/${newChannel.id}`);