Skip to main content

axess_core/principal/
session_resolver.rs

1//! `SessionResolver`: extracts a [`HumanPrincipal`] from an
2//! authenticated [`AuthSession`].
3//!
4//! Lives in axess-core (not in the leaf [`axess_identity`] crate)
5//! because it depends on axess-core's session state machine. Implements
6//! the [`axess_identity::PrincipalResolver`] trait so call sites can
7//! treat `SessionResolver` and `CliResolver` uniformly through the same
8//! trait surface.
9
10use std::collections::BTreeMap;
11
12use axess_identity::{HumanPrincipal, IdentityError, Principal, PrincipalResolver};
13
14use crate::AuthSession;
15
16/// Resolver that extracts a [`HumanPrincipal`] from an authenticated
17/// [`AuthSession`]. Returns [`IdentityError::NotAuthenticated`] when
18/// the session is in any state other than
19/// [`Authenticated`](crate::AuthState::Authenticated).
20///
21/// Cheap to construct; clones the session handle (an `Arc`-backed
22/// shared value) so the resolver owns its own view. Per-request
23/// construction in an Axum handler is the expected use:
24///
25/// ```text
26/// async fn handler(session: AuthSession) -> Result<…, ApiError> {
27///     let principal = SessionResolver::new(session).resolve().await?;
28///     // …
29/// }
30/// ```
31#[derive(Clone)]
32pub struct SessionResolver {
33    session: AuthSession,
34}
35
36impl SessionResolver {
37    /// Construct a resolver over the given session.
38    pub fn new(session: AuthSession) -> Self {
39        Self { session }
40    }
41}
42
43impl PrincipalResolver for SessionResolver {
44    async fn resolve(&self) -> Result<Principal, IdentityError> {
45        let snap = self
46            .session
47            .snapshot()
48            .await
49            .ok_or(IdentityError::NotAuthenticated)?;
50        // The prior `from_bytes` conversion
51        // between two duplicate `SessionId` types here disappeared
52        // when `axess_core::session::id::SessionId` became a
53        // re-export of `axess_identity::SessionId`. The snapshot's
54        // `session_id` is now type-identical to the field on
55        // `HumanPrincipal`; direct assignment, no byte copy.
56        Ok(Principal::Human(HumanPrincipal {
57            user_id: snap.user_id,
58            tenant_id: snap.tenant_id,
59            session_id: Some(snap.session_id),
60            attributes: BTreeMap::new(),
61        }))
62    }
63}