"Email PO to vendor" (issue #14) relies on PdfService fetching /api/po/<id>/export?...&svc=<token> WITHOUT a user session, authenticating with a `svc` token that matches PDF_SERVICE_TOKEN. The route handler validates that token, but the auth middleware runs first and its matcher doesn't exempt the export route — so every unauthenticated fetch was redirected to /login (307) and the svc bypass never executed. Net effect: the feature could never render a real PDF on any deployed env, even with the service configured. Fix: middleware now lets exactly `/api/po/<id>/export` through when its `svc` query param matches `process.env.PDF_SERVICE_TOKEN` (the route handler still re-validates it — defense in depth). Everything else stays auth-gated. The match lives in a dependency-free, edge-safe, unit-tested helper (lib/pdf-export-auth.ts); middleware already reads server env at runtime via auth()/NEXTAUTH_SECRET, so reading PDF_SERVICE_TOKEN there is consistent. Verified on a running build: correct svc + real PO -> 200, correct svc + bogus PO -> 404 (handler ran), wrong/no svc -> 307 (still gated). 324 unit tests green; tsc clean. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
33 lines
1.2 KiB
TypeScript
33 lines
1.2 KiB
TypeScript
import { auth } from "@/auth";
|
|
import { NextResponse } from "next/server";
|
|
import { isPdfExportServiceRequest } from "@/lib/pdf-export-auth";
|
|
|
|
export default auth((req) => {
|
|
const isAuthenticated = !!req.auth;
|
|
const pathname = req.nextUrl.pathname;
|
|
const isLoginPage = pathname === "/login";
|
|
|
|
// PdfService fetches the PO export page unauthenticated, using a `svc` token
|
|
// that matches PDF_SERVICE_TOKEN (the route handler re-validates it). Let that
|
|
// one route through so the service token isn't bounced to /login by the gate
|
|
// below. Everything else stays auth-protected.
|
|
if (isPdfExportServiceRequest(pathname, req.nextUrl.searchParams.get("svc"), process.env.PDF_SERVICE_TOKEN)) {
|
|
return NextResponse.next();
|
|
}
|
|
|
|
if (!isAuthenticated && !isLoginPage) {
|
|
const loginUrl = new URL("/login", req.url);
|
|
loginUrl.searchParams.set("callbackUrl", pathname);
|
|
return NextResponse.redirect(loginUrl);
|
|
}
|
|
|
|
if (isAuthenticated && isLoginPage) {
|
|
return NextResponse.redirect(new URL("/dashboard", req.url));
|
|
}
|
|
});
|
|
|
|
export const config = {
|
|
matcher: [
|
|
"/((?!api/auth|_next/static|_next/image|favicon.ico|.*\\.(?:svg|png|jpg|jpeg|gif|webp)$).*)",
|
|
],
|
|
};
|