Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
28 changes: 28 additions & 0 deletions frontend/e2e/admin-upload-auth.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
import { test, expect } from "@playwright/test";

/**
* Admin upload file-serve auth guard — regression test for GitHub issue #65.
*
* Verifies that `GET /api/admin/upload/{filename}` rejects unauthenticated
* requests with 401 Unauthorized. Before this fix the route returned uploaded
* admin assets to any caller that knew the filename.
*
* Only the unauthenticated boundary is asserted here. The non-admin (403) and
* admin (200) cases depend on seeded NextAuth session state that isn't
* available in the standalone e2e environment used by this suite.
*
* Requires:
* - Next.js dev/preview server on the baseURL defined in playwright.config
* (any AUTH_MODE — the check runs at route entry before mode branching)
*/

test.describe("Admin upload file-serve — auth guard", () => {
test("unauthenticated GET returns 401 Unauthorized", async ({ request }) => {
const response = await request.get(
"/api/admin/upload/does-not-matter.png",
);

expect(response.status()).toBe(401);
await expect(response.json()).resolves.toEqual({ error: "Unauthorized" });
});
});
11 changes: 11 additions & 0 deletions frontend/src/app/api/admin/upload/[filename]/route.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
import { NextRequest, NextResponse } from "next/server";
import { readFile, access, constants } from "fs/promises";
import path from "path";
import { auth } from "@/lib/auth";
import { isAdmin } from "@/types/auth-mode";
import type { UserRole } from "@/types/auth-mode";

// Get upload directory (same as upload route)
function getUploadDir(): string {
Expand All @@ -26,6 +29,14 @@ export async function GET(
{ params }: { params: Promise<{ filename: string }> },
) {
try {
const session = await auth();
if (!session?.user) {
return NextResponse.json({ error: "Unauthorized" }, { status: 401 });
}
if (!isAdmin(session.user.role as UserRole)) {
return NextResponse.json({ error: "Forbidden" }, { status: 403 });
}

const { filename } = await params;

// Security: prevent directory traversal
Expand Down
Loading