1use chrono::{DateTime, Utc};
2use secrecy::{ExposeSecret, SecretString};
3
4pub mod store;
5#[derive(Debug, Clone)]
8pub enum Credential {
9 ApiKey(SecretString),
10 OAuth {
11 access: SecretString,
12 refresh: Option<SecretString>,
13 expires: Option<DateTime<Utc>>,
14 },
15}
16
17impl Credential {
18 pub fn api_key(key: impl Into<String>) -> Self {
19 Credential::ApiKey(SecretString::new(key.into().into_boxed_str()))
20 }
21
22 pub fn expose_key(&self) -> Option<&str> {
23 match self {
24 Credential::ApiKey(k) => Some(k.expose_secret()),
25 Credential::OAuth { access, .. } => Some(access.expose_secret()),
26 }
27 }
28
29 pub fn is_expired(&self) -> bool {
30 match self {
31 Credential::ApiKey(_) => false,
32 Credential::OAuth {
33 expires: Some(exp), ..
34 } => *exp <= Utc::now(),
35 _ => false,
36 }
37 }
38}
39
40pub trait AuthStore: Send + Sync {
45 fn get(&self, provider: &str) -> Option<Credential>;
46 fn set(&self, provider: &str, c: Credential) -> anyhow::Result<()>;
47 fn list(&self) -> Vec<String>;
48 fn remove(&self, provider: &str) -> anyhow::Result<()>;
49}
50
51use std::collections::HashMap;
54use std::sync::RwLock;
55
56pub struct MemoryAuthStore {
57 credentials: RwLock<HashMap<String, Credential>>,
58}
59
60impl MemoryAuthStore {
61 pub fn new() -> Self {
62 let mut store = Self {
63 credentials: RwLock::new(HashMap::new()),
64 };
65 store.load_from_env();
66 store
67 }
68
69 fn load_from_env(&mut self) {
71 let env_map: Vec<(&str, &str)> = vec![
72 ("ANTHROPIC_API_KEY", "anthropic"),
73 ("OPENAI_API_KEY", "openai"),
74 ("GROQ_API_KEY", "groq"),
75 ("TOGETHER_API_KEY", "together"),
76 ("NVIDIA_API_KEY", "nvidia"),
77 ("CEREBRAS_API_KEY", "cerebras"),
78 ("OPENROUTER_API_KEY", "openrouter"),
79 ];
80
81 let mut creds = self.credentials.write().unwrap();
82 for (env_var, provider) in env_map {
83 if let Ok(key) = std::env::var(env_var) {
84 if !key.is_empty() {
85 creds.insert(provider.to_string(), Credential::api_key(key));
86 }
87 }
88 }
89 }
90}
91
92impl AuthStore for MemoryAuthStore {
93 fn get(&self, provider: &str) -> Option<Credential> {
94 let creds = self.credentials.read().unwrap();
95 creds.get(provider).cloned()
96 }
97
98 fn set(&self, provider: &str, c: Credential) -> anyhow::Result<()> {
99 let mut creds = self.credentials.write().unwrap();
100 creds.insert(provider.to_string(), c);
101 Ok(())
102 }
103
104 fn list(&self) -> Vec<String> {
105 let creds = self.credentials.read().unwrap();
106 creds.keys().cloned().collect()
107 }
108
109 fn remove(&self, provider: &str) -> anyhow::Result<()> {
110 let mut creds = self.credentials.write().unwrap();
111 creds.remove(provider);
112 Ok(())
113 }
114}
115
116impl Default for MemoryAuthStore {
117 fn default() -> Self {
118 Self::new()
119 }
120}