import { NextResponse } from "next/server" import { getSessionUser } from "@/lib/session" import { readFile, contentTypeForKey } from "@/lib/storage" // Only these image types are safe to render inline from our origin. Everything // else (including svg, html, documents) is forced to download as an attachment. const INLINE_EXTENSIONS = ["png", "jpg", "jpeg", "gif", "webp"] // Auth-gated file serving. Storage keys are namespaced by user id // (`//`), so a file belongs to the requester iff the key's // first segment equals their session user id. export async function GET(_: Request, { params }: { params: Promise<{ key: string[] }> }) { const user = await getSessionUser() if (!user) return NextResponse.json({ error: "Unauthorized" }, { status: 401 }) const { key: segments } = await params const key = segments.map((s) => decodeURIComponent(s)).join("/") if (!key.startsWith(`${user.id}/`)) { return NextResponse.json({ error: "Forbidden" }, { status: 403 }) } try { const buffer = await readFile(key) const ext = key.split(".").pop()?.toLowerCase() ?? "" const disposition = INLINE_EXTENSIONS.includes(ext) ? "inline" : "attachment" const basename = (key.split("/").pop() ?? "file").replace(/["\\\r\n\x00-\x1f]/g, "") return new NextResponse(new Uint8Array(buffer), { headers: { "Content-Type": contentTypeForKey(key), "Content-Disposition": `${disposition}; filename="${basename}"`, "X-Content-Type-Options": "nosniff", "Cache-Control": "private, max-age=3600", }, }) } catch { return NextResponse.json({ error: "Not found" }, { status: 404 }) } }