Skip to main content

agentic_forge_core/security/
mod.rs

1//! Security module — auth, session, permissions.
2
3use std::collections::HashMap;
4
5pub struct AuthManager {
6    token: Option<String>,
7    sessions: HashMap<String, SessionBinding>,
8    failed_attempts: u32,
9    max_attempts: u32,
10}
11
12impl AuthManager {
13    pub fn new() -> Self {
14        let token = std::env::var("AGENTIC_AUTH_TOKEN").ok();
15        Self {
16            token,
17            sessions: HashMap::new(),
18            failed_attempts: 0,
19            max_attempts: 10,
20        }
21    }
22
23    pub fn is_auth_required(&self) -> bool {
24        self.token.is_some()
25    }
26
27    pub fn authenticate(&mut self, provided_token: &str) -> Result<String, String> {
28        if let Some(ref expected) = self.token {
29            if provided_token == expected {
30                let session_id = uuid::Uuid::new_v4().to_string();
31                self.sessions.insert(
32                    session_id.clone(),
33                    SessionBinding {
34                        session_id: session_id.clone(),
35                        created_at: chrono::Utc::now().timestamp(),
36                        last_activity: chrono::Utc::now().timestamp(),
37                        permissions: Permissions::default(),
38                    },
39                );
40                self.failed_attempts = 0;
41                Ok(session_id)
42            } else {
43                self.failed_attempts += 1;
44                Err("Invalid token".into())
45            }
46        } else {
47            Ok("anonymous".into())
48        }
49    }
50
51    pub fn is_rate_limited(&self) -> bool {
52        self.failed_attempts >= self.max_attempts
53    }
54
55    pub fn session_count(&self) -> usize {
56        self.sessions.len()
57    }
58
59    pub fn validate_session(&self, session_id: &str) -> bool {
60        self.sessions.contains_key(session_id)
61    }
62
63    pub fn revoke_session(&mut self, session_id: &str) -> bool {
64        self.sessions.remove(session_id).is_some()
65    }
66}
67
68impl Default for AuthManager {
69    fn default() -> Self {
70        Self::new()
71    }
72}
73
74#[derive(Debug, Clone)]
75pub struct SessionBinding {
76    pub session_id: String,
77    pub created_at: i64,
78    pub last_activity: i64,
79    pub permissions: Permissions,
80}
81
82#[derive(Debug, Clone, Default)]
83pub struct Permissions {
84    pub can_read: bool,
85    pub can_write: bool,
86    pub can_delete: bool,
87    pub can_admin: bool,
88}
89
90impl Permissions {
91    pub fn full() -> Self {
92        Self {
93            can_read: true,
94            can_write: true,
95            can_delete: true,
96            can_admin: true,
97        }
98    }
99
100    pub fn read_only() -> Self {
101        Self {
102            can_read: true,
103            ..Default::default()
104        }
105    }
106}
107
108#[cfg(test)]
109mod tests {
110    use super::*;
111
112    #[test]
113    fn test_auth_no_token() {
114        let mut auth = AuthManager::new();
115        // With no env var set, auth is not required
116        if !auth.is_auth_required() {
117            let session = auth.authenticate("anything").unwrap();
118            assert_eq!(session, "anonymous");
119        }
120    }
121
122    #[test]
123    fn test_rate_limiting() {
124        let mut auth = AuthManager {
125            token: Some("secret".into()),
126            sessions: HashMap::new(),
127            failed_attempts: 0,
128            max_attempts: 3,
129        };
130        assert!(!auth.is_rate_limited());
131        auth.authenticate("wrong").unwrap_err();
132        auth.authenticate("wrong").unwrap_err();
133        auth.authenticate("wrong").unwrap_err();
134        assert!(auth.is_rate_limited());
135    }
136
137    #[test]
138    fn test_session_management() {
139        let mut auth = AuthManager {
140            token: Some("secret".into()),
141            sessions: HashMap::new(),
142            failed_attempts: 0,
143            max_attempts: 10,
144        };
145        let session = auth.authenticate("secret").unwrap();
146        assert!(auth.validate_session(&session));
147        assert_eq!(auth.session_count(), 1);
148        assert!(auth.revoke_session(&session));
149        assert!(!auth.validate_session(&session));
150    }
151
152    #[test]
153    fn test_permissions() {
154        let full = Permissions::full();
155        assert!(full.can_read && full.can_write && full.can_delete && full.can_admin);
156        let ro = Permissions::read_only();
157        assert!(ro.can_read && !ro.can_write);
158    }
159
160    #[test]
161    fn test_default_permissions() {
162        let p = Permissions::default();
163        assert!(!p.can_read);
164        assert!(!p.can_write);
165    }
166}