clusterflux-public/scripts/cli-browser-login-flow-smoke.js
Clusterflux release 6acd2d6eb7 Public release release-55b5a563d642
Source commit: 55b5a563d6421f49b5f5e77bc3c1f29752da8801

Public tree identity: sha256:52790e23e2b1806b00cc220c92edcfeed445600dcde620342af2b3369e7883fa
2026-07-16 17:13:43 +02:00

222 lines
8.2 KiB
JavaScript

#!/usr/bin/env node
const assert = require("assert");
const cp = require("child_process");
const fs = require("fs");
const net = require("net");
const path = require("path");
const repo = path.resolve(__dirname, "..");
const tmp = path.join(repo, "target", "acceptance", "tmp", "cli-browser-login-flow");
const project = path.join(tmp, "project");
fs.rmSync(tmp, { recursive: true, force: true });
fs.mkdirSync(project, { recursive: true });
function writeOpener() {
const opener = path.join(tmp, "browser-opener.js");
const trace = path.join(tmp, "browser-opener.log");
fs.writeFileSync(
opener,
`#!/usr/bin/env node
const fs = require("fs");
const trace = ${JSON.stringify(trace)};
const loginUrl = new URL(process.argv[2]);
const state = loginUrl.searchParams.get("state");
const nonce = loginUrl.searchParams.get("nonce");
const challenge = loginUrl.searchParams.get("code_challenge");
if (loginUrl.protocol !== "https:" || !state || !nonce || !challenge) {
fs.appendFileSync(trace, "missing server-owned OIDC parameters\\n");
process.exit(1);
}
fs.appendFileSync(trace, "server-owned browser transaction\\n");
// Model a real browser/opener that remains alive after the CLI transaction.
// Its descriptors must not keep the invoking CLI process open.
setTimeout(() => process.exit(0), 5000);
`
);
fs.chmodSync(opener, 0o755);
return opener;
}
function startCoordinator() {
return new Promise((resolve, reject) => {
const requests = [];
const server = net.createServer((socket) => {
let buffered = "";
socket.on("data", (chunk) => {
buffered += chunk.toString("utf8");
while (buffered.includes("\n")) {
const newline = buffered.indexOf("\n");
const line = buffered.slice(0, newline);
buffered = buffered.slice(newline + 1);
const envelope = JSON.parse(line);
requests.push(envelope);
assert.strictEqual(envelope.type, "coordinator_request");
assert.strictEqual(envelope.protocol_version, 1);
assert.strictEqual(envelope.authentication.kind, "none");
const request = envelope.payload;
if (requests.length === 1) {
assert.strictEqual(envelope.request_id, "cli-1");
assert.strictEqual(envelope.operation, "begin_oidc_browser_login");
assert.deepStrictEqual(request, {
type: "begin_oidc_browser_login",
});
socket.write(
`${JSON.stringify({
type: "oidc_browser_login_started",
transaction_id: "login-transaction",
polling_secret: "opaque-polling-secret",
authorization_url:
"https://auth.michelpaulissen.com/application/o/authorize/?state=server-state&nonce=server-nonce&code_challenge=server-pkce&code_challenge_method=S256&redirect_uri=https%3A%2F%2Fclusterflux.michelpaulissen.com%2Fauth%2Fcallback",
expires_at_epoch_seconds: 1800000000,
})}\n`
);
continue;
}
assert.strictEqual(envelope.request_id, "cli-2");
assert.strictEqual(envelope.operation, "poll_oidc_browser_login");
assert.deepStrictEqual(request, {
type: "poll_oidc_browser_login",
transaction_id: "login-transaction",
polling_secret: "opaque-polling-secret",
});
socket.end(
`${JSON.stringify({
type: "oidc_browser_session",
session: {
tenant: "tenant-smoke",
project: "project-smoke",
user: "user-smoke",
cli_session_credential_kind: "CliDeviceSession",
cli_session_secret: "scoped-cli-session-secret",
expires_at_epoch_seconds: 1800000000,
provider_tokens_sent_to_nodes: false,
},
})}\n`
);
server.close();
}
});
});
server.once("error", reject);
server.listen(0, "127.0.0.1", () => {
const address = server.address();
resolve({
url: `${address.address}:${address.port}`,
requests,
close: () =>
new Promise((closeResolve) => {
if (!server.listening) closeResolve();
else server.close(() => closeResolve());
}),
});
});
});
}
function runClusterflux(args, env) {
return new Promise((resolve, reject) => {
const child = cp.spawn(
"cargo",
[
"run",
"-q",
"--manifest-path",
path.join(repo, "Cargo.toml"),
"-p",
"clusterflux-cli",
"--bin",
"clusterflux",
"--",
...args,
],
{ cwd: project, env, stdio: ["ignore", "pipe", "pipe"] }
);
let stdout = "";
let stderr = "";
child.stdout.on("data", (chunk) => (stdout += chunk.toString("utf8")));
child.stderr.on("data", (chunk) => (stderr += chunk.toString("utf8")));
child.once("error", reject);
child.once("close", (code) => {
if (code === 0) resolve(stdout);
else reject(new Error(`clusterflux exited ${code}\n${stderr}\n${stdout}`));
});
});
}
(async () => {
const opener = writeOpener();
const coordinator = await startCoordinator();
try {
const loginStarted = Date.now();
const report = JSON.parse(
await runClusterflux(
[
"login",
"--browser",
"--json",
"--coordinator",
coordinator.url,
"--project-id",
"project-smoke",
],
{
...process.env,
CLUSTERFLUX_BROWSER_OPEN_COMMAND: opener,
CLUSTERFLUX_BROWSER_LOGIN_TIMEOUT_SECONDS: "5",
}
)
);
assert(
Date.now() - loginStarted < 3000,
"a long-lived browser opener must not keep CLI output pipes or login completion open"
);
assert.strictEqual(report.plan.coordinator, coordinator.url);
assert.strictEqual(report.boundary.cli_contacted_coordinator, true);
assert.strictEqual(report.boundary.scoped_cli_session_received, true);
assert.strictEqual(report.boundary.local_cli_session_file_written, true);
assert.strictEqual(report.boundary.provider_tokens_persisted_locally, false);
assert.strictEqual(report.boundary.provider_tokens_exposed_to_cli, false);
assert.strictEqual(report.boundary.provider_tokens_sent_to_nodes, false);
assert.strictEqual(report.boundary.coordinator_session_requests, 2);
assert.strictEqual(coordinator.requests.length, 2);
const sessionFile = path.join(project, ".clusterflux", "session.json");
const sessionText = fs.readFileSync(sessionFile, "utf8");
const session = JSON.parse(sessionText);
assert.strictEqual(session.kind, "human");
assert.strictEqual(session.coordinator, coordinator.url);
assert.strictEqual(session.tenant, "tenant-smoke");
assert.strictEqual(session.project, "project-smoke");
assert.strictEqual(session.user, "user-smoke");
assert.strictEqual(session.cli_session_credential_kind, "CliDeviceSession");
assert.strictEqual(session.token_expiry_posture, "expires_at");
assert.strictEqual(session.expires_at, "1800000000");
assert.strictEqual(session.provider_tokens_exposed_to_cli, false);
assert.strictEqual(session.provider_tokens_sent_to_nodes, false);
assert.doesNotMatch(
sessionText,
/access_token|refresh_token|id_token|provider-secret|authorization_code|Bearer/
);
const authStatus = JSON.parse(
await runClusterflux(["auth", "status", "--json"], process.env)
);
assert.strictEqual(authStatus.active_coordinator, coordinator.url);
assert.strictEqual(authStatus.principal, "user-smoke");
assert.strictEqual(authStatus.tenant, "tenant-smoke");
assert.strictEqual(authStatus.project, "project-smoke");
assert.strictEqual(authStatus.session.kind, "human");
assert.strictEqual(authStatus.session.source, "session_file");
assert.strictEqual(authStatus.session.provider_tokens_exposed_to_cli, false);
assert.strictEqual(authStatus.session.provider_tokens_exposed_to_nodes, false);
} finally {
await coordinator.close();
}
console.log("CLI browser login flow smoke passed");
})().catch((error) => {
console.error(error.stack || error.message);
process.exit(1);
});