Skip to main content

ppoppo_sdk_core/bearer/
config.rs

1//! [`BearerAuthConfig`] — per-consumer perimeter configuration
2//! (cookie source + clearance closure + pre-auth path exemptions).
3
4use std::sync::Arc;
5
6use axum_extra::extract::cookie::CookieJar;
7
8/// Per-consumer perimeter configuration carried into
9/// [`super::BearerAuthLayer`].
10///
11/// Everything the perimeter needs that is *consumer-specific*:
12/// - the fallback bearer cookie name ([`Self::access_cookie_name`]),
13/// - the dead-session cookie-clearance closure ([`Self::on_clear`]),
14/// - the pre-auth path allowlist ([`Self::path_exempt`]).
15///
16/// The Layer uses [`Self::access_cookie_name`] as the fallback bearer
17/// source (when `Authorization` is absent) and the [`Self::on_clear`]
18/// closure to mint Set-Cookie removals on dead-session 401s. Cookie
19/// names are domain-scoped per RFC 6265bis; each consumer owns its
20/// `__Host-*_at` literal in its own `cookies` module.
21#[derive(Clone)]
22pub struct BearerAuthConfig {
23    /// Cookie name carrying the Bearer token in browser contexts —
24    /// e.g. `"__Host-pcs_at"` (chat-auth), `"__Host-rcw_at"` (RCW),
25    /// `"__Host-ctw_at"` (CTW).
26    pub access_cookie_name: &'static str,
27    /// Add-based session-cookie clearance closure. Invoked on
28    /// dead-session 401 with a fresh [`CookieJar`]; the returned jar's
29    /// add-list becomes the response's Set-Cookie headers. Consumers
30    /// typically wrap their `cookies::clear_session_cookies` helper
31    /// (which clears AT + RT, and possibly CSRF in future).
32    pub on_clear: Arc<dyn Fn(CookieJar) -> CookieJar + Send + Sync>,
33    /// Pre-auth path allowlist — requests whose **exact** URI path
34    /// matches an entry skip the perimeter entirely (no Bearer
35    /// extraction, no `verify_token`, no session injected). The sole
36    /// use is anonymous-by-contract transport routes: native gRPC
37    /// health probes (`/grpc.health.v1.Health/Check` + `/Watch`),
38    /// which Kubernetes issues with no credential. Matching is exact
39    /// full-path `==`, never a prefix — a prefix would be a fail-open
40    /// over-exemption (a sibling RPC sharing the prefix would silently
41    /// bypass auth). The empty slice is the fail-closed default (every
42    /// route authenticates); a consumer that exempts nothing passes
43    /// `[].into()` explicitly.
44    pub path_exempt: Arc<[&'static str]>,
45}
46
47impl BearerAuthConfig {
48    /// Build a perimeter config from the cookie name, clearance
49    /// closure, and pre-auth path exemptions.
50    ///
51    /// `path_exempt` is **required**, never defaulted: each perimeter
52    /// declares its exemption posture explicitly at the construction
53    /// site. Pass `[].into()` to exempt nothing (the fail-closed
54    /// default), or a consumer-owned `const &[&str]` of exact gRPC
55    /// method paths for anonymous-by-contract routes.
56    #[must_use]
57    pub fn new(
58        access_cookie_name: &'static str,
59        on_clear: Arc<dyn Fn(CookieJar) -> CookieJar + Send + Sync>,
60        path_exempt: Arc<[&'static str]>,
61    ) -> Self {
62        Self {
63            access_cookie_name,
64            on_clear,
65            path_exempt,
66        }
67    }
68}