diff --git a/web/middleware.ts b/web/middleware.ts new file mode 100644 index 0000000..f27cde4 --- /dev/null +++ b/web/middleware.ts @@ -0,0 +1,42 @@ +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 = { + matcher: [ + "/((?!_next|icon$|apple-icon$|manifest.webmanifest|favicon.ico).*)", + ], +};