Skip to main content

communitas_core/security/
auth_middleware.rs

1//! Authentication middleware for Tauri commands
2//!
3//! This module provides:
4//! - Session-based authentication
5//! - Role-based access control
6//! - Secure session management
7//! - Protection against unauthorized command execution
8
9use anyhow::Result;
10use serde::{Deserialize, Serialize};
11use std::collections::HashMap;
12use std::sync::{Arc, RwLock};
13use std::time::{Duration, Instant};
14use uuid::Uuid;
15
16/// Maximum session duration (1 hour)
17pub const MAX_SESSION_DURATION: Duration = Duration::from_secs(3600);
18
19/// Session cleanup interval (5 minutes)
20pub const SESSION_CLEANUP_INTERVAL: Duration = Duration::from_secs(300);
21
22/// Authentication session information
23#[derive(Debug, Clone)]
24pub struct AuthSession {
25    pub session_id: String,
26    pub user_id: String,
27    pub four_words_identity: String,
28    pub permissions: Vec<Permission>,
29    pub created_at: Instant,
30    pub last_accessed: Instant,
31    pub expires_at: Instant,
32}
33
34impl AuthSession {
35    /// Create a new authentication session
36    pub fn new(user_id: String, four_words_identity: String, permissions: Vec<Permission>) -> Self {
37        let now = Instant::now();
38        Self {
39            session_id: Uuid::new_v4().to_string(),
40            user_id,
41            four_words_identity,
42            permissions,
43            created_at: now,
44            last_accessed: now,
45            expires_at: now + MAX_SESSION_DURATION,
46        }
47    }
48
49    /// Check if the session is still valid
50    pub fn is_valid(&self) -> bool {
51        Instant::now() < self.expires_at
52    }
53
54    /// Update the last accessed timestamp
55    pub fn refresh(&mut self) {
56        self.last_accessed = Instant::now();
57    }
58
59    /// Check if the session has the required permission
60    pub fn has_permission(&self, required: &Permission) -> bool {
61        self.permissions.iter().any(|p| p.allows(required))
62    }
63}
64
65/// Permission system for role-based access control
66#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
67pub struct Permission {
68    pub resource: String,
69    pub action: String,
70    pub scope: PermissionScope,
71}
72
73#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
74pub enum PermissionScope {
75    Own,    // Only own resources
76    Shared, // Shared resources with appropriate access
77    All,    // All resources (admin level)
78}
79
80impl Permission {
81    pub fn new(resource: &str, action: &str, scope: PermissionScope) -> Self {
82        Self {
83            resource: resource.to_string(),
84            action: action.to_string(),
85            scope,
86        }
87    }
88
89    /// Check if this permission allows the required permission
90    pub fn allows(&self, required: &Permission) -> bool {
91        // Resource must match (or this permission is for all resources)
92        let resource_match = self.resource == "*" || self.resource == required.resource;
93
94        // Action must match (or this permission allows all actions)
95        let action_match = self.action == "*" || self.action == required.action;
96
97        // Scope must be sufficient
98        let scope_match = matches!(
99            (&self.scope, &required.scope),
100            (PermissionScope::All, _)
101                | (PermissionScope::Shared, PermissionScope::Own)
102                | (PermissionScope::Shared, PermissionScope::Shared)
103                | (PermissionScope::Own, PermissionScope::Own)
104        );
105
106        resource_match && action_match && scope_match
107    }
108}
109
110/// Authentication middleware for managing sessions
111#[derive(Debug, Clone)]
112pub struct AuthMiddleware {
113    sessions: Arc<RwLock<HashMap<String, AuthSession>>>,
114    last_cleanup: Arc<RwLock<Instant>>,
115}
116
117impl Default for AuthMiddleware {
118    fn default() -> Self {
119        Self::new()
120    }
121}
122
123impl AuthMiddleware {
124    /// Create a new authentication middleware
125    pub fn new() -> Self {
126        Self {
127            sessions: Arc::new(RwLock::new(HashMap::new())),
128            last_cleanup: Arc::new(RwLock::new(Instant::now())),
129        }
130    }
131
132    /// Create a new authenticated session
133    pub fn create_session(
134        &self,
135        user_id: String,
136        four_words_identity: String,
137        permissions: Vec<Permission>,
138    ) -> Result<String> {
139        let session = AuthSession::new(user_id, four_words_identity, permissions);
140        let session_id = session.session_id.clone();
141
142        {
143            let mut sessions = self
144                .sessions
145                .write()
146                .map_err(|_| anyhow::anyhow!("Failed to acquire sessions lock"))?;
147            sessions.insert(session_id.clone(), session);
148        }
149
150        // Trigger cleanup if needed
151        self.cleanup_expired_sessions()?;
152
153        Ok(session_id)
154    }
155
156    /// Validate a session and return the session information
157    pub fn validate_session(&self, session_id: &str) -> Result<AuthSession> {
158        let mut sessions = self
159            .sessions
160            .write()
161            .map_err(|_| anyhow::anyhow!("Failed to acquire sessions lock"))?;
162
163        let session = sessions
164            .get_mut(session_id)
165            .ok_or_else(|| anyhow::anyhow!("Invalid session ID"))?;
166
167        if !session.is_valid() {
168            sessions.remove(session_id);
169            return Err(anyhow::anyhow!("Session expired"));
170        }
171
172        session.refresh();
173        Ok(session.clone())
174    }
175
176    /// Check if a session has the required permission
177    pub fn check_permission(
178        &self,
179        session_id: &str,
180        required_permission: &Permission,
181    ) -> Result<bool> {
182        let session = self.validate_session(session_id)?;
183        Ok(session.has_permission(required_permission))
184    }
185
186    /// Require a specific permission for a session (returns error if not authorized)
187    pub fn require_permission(
188        &self,
189        session_id: &str,
190        required_permission: &Permission,
191    ) -> Result<AuthSession> {
192        let session = self.validate_session(session_id)?;
193
194        if !session.has_permission(required_permission) {
195            return Err(anyhow::anyhow!(
196                "Insufficient permissions. Required: {:?}",
197                required_permission
198            ));
199        }
200
201        Ok(session)
202    }
203
204    /// End a session (logout)
205    pub fn end_session(&self, session_id: &str) -> Result<()> {
206        let mut sessions = self
207            .sessions
208            .write()
209            .map_err(|_| anyhow::anyhow!("Failed to acquire sessions lock"))?;
210
211        sessions.remove(session_id);
212        Ok(())
213    }
214
215    /// Get all active sessions (admin function)
216    pub fn get_active_sessions(&self) -> Result<Vec<AuthSession>> {
217        let sessions = self
218            .sessions
219            .read()
220            .map_err(|_| anyhow::anyhow!("Failed to acquire sessions lock"))?;
221
222        let active_sessions: Vec<AuthSession> = sessions
223            .values()
224            .filter(|session| session.is_valid())
225            .cloned()
226            .collect();
227
228        Ok(active_sessions)
229    }
230
231    /// Clean up expired sessions
232    pub fn cleanup_expired_sessions(&self) -> Result<()> {
233        let now = Instant::now();
234
235        // Check if cleanup is needed
236        {
237            let last_cleanup = self
238                .last_cleanup
239                .read()
240                .map_err(|_| anyhow::anyhow!("Failed to acquire cleanup lock"))?;
241
242            if now.duration_since(*last_cleanup) < SESSION_CLEANUP_INTERVAL {
243                return Ok(()); // Cleanup not needed yet
244            }
245        }
246
247        // Perform cleanup
248        {
249            let mut sessions = self
250                .sessions
251                .write()
252                .map_err(|_| anyhow::anyhow!("Failed to acquire sessions lock"))?;
253
254            sessions.retain(|_, session| session.is_valid());
255        }
256
257        // Update last cleanup time
258        {
259            let mut last_cleanup = self
260                .last_cleanup
261                .write()
262                .map_err(|_| anyhow::anyhow!("Failed to acquire cleanup lock"))?;
263            *last_cleanup = now;
264        }
265
266        Ok(())
267    }
268
269    /// Get session statistics
270    pub fn get_stats(&self) -> Result<SessionStats> {
271        let sessions = self
272            .sessions
273            .read()
274            .map_err(|_| anyhow::anyhow!("Failed to acquire sessions lock"))?;
275
276        let total_sessions = sessions.len();
277        let active_sessions = sessions.values().filter(|s| s.is_valid()).count();
278        let expired_sessions = total_sessions - active_sessions;
279
280        Ok(SessionStats {
281            total_sessions,
282            active_sessions,
283            expired_sessions,
284        })
285    }
286}
287
288/// Session statistics
289#[derive(Debug, Serialize, Deserialize)]
290pub struct SessionStats {
291    pub total_sessions: usize,
292    pub active_sessions: usize,
293    pub expired_sessions: usize,
294}
295
296/// Common permission definitions
297pub mod permissions {
298    use super::{Permission, PermissionScope};
299
300    pub fn read_messages() -> Permission {
301        Permission::new("messages", "read", PermissionScope::Shared)
302    }
303
304    pub fn send_messages() -> Permission {
305        Permission::new("messages", "write", PermissionScope::Shared)
306    }
307
308    pub fn manage_contacts() -> Permission {
309        Permission::new("contacts", "*", PermissionScope::Own)
310    }
311
312    pub fn dht_operations() -> Permission {
313        Permission::new("dht", "*", PermissionScope::Shared)
314    }
315
316    pub fn admin_operations() -> Permission {
317        Permission::new("*", "*", PermissionScope::All)
318    }
319
320    pub fn identity_management() -> Permission {
321        Permission::new("identity", "*", PermissionScope::Own)
322    }
323
324    pub fn file_storage() -> Permission {
325        Permission::new("storage", "*", PermissionScope::Own)
326    }
327}
328
329/// Macro for protecting Tauri commands with authentication
330#[macro_export]
331macro_rules! require_auth {
332    ($auth_middleware:expr, $session_id:expr, $permission:expr) => {
333        match $auth_middleware.require_permission($session_id, &$permission) {
334            Ok(session) => session,
335            Err(e) => return Err(format!("Authentication failed: {}", e)),
336        }
337    };
338}
339
340/// Macro for protecting Tauri commands with session validation only
341#[macro_export]
342macro_rules! require_session {
343    ($auth_middleware:expr, $session_id:expr) => {
344        match $auth_middleware.validate_session($session_id) {
345            Ok(session) => session,
346            Err(e) => return Err(format!("Session validation failed: {}", e)),
347        }
348    };
349}
350
351#[cfg(test)]
352mod tests {
353    use super::*;
354
355    #[test]
356    fn test_permission_matching() {
357        let admin_perm = Permission::new("*", "*", PermissionScope::All);
358        let read_messages = Permission::new("messages", "read", PermissionScope::Shared);
359
360        assert!(admin_perm.allows(&read_messages));
361        assert!(!read_messages.allows(&admin_perm));
362    }
363
364    #[test]
365    fn test_session_creation_and_validation() {
366        let auth = AuthMiddleware::new();
367        let permissions = vec![permissions::read_messages(), permissions::send_messages()];
368
369        let session_id = auth
370            .create_session(
371                "test_user".to_string(),
372                "hello-world-test-net".to_string(),
373                permissions,
374            )
375            .unwrap();
376
377        let session = auth.validate_session(&session_id).unwrap();
378        assert_eq!(session.user_id, "test_user");
379        assert!(session.has_permission(&permissions::read_messages()));
380    }
381
382    #[test]
383    fn test_permission_checking() {
384        let auth = AuthMiddleware::new();
385        let permissions = vec![permissions::read_messages()];
386
387        let session_id = auth
388            .create_session(
389                "test_user".to_string(),
390                "hello-world-test-net".to_string(),
391                permissions,
392            )
393            .unwrap();
394
395        assert!(
396            auth.check_permission(&session_id, &permissions::read_messages())
397                .unwrap()
398        );
399        assert!(
400            !auth
401                .check_permission(&session_id, &permissions::admin_operations())
402                .unwrap()
403        );
404    }
405
406    #[test]
407    fn test_session_expiry() {
408        let auth = AuthMiddleware::new();
409        let permissions = vec![permissions::read_messages()];
410
411        let session_id = auth
412            .create_session(
413                "test_user".to_string(),
414                "hello-world-test-net".to_string(),
415                permissions,
416            )
417            .unwrap();
418
419        // Session should be valid initially
420        assert!(auth.validate_session(&session_id).is_ok());
421
422        // Manually expire the session for testing
423        {
424            let mut sessions = auth.sessions.write().unwrap();
425            if let Some(session) = sessions.get_mut(&session_id) {
426                session.expires_at = Instant::now() - Duration::from_secs(1);
427            }
428        }
429
430        // Session should now be invalid
431        assert!(auth.validate_session(&session_id).is_err());
432    }
433}