import { expect, test, vi } from "vitest"; import { createAuthDiagnoser } from "../src/auth/diagnostics.js"; import { OidcJwksUnavailableError } from "../src/auth/oidc-client.js"; import type { LoadedAuthConfig } from "../src/auth/types.js"; const sentinels = [ "oidc-client-secret-UNIQUE-7P3", "authentik-api-token-UNIQUE-9Q7", "cookie-UNIQUE-5M1", "$argon2id$v=19$password-hash-UNIQUE-2T8", "/private/path-UNIQUE-4K6", ]; function oidcConfig(): LoadedAuthConfig { return { revision: "a".repeat(64), sourcePath: "/safe/auth.yaml", value: { version: 1, mode: "oidc", publicUrl: "https://thothii.example.test", session: { regularTtlSeconds: 1, regularIdleSeconds: 1, rememberTtlSeconds: 1, rememberIdleSeconds: 1, oidcTtlSeconds: 1 }, oidc: { issuer: "https://issuer.example.test/application/o/thothii/", clientId: "thothii", clientSecretRef: "THT_OIDC_CLIENT_SECRET", scopes: ["openid"], groupsClaim: "groups" }, groupCatalog: { driver: "authentik", baseUrl: "https://authentik.example.test", apiTokenRef: "THT_AUTHENTIK_API_TOKEN" }, authorization: { groupRoles: { "TOT Users": ["user"], "TOT Admin": ["admin"] } }, }, }; } test("reports deterministic live OIDC checks and silently ignores unrelated groups", async () => { const oidcDiagnose = vi.fn(async () => undefined); const groupCatalog = { verifyConfiguredGroups: vi.fn(async (names: readonly string[]) => { expect(names).toEqual(["TOT Admin", "TOT Users"]); return []; }) }; const report = await createAuthDiagnoser({ authMode: "oidc", authentication: { current: oidcConfig }, authStateRoot: "/safe/auth-state", secrets: new Map([["THT_OIDC_CLIENT_SECRET", sentinels[0]!], ["THT_AUTHENTIK_API_TOKEN", sentinels[1]!]]), oidcProtocol: { diagnose: oidcDiagnose }, groupCatalog, }).inspect({ live: true }); expect(report).toEqual({ ready: true, mode: "oidc", checks: [expect.objectContaining({ level: "info", code: "auth_ready" })] }); expect(oidcDiagnose).toHaveBeenCalledOnce(); expect(groupCatalog.verifyConfiguredGroups).toHaveBeenCalledOnce(); expect(report.checks).not.toContainEqual(expect.objectContaining({ level: "warning" })); expect(JSON.stringify(report)).not.toContain("Unmapped Corporate Group"); }); test("requires both fixed OIDC and Authentik secrets only in OIDC mode", async () => { const oidc = createAuthDiagnoser({ authMode: "oidc", authentication: { current: oidcConfig }, authStateRoot: "/safe/auth-state", secrets: new Map(), }); const local = createAuthDiagnoser({ authMode: "local", authStateRoot: "/safe/auth-state", secrets: new Map(), authentication: { current: () => ({ revision: "b".repeat(64), sourcePath: "/safe/auth.yaml", value: { version: 1, mode: "local", publicUrl: "http://127.0.0.1:8080", session: { regularTtlSeconds: 1, regularIdleSeconds: 1, rememberTtlSeconds: 1, rememberIdleSeconds: 1, oidcTtlSeconds: 1 }, local: { usersFile: "users.yaml" }, }, }) }, hasEnabledLocalAdmin: async () => false, }); await expect(oidc.inspect({ live: false })).resolves.toMatchObject({ ready: false, checks: [ expect.objectContaining({ code: "oidc_secret_missing" }), ] }); await expect(local.inspect({ live: false })).resolves.toMatchObject({ ready: false, checks: [ expect.objectContaining({ code: "local_admin_missing" }), ] }); }); test("maps OIDC and group catalog failures to only the closed diagnostics code union", async () => { const report = await createAuthDiagnoser({ authMode: "oidc", authentication: { current: oidcConfig }, authStateRoot: "/safe/auth-state", secrets: new Map([["THT_OIDC_CLIENT_SECRET", sentinels[0]!], ["THT_AUTHENTIK_API_TOKEN", sentinels[1]!]]), oidcProtocol: { diagnose: async () => { throw new Error("issuer unavailable"); } }, groupCatalog: { verifyConfiguredGroups: async () => [{ level: "error", code: "oidc_mapped_group_missing", message: "configured group is missing", field: "TOT Users" }] }, }).inspect({ live: true }); expect(report.ready).toBe(false); expect(report.checks.map((check) => check.code)).toEqual(["oidc_discovery_unreachable", "oidc_mapped_group_missing"]); }); test("reports a JWKS validation failure through its closed diagnostic code", async () => { const report = await createAuthDiagnoser({ authMode: "oidc", authentication: { current: oidcConfig }, authStateRoot: "/safe/auth-state", secrets: new Map([["THT_OIDC_CLIENT_SECRET", sentinels[0]!], ["THT_AUTHENTIK_API_TOKEN", sentinels[1]!]]), oidcProtocol: { diagnose: async () => { throw new OidcJwksUnavailableError(); } }, groupCatalog: { verifyConfiguredGroups: async () => [] }, }).inspect({ live: true }); expect(report.checks).toEqual([expect.objectContaining({ code: "oidc_jwks_unreachable" })]); }); test("redacts exceptional configuration, registry, protocol, and catalog errors", async () => { const detail = sentinels.join(" "); const report = await createAuthDiagnoser({ authMode: "oidc", authentication: { current: () => { throw new Error(detail); } }, authStateRoot: sentinels[4]!, secrets: new Map(), oidcProtocol: { diagnose: async () => { throw new Error(detail); } }, groupCatalog: { verifyConfiguredGroups: async () => { throw new Error(detail); } }, }).inspect({ live: true }); const rendered = JSON.stringify(report); for (const sentinel of sentinels) expect(rendered).not.toContain(sentinel); expect(report.checks.every((check) => check.level === "error" || check.level === "info")).toBe(true); });