fix: auth login domain resolution with --domain flag

Domain resolves from: --domain flag > cached token > config
production_host > cluster discovery. Clear error when none available.
This commit is contained in:
2026-03-20 14:29:08 +00:00
parent 184ad85c60
commit 7fab2a7f3c
2 changed files with 54 additions and 19 deletions

View File

@@ -103,6 +103,49 @@ struct OidcDiscovery {
token_endpoint: String,
}
/// Resolve the domain for authentication, trying multiple sources.
async fn resolve_domain(explicit: Option<&str>) -> Result<String> {
// 1. Explicit --domain flag
if let Some(d) = explicit {
if !d.is_empty() {
return Ok(d.to_string());
}
}
// 2. Cached token domain (already logged in to a domain)
if let Ok(tokens) = read_cache() {
if !tokens.domain.is_empty() {
crate::output::ok(&format!("Using cached domain: {}", tokens.domain));
return Ok(tokens.domain);
}
}
// 3. Config: derive from production_host
let config = crate::config::load_config();
if !config.production_host.is_empty() {
let host = &config.production_host;
let raw = host.split('@').last().unwrap_or(host);
let raw = raw.split(':').next().unwrap_or(raw);
// Take the last 2+ segments as the domain (e.g. admin.sunbeam.pt -> sunbeam.pt)
let parts: Vec<&str> = raw.split('.').collect();
if parts.len() >= 2 {
let domain = format!("{}.{}", parts[parts.len() - 2], parts[parts.len() - 1]);
return Ok(domain);
}
}
// 4. Try cluster discovery (may fail if not connected)
match crate::kube::get_domain().await {
Ok(d) if !d.is_empty() && !d.starts_with(".") => return Ok(d),
_ => {}
}
Err(SunbeamError::config(
"Could not determine domain. Use --domain flag, or configure with:\n \
sunbeam config set --host user@your-server.example.com"
))
}
async fn discover_oidc(domain: &str) -> Result<OidcDiscovery> {
let url = format!("https://auth.{domain}/.well-known/openid-configuration");
let client = reqwest::Client::new();
@@ -403,25 +446,11 @@ pub async fn get_token() -> Result<String> {
}
/// Interactive browser-based OAuth2 login.
pub async fn cmd_auth_login() -> Result<()> {
pub async fn cmd_auth_login(domain_override: Option<&str>) -> Result<()> {
crate::output::step("Authenticating with Hydra");
// Resolve domain
let config = crate::config::load_config();
let domain = if !config.production_host.is_empty() {
// Extract domain from production host if available
let host = &config.production_host;
let raw = host.split('@').last().unwrap_or(host);
let raw = raw.split(':').next().unwrap_or(raw);
// If it looks like an IP or hostname, try to get domain from cluster
if raw.contains('.') && !raw.chars().next().unwrap_or('0').is_ascii_digit() {
raw.to_string()
} else {
crate::kube::get_domain().await?
}
} else {
crate::kube::get_domain().await?
};
// Resolve domain: explicit flag > cached token domain > config > cluster discovery
let domain = resolve_domain(domain_override).await?;
crate::output::ok(&format!("Domain: {domain}"));

View File

@@ -168,7 +168,11 @@ pub enum Verb {
#[derive(Subcommand, Debug)]
pub enum AuthAction {
/// Log in via browser (OAuth2 authorization code flow).
Login,
Login {
/// Domain to authenticate against (e.g. sunbeam.pt).
#[arg(long)]
domain: Option<String>,
},
/// Log out (remove cached tokens).
Logout,
/// Show current authentication status.
@@ -1002,7 +1006,9 @@ pub async fn dispatch() -> Result<()> {
None => {
crate::auth::cmd_auth_status().await
}
Some(AuthAction::Login) => crate::auth::cmd_auth_login().await,
Some(AuthAction::Login { domain }) => {
crate::auth::cmd_auth_login(domain.as_deref()).await
}
Some(AuthAction::Logout) => crate::auth::cmd_auth_logout().await,
Some(AuthAction::Status) => crate::auth::cmd_auth_status().await,
},