manta_server/service/
auth.rs

1//! Authentication service — proxies CLI credential exchange to the
2//! configured CSM/OCHAMI backend.
3//!
4//! The CLI never talks to Keycloak directly; it POSTs username+password
5//! to `manta-server /api/v1/auth/token`, which calls
6//! `backend.get_api_token` on the user's behalf and returns the CSM
7//! bearer token. `validate_api_token` exposes a lightweight
8//! "is-this-token-still-valid" probe the CLI can call before sending
9//! a long-running request that would otherwise fail mid-flight.
10
11use std::time::Instant;
12
13use manta_backend_dispatcher::error::Error;
14use manta_backend_dispatcher::interfaces::authentication::AuthenticationTrait;
15
16use crate::server::common::app_context::InfraContext;
17
18/// Exchange `username` + `password` for a CSM bearer token via the
19/// site's configured backend.
20#[tracing::instrument(
21  skip_all,
22  fields(
23    site = %infra.site_name,
24    backend = %infra.backend_kind(),
25    backend_url = %infra.shasta_base_url,
26  )
27)]
28pub async fn get_api_token(
29  infra: &InfraContext<'_>,
30  username: &str,
31  password: &str,
32) -> Result<String, Error> {
33  tracing::info!(user = %username, "backend: requesting token");
34  let started = Instant::now();
35  infra
36    .backend
37    .get_api_token(username, password)
38    .await
39    .inspect(|_| {
40      tracing::debug!(
41        user = %username,
42        elapsed_ms = u64::try_from(started.elapsed().as_millis()).unwrap_or(u64::MAX),
43        "backend: token issued"
44      );
45    })
46    .inspect_err(|e| {
47      tracing::warn!(
48        user = %username,
49        elapsed_ms = u64::try_from(started.elapsed().as_millis()).unwrap_or(u64::MAX),
50        error = %e,
51        "backend: token request rejected"
52      );
53    })
54}
55
56/// Verify that `token` is still accepted by the site's backend.
57#[tracing::instrument(
58  skip_all,
59  fields(
60    site = %infra.site_name,
61    backend = %infra.backend_kind(),
62    backend_url = %infra.shasta_base_url,
63  )
64)]
65pub async fn validate_api_token(
66  infra: &InfraContext<'_>,
67  token: &str,
68) -> Result<(), Error> {
69  tracing::info!("backend: validating token");
70  let started = Instant::now();
71  infra
72    .backend
73    .validate_api_token(token)
74    .await
75    .inspect(|()| {
76      tracing::debug!(
77        elapsed_ms = u64::try_from(started.elapsed().as_millis()).unwrap_or(u64::MAX),
78        "backend: token accepted"
79      );
80    })
81    .inspect_err(|e| {
82      tracing::warn!(
83        elapsed_ms = u64::try_from(started.elapsed().as_millis()).unwrap_or(u64::MAX),
84        error = %e,
85        "backend: token validation rejected"
86      );
87    })
88}