fix(api): always allow opening a dm channel (#2423)

This commit is contained in:
Hampus authored and GitHub committed 2026-09-03 16:35:01 +02:00
1 parent 902dd60ff9
commit 9c1b38aeaf
4 files changed
+23 -52

No files matched your search

@@ -31,7 +31,7 @@ describe('DM Blocking Behaviors', () => {
await harness?.shutdown();
});
describe('DM Creation Blocking', () => {
it('prevents DM creation when the other user has blocked you, even with mutual guild', async () => {
it('allows DM creation when the other user has blocked you', async () => {
const user1 = await createTestAccount(harness);
const user2 = await createTestAccount(harness);
const guild = await createGuild(harness, user1.token, 'Test Community');
@@ -42,10 +42,10 @@ describe('DM Blocking Behaviors', () => {
await createBuilder(harness, user2.token)
.post('/users/@me/channels')
.body({recipient_id: user1.userId})
.expect(HTTP_STATUS.BAD_REQUEST)
.expect(HTTP_STATUS.OK)
.execute();
});
it('prevents DM creation with someone you have blocked, even with mutual guild', async () => {
it('allows DM creation with someone you have blocked', async () => {
const user1 = await createTestAccount(harness);
const user2 = await createTestAccount(harness);
const guild = await createGuild(harness, user1.token, 'Test Community');
@@ -56,7 +56,7 @@ describe('DM Blocking Behaviors', () => {
await createBuilder(harness, user1.token)
.post('/users/@me/channels')
.body({recipient_id: user2.userId})
.expect(HTTP_STATUS.BAD_REQUEST)
.expect(HTTP_STATUS.OK)
.execute();
});
});
@@ -1,11 +1,11 @@
// SPDX-License-Identifier: AGPL-3.0-or-later
import {afterAll, beforeAll, beforeEach, describe, it} from 'vitest';
import {afterAll, beforeAll, beforeEach, describe, expect, it} from 'vitest';
import {createTestAccount} from '../../auth/tests/AuthTestUtils';
import {type ApiTestHarness, createApiTestHarness} from '../../test/ApiTestHarness';
import {createBuilder} from '../../test/TestRequestBuilder';
import {createDmChannel} from './ChannelTestUtils';
describe('DM creation requires friendship or mutual guild', () => {
describe('DM creation allowed with strangers', () => {
let harness: ApiTestHarness;
beforeAll(async () => {
harness = await createApiTestHarness();
@@ -16,13 +16,11 @@ describe('DM creation requires friendship or mutual guild', () => {
afterAll(async () => {
await harness?.shutdown();
});
it('prevents DM creation with strangers who you do not share a guild with and are not friends with', async () => {
it('allows opening a DM with a stranger who shares no guild and is not a friend', async () => {
const user1 = await createTestAccount(harness);
const user2 = await createTestAccount(harness);
await createBuilder(harness, user1.token)
.post('/users/@me/channels')
.body({recipient_id: user2.userId})
.expect(400)
.execute();
const dm = await createDmChannel(harness, user1.token, user2.userId);
expect(dm.id).toBeTruthy();
expect(dm.id.length).toBeGreaterThan(0);
});
});
@@ -5,7 +5,6 @@ import type {LimitKey} from '@fluxer/constants/src/LimitConfigMetadata';
import {MAX_GROUP_DM_RECIPIENTS, MAX_GROUP_DMS_PER_USER} from '@fluxer/constants/src/LimitConstants';
import {RelationshipTypes} from '@fluxer/constants/src/UserConstants';
import {ValidationErrorCodes} from '@fluxer/constants/src/ValidationErrorCodes';
import {CannotSendMessagesToUserError} from '@fluxer/errors/src/domains/channel/CannotSendMessagesToUserError';
import type {GroupDmUnaddableRecipient} from '@fluxer/errors/src/domains/channel/GroupDmRecipientsNotAddableError';
import {GroupDmRecipientsNotAddableError} from '@fluxer/errors/src/domains/channel/GroupDmRecipientsNotAddableError';
import {MaxGroupDmRecipientsError} from '@fluxer/errors/src/domains/channel/MaxGroupDmRecipientsError';
@@ -30,7 +29,6 @@ import {
createMessageResponseDataService,
messageResponseAccessForGuild,
} from '../../channel/services/message/MessageResponseDataService';
import {SYSTEM_USER_ID} from '../../constants/Core';
import type {IGatewayService} from '../../infrastructure/IGatewayService';
import type {ISnowflakeService} from '../../infrastructure/ISnowflakeService';
import type {UserCacheService} from '../../infrastructure/UserCacheService';
@@ -45,7 +43,6 @@ import type {UserPermissionUtils} from '../../utils/UserPermissionUtils';
import type {IUserAccountRepository} from '../repositories/IUserAccountRepository';
import type {IUserChannelRepository} from '../repositories/IUserChannelRepository';
import type {IUserRelationshipRepository} from '../repositories/IUserRelationshipRepository';
import {isBugHunterBotUser} from '../UserHelpers';
import type {DirectMessageSpamMitigationService} from './DirectMessageSpamMitigationService';
import {createDirectMessageSpamMitigationService} from './DirectMessageSpamMitigationService';
@@ -180,12 +177,7 @@ 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;
return await this.createNewDMChannel({userId, recipientId, userCacheService, requestCache});
}
async pinDmChannel({userId, channelId}: {userId: UserID; channelId: ChannelID}): Promise<void> {
@@ -258,30 +250,6 @@ export class UserChannelService {
return await this.userRepository.findExistingDmState(userId, recipientId);
}
private async validateNewDmAllowed({sender, recipient}: {sender: User; recipient: User}): Promise<void> {
if (isBugHunterBotUser(sender)) {
return;
}
const [senderBlockedRecipient, recipientBlockedSender, friendship] = await Promise.all([
this.userRepository.getRelationship(sender.id, recipient.id, RelationshipTypes.BLOCKED),
this.userRepository.getRelationship(recipient.id, sender.id, RelationshipTypes.BLOCKED),
this.userRepository.getRelationship(sender.id, recipient.id, RelationshipTypes.FRIEND),
]);
if (senderBlockedRecipient || recipientBlockedSender) {
throw new CannotSendMessagesToUserError();
}
if (friendship) {
return;
}
const hasMutualGuilds = await this.userPermissionUtils.checkMutualGuildsForDmAccessAsync({
userId: sender.id,
targetId: recipient.id,
});
if (!hasMutualGuilds) {
throw new CannotSendMessagesToUserError();
}
}
async ensureDmOpenForBothUsers({
userId,
recipientId,
@@ -109,7 +109,7 @@ describe('UserChannelService', () => {
.expect(HTTP_STATUS.NOT_FOUND, 'UNKNOWN_USER')
.execute();
});
test('cannot create DM with blocked user', async () => {
test('can create DM with a user who blocked you', async () => {
const user1 = await createTestAccount(harness);
const user2 = await createTestAccount(harness);
const guild = await createGuild(harness, user1.token, 'Test Community');
@@ -117,11 +117,16 @@ describe('UserChannelService', () => {
const invite = await createChannelInvite(harness, user1.token, systemChannel.id);
await acceptInvite(harness, user2.token, invite.code);
await blockUser(harness, user1, user2.userId);
await createBuilder(harness, user1.token)
.post('/users/@me/channels')
.body({recipient_id: user2.userId})
.expect(HTTP_STATUS.BAD_REQUEST, 'CANNOT_SEND_MESSAGES_TO_USER')
.execute();
const channel = await createDmChannel(harness, user1.token, user2.userId);
expect(channel.id).toBeDefined();
expect(channel.type).toBe(ChannelTypes.DM);
});
test('can create DM with a user who shares no mutual community', async () => {
const user1 = await createTestAccount(harness);
const user2 = await createTestAccount(harness);
const channel = await createDmChannel(harness, user1.token, user2.userId);
expect(channel.id).toBeDefined();
expect(channel.type).toBe(ChannelTypes.DM);
});
test('unclaimed account cannot create DM', async () => {
const user1 = await createTestAccount(harness);