1use crate::{AuthToken, AuthType, AgentCapability, UbiquityError};
4use chrono::{Utc, Duration};
5use std::collections::HashMap;
6use std::sync::Arc;
7use tokio::sync::RwLock;
8
9pub struct AuthService {
11 auth_type: AuthType,
12 tokens: Arc<RwLock<HashMap<String, AuthToken>>>,
13 api_keys: Arc<RwLock<HashMap<String, String>>>, secret_key: String,
15}
16
17impl AuthService {
18 pub fn new(auth_type: AuthType) -> Self {
19 Self {
20 auth_type,
21 tokens: Arc::new(RwLock::new(HashMap::new())),
22 api_keys: Arc::new(RwLock::new(HashMap::new())),
23 secret_key: generate_secret_key(),
24 }
25 }
26
27 pub async fn generate_token(
29 &self,
30 agent_id: String,
31 capabilities: AgentCapability,
32 expires_in: Option<Duration>,
33 ) -> Result<AuthToken, UbiquityError> {
34 let token_string = generate_token_string();
35 let expires_at = expires_in.map(|d| Utc::now() + d);
36
37 let token = AuthToken {
38 token: token_string.clone(),
39 agent_id: agent_id.clone(),
40 capabilities,
41 expires_at,
42 };
43
44 self.tokens.write().await.insert(token_string, token.clone());
45
46 Ok(token)
47 }
48
49 pub async fn validate_token(&self, token_str: &str) -> Result<AuthToken, UbiquityError> {
51 let tokens = self.tokens.read().await;
52
53 match tokens.get(token_str) {
54 Some(token) => {
55 if let Some(expires_at) = token.expires_at {
57 if expires_at < Utc::now() {
58 return Err(UbiquityError::ConfigError("Token expired".into()));
59 }
60 }
61 Ok(token.clone())
62 }
63 None => Err(UbiquityError::ConfigError("Invalid token".into())),
64 }
65 }
66
67 pub async fn register_api_key(&self, api_key: String, agent_id: String) -> Result<(), UbiquityError> {
69 self.api_keys.write().await.insert(api_key, agent_id);
70 Ok(())
71 }
72
73 pub async fn validate_api_key(&self, api_key: &str) -> Result<String, UbiquityError> {
75 self.api_keys
76 .read()
77 .await
78 .get(api_key)
79 .cloned()
80 .ok_or_else(|| UbiquityError::ConfigError("Invalid API key".into()))
81 }
82
83 pub async fn authenticate(&self, credentials: &str) -> Result<String, UbiquityError> {
85 match self.auth_type {
86 AuthType::None => Ok("anonymous".to_string()),
87 AuthType::Token => {
88 let token = self.validate_token(credentials).await?;
89 Ok(token.agent_id)
90 }
91 AuthType::ApiKey => {
92 self.validate_api_key(credentials).await
93 }
94 AuthType::Certificate => {
95 Err(UbiquityError::ConfigError("Certificate auth not implemented".into()))
97 }
98 }
99 }
100
101 pub async fn check_capability(
103 &self,
104 token_str: &str,
105 check: impl FnOnce(&AgentCapability) -> bool,
106 ) -> Result<bool, UbiquityError> {
107 let token = self.validate_token(token_str).await?;
108 Ok(check(&token.capabilities))
109 }
110
111 pub async fn revoke_token(&self, token_str: &str) -> Result<(), UbiquityError> {
113 self.tokens.write().await.remove(token_str);
114 Ok(())
115 }
116
117 pub async fn cleanup_expired_tokens(&self) {
119 let now = Utc::now();
120 let mut tokens = self.tokens.write().await;
121
122 tokens.retain(|_, token| {
123 token.expires_at.map(|exp| exp > now).unwrap_or(true)
124 });
125 }
126}
127
128fn generate_token_string() -> String {
130 use rand::Rng;
131 const CHARSET: &[u8] = b"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
132 const TOKEN_LEN: usize = 32;
133
134 let mut rng = rand::thread_rng();
135 (0..TOKEN_LEN)
136 .map(|_| {
137 let idx = rng.gen_range(0..CHARSET.len());
138 CHARSET[idx] as char
139 })
140 .collect()
141}
142
143fn generate_secret_key() -> String {
145 generate_token_string()
146}
147
148pub struct AuthGuard {
150 required_capabilities: Vec<Box<dyn Fn(&AgentCapability) -> bool + Send + Sync>>,
151}
152
153impl AuthGuard {
154 pub fn new() -> Self {
155 Self {
156 required_capabilities: Vec::new(),
157 }
158 }
159
160 pub fn require_write_code(mut self) -> Self {
162 self.required_capabilities.push(Box::new(|cap| cap.can_write_code));
163 self
164 }
165
166 pub fn require_execute_commands(mut self) -> Self {
168 self.required_capabilities.push(Box::new(|cap| cap.can_execute_commands));
169 self
170 }
171
172 pub fn require_coordination(mut self) -> Self {
174 self.required_capabilities.push(Box::new(|cap| cap.can_coordinate));
175 self
176 }
177
178 pub async fn check(&self, auth_service: &AuthService, token: &str) -> Result<(), UbiquityError> {
180 let token_data = auth_service.validate_token(token).await?;
181
182 for check in &self.required_capabilities {
183 if !check(&token_data.capabilities) {
184 return Err(UbiquityError::ConfigError("Insufficient capabilities".into()));
185 }
186 }
187
188 Ok(())
189 }
190}
191
192#[cfg(test)]
193mod tests {
194 use super::*;
195
196 #[tokio::test]
197 async fn test_token_generation_and_validation() {
198 let auth = AuthService::new(AuthType::Token);
199 let capabilities = AgentCapability::default();
200
201 let token = auth.generate_token(
202 "test-agent".to_string(),
203 capabilities,
204 Some(Duration::hours(1)),
205 ).await.unwrap();
206
207 let validated = auth.validate_token(&token.token).await.unwrap();
208 assert_eq!(validated.agent_id, "test-agent");
209 }
210
211 #[tokio::test]
212 async fn test_expired_token() {
213 let auth = AuthService::new(AuthType::Token);
214 let capabilities = AgentCapability::default();
215
216 let token = auth.generate_token(
217 "test-agent".to_string(),
218 capabilities,
219 Some(Duration::seconds(-1)), ).await.unwrap();
221
222 let result = auth.validate_token(&token.token).await;
223 assert!(result.is_err());
224 }
225
226 #[tokio::test]
227 async fn test_api_key() {
228 let auth = AuthService::new(AuthType::ApiKey);
229
230 auth.register_api_key("test-key".to_string(), "test-agent".to_string())
231 .await
232 .unwrap();
233
234 let agent_id = auth.validate_api_key("test-key").await.unwrap();
235 assert_eq!(agent_id, "test-agent");
236 }
237
238 #[tokio::test]
239 async fn test_auth_guard() {
240 let auth = AuthService::new(AuthType::Token);
241 let mut capabilities = AgentCapability::default();
242 capabilities.can_coordinate = true;
243
244 let token = auth.generate_token(
245 "test-agent".to_string(),
246 capabilities,
247 None,
248 ).await.unwrap();
249
250 let guard = AuthGuard::new()
251 .require_write_code()
252 .require_coordination();
253
254 let result = guard.check(&auth, &token.token).await;
255 assert!(result.is_ok());
256 }
257}