103 lines
5.4 KiB
TypeScript
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);
|
|
});
|