import { describe, it, expect, vi, beforeEach } from "vitest"; const mocks = vi.hoisted(() => ({ nextResponse: Symbol("next"), jsonResponse: vi.fn((body, init) => ({ status: init?.status || 200, body, })), getSettings: vi.fn(), getUserById: vi.fn(), validateApiKey: vi.fn(), getConsistentMachineId: vi.fn(), getDashboardAuthSession: vi.fn(), verifyDashboardAuthToken: vi.fn(), })); vi.mock("next/server", () => ({ NextResponse: { next: vi.fn(() => mocks.nextResponse), json: mocks.jsonResponse, redirect: vi.fn((url) => ({ status: 307, url })), }, })); vi.mock("@/lib/localDb", () => ({ getSettings: mocks.getSettings, getUserById: mocks.getUserById, validateApiKey: mocks.validateApiKey, })); vi.mock("@/shared/utils/machineId", () => ({ getConsistentMachineId: mocks.getConsistentMachineId, })); vi.mock("@/lib/auth/dashboardSession", () => ({ getDashboardAuthSession: mocks.getDashboardAuthSession, verifyDashboardAuthToken: mocks.verifyDashboardAuthToken, })); const { proxy, __test__ } = await import("../../src/dashboardGuard.js"); function request(pathname, headers = {}, authToken) { const normalizedHeaders = new Headers(headers); return { nextUrl: { pathname, searchParams: new URL(`http://localhost${pathname}`).searchParams }, headers: normalizedHeaders, cookies: { get: vi.fn(() => authToken ? { value: authToken } : undefined) }, url: `http://localhost${pathname}`, }; } describe("dashboard guard public LLM API access", () => { beforeEach(() => { vi.clearAllMocks(); mocks.getSettings.mockResolvedValue({ requireLogin: true }); mocks.getUserById.mockResolvedValue(null); mocks.validateApiKey.mockResolvedValue(false); mocks.getConsistentMachineId.mockResolvedValue("cli-token"); mocks.getDashboardAuthSession.mockResolvedValue(null); mocks.verifyDashboardAuthToken.mockResolvedValue(false); }); it("allows loopback public LLM API without API key", async () => { const response = await proxy(request("/v1/chat/completions", { host: "localhost:20128" })); expect(response).toBe(mocks.nextResponse); expect(mocks.validateApiKey).not.toHaveBeenCalled(); }); it("rejects remote Host-spoof when real peer IP is non-loopback", async () => { const response = await proxy(request("/v1/chat/completions", { host: "localhost", "x-9r-real-ip": "10.204.111.34", })); expect(response.status).toBe(401); expect(response.body.error).toBe("API key required for remote API access"); }); it("allows loopback peer IP regardless of Host", async () => { const response = await proxy(request("/v1/chat/completions", { host: "localhost:20128", "x-9r-real-ip": "127.0.0.1", })); expect(response).toBe(mocks.nextResponse); expect(mocks.validateApiKey).not.toHaveBeenCalled(); }); it("rejects remote rewritten public LLM API without API key", async () => { const response = await proxy(request("/api/v1/chat/completions", { host: "router.example.com" })); expect(response.status).toBe(401); expect(response.body.error).toBe("API key required for remote API access"); }); it("allows loopback rewritten public LLM API without API key", async () => { const response = await proxy(request("/api/v1/chat/completions", { host: "localhost:20128" })); expect(response).toBe(mocks.nextResponse); expect(mocks.validateApiKey).not.toHaveBeenCalled(); }); it("rejects remote beta public LLM API without API key", async () => { const response = await proxy(request("/v1beta/models", { host: "router.example.com" })); expect(response.status).toBe(401); expect(response.body.error).toBe("API key required for remote API access"); }); it("rejects remote rewritten beta public LLM API without API key", async () => { const response = await proxy(request("/api/v1beta/models", { host: "router.example.com" })); expect(response.status).toBe(401); expect(response.body.error).toBe("API key required for remote API access"); }); it("rejects remote codex rewrite without API key", async () => { const response = await proxy(request("/codex/x", { host: "router.example.com" })); expect(response.status).toBe(401); expect(response.body.error).toBe("API key required for remote API access"); }); it("allows remote codex rewrite with valid API key", async () => { mocks.validateApiKey.mockResolvedValue(true); const response = await proxy(request("/codex/x", { host: "router.example.com", authorization: "Bearer sk-valid", })); expect(response).toBe(mocks.nextResponse); expect(mocks.validateApiKey).toHaveBeenCalledWith("sk-valid"); }); it("allows remote public LLM API with valid bearer API key", async () => { mocks.validateApiKey.mockResolvedValue(true); const response = await proxy(request("/api/v1/chat/completions", { host: "router.example.com", authorization: "Bearer sk-valid", })); expect(response).toBe(mocks.nextResponse); expect(mocks.validateApiKey).toHaveBeenCalledWith("sk-valid"); }); it("allows remote public LLM API with valid x-api-key", async () => { mocks.validateApiKey.mockResolvedValue(true); const response = await proxy(request("/v1/web/fetch", { host: "router.example.com", "x-api-key": "sk-valid", })); expect(response).toBe(mocks.nextResponse); expect(mocks.validateApiKey).toHaveBeenCalledWith("sk-valid"); }); it("allows remote rewritten beta public LLM API with valid API key", async () => { mocks.validateApiKey.mockResolvedValue(true); const response = await proxy(request("/api/v1beta/models", { host: "router.example.com", "x-api-key": "sk-valid", })); expect(response).toBe(mocks.nextResponse); expect(mocks.validateApiKey).toHaveBeenCalledWith("sk-valid"); }); it("allows remote beta public LLM API with valid Google API key header", async () => { mocks.validateApiKey.mockResolvedValue(true); const response = await proxy(request("/v1beta/models", { host: "router.example.com", "x-goog-api-key": "sk-valid", })); expect(response).toBe(mocks.nextResponse); expect(mocks.validateApiKey).toHaveBeenCalledWith("sk-valid"); }); it("allows remote beta public LLM API with valid Google key query parameter", async () => { mocks.validateApiKey.mockResolvedValue(true); const response = await proxy(request("/v1beta/models?key=sk-valid", { host: "router.example.com", })); expect(response).toBe(mocks.nextResponse); expect(mocks.validateApiKey).toHaveBeenCalledWith("sk-valid"); }); }); describe("dashboard guard local-only access", () => { beforeEach(() => { vi.clearAllMocks(); mocks.getSettings.mockResolvedValue({ requireLogin: true }); mocks.getUserById.mockResolvedValue(null); mocks.validateApiKey.mockResolvedValue(false); mocks.getConsistentMachineId.mockResolvedValue("cli-token"); mocks.getDashboardAuthSession.mockResolvedValue(null); mocks.verifyDashboardAuthToken.mockResolvedValue(false); }); it("rejects local-only route from non-loopback host without CLI token", async () => { const response = await proxy(request("/api/mcp/filesystem/sse", { host: "router.example.com", })); expect(response.status).toBe(403); expect(response.body.error).toBe("Local only: CLI token required"); }); it("rejects local-only route on loopback when requireLogin=true and no JWT", async () => { const response = await proxy(request("/api/mcp/filesystem/sse", { host: "localhost:20128", origin: "http://localhost:20128", })); expect(response.status).toBe(403); expect(response.body.error).toBe("Local only: CLI token required"); }); it("requires an administrator for CLI Tools even when dashboard login is disabled", async () => { mocks.getSettings.mockResolvedValue({ requireLogin: false }); const response = await proxy(request("/api/cli-tools/antigravity-mitm", { host: "localhost:20128", origin: "http://localhost:20128", })); expect(response.status).toBe(403); expect(response.body.error).toBe("Administrator access required"); }); it("rejects local-only route from tunnel host even when requireLogin=false", async () => { mocks.getSettings.mockResolvedValue({ requireLogin: false }); const response = await proxy(request("/api/cli-tools/antigravity-mitm", { host: "router.example.com", })); expect(response.status).toBe(403); }); it("rejects local-only route when Origin is non-loopback (CSRF block)", async () => { mocks.getSettings.mockResolvedValue({ requireLogin: false }); const response = await proxy(request("/api/cli-tools/antigravity-mitm", { host: "localhost:20128", origin: "http://evil.example.com", })); expect(response.status).toBe(403); }); it("allows local-only route with valid CLI token", async () => { const response = await proxy(request("/api/mcp/filesystem/sse", { host: "router.example.com", "x-9r-cli-token": "cli-token", })); expect(response).toBe(mocks.nextResponse); }); }); describe("dashboard guard CLI Tools administration access", () => { beforeEach(() => { vi.clearAllMocks(); mocks.getSettings.mockResolvedValue({ requireLogin: true }); mocks.getUserById.mockResolvedValue({ id: "user-1", isActive: true, role: "user" }); mocks.validateApiKey.mockResolvedValue(false); mocks.getConsistentMachineId.mockResolvedValue("cli-token"); mocks.getDashboardAuthSession.mockResolvedValue({ userId: "user-1" }); mocks.verifyDashboardAuthToken.mockResolvedValue(true); }); it("rejects normal users from host-level CLI Tools operations", async () => { const response = await proxy(request("/api/cli-tools/claude-settings", { host: "localhost:20128", origin: "http://localhost:20128", }, "user-token")); expect(response.status).toBe(403); expect(response.body.error).toBe("Administrator access required"); }); it("rejects remote CLI Tools access even with an administrator session", async () => { mocks.getUserById.mockResolvedValue({ id: "user-1", isActive: true, role: "admin" }); const response = await proxy(request("/api/cli-tools/claude-settings", { host: "router.example.com", }, "admin-token")); expect(response.status).toBe(403); expect(response.body.error).toBe("CLI Tools are available only from the local machine"); }); it("allows a local administrator to use CLI Tools", async () => { mocks.getUserById.mockResolvedValue({ id: "user-1", isActive: true, role: "admin" }); const response = await proxy(request("/api/cli-tools/claude-settings", { host: "localhost:20128", origin: "http://localhost:20128", }, "admin-token")); expect(response).toBe(mocks.nextResponse); }); }); describe("dashboard guard token saver administration access", () => { beforeEach(() => { vi.clearAllMocks(); mocks.getSettings.mockResolvedValue({ requireLogin: true }); mocks.getUserById.mockResolvedValue({ id: "user-1", isActive: true, role: "user" }); mocks.getConsistentMachineId.mockResolvedValue("cli-token"); mocks.getDashboardAuthSession.mockResolvedValue({ userId: "user-1" }); mocks.verifyDashboardAuthToken.mockResolvedValue(true); }); it("rejects normal users from Token Saver pages and APIs", async () => { for (const pathname of [ "/api/headroom/status", "/api/pxpipe/status", ]) { const response = await proxy(request(pathname, { host: "localhost:20128" }, "user-token")); expect(response.status).toBe(403); expect(response.body.error).toBe("Administrator access required"); } const response = await proxy(request("/dashboard/token-saver", { host: "localhost:20128" }, "user-token")); expect(response.status).toBe(307); expect(response.url.href).toBe("http://localhost/dashboard"); }); it("allows administrators to access Token Saver pages and APIs", async () => { mocks.getUserById.mockResolvedValue({ id: "user-1", isActive: true, role: "admin" }); expect(await proxy(request("/dashboard/token-saver", { host: "localhost:20128" }, "admin-token"))).toBe(mocks.nextResponse); expect(await proxy(request("/api/headroom/status", { host: "localhost:20128" }, "admin-token"))).toBe(mocks.nextResponse); expect(await proxy(request("/api/pxpipe/status", { host: "localhost:20128" }, "admin-token"))).toBe(mocks.nextResponse); }); }); describe("dashboard guard helpers", () => { it("extracts bearer API keys before x-api-key", () => { const apiRequest = request("/v1/chat/completions", { authorization: "Bearer bearer-key", "x-api-key": "header-key", }); expect(__test__.extractApiKey(apiRequest)).toBe("bearer-key"); }); it("extracts Google API keys after x-api-key", () => { const apiRequest = request("/v1beta/models?key=query-key", { "x-api-key": "header-key", "x-goog-api-key": "google-key", }); expect(__test__.extractApiKey(apiRequest)).toBe("header-key"); }); });