From 1b9df4d20df5b9f1658920cf0f1f66ab6a11bab3 Mon Sep 17 00:00:00 2001 From: Nils-Johan Gynther Date: Thu, 16 Apr 2026 19:10:06 +0200 Subject: [PATCH] feat: add API route for serving images with path validation --- frontend/app/api/images/[filename]/route.ts | 31 +++++++++++++++++++++ frontend/next.config.js | 8 ++++++ 2 files changed, 39 insertions(+) create mode 100644 frontend/app/api/images/[filename]/route.ts diff --git a/frontend/app/api/images/[filename]/route.ts b/frontend/app/api/images/[filename]/route.ts new file mode 100644 index 00000000..51f07b4f --- /dev/null +++ b/frontend/app/api/images/[filename]/route.ts @@ -0,0 +1,31 @@ +import { NextRequest, NextResponse } from 'next/server'; +import * as fs from 'fs'; +import * as path from 'path'; + +const IMAGE_DIR = process.env.IMAGE_DIR || '/app/public/images'; + +export async function GET( + _request: NextRequest, + { params }: { params: Promise<{ filename: string }> }, +) { + const { filename } = await params; + + // Förhindra path traversal + if (!filename || filename.includes('..') || filename.includes('/') || filename.includes('\\')) { + return new NextResponse('Not found', { status: 404 }); + } + + const filePath = path.join(IMAGE_DIR, filename); + + if (!fs.existsSync(filePath)) { + return new NextResponse('Not found', { status: 404 }); + } + + const file = fs.readFileSync(filePath); + return new NextResponse(file, { + headers: { + 'Content-Type': 'image/jpeg', + 'Cache-Control': 'public, max-age=31536000, immutable', + }, + }); +} diff --git a/frontend/next.config.js b/frontend/next.config.js index b408897b..76da2edf 100644 --- a/frontend/next.config.js +++ b/frontend/next.config.js @@ -1,6 +1,14 @@ /** @type {import('next').NextConfig} */ const nextConfig = { output: 'standalone', + async rewrites() { + return [ + { + source: '/images/:filename', + destination: '/api/images/:filename', + }, + ]; + }, }; module.exports = nextConfig;