presenton/servers/nextjs/middleware.ts
sudipnext 5766e252aa feat: Implement PDF export functionality with session handling
- Added middleware to handle session cookies for the PDF export route.
- Introduced a new API endpoint for exporting presentation data using session cookies.
- Updated the PdfMakerPage component to accept and utilize the export cookie.
- Enhanced the presentation export logic to include session token extraction from cookies.
- Updated routing configuration to include the new PDF maker path.
2026-04-27 21:13:57 +05:45

109 lines
3.2 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;
};
const SESSION_COOKIE_NAME = "presenton_session";
const SESSION_TTL_SECONDS = 60 * 60 * 24 * 30;
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" ||
pathname.startsWith("/api/export-presentation-data/")
);
}
export async function middleware(request: NextRequest) {
const { pathname } = request.nextUrl;
if (pathname === "/pdf-maker") {
const exportSession = request.nextUrl.searchParams.get("exportSession");
if (exportSession) {
const redirectUrl = request.nextUrl.clone();
redirectUrl.searchParams.delete("exportSession");
const response = NextResponse.redirect(redirectUrl);
response.cookies.set({
name: SESSION_COOKIE_NAME,
value: exportSession,
maxAge: SESSION_TTL_SECONDS,
httpOnly: true,
secure:
request.headers.get("x-forwarded-proto")?.toLowerCase() === "https" ||
request.nextUrl.protocol === "https:",
sameSite: "lax",
path: "/",
});
return response;
}
return NextResponse.next();
}
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*", "/pdf-maker"],
};