Skip to main content

cairn_mod/cli/
auth.rs

1//! Shared CLI auth helpers (#28).
2//!
3//! Centralizes the `acquire_service_auth` orchestration — get a
4//! Cairn-bound service-auth token from the operator's PDS, with
5//! one-shot refresh-and-retry on a 401 from `getServiceAuth`. The
6//! refresh path also persists the rotated tokens back to the
7//! session file on disk so the next CLI invocation starts with
8//! current credentials (§5.3).
9//!
10//! Callers (cli/audit.rs, cli/report.rs, cli/retention.rs,
11//! cli/trust_chain.rs) used to carry byte-identical local copies of
12//! this function. Factoring threshold per session N3 was 6+
13//! identical copies; the trust-chain CLI (#37) brought the count
14//! to 8 callsites across 4 modules and tripped the rule.
15
16use std::path::Path;
17
18use super::error::CliError;
19use super::pds::{PdsClient, PdsError};
20use super::session::SessionFile;
21
22/// Acquire a service-auth token bound to the given lexicon method,
23/// refreshing the moderator session file in-place on a 401.
24///
25/// Behavior contract preserved from the pre-factor copies:
26///
27/// 1. Call `getServiceAuth(access_jwt, cairn_service_did, lxm)`.
28/// 2. On `Unauthorized { context: "getServiceAuth" }`, refresh
29///    the session via `refreshSession(refresh_jwt)`, write the
30///    rotated tokens back to `session_path` (mode 0600 owner
31///    invariants per §5.3), then retry `getServiceAuth` once.
32/// 3. Any other PDS error propagates as `CliError::Pds`.
33///
34/// One-shot retry — a second 401 is propagated, not chained into
35/// another refresh.
36pub(super) async fn acquire_service_auth(
37    pds: &PdsClient,
38    session: &mut SessionFile,
39    session_path: &Path,
40    lxm: &str,
41) -> Result<String, CliError> {
42    match pds
43        .get_service_auth(&session.access_jwt, &session.cairn_service_did, lxm)
44        .await
45    {
46        Ok(t) => Ok(t),
47        Err(PdsError::Unauthorized {
48            context: "getServiceAuth",
49            ..
50        }) => {
51            let refreshed = pds.refresh_session(&session.refresh_jwt).await?;
52            session.access_jwt = refreshed.access_jwt;
53            session.refresh_jwt = refreshed.refresh_jwt;
54            session.save(session_path)?;
55            Ok(pds
56                .get_service_auth(&session.access_jwt, &session.cairn_service_did, lxm)
57                .await?)
58        }
59        Err(other) => Err(other.into()),
60    }
61}