gestura_core_sessions/
session_manager.rs

1//! Session management for authentication and authorization
2//! Handles user sessions, tokens, and access control
3
4use gestura_core_foundation::AppError;
5use std::collections::HashMap;
6use std::sync::Arc;
7use std::time::{Duration, SystemTime};
8use tokio::sync::RwLock;
9
10/// User session information
11#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
12pub struct UserSession {
13    pub session_id: String,
14    pub user_id: String,
15    pub username: String,
16    pub email: Option<String>,
17    pub roles: Vec<String>,
18    pub permissions: Vec<String>,
19    pub created_at: SystemTime,
20    pub last_accessed: SystemTime,
21    pub expires_at: SystemTime,
22    pub is_active: bool,
23    pub metadata: HashMap<String, serde_json::Value>,
24}
25
26/// Authentication token
27#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
28pub struct AuthToken {
29    pub token: String,
30    pub token_type: TokenType,
31    pub session_id: String,
32    pub expires_at: SystemTime,
33    pub scopes: Vec<String>,
34}
35
36/// Token types
37#[derive(Debug, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
38pub enum TokenType {
39    Bearer,
40    ApiKey,
41    Refresh,
42    Temporary,
43}
44
45/// Session manager
46pub struct SessionManager {
47    sessions: Arc<RwLock<HashMap<String, UserSession>>>,
48    tokens: Arc<RwLock<HashMap<String, AuthToken>>>,
49    default_session_duration: Duration,
50    max_sessions_per_user: usize,
51}
52
53impl SessionManager {
54    /// Create a new session manager
55    pub fn new(default_session_duration: Duration, max_sessions_per_user: usize) -> Self {
56        Self {
57            sessions: Arc::new(RwLock::new(HashMap::new())),
58            tokens: Arc::new(RwLock::new(HashMap::new())),
59            default_session_duration,
60            max_sessions_per_user,
61        }
62    }
63
64    /// Create a new user session
65    pub async fn create_session(
66        &self,
67        user_id: String,
68        username: String,
69        email: Option<String>,
70        roles: Vec<String>,
71    ) -> Result<UserSession, AppError> {
72        let session_id = self.generate_session_id();
73        let now = SystemTime::now();
74        let expires_at = now + self.default_session_duration;
75
76        // Check if user has too many active sessions
77        self.cleanup_user_sessions(&user_id).await?;
78
79        let session = UserSession {
80            session_id: session_id.clone(),
81            user_id: user_id.clone(),
82            username,
83            email,
84            roles,
85            permissions: Vec::new(), // Will be populated based on roles
86            created_at: now,
87            last_accessed: now,
88            expires_at,
89            is_active: true,
90            metadata: HashMap::new(),
91        };
92
93        let mut sessions = self.sessions.write().await;
94        sessions.insert(session_id.clone(), session.clone());
95
96        tracing::info!("Created session {} for user {}", session_id, user_id);
97        Ok(session)
98    }
99
100    /// Get session by ID
101    pub async fn get_session(&self, session_id: &str) -> Option<UserSession> {
102        let mut sessions = self.sessions.write().await;
103
104        if let Some(session) = sessions.get_mut(session_id) {
105            // Check if session is expired
106            if SystemTime::now() > session.expires_at || !session.is_active {
107                sessions.remove(session_id);
108                return None;
109            }
110
111            // Update last accessed time
112            session.last_accessed = SystemTime::now();
113            Some(session.clone())
114        } else {
115            None
116        }
117    }
118
119    /// Validate session and return user info
120    pub async fn validate_session(&self, session_id: &str) -> Result<UserSession, AppError> {
121        self.get_session(session_id).await.ok_or_else(|| {
122            AppError::Io(std::io::Error::new(
123                std::io::ErrorKind::PermissionDenied,
124                "Invalid or expired session",
125            ))
126        })
127    }
128
129    /// Create authentication token
130    pub async fn create_token(
131        &self,
132        session_id: &str,
133        token_type: TokenType,
134        scopes: Vec<String>,
135        duration: Option<Duration>,
136    ) -> Result<AuthToken, AppError> {
137        // Validate session exists
138        let session = self.validate_session(session_id).await?;
139
140        let token = self.generate_token();
141        let expires_at = SystemTime::now() + duration.unwrap_or(Duration::from_secs(3600));
142
143        let auth_token = AuthToken {
144            token: token.clone(),
145            token_type,
146            session_id: session.session_id,
147            expires_at,
148            scopes,
149        };
150
151        let mut tokens = self.tokens.write().await;
152        tokens.insert(token.clone(), auth_token.clone());
153
154        tracing::info!("Created token for session {}", session_id);
155        Ok(auth_token)
156    }
157
158    /// Validate authentication token
159    pub async fn validate_token(&self, token: &str) -> Result<(UserSession, AuthToken), AppError> {
160        let tokens = self.tokens.read().await;
161
162        if let Some(auth_token) = tokens.get(token) {
163            // Check if token is expired
164            if SystemTime::now() > auth_token.expires_at {
165                drop(tokens);
166                self.revoke_token(token).await?;
167                return Err(AppError::Io(std::io::Error::new(
168                    std::io::ErrorKind::PermissionDenied,
169                    "Token expired",
170                )));
171            }
172
173            // Get associated session
174            let session = self.validate_session(&auth_token.session_id).await?;
175            Ok((session, auth_token.clone()))
176        } else {
177            Err(AppError::Io(std::io::Error::new(
178                std::io::ErrorKind::PermissionDenied,
179                "Invalid token",
180            )))
181        }
182    }
183
184    /// Revoke authentication token
185    pub async fn revoke_token(&self, token: &str) -> Result<(), AppError> {
186        let mut tokens = self.tokens.write().await;
187        if tokens.remove(token).is_some() {
188            tracing::info!("Revoked token: {}", &token[..8]);
189            Ok(())
190        } else {
191            Err(AppError::Io(std::io::Error::new(
192                std::io::ErrorKind::NotFound,
193                "Token not found",
194            )))
195        }
196    }
197
198    /// End user session
199    pub async fn end_session(&self, session_id: &str) -> Result<(), AppError> {
200        let mut sessions = self.sessions.write().await;
201
202        if let Some(session) = sessions.get_mut(session_id) {
203            session.is_active = false;
204
205            // Revoke all tokens for this session
206            let mut tokens = self.tokens.write().await;
207            let tokens_to_remove: Vec<String> = tokens
208                .iter()
209                .filter(|(_, token)| token.session_id == session_id)
210                .map(|(token_str, _)| token_str.clone())
211                .collect();
212
213            for token in tokens_to_remove {
214                tokens.remove(&token);
215            }
216
217            tracing::info!("Ended session: {}", session_id);
218            Ok(())
219        } else {
220            Err(AppError::Io(std::io::Error::new(
221                std::io::ErrorKind::NotFound,
222                "Session not found",
223            )))
224        }
225    }
226
227    /// Get all active sessions for a user
228    pub async fn get_user_sessions(&self, user_id: &str) -> Vec<UserSession> {
229        let sessions = self.sessions.read().await;
230        sessions
231            .values()
232            .filter(|session| session.user_id == user_id && session.is_active)
233            .cloned()
234            .collect()
235    }
236
237    /// Cleanup old sessions for a user (keep only the most recent ones)
238    async fn cleanup_user_sessions(&self, user_id: &str) -> Result<(), AppError> {
239        let mut sessions = self.sessions.write().await;
240        let mut user_sessions: Vec<_> = sessions
241            .iter()
242            .filter(|(_, session)| session.user_id == user_id && session.is_active)
243            .map(|(id, session)| (id.clone(), session.last_accessed))
244            .collect();
245
246        if user_sessions.len() >= self.max_sessions_per_user {
247            // Sort by last accessed time (oldest first)
248            user_sessions.sort_by_key(|(_, last_accessed)| *last_accessed);
249
250            // Remove oldest sessions
251            let sessions_to_remove = user_sessions.len() - self.max_sessions_per_user + 1;
252            for (session_id, _) in user_sessions.iter().take(sessions_to_remove) {
253                sessions.remove(session_id);
254                tracing::info!("Removed old session {} for user {}", session_id, user_id);
255            }
256        }
257
258        Ok(())
259    }
260
261    /// Generate unique session ID
262    fn generate_session_id(&self) -> String {
263        format!("sess_{}", uuid::Uuid::new_v4().to_string().replace('-', ""))
264    }
265
266    /// Generate authentication token
267    fn generate_token(&self) -> String {
268        format!("tok_{}", uuid::Uuid::new_v4().to_string().replace('-', ""))
269    }
270
271    /// Get session statistics
272    pub async fn get_stats(&self) -> serde_json::Value {
273        let sessions = self.sessions.read().await;
274        let tokens = self.tokens.read().await;
275
276        let active_sessions = sessions.values().filter(|s| s.is_active).count();
277        let total_sessions = sessions.len();
278        let total_tokens = tokens.len();
279
280        serde_json::json!({
281            "active_sessions": active_sessions,
282            "total_sessions": total_sessions,
283            "total_tokens": total_tokens,
284            "default_session_duration_hours": self.default_session_duration.as_secs() / 3600,
285            "max_sessions_per_user": self.max_sessions_per_user
286        })
287    }
288}
289
290/// Global session manager instance
291static SESSION_MANAGER: tokio::sync::OnceCell<SessionManager> = tokio::sync::OnceCell::const_new();
292
293/// Get the global session manager
294pub async fn get_session_manager() -> &'static SessionManager {
295    SESSION_MANAGER
296        .get_or_init(|| async { SessionManager::new(Duration::from_secs(24 * 3600), 5) })
297        .await
298}