1use std::sync::Arc;
4
5use base64::{Engine, engine::general_purpose::STANDARD};
6use futures::future::BoxFuture;
7
8use crate::error::Result;
9
10pub trait TokenProvider: Send + Sync {
18 fn token(&self) -> BoxFuture<'_, Result<String>>;
20}
21
22#[derive(Clone)]
28#[non_exhaustive]
29pub enum Auth {
30 ApiKey(String),
32 Token(String),
34 Provider(Arc<dyn TokenProvider>),
36}
37
38impl Auth {
39 pub fn api_key(key: impl Into<String>) -> Self {
41 Auth::ApiKey(key.into())
42 }
43
44 pub fn token(token: impl Into<String>) -> Self {
46 Auth::Token(token.into())
47 }
48
49 pub fn provider(p: Arc<dyn TokenProvider>) -> Self {
51 Auth::Provider(p)
52 }
53
54 pub(crate) fn header_value(&self) -> String {
56 match self {
57 Auth::ApiKey(k) => format!("Basic {}", STANDARD.encode(k.as_bytes())),
58 Auth::Token(t) => format!("Bearer {t}"),
59 Auth::Provider(_) => unreachable!(
60 "Auth::Provider carries no static header; see AuthState::Provider in client.rs"
61 ),
62 }
63 }
64}
65
66impl std::fmt::Debug for Auth {
67 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
68 match self {
69 Auth::ApiKey(_) => f.write_str("Auth::ApiKey(<redacted>)"),
70 Auth::Token(_) => f.write_str("Auth::Token(<redacted>)"),
71 Auth::Provider(_) => f.write_str("Auth::Provider(<dyn TokenProvider>)"),
72 }
73 }
74}
75
76#[cfg(any(feature = "jwt", feature = "token-issuance"))]
82pub(crate) fn split_api_key(api_key: &str) -> crate::error::Result<(&str, &str)> {
83 let (name, secret) = api_key.split_once(':').ok_or_else(|| {
84 crate::error::Error::InvalidRequest("API key must be `keyName:keySecret`".into())
85 })?;
86 if name.is_empty() || secret.is_empty() {
87 return Err(crate::error::Error::InvalidRequest(
88 "API key name and secret must be non-empty".into(),
89 ));
90 }
91 Ok((name, secret))
92}
93
94#[cfg(test)]
95mod tests {
96 use super::*;
97
98 #[test]
99 fn api_key_becomes_basic_header() {
100 let a = Auth::api_key("app.key:secret");
101 assert_eq!(a.header_value(), "Basic YXBwLmtleTpzZWNyZXQ=");
102 }
103
104 #[test]
105 fn token_becomes_bearer_header() {
106 assert_eq!(Auth::token("tok123").header_value(), "Bearer tok123");
107 }
108
109 #[test]
110 fn debug_redacts_credentials() {
111 let dbg = format!("{:?}", Auth::api_key("app.key:secret"));
112 assert!(!dbg.contains("secret"));
113 let dbg = format!("{:?}", Auth::token("tok123"));
114 assert!(!dbg.contains("tok123"));
115 }
116
117 #[test]
118 fn provider_debug_redacts() {
119 use futures::future::BoxFuture;
120 use std::sync::Arc;
121
122 struct P;
123 impl crate::config::TokenProvider for P {
124 fn token(&self) -> BoxFuture<'_, crate::error::Result<String>> {
125 Box::pin(async { Ok("jwt-abc".to_string()) })
126 }
127 }
128 let a = Auth::provider(Arc::new(P));
129 let dbg = format!("{a:?}");
130 assert!(dbg.contains("Provider"));
131 assert!(!dbg.contains("jwt-abc"));
132 }
133}