import { NextRequest, NextResponse } from "next/server"; import { unsealData } from "iron-session"; const COOKIE_NAME = "cm_auth"; const PUBLIC_PATHS = new Set(["/cm-auth"]); type SessionShape = { username: string; authenticatedAt: number; }; async function isAuthenticated(req: NextRequest): Promise { const raw = req.cookies.get(COOKIE_NAME)?.value; if (!raw) return false; const secret = process.env.CM_AUTH_SECRET; if (!secret || secret.length < 32) return false; try { const session = await unsealData(raw, { password: secret }); return Boolean(session?.username); } catch { return false; } } export async function middleware(req: NextRequest) { const path = req.nextUrl.pathname; const normalized = path.length > 1 && path.endsWith("/") ? path.slice(0, -1) : path; if (PUBLIC_PATHS.has(normalized)) return NextResponse.next(); if (await isAuthenticated(req)) return NextResponse.next(); const url = req.nextUrl.clone(); url.pathname = "/cm-auth"; url.searchParams.set("next", path); return NextResponse.redirect(url); } export const config = { // next.config.ts sets `trailingSlash: true`, so /icon redirects to /icon/. // The icon$/apple-icon$ alternatives below allow the optional slash so the // canonical (slashed) URL bypasses the auth gate too — otherwise the // browser hits the redirect, follows it to the slashed form, and the gate // refuses to serve the image and bounces to /cm-auth. matcher: [ "/((?!_next|icon/?$|apple-icon/?$|manifest.webmanifest|favicon.ico).*)", ], };