web_server_abstraction/
session.rs

1//! Session management for user sessions and authentication.
2
3use crate::types::{Cookie, Request, Response};
4use serde::{Deserialize, Serialize};
5use std::collections::HashMap;
6use std::sync::{Arc, RwLock};
7use std::time::{Duration, SystemTime};
8use uuid::Uuid;
9
10/// Session data
11#[derive(Debug, Clone, Serialize, Deserialize)]
12pub struct Session {
13    pub id: String,
14    pub data: HashMap<String, String>,
15    pub created_at: SystemTime,
16    pub expires_at: SystemTime,
17}
18
19impl Session {
20    /// Create a new session
21    pub fn new(duration: Duration) -> Self {
22        let now = SystemTime::now();
23        Self {
24            id: Uuid::new_v4().to_string(),
25            data: HashMap::new(),
26            created_at: now,
27            expires_at: now + duration,
28        }
29    }
30
31    /// Check if session is expired
32    pub fn is_expired(&self) -> bool {
33        SystemTime::now() > self.expires_at
34    }
35
36    /// Extend session expiration
37    pub fn extend(&mut self, duration: Duration) {
38        self.expires_at = SystemTime::now() + duration;
39    }
40
41    /// Get a value from session data
42    pub fn get(&self, key: &str) -> Option<&String> {
43        self.data.get(key)
44    }
45
46    /// Set a value in session data
47    pub fn set(&mut self, key: String, value: String) {
48        self.data.insert(key, value);
49    }
50
51    /// Remove a value from session data
52    pub fn remove(&mut self, key: &str) -> Option<String> {
53        self.data.remove(key)
54    }
55
56    /// Clear all session data
57    pub fn clear(&mut self) {
58        self.data.clear();
59    }
60}
61
62/// Session store trait
63pub trait SessionStore: Send + Sync {
64    /// Get a session by ID
65    fn get(&self, session_id: &str) -> Option<Session>;
66
67    /// Store a session
68    fn set(&self, session: Session);
69
70    /// Remove a session
71    fn remove(&self, session_id: &str);
72
73    /// Clean up expired sessions
74    fn cleanup_expired(&self);
75}
76
77/// In-memory session store
78#[derive(Debug)]
79pub struct MemorySessionStore {
80    sessions: Arc<RwLock<HashMap<String, Session>>>,
81}
82
83impl MemorySessionStore {
84    pub fn new() -> Self {
85        Self {
86            sessions: Arc::new(RwLock::new(HashMap::new())),
87        }
88    }
89}
90
91impl Default for MemorySessionStore {
92    fn default() -> Self {
93        Self::new()
94    }
95}
96
97impl SessionStore for MemorySessionStore {
98    fn get(&self, session_id: &str) -> Option<Session> {
99        let sessions = self.sessions.read().unwrap();
100        sessions.get(session_id).cloned()
101    }
102
103    fn set(&self, session: Session) {
104        let mut sessions = self.sessions.write().unwrap();
105        sessions.insert(session.id.clone(), session);
106    }
107
108    fn remove(&self, session_id: &str) {
109        let mut sessions = self.sessions.write().unwrap();
110        sessions.remove(session_id);
111    }
112
113    fn cleanup_expired(&self) {
114        let mut sessions = self.sessions.write().unwrap();
115        let now = SystemTime::now();
116        sessions.retain(|_, session| session.expires_at > now);
117    }
118}
119
120/// Session manager
121pub struct SessionManager {
122    store: Box<dyn SessionStore>,
123    cookie_name: String,
124    session_duration: Duration,
125    secure: bool,
126    http_only: bool,
127}
128
129impl SessionManager {
130    /// Create a new session manager
131    pub fn new(store: Box<dyn SessionStore>) -> Self {
132        Self {
133            store,
134            cookie_name: "session_id".to_string(),
135            session_duration: Duration::from_secs(24 * 60 * 60), // 24 hours
136            secure: false,
137            http_only: true,
138        }
139    }
140
141    /// Create with memory store
142    pub fn memory() -> Self {
143        Self::new(Box::new(MemorySessionStore::new()))
144    }
145
146    /// Set cookie name
147    pub fn cookie_name(mut self, name: String) -> Self {
148        self.cookie_name = name;
149        self
150    }
151
152    /// Set session duration
153    pub fn duration(mut self, duration: Duration) -> Self {
154        self.session_duration = duration;
155        self
156    }
157
158    /// Set secure flag for cookies
159    pub fn secure(mut self, secure: bool) -> Self {
160        self.secure = secure;
161        self
162    }
163
164    /// Set http_only flag for cookies
165    pub fn http_only(mut self, http_only: bool) -> Self {
166        self.http_only = http_only;
167        self
168    }
169
170    /// Get session from request
171    pub fn get_session(&self, request: &Request) -> Option<Session> {
172        let cookie = request.cookie(&self.cookie_name)?;
173        let session = self.store.get(&cookie.value)?;
174
175        if session.is_expired() {
176            self.store.remove(&session.id);
177            return None;
178        }
179
180        Some(session)
181    }
182
183    /// Create a new session
184    pub fn create_session(&self) -> Session {
185        Session::new(self.session_duration)
186    }
187
188    /// Save session and return response with session cookie
189    pub fn save_session(&self, mut session: Session, response: Response) -> Response {
190        // Extend session if it's about to expire
191        if session
192            .expires_at
193            .duration_since(SystemTime::now())
194            .unwrap_or(Duration::ZERO)
195            < self.session_duration / 4
196        {
197            session.extend(self.session_duration);
198        }
199
200        let cookie = Cookie::new(&self.cookie_name, &session.id)
201            .path("/")
202            .http_only(self.http_only)
203            .secure(self.secure)
204            .max_age(self.session_duration);
205
206        self.store.set(session);
207        response.cookie(cookie)
208    }
209
210    /// Destroy session
211    pub fn destroy_session(&self, request: &Request, mut response: Response) -> Response {
212        if let Some(cookie) = request.cookie(&self.cookie_name) {
213            self.store.remove(&cookie.value);
214
215            // Send cookie with expired date to clear it
216            let expired_cookie = Cookie::new(&self.cookie_name, "")
217                .path("/")
218                .max_age(Duration::ZERO);
219
220            response = response.cookie(expired_cookie);
221        }
222        response
223    }
224
225    /// Clean up expired sessions
226    pub fn cleanup_expired(&self) {
227        self.store.cleanup_expired();
228    }
229}
230
231/// Session middleware extension trait
232pub trait SessionExt {
233    /// Get or create session
234    fn session(&self, manager: &SessionManager) -> Session;
235}
236
237impl SessionExt for Request {
238    fn session(&self, manager: &SessionManager) -> Session {
239        manager
240            .get_session(self)
241            .unwrap_or_else(|| manager.create_session())
242    }
243}
244
245#[cfg(test)]
246mod tests {
247    use super::*;
248
249    #[test]
250    fn test_session_creation() {
251        let session = Session::new(Duration::from_secs(3600));
252        assert!(!session.is_expired());
253        assert_eq!(session.data.len(), 0);
254    }
255
256    #[test]
257    fn test_session_data() {
258        let mut session = Session::new(Duration::from_secs(3600));
259
260        session.set("user_id".to_string(), "123".to_string());
261        assert_eq!(session.get("user_id"), Some(&"123".to_string()));
262
263        session.remove("user_id");
264        assert_eq!(session.get("user_id"), None);
265    }
266
267    #[test]
268    fn test_memory_store() {
269        let store = MemorySessionStore::new();
270        let session = Session::new(Duration::from_secs(3600));
271        let session_id = session.id.clone();
272
273        store.set(session);
274        assert!(store.get(&session_id).is_some());
275
276        store.remove(&session_id);
277        assert!(store.get(&session_id).is_none());
278    }
279
280    #[test]
281    fn test_session_manager() {
282        let manager = SessionManager::memory()
283            .cookie_name("test_session".to_string())
284            .duration(Duration::from_secs(1800));
285
286        let session = manager.create_session();
287        assert!(!session.is_expired());
288    }
289}