From 8472e4a4fc6a617a55e1302d8235ca70d9a5c042 Mon Sep 17 00:00:00 2001 From: hexastack Date: Tue, 8 Apr 2025 12:09:51 +0100 Subject: [PATCH] feat: deal with file upload size locally --- .../controllers/attachment.controller.ts | 17 ++++++++++++++ api/src/attachment/dto/attachment.dto.ts | 8 +++++++ api/src/attachment/utilities/index.ts | 23 +++++++++++++++++++ api/src/config/index.ts | 20 ++++++++++++++++ api/src/config/types.ts | 8 +++++++ docker/.env.example | 6 +++++ 6 files changed, 82 insertions(+) diff --git a/api/src/attachment/controllers/attachment.controller.ts b/api/src/attachment/controllers/attachment.controller.ts index 44cbe661..567cd04a 100644 --- a/api/src/attachment/controllers/attachment.controller.ts +++ b/api/src/attachment/controllers/attachment.controller.ts @@ -46,6 +46,7 @@ import { AttachmentGuard } from '../guards/attachment-ability.guard'; import { Attachment } from '../schemas/attachment.schema'; import { AttachmentService } from '../services/attachment.service'; import { AttachmentAccess, AttachmentCreatedByRef } from '../types'; +import { attachmentSizeLimits } from '../utilities'; @UseInterceptors(CsrfInterceptor) @Controller('attachment') @@ -131,12 +132,28 @@ export class AttachmentController extends BaseController { { resourceRef, access = AttachmentAccess.Public, + maxSize, }: AttachmentContextParamDto, ): Promise { if (!files || !Array.isArray(files?.file) || files.file.length === 0) { throw new BadRequestException('No file was selected'); } + // Determine the size limit + const sizeLimit = + maxSize ?? + attachmentSizeLimits[resourceRef] ?? + config.parameters.maxUploadSize; + + // Validate file sizes against the limit + for (const file of files.file) { + if (file.size > sizeLimit) { + throw new BadRequestException( + `File ${file.originalname} exceeds maximum size of ${sizeLimit} bytes`, + ); + } + } + const userId = req.session?.passport?.user?.id; if (!userId) { throw new ForbiddenException( diff --git a/api/src/attachment/dto/attachment.dto.ts b/api/src/attachment/dto/attachment.dto.ts index c10d0096..256eefa7 100644 --- a/api/src/attachment/dto/attachment.dto.ts +++ b/api/src/attachment/dto/attachment.dto.ts @@ -154,4 +154,12 @@ export class AttachmentContextParamDto { @IsIn(Object.values(AttachmentAccess)) @IsOptional() access?: AttachmentAccess; + + @ApiPropertyOptional({ + description: 'Maximum file size in bytes for this upload', + type: Number, + }) + @IsNumber() + @IsOptional() + maxSize?: number; } diff --git a/api/src/attachment/utilities/index.ts b/api/src/attachment/utilities/index.ts index 13dead20..f8c0fc64 100644 --- a/api/src/attachment/utilities/index.ts +++ b/api/src/attachment/utilities/index.ts @@ -109,3 +109,26 @@ export const isAttachmentResourceRefArray = ( refList.every(isAttachmentResourceRef) ); }; + +/** + * Retrieves the maximum size limit for a given resource reference. + * + * @param resourceRef - The resource reference to check. + * @returns The maximum size limit in bytes. + */ +export const attachmentSizeLimits: Partial< + Record +> = { + [AttachmentResourceRef.SettingAttachment]: + config.attachmentSizeLimits.settingAttachmentSizeLimit, + [AttachmentResourceRef.UserAvatar]: + config.attachmentSizeLimits.userAvatarSizeLimit, + [AttachmentResourceRef.SubscriberAvatar]: + config.attachmentSizeLimits.subscriberAvatarSizeLimit, + [AttachmentResourceRef.BlockAttachment]: + config.attachmentSizeLimits.blockAttachmentSizeLimit, + [AttachmentResourceRef.ContentAttachment]: + config.attachmentSizeLimits.contentAttachmentSizeLimit, + [AttachmentResourceRef.MessageAttachment]: + config.attachmentSizeLimits.messageAttachmentSizeLimit, +}; diff --git a/api/src/config/index.ts b/api/src/config/index.ts index 4c8a66d5..1b770c80 100644 --- a/api/src/config/index.ts +++ b/api/src/config/index.ts @@ -194,4 +194,24 @@ export const config: Config = { retentionReset: 24 * 60 * 60 * 1000, // 1 day }, }, + attachmentSizeLimits: { + settingAttachmentSizeLimit: process.env.SETTING_ATTACHMENT_MAX_SIZE_IN_BYTES + ? Number(process.env.SETTING_ATTACHMENT_MAX_SIZE_IN_BYTES) + : 5 * 1024 * 1024, + userAvatarSizeLimit: process.env.USER_AVATAR_MAX_SIZE_IN_BYTES + ? Number(process.env.USER_AVATAR_MAX_SIZE_IN_BYTES) + : 2 * 1024 * 1024, + subscriberAvatarSizeLimit: process.env.SUBSCRIBER_AVATAR_MAX_SIZE_IN_BYTES + ? Number(process.env.SUBSCRIBER_AVATAR_MAX_SIZE_IN_BYTES) + : 2 * 1024 * 1024, + blockAttachmentSizeLimit: process.env.BLOCK_ATTACHMENT_MAX_SIZE_IN_BYTES + ? Number(process.env.BLOCK_ATTACHMENT_MAX_SIZE_IN_BYTES) + : 5 * 1024 * 1024, + contentAttachmentSizeLimit: process.env.CONTENT_ATTACHMENT_MAX_SIZE_IN_BYTES + ? Number(process.env.CONTENT_ATTACHMENT_MAX_SIZE_IN_BYTES) + : 10 * 1024 * 1024, + messageAttachmentSizeLimit: process.env.MESSAGE_ATTACHMENT_MAX_SIZE_IN_BYTES + ? Number(process.env.MESSAGE_ATTACHMENT_MAX_SIZE_IN_BYTES) + : 10 * 1024 * 1024, + }, }; diff --git a/api/src/config/types.ts b/api/src/config/types.ts index 97850130..a6601863 100644 --- a/api/src/config/types.ts +++ b/api/src/config/types.ts @@ -123,4 +123,12 @@ export type Config = { retentionReset: number; }; }; + attachmentSizeLimits: { + settingAttachmentSizeLimit: number; + userAvatarSizeLimit: number; + subscriberAvatarSizeLimit: number; + blockAttachmentSizeLimit: number; + contentAttachmentSizeLimit: number; + messageAttachmentSizeLimit: number; + }; }; diff --git a/docker/.env.example b/docker/.env.example index d2941d2d..28482369 100644 --- a/docker/.env.example +++ b/docker/.env.example @@ -22,6 +22,12 @@ UPLOAD_DIR=/uploads STORAGE_MODE=disk # Max attachments upload size in bytes UPLOAD_MAX_SIZE_IN_BYTES=20971520 +SETTING_ATTACHMENT_MAX_SIZE_IN_BYTES=5242880 +USER_AVATAR_MAX_SIZE_IN_BYTES=2097152 +SUBSCRIBER_AVATAR_MAX_SIZE_IN_BYTES=2097152 +BLOCK_ATTACHMENT_MAX_SIZE_IN_BYTES=5242880 +CONTENT_ATTACHMENT_MAX_SIZE_IN_BYTES=10485760 +MESSAGE_ATTACHMENT_MAX_SIZE_IN_BYTES=10485760 INVITATION_JWT_SECRET=dev_only INVITATION_EXPIRES_IN=24h PASSWORD_RESET_JWT_SECRET=dev_only