diff --git a/fluxer_api/src/api/channel/services/BaseChannelAuthService.ts b/fluxer_api/src/api/channel/services/BaseChannelAuthService.ts index 9103c8d42..e6929aa3e 100644 --- a/fluxer_api/src/api/channel/services/BaseChannelAuthService.ts +++ b/fluxer_api/src/api/channel/services/BaseChannelAuthService.ts @@ -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 { - const channel = await this.channelRepository.channelData.findUnique(channelId); + async validateDMSendPermissions(params: DMSendPermissionsParams): Promise { + 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; diff --git a/fluxer_api/src/api/channel/services/MessageInteractionService.ts b/fluxer_api/src/api/channel/services/MessageInteractionService.ts index 8bc3f2fd1..bf8f6cb3b 100644 --- a/fluxer_api/src/api/channel/services/MessageInteractionService.ts +++ b/fluxer_api/src/api/channel/services/MessageInteractionService.ts @@ -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 { 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 { 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}); } diff --git a/fluxer_api/src/api/channel/services/message/MessageSendService.ts b/fluxer_api/src/api/channel/services/message/MessageSendService.ts index dd80dc07c..a635a6612 100644 --- a/fluxer_api/src/api/channel/services/message/MessageSendService.ts +++ b/fluxer_api/src/api/channel/services/message/MessageSendService.ts @@ -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; hasPermission: (permission: bigint) => Promise; - 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; diff --git a/fluxer_api/src/api/channel/tests/MessageSendPermissions.test.ts b/fluxer_api/src/api/channel/tests/MessageSendPermissions.test.ts index 04bcf97c5..25f1a97f8 100644 --- a/fluxer_api/src/api/channel/tests/MessageSendPermissions.test.ts +++ b/fluxer_api/src/api/channel/tests/MessageSendPermissions.test.ts @@ -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(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);