Skip to main content

gestalt/public/
auth.rs

1//! Authentication helpers for the public Gestalt transport client.
2
3use std::sync::{Arc, RwLock};
4
5/// Supplies credentials for public gestaltd requests.
6pub trait Auth: Send + Sync {
7    /// Returns an `Authorization` header value when credentials are present.
8    fn authorization_header(&self) -> Option<String>;
9}
10
11/// Bearer token authentication for REST and gRPC.
12#[derive(Clone)]
13pub struct BearerAuth {
14    provider: Arc<dyn Fn() -> String + Send + Sync>,
15}
16
17impl std::fmt::Debug for BearerAuth {
18    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
19        f.debug_struct("BearerAuth").finish_non_exhaustive()
20    }
21}
22
23impl BearerAuth {
24    /// Creates bearer auth from a static token string.
25    pub fn new(token: impl Into<String>) -> Self {
26        let token = token.into();
27        Self::dynamic(move || token.clone())
28    }
29
30    /// Creates bearer auth from a provider evaluated for each request.
31    pub fn dynamic(provider: impl Fn() -> String + Send + Sync + 'static) -> Self {
32        Self {
33            provider: Arc::new(provider),
34        }
35    }
36
37    /// Creates bearer auth backed by a shared, rotatable token value.
38    pub fn shared(token: Arc<RwLock<String>>) -> Self {
39        Self::dynamic(move || token.read().expect("token lock poisoned").clone())
40    }
41}
42
43impl Auth for BearerAuth {
44    fn authorization_header(&self) -> Option<String> {
45        let mut token = (self.provider)().trim().to_string();
46        if token.is_empty() {
47            return None;
48        }
49        if let Some(rest) = token.strip_prefix("Bearer ") {
50            token = rest.to_string();
51        }
52        Some(format!("Bearer {token}"))
53    }
54}
55
56/// Unauthenticated requests.
57#[derive(Clone, Copy, Debug, Default)]
58pub struct NoAuth;
59
60impl Auth for NoAuth {
61    fn authorization_header(&self) -> Option<String> {
62        None
63    }
64}