fix: allow POST /users/@me/channels with recipient_id 0 (#1160)

This commit is contained in:
dogbonewish
2026-06-24 23:11:29 +01:00
committed by GitHub
parent a45693f581
commit 41ec2544cf
4 changed files with 50 additions and 4 deletions
@@ -23,6 +23,7 @@ import {requireEmailVerified} from '../../auth/EmailVerificationUtils';
import type {ChannelID, UserID} from '../../BrandedTypes';
import {createChannelID, createMessageID, createUserID} from '../../BrandedTypes';
import {mapChannelToResponse} from '../../channel/ChannelMappers';
import {SYSTEM_USER_ID} from '../../constants/Core';
import type {IChannelRepository} from '../../channel/IChannelRepository';
import type {ChannelService} from '../../channel/services/ChannelService';
import {dispatchMessageCreateBroadcast} from '../../channel/services/message/MessageGatewayDispatch';
@@ -161,7 +162,7 @@ export class UserChannelService {
requestCache,
});
}
if (!data.recipient_id) {
if (data.recipient_id == null) {
throw InputValidationError.fromCode('recipient_id', ValidationErrorCodes.RECIPIENT_IDS_CANNOT_BE_EMPTY);
}
const recipientId = createUserID(data.recipient_id);
@@ -179,6 +180,9 @@ export class UserChannelService {
}
const targetUser = await this.userRepository.findUnique(recipientId);
if (!targetUser) throw new UnknownUserError();
if (recipientId === SYSTEM_USER_ID) {
return await this.createNewDMChannel({userId, recipientId, userCacheService, requestCache});
}
await this.validateNewDmAllowed({sender: callingUser, recipient: targetUser});
const channel = await this.createNewDMChannel({userId, recipientId, userCacheService, requestCache});
return channel;
@@ -1,5 +1,6 @@
// SPDX-License-Identifier: AGPL-3.0-or-later
import {FLUXERBOT_ID} from '@fluxer/constants/src/AppConstants';
import {ChannelTypes} from '@fluxer/constants/src/ChannelConstants';
import {UserFlags} from '@fluxer/constants/src/UserConstants';
import {afterAll, beforeAll, beforeEach, describe, expect, test} from 'vitest';
@@ -13,10 +14,14 @@ import {
createFriendship,
createGroupDmChannel,
createGuild,
deleteChannel,
getChannel,
type MinimalChannelResponse,
sendChannelMessage,
} from '../../channel/tests/ChannelTestUtils';
import {createChannelID, createUserID} from '../../BrandedTypes';
import {SYSTEM_USER_ID} from '../../constants/Core';
import {UserRepository} from '../../user/repositories/UserRepository';
import {ensureSessionStarted} from '../../message/tests/MessageTestUtils';
import {type ApiTestHarness, createApiTestHarness} from '../../test/ApiTestHarness';
import {HTTP_STATUS} from '../../test/TestConstants';
@@ -149,6 +154,32 @@ describe('UserChannelService', () => {
const channel2 = await createDmChannel(harness, user1.token, user2.userId);
expect(channel2.id).toBe(channel1.id);
});
test('reopening closed system user DM accepts recipient_id 0', async () => {
const user = await createTestAccount(harness);
const userId = createUserID(BigInt(user.userId));
const channelId = createChannelID(1000000000000000001n);
const userRepository = new UserRepository();
const channel = await userRepository.createDmChannelAndState(userId, SYSTEM_USER_ID, channelId);
await userRepository.openPrivateChannelForUser(userId, channel);
await deleteChannel(harness, user.token, channelId.toString());
const reopened = await createBuilder<MinimalChannelResponse>(harness, user.token)
.post('/users/@me/channels')
.body({recipient_id: FLUXERBOT_ID})
.expect(HTTP_STATUS.OK)
.execute();
expect(reopened.id).toBe(channelId.toString());
expect(reopened.type).toBe(ChannelTypes.DM);
});
test('can create new system user DM without friendship or mutual guilds', async () => {
const user = await createTestAccount(harness);
const channel = await createBuilder<MinimalChannelResponse>(harness, user.token)
.post('/users/@me/channels')
.body({recipient_id: FLUXERBOT_ID})
.expect(HTTP_STATUS.OK)
.execute();
expect(channel.id).toBeDefined();
expect(channel.type).toBe(ChannelTypes.DM);
});
});
describe('Group DM creation', () => {
test('can create group DM with friends', async () => {
@@ -79,4 +79,11 @@ describe('CreatePrivateChannelRequest', () => {
);
expect(CreatePrivateChannelRequest.safeParse({recipients}).success).toBe(false);
});
it('allows DM requests to the system user id 0', () => {
const parsed = CreatePrivateChannelRequest.safeParse({recipient_id: '0'});
expect(parsed.success).toBe(true);
if (parsed.success) {
expect(parsed.data.recipient_id).toBe(0n);
}
});
});
@@ -272,9 +272,13 @@ export const CreatePrivateChannelRequest = z
.optional()
.describe(`Array of user IDs for creating a group DM (max ${MAX_GROUP_DM_OTHER_RECIPIENTS})`),
})
.refine((data) => (data.recipient_id && !data.recipients) || (!data.recipient_id && data.recipients), {
message: 'Either recipient_id or recipients must be provided, but not both',
});
.refine(
(data) =>
(data.recipient_id != null && data.recipients == null) || (data.recipient_id == null && data.recipients != null),
{
message: 'Either recipient_id or recipients must be provided, but not both',
},
);
export type CreatePrivateChannelRequest = z.infer<typeof CreatePrivateChannelRequest>;