test(auth): harden Task 15 OIDC smoke evidence

This commit is contained in:
2026-08-18 06:33:48 +02:00
parent 8a3fa5031d
commit 2b618c5a0f
5 changed files with 758 additions and 82 deletions
+27
View File
@@ -25,6 +25,33 @@ test("the loopback fixture exposes signed OIDC discovery, device authorization,
});
});
test("the production OIDC client rejects a wrong registration and wrong mounted client secret", async ({ page }) => {
await stack.useOidcMode("ordinary", "wrong-client-id");
await page.goto(stack.publicUrl);
await signInWithOidc(page);
await expect(page.locator("body")).toContainText("invalid_request");
await expect(page.getByTestId("app-shell")).toHaveCount(0);
await stack.useOidcMode("ordinary", "wrong-client-secret");
await page.goto(stack.publicUrl);
await signInWithOidc(page);
await expectOidcCallbackDenied(page);
});
test("production diagnostics reject a wrong group token and pass with the correct mounted secrets", async () => {
await stack.useOidcMode("ordinary", "wrong-api-token");
await expect(stack.authDiagnostics()).resolves.toMatchObject({
status: 1,
report: { ready: false, checks: [{ code: "oidc_group_catalog_unauthorized" }] },
});
await stack.useOidcMode("ordinary", "correct");
await expect(stack.authDiagnostics()).resolves.toMatchObject({
status: 0,
report: { ready: true, checks: [{ code: "auth_ready" }] },
});
});
async function expectShell(page: Page): Promise<void> {
await expect(page.getByTestId("app-shell")).toBeVisible({ timeout: 30_000 });
}
+132 -45
View File
@@ -17,6 +17,15 @@ const harnessRoot = join(repositoryRoot, "harness");
const thtRoot = join(repositoryRoot, "tools", "tht");
const fakePi = join(frontendRoot, "e2e", "fixtures", "fake-pi.mjs");
const fakeTht = join(frontendRoot, "e2e", "fixtures", "fake-tht.mjs");
const FIXTURE_CLIENT_ID = "thothii-e2e-client";
const FIXTURE_CLIENT_SECRET = "e2e-client-secret-not-a-production-secret";
const FIXTURE_API_TOKEN = "e2e-group-catalog-token-not-a-production-secret";
const WRONG_CLIENT_ID = "thothii-e2e-unregistered-client";
const WRONG_CLIENT_SECRET = "e2e-wrong-client-secret-not-production";
const WRONG_API_TOKEN = "e2e-wrong-api-token-not-production";
const OIDC_CREDENTIAL_VARIANTS = new Set([
"correct", "wrong-client-id", "wrong-client-secret", "wrong-api-token",
]);
function safeError(code) {
return new Error(code);
@@ -247,6 +256,48 @@ function managedProcess(command, args, options) {
};
}
function oneShotJson(command, args, options) {
return new Promise((resolveCommand) => {
const child = spawn(command, args, {
cwd: options.cwd,
env: options.env,
stdio: ["ignore", "pipe", "ignore"],
});
let output = "";
let outputValid = true;
let settled = false;
const finish = (status) => {
if (settled) return;
settled = true;
clearTimeout(timer);
let report;
try {
report = outputValid ? JSON.parse(output) : undefined;
} catch {
report = undefined;
}
resolveCommand({
status: Number.isInteger(status) ? status : 1,
report: report && typeof report === "object"
? report
: { ready: false, mode: "none", checks: [{ code: "fixture_command_invalid" }] },
});
};
child.stdout?.on("data", (chunk) => {
if (!outputValid) return;
output += String(chunk);
if (Buffer.byteLength(output) > 64 * 1024) {
output = "";
outputValid = false;
child.kill("SIGKILL");
}
});
child.once("error", () => finish(1));
child.once("exit", (status) => finish(status));
const timer = setTimeout(() => child.kill("SIGKILL"), 35_000);
});
}
async function waitForOk(url, processHandle) {
for (let attempt = 0; attempt < 300; attempt += 1) {
if (processHandle.exited()) {
@@ -303,7 +354,15 @@ export async function createAuthenticationStack({ withF1Workspace = false } = {}
const publicUrl = `http://127.0.0.1:${frontendPort}`;
const backendUrl = `http://127.0.0.1:${backendPort}`;
const workspace = withF1Workspace ? await prepareF1Workspace(root) : undefined;
const provider = await startFakeOidcProvider({ directory: providerRoot });
const provider = await startFakeOidcProvider({
directory: providerRoot,
registration: {
clientId: FIXTURE_CLIENT_ID,
clientSecret: FIXTURE_CLIENT_SECRET,
redirectUri: new URL("/api/auth/oidc/callback", publicUrl).href,
},
apiToken: FIXTURE_API_TOKEN,
});
await buildAuthenticationStorageBridge(authStorageBinary);
const localPassword = "e2e-local-password";
const passwordHash = await testPasswordHash(localPassword);
@@ -334,11 +393,17 @@ export async function createAuthenticationStack({ withF1Workspace = false } = {}
},
],
}));
writeSecure(secretsFile, [
"THT_OIDC_CLIENT_SECRET=e2e-client-secret-not-a-production-secret",
"THT_AUTHENTIK_API_TOKEN=e2e-group-catalog-token-not-a-production-secret",
"",
].join("\n"));
function writeOidcSecrets(variant) {
if (!OIDC_CREDENTIAL_VARIANTS.has(variant)) throw safeError("e2e_oidc_credential_variant_invalid");
const selectedClientSecret = variant === "wrong-client-secret" ? WRONG_CLIENT_SECRET : FIXTURE_CLIENT_SECRET;
const selectedApiToken = variant === "wrong-api-token" ? WRONG_API_TOKEN : FIXTURE_API_TOKEN;
writeSecure(secretsFile, [
`THT_OIDC_CLIENT_SECRET=${selectedClientSecret}`,
`THT_AUTHENTIK_API_TOKEN=${selectedApiToken}`,
"",
].join("\n"));
}
writeOidcSecrets("correct");
if (workspace) {
writeSecure(fixtureDwhPasswordFile, "fixture-password-not-a-secret\n");
writeSecure(fixtureDwhCaFile, "fixture-ca-not-a-production-certificate\n");
@@ -378,7 +443,7 @@ export async function createAuthenticationStack({ withF1Workspace = false } = {}
},
local: { usersFile: "users.yaml" },
});
const oidcConfig = () => ({
const oidcConfig = (variant = "correct") => ({
version: 1,
mode: "oidc",
publicUrl,
@@ -389,7 +454,7 @@ export async function createAuthenticationStack({ withF1Workspace = false } = {}
},
oidc: {
issuer: provider.issuer,
clientId: "thothii-e2e-client",
clientId: variant === "wrong-client-id" ? WRONG_CLIENT_ID : FIXTURE_CLIENT_ID,
clientSecretRef: "THT_OIDC_CLIENT_SECRET",
scopes: ["openid", "profile", "groups"],
groupsClaim: "groups",
@@ -407,42 +472,46 @@ export async function createAuthenticationStack({ withF1Workspace = false } = {}
},
});
function backendEnvironment() {
return cleanBackendEnvironment({
NODE_ENV: "test",
HOST: "127.0.0.1",
PORT: String(backendPort),
PI_BIN: fakePi,
THT_BIN: fakeTht,
THT_AUTH_STORAGE_BIN: authStorageBinary,
THT_HARNESS_DIR: harnessRoot,
THT_AUTH_CONFIG_FILE: authConfigFile,
THT_AUTH_STATE_ROOT: stateRoot,
THT_SECRETS_FILE: secretsFile,
NODE_EXTRA_CA_CERTS: provider.caFile,
SETTINGS_FILE: settingsFile,
THT_MAINTENANCE_FILE: maintenanceFile,
THT_WORKSPACE_REGISTRY_ROOT: registryRoot,
THT_WORKSPACE_SECRET_STORE_ROOT: workspaceSecretRoot,
THT_WORKSPACE_SECRET_RUNTIME_ROOT: workspaceRuntimeRoot,
THT_WORKSPACE_INSTALLATION_ID: "e2e",
THT_DATA_ROOT: join(root, "data"),
...(qdrant ? { THT_INTERNAL_QDRANT_URL: qdrant.baseUrl } : {}),
...(workspace ? {
THT_WORKSPACE_GIT_REMOTE: workspace.remote,
THT_WORKSPACE_GIT_BRANCH: "main",
THT_WORKSPACE_SECRET_ROOTS: fixtureSecretRoot,
THT_WS_FIXTURE_WORKSPACE_DWH_TRANSPORT: "postgres_direct",
THT_WS_FIXTURE_WORKSPACE_DWH_HOST: "127.0.0.1",
THT_WS_FIXTURE_WORKSPACE_DWH_PORT: "5432",
THT_WS_FIXTURE_WORKSPACE_DWH_USER: "fixture",
THT_WS_FIXTURE_WORKSPACE_DWH_PASSWORD_FILE: fixtureDwhPasswordFile,
THT_WS_FIXTURE_WORKSPACE_DWH_TLS_CA_FILE: fixtureDwhCaFile,
} : {}),
});
}
async function startBackend() {
if (mode === undefined) throw safeError("e2e_auth_mode_not_configured");
backend = managedProcess(join(backendRoot, "node_modules", ".bin", "tsx"), ["src/server.ts"], {
cwd: backendRoot,
env: cleanBackendEnvironment({
NODE_ENV: "test",
HOST: "127.0.0.1",
PORT: String(backendPort),
PI_BIN: fakePi,
THT_BIN: fakeTht,
THT_AUTH_STORAGE_BIN: authStorageBinary,
THT_HARNESS_DIR: harnessRoot,
THT_AUTH_CONFIG_FILE: authConfigFile,
THT_AUTH_STATE_ROOT: stateRoot,
THT_SECRETS_FILE: secretsFile,
NODE_EXTRA_CA_CERTS: provider.caFile,
SETTINGS_FILE: settingsFile,
THT_MAINTENANCE_FILE: maintenanceFile,
THT_WORKSPACE_REGISTRY_ROOT: registryRoot,
THT_WORKSPACE_SECRET_STORE_ROOT: workspaceSecretRoot,
THT_WORKSPACE_SECRET_RUNTIME_ROOT: workspaceRuntimeRoot,
THT_WORKSPACE_INSTALLATION_ID: "e2e",
THT_DATA_ROOT: join(root, "data"),
...(qdrant ? { THT_INTERNAL_QDRANT_URL: qdrant.baseUrl } : {}),
...(workspace ? {
THT_WORKSPACE_GIT_REMOTE: workspace.remote,
THT_WORKSPACE_GIT_BRANCH: "main",
THT_WORKSPACE_SECRET_ROOTS: fixtureSecretRoot,
THT_WS_FIXTURE_WORKSPACE_DWH_TRANSPORT: "postgres_direct",
THT_WS_FIXTURE_WORKSPACE_DWH_HOST: "127.0.0.1",
THT_WS_FIXTURE_WORKSPACE_DWH_PORT: "5432",
THT_WS_FIXTURE_WORKSPACE_DWH_USER: "fixture",
THT_WS_FIXTURE_WORKSPACE_DWH_PASSWORD_FILE: fixtureDwhPasswordFile,
THT_WS_FIXTURE_WORKSPACE_DWH_TLS_CA_FILE: fixtureDwhCaFile,
} : {}),
}),
env: backendEnvironment(),
});
await waitForOk(`${backendUrl}/health`, backend);
}
@@ -468,15 +537,23 @@ export async function createAuthenticationStack({ withF1Workspace = false } = {}
const jwks = await providerJson(`${provider.issuer}jwks`, provider.caFile);
const device = await providerJson(`${provider.issuer}device_authorization`, provider.caFile, {
method: "POST",
body: "client_id=thothii-e2e-client",
body: new URLSearchParams({
client_id: FIXTURE_CLIENT_ID,
client_secret: FIXTURE_CLIENT_SECRET,
}).toString(),
});
const deviceCode = typeof device.body?.device_code === "string" ? device.body.device_code : "";
const deviceToken = deviceCode.length === 0 ? { status: 0, body: {} } : await providerJson(`${provider.issuer}token`, provider.caFile, {
method: "POST",
body: `grant_type=urn%3Aietf%3Aparams%3Aoauth%3Agrant-type%3Adevice_code&device_code=${encodeURIComponent(deviceCode)}`,
body: new URLSearchParams({
grant_type: "urn:ietf:params:oauth:grant-type:device_code",
client_id: FIXTURE_CLIENT_ID,
client_secret: FIXTURE_CLIENT_SECRET,
device_code: deviceCode,
}).toString(),
});
const groups = await providerJson(`${provider.baseUrl}/api/v3/core/groups/?name=fixture-users`, provider.caFile, {
headers: { authorization: "Bearer e2e-fixture" },
headers: { authorization: `Bearer ${FIXTURE_API_TOKEN}` },
});
return {
discovery: discovery.status === 200 && discovery.body?.issuer === provider.issuer,
@@ -495,12 +572,22 @@ export async function createAuthenticationStack({ withF1Workspace = false } = {}
mode = "local";
await restartBackend();
},
async useOidcMode(identity) {
async useOidcMode(identity, variant = "correct") {
if (!OIDC_CREDENTIAL_VARIANTS.has(variant)) throw safeError("e2e_oidc_credential_variant_invalid");
provider.setIdentity(identity);
writeSecure(authConfigFile, JSON.stringify(oidcConfig()));
writeOidcSecrets(variant);
writeSecure(authConfigFile, JSON.stringify(oidcConfig(variant)));
mode = "oidc";
await restartBackend();
},
async authDiagnostics() {
if (mode !== "oidc") throw safeError("e2e_oidc_mode_required");
return oneShotJson(
join(backendRoot, "node_modules", ".bin", "tsx"),
["src/auth/diagnostic-command.ts", "--json"],
{ cwd: backendRoot, env: backendEnvironment() },
);
},
restartBackend,
async close() {
await backend?.close();