diff --git a/src/app/api/auth/status/route.js b/src/app/api/auth/status/route.js index 32cc503b..ebef5766 100644 --- a/src/app/api/auth/status/route.js +++ b/src/app/api/auth/status/route.js @@ -24,6 +24,7 @@ export async function GET() { hasPassword: !!settings.password, displayName, loginMethod, + authenticated: !!session, oidcName: oidcName || null, oidcEmail: oidcEmail || null, oidcLogin: !!session?.oidc, @@ -37,6 +38,7 @@ export async function GET() { hasPassword: false, displayName: "Password user", loginMethod: "Password", + authenticated: false, oidcName: null, oidcEmail: null, oidcLogin: false, diff --git a/src/app/login/page.js b/src/app/login/page.js index 8e50a191..86c7b7d4 100644 --- a/src/app/login/page.js +++ b/src/app/login/page.js @@ -37,7 +37,7 @@ export default function LoginPage() { if (res.ok) { const data = await res.json(); - if (data.requireLogin === false) { + if (data.authenticated === true || data.requireLogin === false) { window.location.assign("/dashboard"); return; } diff --git a/tests/unit/auth-status.test.js b/tests/unit/auth-status.test.js new file mode 100644 index 00000000..e384bb72 --- /dev/null +++ b/tests/unit/auth-status.test.js @@ -0,0 +1,69 @@ +import { describe, it, expect, vi, beforeEach } from "vitest"; + +const mocks = vi.hoisted(() => ({ + json: vi.fn((body, init) => ({ + status: init?.status || 200, + body, + })), + cookies: vi.fn(), + getSettings: vi.fn(), + isOidcConfigured: vi.fn(), + getDashboardAuthSession: vi.fn(), +})); + +vi.mock("next/server", () => ({ + NextResponse: { json: mocks.json }, +})); + +vi.mock("next/headers", () => ({ + cookies: mocks.cookies, +})); + +vi.mock("@/lib/localDb", () => ({ + getSettings: mocks.getSettings, +})); + +vi.mock("@/lib/auth/oidc", () => ({ + isOidcConfigured: mocks.isOidcConfigured, +})); + +vi.mock("@/lib/auth/dashboardSession", () => ({ + getDashboardAuthSession: mocks.getDashboardAuthSession, +})); + +const { GET } = await import("../../src/app/api/auth/status/route.js"); + +describe("GET /api/auth/status", () => { + beforeEach(() => { + vi.clearAllMocks(); + mocks.getSettings.mockResolvedValue({ requireLogin: true, authMode: "password" }); + mocks.cookies.mockResolvedValue({ get: vi.fn(() => ({ value: "session-token" })) }); + mocks.isOidcConfigured.mockReturnValue(false); + }); + + it("reports an authenticated session when the auth cookie is valid", async () => { + mocks.getDashboardAuthSession.mockResolvedValue({ authenticated: true }); + + const response = await GET(); + + expect(response.body.authenticated).toBe(true); + expect(mocks.getDashboardAuthSession).toHaveBeenCalledWith("session-token"); + }); + + it("reports unauthenticated when the auth cookie is invalid", async () => { + mocks.getDashboardAuthSession.mockResolvedValue(null); + + const response = await GET(); + + expect(response.body.authenticated).toBe(false); + }); + + it("fails closed when status dependencies throw", async () => { + mocks.getSettings.mockRejectedValue(new Error("database unavailable")); + + const response = await GET(); + + expect(response.body.authenticated).toBe(false); + expect(response.body.requireLogin).toBe(true); + }); +});