Files
madbase/auth/src/session.rs.bak
Vlad Durnea cffdf8af86
Some checks failed
CI/CD Pipeline / unit-tests (push) Failing after 1m16s
CI/CD Pipeline / integration-tests (push) Failing after 2m32s
CI/CD Pipeline / lint (push) Successful in 5m22s
CI/CD Pipeline / e2e-tests (push) Has been skipped
CI/CD Pipeline / build (push) Has been skipped
wip:milestone 0 fixes
2026-03-15 12:35:42 +02:00

200 lines
7.2 KiB
Rust

```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<String> {
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<Option<SessionData>> {
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<Option<SessionData>> {
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<bool> {
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<usize> {
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<String> = 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<Vec<SessionData>> {
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<String> = 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<usize> {
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: }
```