From 6eceb9b88621a8a17da974f963ef260eb5a1464f Mon Sep 17 00:00:00 2001 From: Dotta Date: Mon, 16 Mar 2026 10:13:19 -0500 Subject: [PATCH] Use attachment-size limit for company logos --- docs/api/companies.md | 2 ++ server/src/__tests__/assets.test.ts | 13 +++++++------ server/src/routes/assets.ts | 9 ++------- ui/src/pages/CompanySettings.tsx | 6 +----- 4 files changed, 12 insertions(+), 18 deletions(-) diff --git a/docs/api/companies.md b/docs/api/companies.md index ca9fa2f1..00e7ab66 100644 --- a/docs/api/companies.md +++ b/docs/api/companies.md @@ -61,6 +61,8 @@ Valid image content types: - `image/gif` - `image/svg+xml` +Company logo uploads use the normal Paperclip attachment size limit. + Then set the company logo by PATCHing the returned `assetId` into `logoAssetId`. ## Archive Company diff --git a/server/src/__tests__/assets.test.ts b/server/src/__tests__/assets.test.ts index b6f740b7..b7bec332 100644 --- a/server/src/__tests__/assets.test.ts +++ b/server/src/__tests__/assets.test.ts @@ -1,6 +1,7 @@ import { afterEach, describe, expect, it, vi } from "vitest"; import express from "express"; import request from "supertest"; +import { MAX_ATTACHMENT_BYTES } from "../attachment-types.js"; import { assetRoutes } from "../routes/assets.js"; import type { StorageService } from "../storage/types.js"; @@ -195,30 +196,30 @@ describe("POST /api/companies/:companyId/logo", () => { expect(body).not.toContain("https://evil.example/"); }); - it("allows a logo exactly 100 KB in size", async () => { + it("allows logo uploads within the general attachment limit", async () => { const png = createStorageService("image/png"); const app = createApp(png); createAssetMock.mockResolvedValue(createAsset()); - const file = Buffer.alloc(100 * 1024, "a"); + const file = Buffer.alloc(150 * 1024, "a"); const res = await request(app) .post("/api/companies/company-1/logo") - .attach("file", file, "exact-limit.png"); + .attach("file", file, "within-limit.png"); expect(res.status).toBe(201); }); - it("rejects logo files larger than 100 KB", async () => { + it("rejects logo files larger than the general attachment limit", async () => { const app = createApp(createStorageService()); createAssetMock.mockResolvedValue(createAsset()); - const file = Buffer.alloc(100 * 1024 + 1, "a"); + const file = Buffer.alloc(MAX_ATTACHMENT_BYTES + 1, "a"); const res = await request(app) .post("/api/companies/company-1/logo") .attach("file", file, "too-large.png"); expect(res.status).toBe(422); - expect(res.body.error).toBe("Image exceeds 102400 bytes"); + expect(res.body.error).toBe(`Image exceeds ${MAX_ATTACHMENT_BYTES} bytes`); }); it("rejects unsupported image types", async () => { diff --git a/server/src/routes/assets.ts b/server/src/routes/assets.ts index ce2793a5..0a6f857a 100644 --- a/server/src/routes/assets.ts +++ b/server/src/routes/assets.ts @@ -8,7 +8,6 @@ import type { StorageService } from "../storage/types.js"; import { assetService, logActivity } from "../services/index.js"; import { isAllowedContentType, MAX_ATTACHMENT_BYTES } from "../attachment-types.js"; import { assertCompanyAccess, getActorInfo } from "./authz.js"; -const MAX_COMPANY_LOGO_BYTES = 100 * 1024; const SVG_CONTENT_TYPE = "image/svg+xml"; const ALLOWED_COMPANY_LOGO_CONTENT_TYPES = new Set([ "image/png", @@ -92,7 +91,7 @@ export function assetRoutes(db: Db, storage: StorageService) { }); const companyLogoUpload = multer({ storage: multer.memoryStorage(), - limits: { fileSize: MAX_COMPANY_LOGO_BYTES + 1, files: 1 }, + limits: { fileSize: MAX_ATTACHMENT_BYTES, files: 1 }, }); async function runSingleFileUpload( @@ -157,10 +156,6 @@ export function assetRoutes(db: Db, storage: StorageService) { res.status(422).json({ error: "Image is empty" }); return; } - if (fileBody.length > MAX_COMPANY_LOGO_BYTES) { - res.status(422).json({ error: `Image exceeds ${MAX_COMPANY_LOGO_BYTES} bytes` }); - return; - } const actor = getActorInfo(req); const stored = await storage.putFile({ @@ -224,7 +219,7 @@ export function assetRoutes(db: Db, storage: StorageService) { } catch (err) { if (err instanceof multer.MulterError) { if (err.code === "LIMIT_FILE_SIZE") { - res.status(422).json({ error: `Image exceeds ${MAX_COMPANY_LOGO_BYTES} bytes` }); + res.status(422).json({ error: `Image exceeds ${MAX_ATTACHMENT_BYTES} bytes` }); return; } res.status(400).json({ error: err.message }); diff --git a/ui/src/pages/CompanySettings.tsx b/ui/src/pages/CompanySettings.tsx index f805eebc..225b7398 100644 --- a/ui/src/pages/CompanySettings.tsx +++ b/ui/src/pages/CompanySettings.tsx @@ -160,10 +160,6 @@ export function CompanySettings() { const file = event.target.files?.[0] ?? null; event.currentTarget.value = ""; if (!file) return; - if (file.size > 100 * 1024) { - setLogoUploadError("Logo image must be 100 KB or smaller."); - return; - } setLogoUploadError(null); logoUploadMutation.mutate(file); } @@ -276,7 +272,7 @@ export function CompanySettings() {