Update public browser login session flow
This commit is contained in:
parent
afd7456423
commit
cec080aa98
4 changed files with 388 additions and 29 deletions
|
|
@ -9,7 +9,10 @@ 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(tmp, { recursive: true });
|
||||
fs.mkdirSync(project, { recursive: true });
|
||||
|
||||
function writeOpener() {
|
||||
const opener = path.join(tmp, "browser-opener.js");
|
||||
|
|
@ -86,6 +89,7 @@ async function startCoordinator() {
|
|||
user: request.user,
|
||||
browser_credential_kind: "BrowserSession",
|
||||
cli_session_credential_kind: "CliDeviceSession",
|
||||
expires_at: "2026-07-04T00:00:00Z",
|
||||
provider_tokens_sent_to_nodes: false,
|
||||
flow: {
|
||||
authorization_url: "http://127.0.0.1:1/application/o/authorize/",
|
||||
|
|
@ -115,8 +119,19 @@ async function startCoordinator() {
|
|||
|
||||
function runDisasmer(args, env) {
|
||||
return new Promise((resolve, reject) => {
|
||||
const child = cp.spawn("cargo", args, {
|
||||
cwd: repo,
|
||||
const child = cp.spawn("cargo", [
|
||||
"run",
|
||||
"-q",
|
||||
"--manifest-path",
|
||||
path.join(repo, "Cargo.toml"),
|
||||
"-p",
|
||||
"disasmer-cli",
|
||||
"--bin",
|
||||
"disasmer",
|
||||
"--",
|
||||
...args,
|
||||
], {
|
||||
cwd: project,
|
||||
env,
|
||||
stdio: ["ignore", "pipe", "pipe"],
|
||||
});
|
||||
|
|
@ -157,13 +172,6 @@ function runDisasmer(args, env) {
|
|||
try {
|
||||
const stdout = await runDisasmer(
|
||||
[
|
||||
"run",
|
||||
"-q",
|
||||
"-p",
|
||||
"disasmer-cli",
|
||||
"--bin",
|
||||
"disasmer",
|
||||
"--",
|
||||
"login",
|
||||
"--browser",
|
||||
"--json",
|
||||
|
|
@ -190,10 +198,45 @@ function runDisasmer(args, env) {
|
|||
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, 1);
|
||||
assert.strictEqual(coordinator.requests.length, 1);
|
||||
|
||||
const sessionFile = path.join(project, ".disasmer", "session.json");
|
||||
assert(fs.existsSync(sessionFile), "browser login should write a local CLI session file");
|
||||
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, "2026-07-04T00:00:00Z");
|
||||
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|browser-smoke-code|Bearer/
|
||||
);
|
||||
|
||||
const authStatus = JSON.parse(
|
||||
await runDisasmer(["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 {
|
||||
if (coordinator.requests.length === 0) {
|
||||
await coordinator.close().catch(() => {});
|
||||
|
|
|
|||
|
|
@ -36,6 +36,7 @@ const coordinator = read("crates/disasmer-coordinator/src/service.rs");
|
|||
const cliFirstAcceptance = read("scripts/acceptance-cli-first.sh");
|
||||
const outputModeSmoke = read("scripts/cli-output-mode-smoke.js");
|
||||
const errorExitSmoke = read("scripts/cli-error-exit-smoke.js");
|
||||
const browserLoginFlowSmoke = read("scripts/cli-browser-login-flow-smoke.js");
|
||||
|
||||
expect(criteria, "header", /^# Disasmer CLI-First MVP Acceptance Criteria/m);
|
||||
expect(
|
||||
|
|
@ -143,6 +144,12 @@ for (const [name, pattern] of [
|
|||
["top-level logout command", /enum Commands[\s\S]*Logout\(AuthLogoutArgs\)[\s\S]*Auth \{/],
|
||||
["login non-interactive flag", /struct LoginArgs[\s\S]*non_interactive: bool/],
|
||||
["run non-interactive flag", /struct RunArgs[\s\S]*non_interactive: bool/],
|
||||
["stored CLI session model", /struct StoredCliSession[\s\S]*cli_session_credential_kind[\s\S]*provider_tokens_exposed_to_cli[\s\S]*provider_tokens_sent_to_nodes/],
|
||||
["read CLI session helper", /fn read_cli_session\(project: &Path\) -> Result<Option<StoredCliSession>>/],
|
||||
["write CLI session helper", /fn write_cli_session\(project: &Path, session: &StoredCliSession\) -> Result<PathBuf>/],
|
||||
["session source fallback", /fn session_from_sources\(project: &Path\) -> Result<CliSession>[\s\S]*read_cli_session\(project\)\?\.is_some\(\)/],
|
||||
["browser login writes local CLI session", /local_cli_session_file_written[\s\S]*write_cli_session\(&cwd, &stored_session\)\?/],
|
||||
["browser login does not persist provider tokens", /provider_tokens_persisted_locally:\s*false/],
|
||||
["non-interactive auth report", /fn non_interactive_auth_machine_error[\s\S]*browser_opened[\s\S]*false/],
|
||||
["human report renderer", /fn human_report\(value: &Value\) -> String/],
|
||||
["shared report emitter", /fn emit_report<T: Serialize>\(report: &T, json_output: bool\) -> Result<\(\)>/],
|
||||
|
|
@ -171,6 +178,8 @@ for (const [name, pattern] of [
|
|||
["CLI primary workflow help coverage", /fn top_level_help_exposes_primary_workflow_without_auth\(\)/],
|
||||
["CLI non-interactive run auth coverage", /fn non_interactive_run_without_session_requires_explicit_auth_or_local\(\)/],
|
||||
["CLI non-interactive browser login coverage", /fn browser_login_non_interactive_fails_before_opening_browser\(\)/],
|
||||
["CLI stored browser session coverage", /fn stored_browser_login_session_omits_provider_token_values\(\)/],
|
||||
["CLI auth status session-file coverage", /fn auth_status_reads_stored_cli_session_without_provider_tokens\(\)/],
|
||||
["CLI version coverage", /fn top_level_version_is_available\(\)/],
|
||||
["CLI JSON parse coverage", /fn cli_first_json_mode_parses_for_primary_commands\(\)/],
|
||||
["CLI human output coverage", /fn human_report_is_text_not_json\(\)/],
|
||||
|
|
@ -204,6 +213,17 @@ for (const [name, pattern] of [
|
|||
expect(cli, name, pattern);
|
||||
}
|
||||
|
||||
expect(
|
||||
browserLoginFlowSmoke,
|
||||
"browser login smoke checks session file",
|
||||
/provider_tokens_persisted_locally[\s\S]*path\.join\(project, "\.disasmer", "session\.json"\)[\s\S]*\["auth", "status", "--json"\]/
|
||||
);
|
||||
expect(
|
||||
browserLoginFlowSmoke,
|
||||
"browser login smoke rejects provider token persistence",
|
||||
/assert\.doesNotMatch\([\s\S]*access_token\|refresh_token\|id_token/
|
||||
);
|
||||
|
||||
expect(
|
||||
coordinator,
|
||||
"coordinator whole-process cancellation coverage",
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue