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}