cm_whatsapp_bot_v1/apps/web/src/middleware.test.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

85 lines
2.6 KiB
TypeScript

import { describe, it, expect, beforeAll } from "vitest";
import { NextRequest } from "next/server";
const SECRET = "test-secret";
beforeAll(() => {
process.env.AUTH_SECRET = SECRET;
process.env.OPERATOR_TOKEN_VERSION = "1";
});
import { signSession } from "./lib/auth-cookie";
import { middleware } from "./middleware";
async function makeReq(path: string, cookie?: string): Promise<NextRequest> {
const url = new URL(`https://wabot.04080616.xyz${path}`);
const headers = new Headers();
if (cookie) headers.set("cookie", `session=${cookie}`);
return new NextRequest(url, { headers });
}
async function validCookie(): Promise<string> {
const now = Math.floor(Date.now() / 1000);
return signSession(
{
userId: "00000000-0000-0000-0000-000000000000",
role: "admin",
iat: now,
exp: now + 3600,
v: 1,
},
SECRET,
);
}
describe("middleware", () => {
it("page request without a cookie redirects to /login?next=…", async () => {
const r = await middleware(await makeReq("/dashboard"));
expect(r.status).toBe(307);
expect(r.headers.get("location")).toContain("/login");
expect(r.headers.get("location")).toContain("next=%2Fdashboard");
});
it("/api/* request without a cookie returns 401 with no body", async () => {
const r = await middleware(await makeReq("/api/events"));
expect(r.status).toBe(401);
});
it("page request with a valid cookie passes through", async () => {
const r = await middleware(await makeReq("/dashboard", await validCookie()));
// NextResponse.next() returns a 200 with the x-middleware-next header.
expect(r.status).toBe(200);
});
it("page request with a tampered cookie redirects to /login", async () => {
const cookie = (await validCookie()).slice(0, -4) + "AAAA";
const r = await middleware(await makeReq("/dashboard", cookie));
expect(r.status).toBe(307);
expect(r.headers.get("location")).toContain("/login");
});
it("allowlisted paths bypass auth (login, logout, health, manifest, icons)", async () => {
for (const path of [
"/login",
"/logout",
"/api/health",
"/manifest.webmanifest",
"/icon-192.png",
"/favicon.ico",
]) {
const r = await middleware(await makeReq(path));
expect(r.status).toBe(200);
}
});
it("/api/events and /api/qr/<id> are NOT in the allowlist (regression)", async () => {
expect((await middleware(await makeReq("/api/events"))).status).toBe(401);
expect(
(
await middleware(
await makeReq("/api/qr/11111111-1111-1111-1111-111111111111"),
)
).status,
).toBe(401);
});
});