import type { NextRequest } from "next/server"; import { NextResponse } from "next/server"; const SESSION_COOKIE = "inven_session"; function getAuthSecret() { return process.env.AUTH_SECRET || "dev-insecure-auth-secret"; } async function sign(value: string) { const key = await crypto.subtle.importKey( "raw", new TextEncoder().encode(getAuthSecret()), { name: "HMAC", hash: "SHA-256" }, false, ["sign"] ); const signature = await crypto.subtle.sign("HMAC", key, new TextEncoder().encode(value)); return Array.from(new Uint8Array(signature)) .map((byte) => byte.toString(16).padStart(2, "0")) .join(""); } function decodeBase64Url(value: string) { const normalized = value.replace(/-/g, "+").replace(/_/g, "/"); const padded = normalized.padEnd(Math.ceil(normalized.length / 4) * 4, "="); return atob(padded); } async function hasValidSession(request: NextRequest) { const raw = request.cookies.get(SESSION_COOKIE)?.value; if (!raw) { return false; } const [base, signature] = raw.split("."); if (!base || !signature || (await sign(base)) !== signature) { return false; } try { const payload = JSON.parse(decodeBase64Url(base)) as { expiresAt?: number }; return typeof payload.expiresAt === "number" && payload.expiresAt > Date.now(); } catch { return false; } } export async function proxy(request: NextRequest) { const { pathname } = request.nextUrl; const isPublic = pathname === "/login" || pathname.startsWith("/_next") || pathname.startsWith("/favicon") || pathname === "/api/health"; const authenticated = await hasValidSession(request); if (!authenticated && !isPublic) { const url = request.nextUrl.clone(); url.pathname = "/login"; return NextResponse.redirect(url); } if (authenticated && pathname === "/login") { const url = request.nextUrl.clone(); url.pathname = "/"; return NextResponse.redirect(url); } return NextResponse.next(); } export const config = { matcher: ["/((?!.*\\..*).*)"] };