From 8fd39d6e1afb8f3153130c8290b8ec90d98511f7 Mon Sep 17 00:00:00 2001 From: a2nr Date: Sat, 3 Oct 2026 05:16:41 +0700 Subject: [PATCH] =?UTF-8?q?fix(frontend):=20upload=20asset=20lolos=20proxy?= =?UTF-8?q?=20=E2=80=94=20BODY=5FSIZE=5FLIMIT=208M=20+=20proxy=20413=20jel?= =?UTF-8?q?as?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Body upload (JSON base64, ~7 MiB untuk file 5 MiB) ditolak default adapter-node 512K sebelum sampai backend Flask, lalu error itu terlapor sebagai 502 'Backend tidak tersedia'. - podman-compose: BODY_SIZE_LIMIT=8M untuk elemes-frontend - hooks.server: SvelteKitError body-size (413) dipisah dari kegagalan koneksi — balas 413 'Ukuran file terlalu besar' bukan 502 - test: proxy balas 413 untuk body melebihi limit --- frontend/src/hooks.server.test.ts | 33 +++++++++++++++++++++++++++++++ frontend/src/hooks.server.ts | 23 +++++++++++++++++++-- podman-compose.yml | 3 +++ 3 files changed, 57 insertions(+), 2 deletions(-) diff --git a/frontend/src/hooks.server.test.ts b/frontend/src/hooks.server.test.ts index 9a81156..0892214 100644 --- a/frontend/src/hooks.server.test.ts +++ b/frontend/src/hooks.server.test.ts @@ -57,6 +57,39 @@ describe('hooks.server handle', () => { expect(response.headers.get('Cross-Origin-Resource-Policy')).toBe('cross-origin'); }); + it('should return 413 with size message when body exceeds adapter limit', async () => { + // SvelteKitError dari adapter-node saat body POST melebihi BODY_SIZE_LIMIT. + // Proxy harus balas 413 dengan pesan ukuran — bukan 502 "Backend tidak tersedia". + const arrayBufferMock = vi.fn().mockRejectedValue( + Object.assign(new Error('Content-length of 947488 exceeds limit of 524288 bytes.'), { status: 413 }) + ); + const mockEvent: any = { + request: { + method: 'POST', + headers: new Headers({ 'content-type': 'application/json' }), + arrayBuffer: arrayBufferMock + }, + url: new URL('https://sinau-c-dev.manakin-gentoo.ts.net/api/content/assets/upload') + }; + + const origFetch = global.fetch; + global.fetch = vi.fn() as any; + + try { + const resolve = vi.fn(); + const response = await handle({ event: mockEvent, resolve }); + + expect(response.status).toBe(413); + const body = await response.json(); + expect(body.success).toBe(false); + expect(body.message).toMatch(/terlalu besar/i); + // Tidak boleh dikirim ke backend sama sekali + expect(global.fetch).not.toHaveBeenCalled(); + } finally { + global.fetch = origFetch; + } + }); + it('should read body as arrayBuffer when proxying POST requests', async () => { const arrayBufferMock = vi.fn().mockResolvedValue(new Uint8Array([1, 2, 3]).buffer); const mockEvent: any = { diff --git a/frontend/src/hooks.server.ts b/frontend/src/hooks.server.ts index 2bb26ab..31ebdf4 100644 --- a/frontend/src/hooks.server.ts +++ b/frontend/src/hooks.server.ts @@ -77,9 +77,28 @@ export const handle: Handle = async ({ event, resolve }) => { status: res.status, headers: resHeaders, }); - } catch (err) { + } catch (err: any) { + // adapter-node melempar SvelteKitError saat body POST melebihi + // BODY_SIZE_LIMIT — bukan berarti backend mati. Laporkan sebagai 413 + // dengan pesan yang jelas, jangan 502 "Backend tidak tersedia". + const isBodyTooLarge = + err?.status === 413 || /exceeds limit/i.test(String(err?.message ?? '')); + if (isBodyTooLarge) { + console.warn(`[hooks] Body too large for ${event.request.method} ${event.url.pathname}`); + return new Response( + JSON.stringify({ + success: false, + error: 'Payload too large', + message: 'Ukuran file terlalu besar (maksimum 5 MiB per asset)' + }), + { + status: 413, + headers: { 'content-type': 'application/json' } + } + ); + } console.error(`API proxy error (${backendUrl}):`, err); - return new Response(JSON.stringify({ error: 'Backend unavailable' }), { + return new Response(JSON.stringify({ success: false, error: 'Backend unavailable', message: 'Backend tidak tersedia' }), { status: 502, headers: { 'content-type': 'application/json' }, }); diff --git a/podman-compose.yml b/podman-compose.yml index 7b4145f..50a5290 100644 --- a/podman-compose.yml +++ b/podman-compose.yml @@ -83,6 +83,9 @@ services: environment: - ORIGIN=${ORIGIN:-http://localhost:3000} - API_BACKEND=http://127.0.0.1:5000 + # Backend menerima asset ≤ 5 MiB (JSON base64 ≈ 7 MiB di wire). + # Default adapter-node 512K menolak upload gambar sebelum sampai backend. + - BODY_SIZE_LIMIT=8M - PUBLIC_APP_BAR_TITLE=${APP_BAR_TITLE} - PUBLIC_COPYRIGHT_TEXT=${COPYRIGHT_TEXT} - PUBLIC_PAGE_TITLE_SUFFIX=${PAGE_TITLE_SUFFIX}