cm_whatsapp_bot_v1/apps/web/src/middleware.ts
yiekheng b77a9d106d feat(web): middleware gates non-allowlisted paths on session cookie
Edge-runtime check via auth-cookie.verifySession. /api/* paths get a
401 (no body) when unauthenticated; pages get a 307 to /login with
the original path encoded into ?next=. Allowlist explicitly excludes
/api/events and /api/qr — both were unauthenticated in v1.1.0 and
let an unauthenticated client snoop the entire SSE event stream and
enumerate paired account QR codes.
2026-05-10 17:57:07 +08:00

42 lines
1.2 KiB
TypeScript

import { NextRequest, NextResponse } from "next/server";
import { COOKIE_NAME, verifySession } from "./lib/auth-cookie";
const PUBLIC_PATHS = new Set<string>([
"/login",
"/logout",
"/api/health",
"/manifest.webmanifest",
"/favicon.ico",
"/robots.txt",
]);
function isPublic(path: string): boolean {
if (PUBLIC_PATHS.has(path)) return true;
if (path.startsWith("/icon-")) return true;
if (path.startsWith("/_next/")) return true;
return false;
}
export async function middleware(req: NextRequest): Promise<NextResponse> {
const path = req.nextUrl.pathname;
if (isPublic(path)) return NextResponse.next();
const cookie = req.cookies.get(COOKIE_NAME)?.value;
const secret = process.env.AUTH_SECRET;
const ok =
!!cookie && !!secret && (await verifySession(cookie, secret)) !== null;
if (ok) return NextResponse.next();
if (path.startsWith("/api/")) {
return new NextResponse("Unauthorized", { status: 401 });
}
const url = req.nextUrl.clone();
url.pathname = "/login";
url.searchParams.set("next", path + (req.nextUrl.search || ""));
return NextResponse.redirect(url);
}
export const config = {
matcher: ["/((?!_next/static|_next/image).*)"],
};