```rust 1: //! Distributed session management using Redis 2: //! 3: //! This module provides session storage that works across multiple proxy nodes. 4: //! Sessions are stored in Redis and can be accessed by any proxy instance. 5: 6: use common::{CacheLayer, CacheError, CacheResult, SessionData}; 7: use uuid::Uuid; 8: use chrono::{DateTime, Utc, Duration}; 9: 10: /// Session manager for distributed auth sessions 11: #[derive(Clone)] 12: pub struct SessionManager { 13: cache: CacheLayer, 14: session_ttl: u64, // Session TTL in seconds 15: } 16: 17: impl SessionManager { 18: /// Create a new session manager 19: pub fn new(cache: CacheLayer, session_ttl: u64) -> Self { 20: Self { cache, session_ttl } 21: } 22: 23: /// Create a new session for a user 24: pub async fn create_session( 25: &self, 26: user_id: Uuid, 27: email: String, 28: role: String, 29: ) -> CacheResult { 30: let session_token = Uuid::new_v4().to_string(); 31: let now = Utc::now(); 32: let expires_at = now + Duration::seconds(self.session_ttl as i64); 33: 34: let session = SessionData { 35: user_id, 36: email, 37: role, 38: created_at: now, 39: expires_at, 40: }; 41: 42: // Store session in Redis 43: let key = format!("session:{}", session_token); 44: self.cache.set(&key, &session).await?; 45: 46: // Also add to user's active sessions set (for multi-device logout) 47: let user_sessions_key = format!("user:{}:sessions", user_id); 48: if let Some(redis) = &self.cache.redis { 49: let mut conn = redis.get_async_connection().await?; 50: redis::cmd("SADD") 51: .arg(&user_sessions_key) 52: .arg(&session_token) 53: .query_async(&mut conn) 54: .await?; 55: 56: // Set expiration on the set 57: redis::cmd("EXPIRE") 58: .arg(&user_sessions_key) 59: .arg(self.session_ttl * 2) 60: .query_async(&mut conn) 61: .await?; 62: } 63: 64: Ok(session_token) 65: } 66: 67: /// Get a session by token 68: pub async fn get_session(&self, session_token: &str) -> CacheResult> { 69: self.cache.get_session(session_token.to_string()).await 70: } 71: 72: /// Validate a session (check if it exists and is not expired) 73: pub async fn validate_session(&self, session_token: &str) -> CacheResult> { 74: let session = self.get_session(session_token).await?; 75: 76: if let Some(session) = session { 77: let now = Utc::now(); 78: if now < session.expires_at { 79: return Ok(Some(session)); 80: } 81: } 82: 83: Ok(None) 84: } 85: 86: /// Refresh a session (extend expiration) 87: pub async fn refresh_session(&self, session_token: &str) -> CacheResult { 88: if let Some(mut session) = self.get_session(session_token).await? { 89: let now = Utc::now(); 90: session.expires_at = now + Duration::seconds(self.session_ttl as i64); 91: 92: let key = format!("session:{}", session_token); 93: self.cache.set(&key, &session).await?; 94: return Ok(true); 95: } 96: 97: Ok(false) 98: } 99: 100: /// Delete a session (logout) 101: pub async fn delete_session(&self, session_token: &str) -> CacheResult<()> { 102: // Get the session first to remove from user's session set 103: if let Some(session) = self.get_session(session_token).await? { 104: let user_sessions_key = format!("user:{}:sessions", session.user_id); 105: 106: if let Some(redis) = &self.cache.redis { 107: let mut conn = redis.get_async_connection().await?; 108: redis::cmd("SREM") 109: .arg(&user_sessions_key) 110: .arg(session_token) 111: .query_async(&mut conn) 112: .await?; 113: } 114: } 115: 116: self.cache.delete_session(session_token.to_string()).await 117: } 118: 119: /// Delete all sessions for a user (logout from all devices) 120: pub async fn delete_all_user_sessions(&self, user_id: Uuid) -> CacheResult { 121: let user_sessions_key = format!("user:{}:sessions", user_id); 122: 123: if let Some(redis) = &self.cache.redis { 124: let mut conn = redis.get_async_connection().await?; 125: 126: // Get all session tokens for this user 127: let session_tokens: Vec = redis::cmd("SMEMBERS") 128: .arg(&user_sessions_key) 129: .query_async(&mut conn) 130: .await?; 131: 132: let count = session_tokens.len(); 133: 134: // Delete each session 135: for token in &session_tokens { 136: let session_key = format!("session:{}", token); 137: redis::cmd("DEL") 138: .arg(&session_key) 139: .query_async(&mut conn) 140: .await?; 141: } 142: 143: // Delete the user's session set 144: redis::cmd("DEL") 145: .arg(&user_sessions_key) 146: .query_async(&mut conn) 147: .await?; 148: 149: Ok(count) 150: } else { 151: Ok(0) 152: } 153: } 154: 155: /// Get all active sessions for a user 156: pub async fn get_user_sessions(&self, user_id: Uuid) -> CacheResult> { 157: let user_sessions_key = format!("user:{}:sessions", user_id); 158: 159: if let Some(redis) = &self.cache.redis { 160: let mut conn = redis.get_async_connection().await?; 161: 162: let session_tokens: Vec = redis::cmd("SMEMBERS") 163: .arg(&user_sessions_key) 164: .query_async(&mut conn) 165: .await?; 166: 167: let mut sessions = Vec::new(); 168: for token in session_tokens { 169: if let Some(session) = self.get_session(&token).await? { 170: sessions.push(session); 171: } 172: } 173: 174: Ok(sessions) 175: } else { 176: Ok(vec![]) 177: } 178: } 179: 180: /// Count active sessions for a user 181: pub async fn get_user_session_count(&self, user_id: Uuid) -> CacheResult { 182: let sessions = self.get_user_sessions(user_id).await?; 183: Ok(sessions.len()) 184: } 185: } 186: 187: #[cfg(test)] 188: mod tests { 189: use super::*; 190: 191: #[tokio::test] 192: async fn test_session_manager_creation() { 193: let cache = CacheLayer::new(None, 3600); 194: let manager = SessionManager::new(cache, 3600); 195: assert_eq!(manager.session_ttl, 3600); 196: } 197: } ```