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}