Files
HIPCTF2/.kilo/plans/885.md
T

5.4 KiB

Implementation Plan: Admin Area General Settings and Categories 1.03

1. Architectural Reconnaissance

  • Codebase style & conventions: TypeScript monorepo with a NestJS 10 REST backend and Angular 17 standalone components. Upload requests are encapsulated in AdminService; AdminGeneralComponent uses reactive forms, signals, async handlers, and inline data-testid feedback. Backend upload handling currently lives directly in UploadsController, uses Multer memory buffers, sharp, collision-resistant safeFilename, and the global exception filter's standard { code, message, path, timestamp } envelope. The Job is not already implemented: handleLogoUpload only checks presence/size before writing raw bytes, while the existing frontend error branch is never reached for malformed data.
  • Data Layer: SQLite through TypeORM for settings, but this fix does not require a schema or migration. Logo bytes are stored under configured UPLOAD_DIR (default /data/hipctf/uploads) and become active only after the returned URL is assigned to the form and settings are saved.
  • Test Framework & Structure: Root Jest 29/ts-jest multi-project configuration. Backend endpoint tests live in tests/backend, use Nest's real application, in-memory SQLite, Supertest, CSRF/auth setup, and an isolated upload directory; frontend logic tests live in tests/frontend with jsdom. All tests run with root npm test, with focused backend runs available through npm run test:backend.
  • Required Tools & Dependencies: No new system tool, global CLI, package, or setup.sh change is required. Reuse the already installed sharp dependency to decode the full image and obtain authoritative format metadata rather than trusting the multipart MIME type, extension, or magic bytes alone. Existing setup already installs native dependencies, prepares /data/hipctf/uploads, and builds both workspaces.

2. Impacted Files

  • To Modify:
    • backend/src/modules/uploads/uploads.controller.ts — make logo handling asynchronous; decode and validate the complete image against an explicit logo format policy before creating a filename or writing bytes.
    • tests/backend/uploads-logo.spec.ts — replace the header-only pseudo-PNG success fixture with a genuinely decodable image and add focused missing/malformed/unsupported rejection assertions, including proof that rejected payloads create no files.
  • To Create: None.

3. Proposed Changes

  1. Database / Schema Migration: No database change. Invalid uploads must never return a URL or mutate the General form's existing logo control, so the persisted setting and subsequent bootstrap pageLogo remain unchanged.
  2. Backend Logic & APIs:
    • Convert handleLogoUpload to return a promise and await it from uploadLogo.
    • Retain the current missing-file and configured-size checks, then call sharp(file.buffer).metadata() (or an equivalent full decode operation) before any filesystem side effect. Treat decode failures, absent format/dimensions, truncated/corrupt images, and formats outside an explicit configured logo allowlist as 400 BadRequestException/VALIDATION_FAILED with a stable, user-readable message such as Logo must be a valid PNG, JPEG, GIF, or WebP image.
    • Define the logo policy beside the handler as a readonly set/literal union so accepted formats are auditable. Base acceptance on Sharp's detected format, not file.mimetype, original extension, or browser accept filtering. Keep the current upload-size policy and collision-resistant filename behavior for accepted files.
    • Perform validation before mkdir/writeFileSync; only valid bytes reach UPLOAD_DIR. Preserve the response contract { publicUrl, originalFilename } and existing 201 status for valid uploads.
  3. Frontend UI Integration: No frontend code change is expected. AdminGeneralComponent.onLogoFileChange already clears the prior error, updates form.controls.logo only after a successful uploadLogo response, catches the server's standard error message into logoUploadError, and resets uploadingLogo in finally. Once the backend rejects malformed payloads, [data-testid="general-logo-error"] will render, the previous hidden [data-testid="general-logo"] value will be preserved, and the form remains usable. Do not add client-only validation as a security boundary; accept="image/*" remains a picker hint.

4. Test Strategy

  • Target Unit Test File: Modify tests/backend/uploads-logo.spec.ts; no broad component harness or UI/E2E suite is needed because the existing component's success-only mutation and catch/finally behavior already provides the required preservation/feedback flow.
  • Mocking Strategy: Use no mocks for image validation or filesystem behavior. Generate a tiny valid PNG in-memory with the existing sharp package for the core success test. Through the authenticated Supertest endpoint, assert: missing multipart file returns 400; representative malformed/unsupported inputs (plain text with a misleading image name/MIME, a truncated GIF, and an eight-zero-byte PNG sent as image/png) return 400 with the standard validation envelope; and the upload directory listing is unchanged after each rejection. Keep one valid-image assertion proving a URL is returned, the file is persisted/fetchable, and cleanup remains hermetic. Run all coverage from the repository root with npm test; during implementation also run the existing root build/typecheck (npm run build) since no standalone lint script is defined.