From 0904acb2626ea963d378b253edb1e0af96e6e5bc Mon Sep 17 00:00:00 2001 From: Javier Tia Date: Tue, 21 Jul 2026 15:01:45 -0600 Subject: [PATCH] connect/auth: honor --org before the zero-orgs shortcut `avocado connect auth login --org ` silently dropped the requested org whenever the auth server returned no organizations. pick_org tested `orgs.is_empty()` first and returned None, so the code exchange minted an unscoped token and the CLI reported a successful login for an org it never scoped to, leaving a bogus "all orgs" profile behind with no error. Resolve an explicit hint before the empty-list shortcut so a hint that matches nothing, including an empty list, fails loudly. The no-hint zero-orgs path still returns None unchanged. Signed-off-by: Javier Tia --- src/commands/connect/auth.rs | 38 +++++++++++++++++++++++++++--------- 1 file changed, 29 insertions(+), 9 deletions(-) diff --git a/src/commands/connect/auth.rs b/src/commands/connect/auth.rs index eb05b36..a67266e 100644 --- a/src/commands/connect/auth.rs +++ b/src/commands/connect/auth.rs @@ -431,24 +431,30 @@ enum OrgPick { /// Pure logic. No HTTP, no stdin, no stdout. The wrapper `resolve_org_for_login` /// adds the IO around this. fn pick_org(orgs: Vec, org_hint: Option<&str>, output: OutputFormat) -> Result { - if orgs.is_empty() { - return Ok(OrgPick::None); - } - + // An explicit `--org` must be honored or fail loudly — never silently + // dropped. Resolve it before the zero-orgs shortcut so a hint against an + // empty org list errors instead of falling through to an unscoped token. if let Some(hint) = org_hint { return match orgs.iter().find(|o| o.id == hint) { Some(o) => Ok(OrgPick::Resolved(o.clone())), None => { - let available = orgs - .iter() - .map(|o| format!("{} ({})", o.name, o.id)) - .collect::>() - .join(", "); + let available = if orgs.is_empty() { + "none".to_string() + } else { + orgs.iter() + .map(|o| format!("{} ({})", o.name, o.id)) + .collect::>() + .join(", ") + }; anyhow::bail!("organization '{hint}' not found. Available: {available}"); } }; } + if orgs.is_empty() { + return Ok(OrgPick::None); + } + if orgs.len() == 1 { return Ok(OrgPick::Resolved(orgs.into_iter().next().unwrap())); } @@ -843,6 +849,20 @@ mod tests { assert!(matches!(result, OrgPick::None)); } + #[test] + fn pick_org_explicit_hint_with_no_orgs_errors_not_dropped() { + // An explicit --org must never be silently dropped: an empty org + // list combined with a hint has to fail loudly rather than fall + // through to an unscoped token. Regression guard for the silent-drop + // login bug — the auth server returned zero orgs, so the hint was + // ignored and a bogus unscoped profile was minted while reporting + // success. + let result = pick_org(vec![], Some("uuid-x"), OutputFormat::Json); + let err = result.unwrap_err().to_string(); + assert!(err.contains("uuid-x"), "got: {err}"); + assert!(err.contains("Available: none"), "got: {err}"); + } + #[test] fn pick_org_single_org_is_resolved_without_hint() { let only = org("uuid-1", "Acme");