1use gestura_core_foundation::AppError;
5use std::collections::HashMap;
6use std::sync::Arc;
7use std::time::{Duration, SystemTime};
8use tokio::sync::RwLock;
9
10#[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#[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#[derive(Debug, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
38pub enum TokenType {
39 Bearer,
40 ApiKey,
41 Refresh,
42 Temporary,
43}
44
45pub 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 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 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 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(), 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 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 if SystemTime::now() > session.expires_at || !session.is_active {
107 sessions.remove(session_id);
108 return None;
109 }
110
111 session.last_accessed = SystemTime::now();
113 Some(session.clone())
114 } else {
115 None
116 }
117 }
118
119 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 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 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 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 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 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 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 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 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 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 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 user_sessions.sort_by_key(|(_, last_accessed)| *last_accessed);
249
250 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 fn generate_session_id(&self) -> String {
263 format!("sess_{}", uuid::Uuid::new_v4().to_string().replace('-', ""))
264 }
265
266 fn generate_token(&self) -> String {
268 format!("tok_{}", uuid::Uuid::new_v4().to_string().replace('-', ""))
269 }
270
271 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
290static SESSION_MANAGER: tokio::sync::OnceCell<SessionManager> = tokio::sync::OnceCell::const_new();
292
293pub 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}