pas-external 0.1.0

Ppoppo Accounts System (PAS) external SDK -- OAuth2 PKCE, PASETO verification, Axum middleware, session liveness
Documentation
//! [`PasAuth`] — the single public entry point that wires a PAS OAuth2 router
//! together with a matching [`SvAwareSessionResolver`].
//!
//! Construct one per application, then mount the router and share the resolver
//! with whatever custom Axum middleware needs to read the authenticated
//! context. Both halves share the same [`SessionStore`] and cookie name by
//! construction, so the "write side" (OAuth callback) and the "read side"
//! (auth middleware) cannot drift out of sync.

use std::sync::Arc;

use axum::Router;

use super::config::PasAuthConfig;
use super::routes;
use super::session::SessionResolver;
use super::state::AuthState;
use super::sv_aware::SvAwareSessionResolver;
use super::traits::{AccountResolver, RefreshTokenResolver, SessionStore};
use crate::oauth::AuthClient;
use crate::pas_port::PasAuthPort;
use crate::session_version::{MemorySessionVersionCache, SessionVersionCache};

/// Bundle of the PAS auth router + a matching [`SvAwareSessionResolver`].
///
/// The `P` type parameter is the [`PasAuthPort`] adapter. In production
/// it defaults to [`AuthClient`]; the only consumers passing a different
/// `P` are the SDK's own boundary tests (using `MemoryPasAuth`).
pub struct PasAuth<S, R, P = AuthClient>
where
    S: SessionStore,
    R: RefreshTokenResolver,
    P: PasAuthPort,
{
    router: Router,
    base_resolver: SessionResolver<S>,
    store: Arc<S>,
    refresh_resolver: Arc<R>,
    pas: Arc<P>,
    default_cache: Arc<MemorySessionVersionCache>,
}

// Production constructor: P = AuthClient.
impl<S: SessionStore, R: RefreshTokenResolver> PasAuth<S, R, AuthClient> {
    /// Assemble the PAS auth router + supporting handles.
    pub fn new<U: AccountResolver>(
        config: PasAuthConfig,
        account_resolver: Arc<U>,
        session_store: Arc<S>,
        refresh_resolver: Arc<R>,
    ) -> Self {
        let cookie_name: Arc<str> = Arc::from(config.settings.session_cookie_name.as_str());
        let auth_client = Arc::new(config.client);

        let state = AuthState {
            client: Arc::clone(&auth_client),
            account_resolver,
            session_store: Arc::clone(&session_store),
            settings: config.settings,
        };

        let router = routes::build_router(state);
        let base_resolver = SessionResolver::new(Arc::clone(&session_store), cookie_name);

        Self {
            router,
            base_resolver,
            store: session_store,
            refresh_resolver,
            pas: auth_client,
            default_cache: Arc::new(MemorySessionVersionCache::new()),
        }
    }
}

// Resolver-construction methods are generic over P so SDK tests can
// build a PasAuth-equivalent shape against MemoryPasAuth without going
// through PasAuthConfig.
impl<S, R, P> PasAuth<S, R, P>
where
    S: SessionStore,
    R: RefreshTokenResolver,
    P: PasAuthPort,
{
    /// Build the default sv-aware resolver, backed by an in-process
    /// [`MemorySessionVersionCache`] (60 s TTL).
    #[must_use]
    pub fn resolver(&self) -> SvAwareSessionResolver<S, R, MemorySessionVersionCache, P> {
        SvAwareSessionResolver::new(
            self.base_resolver.clone(),
            Arc::clone(&self.store),
            Arc::clone(&self.refresh_resolver),
            Arc::clone(&self.pas),
            Arc::clone(&self.default_cache),
        )
    }

    /// Build a sv-aware resolver backed by a custom cache implementation.
    #[must_use]
    pub fn resolver_with_cache<C: SessionVersionCache>(
        &self,
        cache: C,
    ) -> SvAwareSessionResolver<S, R, C, P> {
        SvAwareSessionResolver::new(
            self.base_resolver.clone(),
            Arc::clone(&self.store),
            Arc::clone(&self.refresh_resolver),
            Arc::clone(&self.pas),
            Arc::new(cache),
        )
    }

    /// Consume the bundle and return the router.
    pub fn into_router(self) -> Router {
        self.router
    }
}