pulseengine_mcp_auth/session/
mod.rs

1//! Session Management Module
2//!
3//! This module provides comprehensive session management for MCP authentication
4//! including JWT tokens, session storage, and lifecycle management.
5
6pub mod session_manager;
7
8pub use session_manager::{
9    MemorySessionStorage, Session, SessionConfig, SessionError, SessionManager, SessionStats,
10    SessionStorage,
11};
12
13#[cfg(test)]
14mod tests {
15    use super::*;
16    use crate::models::Role;
17    use crate::AuthContext;
18    use std::sync::Arc;
19
20    #[test]
21    fn test_session_module_exports() {
22        // Test that all session types are accessible
23
24        let config = SessionConfig::default();
25        assert!(config.default_duration > chrono::Duration::zero());
26        assert!(config.enable_jwt);
27
28        let _storage = MemorySessionStorage::new();
29        // MemorySessionStorage should be creatable
30
31        let _stats = SessionStats {
32            total_sessions: 0,
33            active_sessions: 0,
34            expired_sessions: 0,
35        };
36    }
37
38    #[tokio::test]
39    async fn test_session_manager_integration() {
40        let config = SessionConfig {
41            default_duration: chrono::Duration::hours(1),
42            enable_jwt: true,
43            ..Default::default()
44        };
45
46        let storage = Arc::new(MemorySessionStorage::new());
47        let manager = SessionManager::new(config, storage);
48
49        let auth_context = AuthContext {
50            user_id: Some("test-user".to_string()),
51            roles: vec![Role::Operator],
52            api_key_id: Some("test-key".to_string()),
53            permissions: vec!["session:create".to_string()],
54        };
55
56        // Test session creation
57        let session = manager
58            .create_session(
59                "test-user".to_string(),
60                auth_context,
61                None,                           // duration
62                Some("127.0.0.1".to_string()),  // client_ip
63                Some("test-agent".to_string()), // user_agent
64            )
65            .await;
66        assert!(session.is_ok());
67
68        let (session, _jwt_token) = session.unwrap();
69        assert_eq!(session.user_id, "test-user");
70        assert!(!session.session_id.is_empty());
71        assert!(session.expires_at > chrono::Utc::now());
72
73        // Test session retrieval
74        let retrieved = manager.get_session(&session.session_id).await;
75        assert!(retrieved.is_ok());
76
77        let retrieved = retrieved.unwrap();
78        assert_eq!(retrieved.session_id, session.session_id);
79        assert_eq!(retrieved.user_id, session.user_id);
80    }
81
82    #[tokio::test]
83    async fn test_session_storage_types() {
84        // Test memory storage creation
85        let memory_storage = MemorySessionStorage::new();
86
87        let auth_context = AuthContext {
88            user_id: Some("test-user".to_string()),
89            roles: vec![Role::Operator],
90            api_key_id: Some("test-key".to_string()),
91            permissions: vec!["session:create".to_string()],
92        };
93
94        let session = Session {
95            session_id: "test-session".to_string(),
96            user_id: "test-user".to_string(),
97            auth_context,
98            created_at: chrono::Utc::now(),
99            expires_at: chrono::Utc::now() + chrono::Duration::hours(1),
100            last_accessed: chrono::Utc::now(),
101            client_ip: Some("127.0.0.1".to_string()),
102            user_agent: Some("test-agent".to_string()),
103            metadata: std::collections::HashMap::new(),
104            is_active: true,
105            refresh_token: None,
106        };
107
108        // Test storage operations
109        let result = memory_storage.store_session(&session).await;
110        assert!(result.is_ok());
111
112        let retrieved = memory_storage.get_session(&session.session_id).await;
113        assert!(retrieved.is_ok());
114
115        let retrieved = retrieved.unwrap();
116        assert!(retrieved.is_some());
117        let retrieved = retrieved.unwrap();
118        assert_eq!(retrieved.session_id, session.session_id);
119        assert_eq!(retrieved.user_id, session.user_id);
120    }
121
122    #[test]
123    fn test_session_error_types() {
124        let errors = vec![
125            SessionError::SessionNotFound {
126                session_id: "test".to_string(),
127            },
128            SessionError::SessionExpired {
129                session_id: "test".to_string(),
130            },
131            SessionError::SessionInvalid {
132                reason: "test".to_string(),
133            },
134            SessionError::MaxSessionsExceeded {
135                user_id: "test".to_string(),
136            },
137            SessionError::CreationFailed {
138                reason: "test".to_string(),
139            },
140            SessionError::StorageError("test".to_string()),
141            SessionError::InvalidToken,
142        ];
143
144        for error in errors {
145            let error_string = error.to_string();
146            assert!(!error_string.is_empty());
147            assert!(error_string.len() > 5);
148        }
149    }
150
151    #[test]
152    fn test_session_config_defaults() {
153        let config = SessionConfig::default();
154
155        assert!(config.default_duration > chrono::Duration::zero());
156        assert!(config.default_duration <= chrono::Duration::hours(24)); // Reasonable default
157        assert!(config.enable_jwt);
158        // Other defaults should be reasonable
159    }
160
161    #[tokio::test]
162    async fn test_session_lifecycle() {
163        let config = SessionConfig::default();
164        let storage = Arc::new(MemorySessionStorage::new());
165        let manager = SessionManager::new(config, storage);
166
167        let auth_context = AuthContext {
168            user_id: Some("lifecycle-user".to_string()),
169            roles: vec![Role::Operator],
170            api_key_id: Some("lifecycle-key".to_string()),
171            permissions: vec!["session:create".to_string()],
172        };
173
174        // Create session
175        let session = manager
176            .create_session(
177                "lifecycle-user".to_string(),
178                auth_context,
179                Some(chrono::Duration::minutes(1)), // duration
180                Some("127.0.0.1".to_string()),      // client_ip
181                Some("test-agent".to_string()),     // user_agent
182            )
183            .await
184            .unwrap();
185        let (session, _jwt_token) = session;
186        let session_id = session.session_id.clone();
187
188        // Verify session exists and is active
189        let retrieved = manager.get_session(&session_id).await.unwrap();
190        assert!(retrieved.is_active);
191        assert!(retrieved.expires_at > chrono::Utc::now());
192
193        // Test session refresh (if we have a refresh token)
194        if let Some(refresh_token) = &session.refresh_token {
195            let refreshed = manager.refresh_session(&session_id, refresh_token).await;
196            assert!(refreshed.is_ok());
197            let (refreshed_session, _new_jwt) = refreshed.unwrap();
198            assert!(refreshed_session.expires_at > retrieved.expires_at);
199        }
200
201        // Test session termination
202        let terminated = manager.terminate_session(&session_id).await;
203        assert!(terminated.is_ok());
204
205        // Session should no longer be retrievable as active
206        let after_revoke = manager.get_session(&session_id).await;
207        // Depending on implementation, this might return NotFound or an inactive session
208        assert!(after_revoke.is_err() || !after_revoke.unwrap().is_active);
209    }
210}