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; validateNsfw: boolean;
} }
interface DMSendPermissionsByChannelParams {
channel: Channel;
userId: UserID;
}
interface DMSendPermissionsByChannelIdParams {
channelId: ChannelID;
userId: UserID;
}
type DMSendPermissionsParams = DMSendPermissionsByChannelParams | DMSendPermissionsByChannelIdParams;
export abstract class BaseChannelAuthService { export abstract class BaseChannelAuthService {
protected abstract readonly options: ChannelAuthOptions; protected abstract readonly options: ChannelAuthOptions;
protected dmPermissionValidator: DMPermissionValidator; protected dmPermissionValidator: DMPermissionValidator;
@@ -136,8 +148,10 @@ export abstract class BaseChannelAuthService {
}; };
} }
async validateDMSendPermissions({channelId, userId}: {channelId: ChannelID; userId: UserID}): Promise<void> { async validateDMSendPermissions(params: DMSendPermissionsParams): Promise<void> {
const channel = await this.channelRepository.channelData.findUnique(channelId); const {userId} = params;
const channel =
'channel' in params ? params.channel : await this.channelRepository.channelData.findUnique(params.channelId);
if (!channel) throw new UnknownChannelError(); if (!channel) throw new UnknownChannelError();
if (channel.type === ChannelTypes.GROUP_DM || channel.type === ChannelTypes.DM_PERSONAL_NOTES) { if (channel.type === ChannelTypes.GROUP_DM || channel.type === ChannelTypes.DM_PERSONAL_NOTES) {
return; return;
@@ -47,12 +47,7 @@ export class MessageInteractionService {
guildRepository, guildRepository,
gatewayService, gatewayService,
); );
this.pinAuthService = new MessagePinAuthService( this.pinAuthService = new MessagePinAuthService(channelRepository, userRepository, guildRepository, gatewayService);
channelRepository,
userRepository,
guildRepository,
gatewayService,
);
this.readStateService = new MessageReadStateService(gatewayService); this.readStateService = new MessageReadStateService(gatewayService);
this.pinService = new MessagePinService( this.pinService = new MessagePinService(
gatewayService, gatewayService,
@@ -110,7 +105,7 @@ export class MessageInteractionService {
}): Promise<void> { }): Promise<void> {
const authChannel = await this.authService.getChannelAuthenticated({userId, channelId}); const authChannel = await this.authService.getChannelAuthenticated({userId, channelId});
if (!authChannel.guild && authChannel.channel.type !== ChannelTypes.DM_PERSONAL_NOTES) { 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}); await this.pinService.pinMessage({authChannel, messageId, userId, requestCache});
} }
@@ -128,7 +123,7 @@ export class MessageInteractionService {
}): Promise<void> { }): Promise<void> {
const authChannel = await this.authService.getChannelAuthenticated({userId, channelId}); const authChannel = await this.authService.getChannelAuthenticated({userId, channelId});
if (!authChannel.guild && authChannel.channel.type !== ChannelTypes.DM_PERSONAL_NOTES) { 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}); await this.pinService.unpinMessage({authChannel, messageId, userId, requestCache});
} }
@@ -238,7 +238,6 @@ export class MessageSendService {
user, user,
checkPermission, checkPermission,
hasPermission, hasPermission,
channelId,
}: { }: {
guild: GuildResponse | null; guild: GuildResponse | null;
member: GuildMemberResponse | null; member: GuildMemberResponse | null;
@@ -247,7 +246,6 @@ export class MessageSendService {
user: User; user: User;
checkPermission: (permission: bigint) => Promise<void>; checkPermission: (permission: bigint) => Promise<void>;
hasPermission: (permission: bigint) => Promise<boolean>; hasPermission: (permission: bigint) => Promise<boolean>;
channelId: ChannelID;
}): Promise<{ }): Promise<{
canEmbedLinks: boolean; canEmbedLinks: boolean;
canMentionEveryone: boolean; canMentionEveryone: boolean;
@@ -286,7 +284,7 @@ export class MessageSendService {
} }
await this.deps.channelAuthService.checkGuildVerification({user, guild, member}); await this.deps.channelAuthService.checkGuildVerification({user, guild, member});
} else if (channel.type === ChannelTypes.DM || channel.type === ChannelTypes.GROUP_DM) { } 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}; return {canEmbedLinks, canMentionEveryone, canAttachFiles};
} }
@@ -320,7 +318,6 @@ export class MessageSendService {
user, user,
checkPermission, checkPermission,
hasPermission, hasPermission,
channelId,
}); });
this.deps.validationService.ensureTextChannel(channel); this.deps.validationService.ensureTextChannel(channel);
const isForwardMessage = this.ensureMessageRequestIsValid({user, data, guildFeatures: guild?.features ?? null}); const isForwardMessage = this.ensureMessageRequestIsValid({user, data, guildFeatures: guild?.features ?? null});
@@ -809,7 +806,6 @@ export class MessageSendService {
user, user,
checkPermission, checkPermission,
hasPermission, hasPermission,
channelId,
}); });
const needsSlowmodeCheck = guild && channel.rateLimitPerUser && channel.rateLimitPerUser > 0 && !user.isBot; const needsSlowmodeCheck = guild && channel.rateLimitPerUser && channel.rateLimitPerUser > 0 && !user.isBot;
const slowmodeBypass = needsSlowmodeCheck ? await hasPermission(Permissions.BYPASS_SLOWMODE) : false; 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 {MAX_MESSAGE_LENGTH_PREMIUM} from '@fluxer/constants/src/LimitConstants';
import type {MessageResponse} from '@fluxer/schema/src/domains/message/MessageResponseSchemas'; import type {MessageResponse} from '@fluxer/schema/src/domains/message/MessageResponseSchemas';
import {afterAll, beforeAll, beforeEach, describe, expect, it, vi} from 'vitest'; import {afterAll, beforeAll, beforeEach, describe, expect, it, vi} from 'vitest';
import {createTestAccount} from '../../auth/tests/AuthTestUtils';
import {authorizeBot, createTestBotAccount} from '../../bot/tests/BotTestUtils'; import {authorizeBot, createTestBotAccount} from '../../bot/tests/BotTestUtils';
import {ensureSessionStarted} from '../../message/tests/MessageTestUtils'; import {ensureSessionStarted} from '../../message/tests/MessageTestUtils';
import {getGatewayService} from '../../middleware/ServiceRegistry'; import {getGatewayService} from '../../middleware/ServiceRegistry';
import {type ApiTestHarness, createApiTestHarness} from '../../test/ApiTestHarness'; import {type ApiTestHarness, createApiTestHarness} from '../../test/ApiTestHarness';
import {HTTP_STATUS} from '../../test/TestConstants'; import {HTTP_STATUS} from '../../test/TestConstants';
import {createBuilder} from '../../test/TestRequestBuilder'; 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', () => { describe('Message send permissions', () => {
let harness: ApiTestHarness; 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 () => { it('allows bot message content up to 4000 characters', async () => {
const {owner, guild, systemChannel} = await setupTestGuildWithMembers(harness, 0); const {owner, guild, systemChannel} = await setupTestGuildWithMembers(harness, 0);
const botAccount = await createTestBotAccount(harness); const botAccount = await createTestBotAccount(harness);