import { NextResponse } from "next/server"; import type { NextRequest } from "next/server"; import jwt from "jsonwebtoken"; const PROTECTED_PREFIXES = ["/account", "/admin"]; const API_BASE = process.env.NEXT_PUBLIC_API_URL || "http://localhost:8000/api/v1"; const JWT_PUBLIC_KEY_RAW = process.env.JWT_PUBLIC_KEY; if (!JWT_PUBLIC_KEY_RAW) { throw new Error("JWT_PUBLIC_KEY environment variable is required"); } const JWT_PUBLIC_KEY = JWT_PUBLIC_KEY_RAW; function isExpired(token: string): boolean { try { const decoded = jwt.verify(token, JWT_PUBLIC_KEY, { algorithms: ["RS256"], }) as any; return Date.now() / 1000 >= decoded.exp; } catch { return true; } } export async function middleware(req: NextRequest) { const { pathname } = req.nextUrl; const isProtected = PROTECTED_PREFIXES.some( (p) => pathname === p || pathname.startsWith(`${p}/`), ); if (!isProtected) return NextResponse.next(); const access = req.cookies.get("access_token")?.value; if (access && !isExpired(access)) return NextResponse.next(); // Server-side refresh: validate the session against the backend using the // refresh cookie instead of trusting the (possibly forged/expired) access JWT. const refresh = req.cookies.get("refresh_token")?.value; if (refresh) { try { const res = await fetch(`${API_BASE}/auth/refresh/`, { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ refresh }), cache: "no-store", }); if (res.ok) { const data = await res.json(); const newAccess = data.access as string | undefined; const newRefresh = (data.refresh as string) || refresh; if (newAccess) { const response = NextResponse.next(); const secure = req.nextUrl.protocol === "https:"; const base = { httpOnly: true, sameSite: "lax" as const, path: "/", secure, }; response.cookies.set("access_token", newAccess, { ...base, maxAge: 1800, }); response.cookies.set("refresh_token", newRefresh, { ...base, maxAge: 60 * 60 * 24 * 30, }); return response; } } } catch { // fall through to redirect } } const url = req.nextUrl.clone(); url.pathname = "/login"; url.searchParams.set("next", pathname); return NextResponse.redirect(url); } export const config = { matcher: ["/account/:path*", "/admin/:path*"], };