Skip to main content

memstead_cli/auth/
mod.rs

1//! Auth plumbing for the registry commands.
2//!
3//! Token resolution (first hit wins) — the policy is centralised in
4//! [`resolve_token`] so `publish`, `install` (future auth), and
5//! `login` all read from the same ladder.
6
7pub mod credentials;
8pub mod device_flow;
9pub mod domain_key;
10
11/// Outcome of resolving a token for a given registry host. Source is
12/// tracked for future telemetry / "logged in via X" UX; callers today
13/// only read `token`.
14#[allow(dead_code)]
15pub enum TokenSource {
16    /// `--token` flag on the command line.
17    Flag,
18    /// `MEMSTEAD_TOKEN` environment variable.
19    Env,
20    /// Stored credentials at `~/.config/memstead/credentials`.
21    Credentials,
22}
23
24pub struct ResolvedToken {
25    pub token: String,
26    #[allow(dead_code)]
27    pub source: TokenSource,
28}
29
30/// Resolve a token without prompting. Returns `Ok(None)` when no token
31/// is available — the caller decides whether to trigger device flow,
32/// fail, or proceed without auth (install doesn't need one).
33pub fn resolve_token(
34    registry_host: &str,
35    flag_token: Option<&str>,
36) -> anyhow::Result<Option<ResolvedToken>> {
37    if let Some(token) = flag_token {
38        return Ok(Some(ResolvedToken {
39            token: token.to_string(),
40            source: TokenSource::Flag,
41        }));
42    }
43    if let Ok(token) = std::env::var("MEMSTEAD_TOKEN")
44        && !token.is_empty()
45    {
46        return Ok(Some(ResolvedToken {
47            token,
48            source: TokenSource::Env,
49        }));
50    }
51    if let Some(entry) = credentials::load_for(registry_host)? {
52        return Ok(Some(ResolvedToken {
53            token: entry.token,
54            source: TokenSource::Credentials,
55        }));
56    }
57    Ok(None)
58}