perf(message): reuse the resolved channel for dm send checks (#2197)

This commit is contained in:
Hampus authored and GitHub committed 2026-08-31 00:06:25 +02:00
1 parent d5fb495e19
commit b5496097d2
4 files changed
+50 -16

No files matched your search

@@ -37,6 +37,18 @@ export interface ChannelAuthOptions {
validateNsfw: boolean;
}
interface DMSendPermissionsByChannelParams {
channel: Channel;
userId: UserID;
}
interface DMSendPermissionsByChannelIdParams {
channelId: ChannelID;
userId: UserID;
}
type DMSendPermissionsParams = DMSendPermissionsByChannelParams | DMSendPermissionsByChannelIdParams;
export abstract class BaseChannelAuthService {
protected abstract readonly options: ChannelAuthOptions;
protected dmPermissionValidator: DMPermissionValidator;
@@ -136,8 +148,10 @@ export abstract class BaseChannelAuthService {
};
}
async validateDMSendPermissions({channelId, userId}: {channelId: ChannelID; userId: UserID}): Promise<void> {
const channel = await this.channelRepository.channelData.findUnique(channelId);
async validateDMSendPermissions(params: DMSendPermissionsParams): Promise<void> {
const {userId} = params;
const channel =
'channel' in params ? params.channel : await this.channelRepository.channelData.findUnique(params.channelId);
if (!channel) throw new UnknownChannelError();
if (channel.type === ChannelTypes.GROUP_DM || channel.type === ChannelTypes.DM_PERSONAL_NOTES) {
return;
@@ -47,12 +47,7 @@ export class MessageInteractionService {
guildRepository,
gatewayService,
);
this.pinAuthService = new MessagePinAuthService(
channelRepository,
userRepository,
guildRepository,
gatewayService,
);
this.pinAuthService = new MessagePinAuthService(channelRepository, userRepository, guildRepository, gatewayService);
this.readStateService = new MessageReadStateService(gatewayService);
this.pinService = new MessagePinService(
gatewayService,
@@ -110,7 +105,7 @@ export class MessageInteractionService {
}): Promise<void> {
const authChannel = await this.authService.getChannelAuthenticated({userId, channelId});
if (!authChannel.guild && authChannel.channel.type !== ChannelTypes.DM_PERSONAL_NOTES) {
await this.authService.validateDMSendPermissions({channelId, userId});
await this.authService.validateDMSendPermissions({channel: authChannel.channel, userId});
}
await this.pinService.pinMessage({authChannel, messageId, userId, requestCache});
}
@@ -128,7 +123,7 @@ export class MessageInteractionService {
}): Promise<void> {
const authChannel = await this.authService.getChannelAuthenticated({userId, channelId});
if (!authChannel.guild && authChannel.channel.type !== ChannelTypes.DM_PERSONAL_NOTES) {
await this.authService.validateDMSendPermissions({channelId, userId});
await this.authService.validateDMSendPermissions({channel: authChannel.channel, userId});
}
await this.pinService.unpinMessage({authChannel, messageId, userId, requestCache});
}
@@ -238,7 +238,6 @@ export class MessageSendService {
user,
checkPermission,
hasPermission,
channelId,
}: {
guild: GuildResponse | null;
member: GuildMemberResponse | null;
@@ -247,7 +246,6 @@ export class MessageSendService {
user: User;
checkPermission: (permission: bigint) => Promise<void>;
hasPermission: (permission: bigint) => Promise<boolean>;
channelId: ChannelID;
}): Promise<{
canEmbedLinks: boolean;
canMentionEveryone: boolean;
@@ -286,7 +284,7 @@ export class MessageSendService {
}
await this.deps.channelAuthService.checkGuildVerification({user, guild, member});
} else if (channel.type === ChannelTypes.DM || channel.type === ChannelTypes.GROUP_DM) {
await this.deps.channelAuthService.validateDMSendPermissions({channelId, userId: user.id});
await this.deps.channelAuthService.validateDMSendPermissions({channel, userId: user.id});
}
return {canEmbedLinks, canMentionEveryone, canAttachFiles};
}
@@ -320,7 +318,6 @@ export class MessageSendService {
user,
checkPermission,
hasPermission,
channelId,
});
this.deps.validationService.ensureTextChannel(channel);
const isForwardMessage = this.ensureMessageRequestIsValid({user, data, guildFeatures: guild?.features ?? null});
@@ -809,7 +806,6 @@ export class MessageSendService {
user,
checkPermission,
hasPermission,
channelId,
});
const needsSlowmodeCheck = guild && channel.rateLimitPerUser && channel.rateLimitPerUser > 0 && !user.isBot;
const slowmodeBypass = needsSlowmodeCheck ? await hasPermission(Permissions.BYPASS_SLOWMODE) : false;
@@ -4,13 +4,20 @@ import {Permissions} from '@fluxer/constants/src/ChannelConstants';
import {MAX_MESSAGE_LENGTH_PREMIUM} from '@fluxer/constants/src/LimitConstants';
import type {MessageResponse} from '@fluxer/schema/src/domains/message/MessageResponseSchemas';
import {afterAll, beforeAll, beforeEach, describe, expect, it, vi} from 'vitest';
import {createTestAccount} from '../../auth/tests/AuthTestUtils';
import {authorizeBot, createTestBotAccount} from '../../bot/tests/BotTestUtils';
import {ensureSessionStarted} from '../../message/tests/MessageTestUtils';
import {getGatewayService} from '../../middleware/ServiceRegistry';
import {type ApiTestHarness, createApiTestHarness} from '../../test/ApiTestHarness';
import {HTTP_STATUS} from '../../test/TestConstants';
import {createBuilder} from '../../test/TestRequestBuilder';
import {createPermissionOverwrite, setupTestGuildWithMembers} from './ChannelTestUtils';
import {ChannelDataRepository} from '../repositories/ChannelDataRepository';
import {
createDmChannel,
createFriendship,
createPermissionOverwrite,
setupTestGuildWithMembers,
} from './ChannelTestUtils';
describe('Message send permissions', () => {
let harness: ApiTestHarness;
@@ -140,6 +147,28 @@ describe('Message send permissions', () => {
}
});
it('resolves the DM channel once for routing and once for authentication when sending', async () => {
const sender = await createTestAccount(harness);
const recipient = await createTestAccount(harness);
await ensureSessionStarted(harness, sender.token);
await createFriendship(harness, sender, recipient);
const channel = await createDmChannel(harness, sender.token, recipient.userId);
const findUnique = vi.spyOn(ChannelDataRepository.prototype, 'findUnique');
try {
await createBuilder<MessageResponse>(harness, sender.token)
.post(`/channels/${channel.id}/messages`)
.body({content: 'no redundant reads'})
.expect(HTTP_STATUS.OK)
.execute();
const channelReads = findUnique.mock.calls.filter((call) => call[0].toString() === channel.id).length;
expect(channelReads).toBe(2);
} finally {
findUnique.mockRestore();
}
});
it('allows bot message content up to 4000 characters', async () => {
const {owner, guild, systemChannel} = await setupTestGuildWithMembers(harness, 0);
const botAccount = await createTestBotAccount(harness);