ppoppo-sdk-core 0.2.0

Internal shared primitives for the Ppoppo SDK family (pas-external, pas-plims, pcs-external) — verifier port, audit trait, session liveness port, OIDC discovery, perimeter Bearer-auth Layer kit, identity types. Not a stable public API; do not depend on this crate directly. Consume the SDK crates that re-export from it (e.g. `pas-external`).
Documentation
//! Edge authentication layer — the SDK's perimeter for Bearer-token
//! authenticated HTTP traffic.
//!
//! Mounts at the HTTP edge of any axum router and produces a single,
//! consumer-shaped session value as a request extension that handlers
//! consume by capability passing rather than re-authenticating in body.
//!
//! ## Why sdk-core ships this Layer
//!
//! Phase 11 — chat-auth was the 1st integrator (`chat-auth::middleware`),
//! RCW the 2nd (`rollcall_web::infrastructure::auth::bearer_auth_layer`),
//! CTW the 3rd (`classytime_web::infrastructure::auth::bearer_auth_layer`).
//! All three carried near-identical mechanism — Authorization > cookie
//! extraction, three HTTP dispositions (200 / 401 / 503), add-based
//! cookie clearance on dead-session 401, verbatim cookie value passing
//! to the validator. With **N=3 evidence** in hand, the mechanism
//! collapses into one SDK Layer; consumers ship only the substrate
//! adapter ([`AuthProvider<S>`] impl) + their cookie-name constant +
//! their clearance closure.
//!
//! Phase A Slice 4 lifted the Layer kit from `pas-external::oidc::axum`
//! into sdk-core so 1st-party services (chat-auth) can import it
//! directly without travelling through the External-Developer-facing
//! pas-external surface — the trust-boundary asymmetry pattern locked
//! by audit decision B (`RFC_2026-05-08_app-credential-collapse.md`).
//! pas-external still re-exports the kit at `pas_external::bearer::*`
//! for 3rd-party RCW/CTW consumers (audit decision D).
//!
//! ## Surface — four public types
//!
//! - [`AuthProvider<S>`] — the trait that consumers implement. One
//!   async method, generic over the consumer's perimeter session type
//!   `S`. Cryptographic verify + substrate liveness happen inside the
//!   impl as one atomic decision; the Layer never spells substrate
//!   internals.
//! - [`VerifyError`] — exactly two HTTP dispositions. Richer error
//!   taxonomies (per-substrate break-glass dashboards) collapse here
//!   at the SDK boundary.
//! - [`BearerAuthConfig`] — `&'static str` cookie name + an
//!   `Arc<dyn Fn(CookieJar) -> CookieJar + Send + Sync>` clearance
//!   closure. A value struct so future fields (e.g. `audit_sink`) can
//!   be added with `Default` without breaking call sites.
//! - [`BearerAuthLayer<Sess, P>`] — the tower [`tower::Layer`] wired
//!   into axum routers via `.layer(...)`.
//!
//! ## Sources (RFC 6750 + RFC 9700 §6.3)
//!
//! - `Authorization: Bearer <token>` HTTP header — preferred. RFC 6750
//!   §2.1. Used by any non-browser caller (CLI clients, server-to-
//!   server requests, native gRPC over h2 with a synthesised Bearer).
//! - HttpOnly cookie identified by [`BearerAuthConfig::access_cookie_name`]
//!   — fallback. RFC 9700 §6.3 browser-context BCP. Each consumer ships
//!   its own `__Host-*_at` constant (cookie names are domain-scoped per
//!   RFC 6265bis); the SDK never spells the literal.
//!
//! `Authorization` wins precedence: a request carrying both is treated
//! as header-source.
//!
//! ## Errors
//!
//! - Missing Bearer source (no header, no cookie) → 401 without cookie
//!   clearance. The browser may simply have not yet authenticated; no
//!   session exists to clear.
//! - Token rejected ([`VerifyError::Rejected`]) → 401 + add-based
//!   cookie clearance via the consumer's [`BearerAuthConfig::on_clear`]
//!   closure. Browsers stop replaying a stale token.
//! - Substrate transient ([`VerifyError::SubstrateTransient`]) → 503
//!   with cookies preserved. The browser may retry; the session is
//!   still valid even if the validator can't currently say so.
//!
//! ## Why no SDK perimeter session type
//!
//! The trait's generic `S` IS the perimeter session — there is no SDK
//! `AuthClaims` or `BearerAuthSession` for the consumer to project from.
//! The consumer's `AuthProvider<MySession>::verify_token` impl produces
//! `MySession` directly, in one atomic step. Eliminates the otherwise-
//! tempting pass-through `claims → session` projection, and lets each
//! consumer keep its native session shape without SDK opinion. RCW/CTW
//! ship `BearerAuthSession` (3 fields); chat-auth ships `AuthSession`
//! (5 fields including `account_type`/`scopes`/`audience` for OAuth
//! 3rd-party scope-gating). Both coexist behind the same SDK Layer.
//!
//! See `RFC_2026-05-07_oidc-rp-phase-11x-rcw-ctw-migration.md` §11 and
//! `RFC_2026-05-08_app-credential-collapse.md` §3 (audit B/D/F) for
//! the contract-lock rationale.

mod config;
mod error;
mod layer;
mod port;

#[cfg(any(test, feature = "test-support"))]
mod memory;

pub use config::BearerAuthConfig;
pub use error::VerifyError;
pub use layer::{BearerAuthLayer, BearerAuthService};
pub use port::AuthProvider;

#[cfg(any(test, feature = "test-support"))]
pub use memory::MemoryAuthProvider;