Skip to main content

openapp_sdk_core/
auth.rs

1//! Authentication providers.
2//!
3//! The SDK ships a single [`TokenProvider`] trait so higher layers (bridge, Python
4//! wrappers) can plug in custom auth — session cookies, JWT, Vault-minted tokens —
5//! without breaking the rest of the client. The v1 default is [`StaticApiKey`], which
6//! forwards the parsed `OpenApp` API key as an `Authorization: Bearer` header.
7
8use std::sync::Arc;
9
10use async_trait::async_trait;
11use openapp_sdk_common::ApiKey;
12
13use crate::error::SdkError;
14
15/// Credentials returned by a [`TokenProvider`] for a single outgoing request.
16#[derive(Debug, Clone)]
17pub struct AuthToken {
18    /// Full value of the `Authorization` header (typically `Bearer <token>`).
19    pub authorization: String,
20}
21
22/// Produces the `Authorization` header for every outgoing SDK request.
23#[async_trait]
24pub trait TokenProvider: Send + Sync + std::fmt::Debug {
25    /// Return the credentials to attach to the next request. May be called on the hot
26    /// path, so implementations should cache aggressively.
27    async fn token(&self) -> Result<AuthToken, SdkError>;
28}
29
30/// Shared-ownership handle used throughout the SDK.
31pub type SharedTokenProvider = Arc<dyn TokenProvider>;
32
33/// Static API-key provider: the token never changes for the lifetime of the client.
34#[derive(Debug, Clone)]
35pub struct StaticApiKey {
36    key: ApiKey,
37}
38
39impl StaticApiKey {
40    /// Wrap a parsed [`ApiKey`] into a provider.
41    #[must_use]
42    pub fn new(key: ApiKey) -> Self {
43        Self { key }
44    }
45
46    /// Parse and wrap a raw token string.
47    pub fn from_raw(token: impl Into<String>) -> Result<Self, SdkError> {
48        Ok(Self::new(ApiKey::parse(token)?))
49    }
50
51    /// Access the underlying [`ApiKey`] (e.g. to derive the base URL).
52    #[must_use]
53    pub fn api_key(&self) -> &ApiKey {
54        &self.key
55    }
56}
57
58#[async_trait]
59impl TokenProvider for StaticApiKey {
60    async fn token(&self) -> Result<AuthToken, SdkError> {
61        Ok(AuthToken {
62            authorization: format!("Bearer {}", self.key.as_bearer()),
63        })
64    }
65}
66
67#[cfg(test)]
68mod tests {
69    use super::*;
70
71    #[tokio::test]
72    async fn static_provider_emits_bearer() {
73        let provider =
74            StaticApiKey::from_raw("https://api.openapp.house/api/v1_openapp_SECRET").unwrap();
75        let token = provider.token().await.unwrap();
76        assert_eq!(
77            token.authorization,
78            "Bearer https://api.openapp.house/api/v1_openapp_SECRET"
79        );
80    }
81}