Files
ThothII/backend/test/auth-diagnostics.test.ts
T

103 lines
5.4 KiB
TypeScript

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);
});