presenton/servers/nextjs/middleware.ts

80 lines
2.3 KiB
TypeScript

import { NextRequest, NextResponse } from "next/server";
/**
* API-only: session required for all /api/* except auth and telemetry.
* Page routes are protected in server layouts (unknown URLs still 404; login uses relative redirects).
*/
function getFastApiBaseUrl(request: NextRequest): string {
const internal = process.env.FAST_API_INTERNAL_URL?.trim();
if (internal) {
return internal.replace(/\/+$/, "");
}
const configured = process.env.NEXT_PUBLIC_FAST_API?.trim();
if (configured) {
return configured.replace(/\/+$/, "");
}
if (process.env.NODE_ENV === "development") {
return "http://127.0.0.1:8000";
}
return "http://127.0.0.1:8000";
}
type AuthStatus = {
configured: boolean;
authenticated: boolean;
};
async function getAuthStatus(request: NextRequest): Promise<AuthStatus> {
const cookieHeader = request.headers.get("cookie");
const authStatusUrl = `${getFastApiBaseUrl(request)}/api/v1/auth/status`;
try {
const response = await fetch(authStatusUrl, {
method: "GET",
headers: cookieHeader ? { Cookie: cookieHeader } : undefined,
cache: "no-store",
});
if (!response.ok) {
return { configured: true, authenticated: false };
}
const payload = (await response.json()) as Partial<AuthStatus>;
return {
configured: Boolean(payload.configured),
authenticated: Boolean(payload.authenticated),
};
} catch {
return { configured: true, authenticated: false };
}
}
function isApiAuthExempt(pathname: string): boolean {
return (
pathname.startsWith("/api/v1/auth/") || pathname === "/api/telemetry-status"
);
}
export async function middleware(request: NextRequest) {
const { pathname } = request.nextUrl;
if (request.method === "OPTIONS" || isApiAuthExempt(pathname)) {
return NextResponse.next();
}
const authStatus = await getAuthStatus(request);
if (authStatus.authenticated) {
return NextResponse.next();
}
if (!authStatus.configured) {
return NextResponse.json(
{ detail: "Login setup is required", setup_required: true },
{ status: 428, headers: { "Cache-Control": "no-store" } }
);
}
return NextResponse.json(
{ detail: "Unauthorized" },
{ status: 401, headers: { "Cache-Control": "no-store" } }
);
}
export const config = {
matcher: ["/api/:path*"],
};