pas-external 0.7.0

Ppoppo Accounts System (PAS) external SDK — OAuth2 PKCE, JWT verification port, Axum middleware, session liveness
Documentation
//! In-memory [`BearerVerifier`] adapter for tests + boundary fixtures.
//!
//! Gated behind `cfg(any(test, feature = "test-support"))` so production
//! builds never link the test machinery. Mirrors the
//! [`pas_port::MemoryPasAuth`](crate::pas_port::MemoryPasAuth) layout
//! conceptually — same in-memory pattern, different port.
//!
//! Two ways to fail a verification:
//!
//! - **Lookup miss**: the token string isn't in the `sessions` map.
//!   Surfaces as [`VerifyError::SignatureInvalid`] (the boundary
//!   semantic of "this token doesn't verify against our keyset" maps
//!   identically to "this token is unknown to the test fixture").
//! - **Default failure**: any token returns the configured error.
//!   Used to simulate keyset outages, expired tokens, etc. without
//!   pre-registering every variant.

use std::collections::HashMap;

use async_trait::async_trait;

use super::port::{AuthSession, BearerVerifier, Expectations, VerifyError};

/// In-memory verifier — pre-registered token → AuthSession map.
#[derive(Default)]
pub struct MemoryBearerVerifier {
    sessions: HashMap<String, AuthSession>,
    default_failure: Option<VerifyError>,
    /// Optional expectations stored for symmetry with [`super::PasJwtVerifier`].
    /// The in-memory verifier doesn't consult them by default — the
    /// test fixture controls success/failure via the `sessions` map and
    /// `default_failure`. Callers that want to simulate iss/aud
    /// mismatches do so by inserting an `IssuerInvalid` /
    /// `AudienceInvalid` `default_failure`.
    #[allow(dead_code)]
    expectations: Option<Expectations>,
}

impl MemoryBearerVerifier {
    #[must_use]
    pub fn new() -> Self {
        Self::default()
    }

    /// Attach Expectations for symmetry with PasJwtVerifier construction.
    /// The in-memory adapter does not currently consult them; it's a
    /// hook point for future "simulate iss/aud check" semantics.
    #[must_use]
    pub fn with_expectations(mut self, expectations: Expectations) -> Self {
        self.expectations = Some(expectations);
        self
    }

    /// Register a successful verification: when `token` is presented,
    /// return `session`.
    pub fn insert(&mut self, token: impl Into<String>, session: AuthSession) -> &mut Self {
        self.sessions.insert(token.into(), session);
        self
    }

    /// Configure a global default failure — every token (including
    /// those in the `sessions` map) returns this error. Useful for
    /// simulating a keyset outage in consumer integration tests.
    pub fn fail_with(&mut self, err: VerifyError) -> &mut Self {
        self.default_failure = Some(err);
        self
    }
}

#[async_trait]
impl BearerVerifier for MemoryBearerVerifier {
    async fn verify(&self, bearer_token: &str) -> Result<AuthSession, VerifyError> {
        if let Some(err) = self.default_failure.clone() {
            return Err(err);
        }
        self.sessions
            .get(bearer_token)
            .cloned()
            .ok_or(VerifyError::SignatureInvalid)
    }
}