Skip to main content

paas_server/
session_storage.rs

1use chrono::{TimeZone, Utc};
2use libpep::factors::EncryptionContext;
3use r2d2::{Pool, PooledConnection};
4use rand::distr::Alphanumeric;
5use rand::RngExt;
6use redis::{Client, Commands};
7use redis::{IntoConnectionInfo, RedisError};
8use std::fmt::Error;
9use std::io::Error as ioError;
10use std::sync::{Arc, Mutex};
11use std::time::Duration;
12
13pub trait SessionStorage: Send + Sync {
14    fn start_session(&self, username: String) -> Result<String, Error>;
15    fn end_session(&self, username: String, session_id: EncryptionContext) -> Result<(), Error>;
16    fn get_sessions_for_user(&self, username: String) -> Result<Vec<EncryptionContext>, Error>;
17    fn get_all_sessions(&self) -> Result<Vec<EncryptionContext>, Error>;
18    fn session_exists(
19        &self,
20        username: String,
21        session_id: EncryptionContext,
22    ) -> Result<bool, Error>;
23    fn clone_box(&self) -> Box<dyn SessionStorage>;
24}
25
26impl Clone for Box<dyn SessionStorage> {
27    fn clone(&self) -> Self {
28        self.clone_box()
29    }
30}
31
32pub trait ToSessionKey {
33    fn to_key_string(&self) -> Result<String, Error>;
34}
35
36impl ToSessionKey for EncryptionContext {
37    fn to_key_string(&self) -> Result<String, Error> {
38        match self {
39            EncryptionContext::Specific(s) => Ok(s.clone()),
40            EncryptionContext::Global => Err(Error),
41        }
42    }
43}
44
45#[derive(Clone)]
46pub struct RedisOptions {
47    pub max_pool_size: u32,
48    pub min_idle: Option<u32>,
49    pub max_lifetime: Option<Duration>,
50    pub connection_timeout: Option<Duration>,
51}
52
53impl Default for RedisOptions {
54    fn default() -> Self {
55        Self {
56            max_pool_size: 15,
57            min_idle: Some(2),
58            max_lifetime: Some(Duration::from_secs(300)),
59            connection_timeout: Some(Duration::from_secs(60)),
60        }
61    }
62}
63
64#[derive(Clone)]
65pub struct RedisSessionStorage {
66    pool: Pool<Client>,
67    session_expiry: Duration,
68    new_session_length: usize,
69}
70
71impl RedisSessionStorage {
72    pub fn new<T: IntoConnectionInfo>(
73        connection_info: T,
74        session_expiry: Duration,
75        new_session_length: usize,
76        options: RedisOptions,
77    ) -> Result<Self, RedisError> {
78        let client = Client::open(connection_info)?;
79
80        let pool = Pool::builder()
81            .max_size(options.max_pool_size)
82            .min_idle(options.min_idle)
83            .max_lifetime(options.max_lifetime)
84            .idle_timeout(options.connection_timeout)
85            .build(client)
86            .map_err(|e| RedisError::from(ioError::other(e.to_string())))?;
87
88        Ok(Self {
89            pool,
90            session_expiry,
91            new_session_length,
92        })
93    }
94
95    fn get_connection(&self) -> Result<PooledConnection<Client>, Error> {
96        self.pool.get().map_err(|_| Error)
97    }
98}
99
100impl SessionStorage for RedisSessionStorage {
101    fn start_session(&self, username: String) -> Result<String, Error> {
102        // Generate a random string for the session ID
103        let session_postfix: String = rand::rng()
104            .sample_iter(&Alphanumeric)
105            .take(self.new_session_length) // Random string length
106            .map(char::from)
107            .collect();
108
109        let session_time = Utc::now().timestamp();
110
111        let session_id = format!("{}_{}", username, session_postfix);
112        let key = format!("sessions:{}:{}", username, session_id);
113
114        let mut connection = self.get_connection()?;
115
116        let _: () = redis::pipe()
117            .set(&key, session_time)
118            .expire(&key, self.session_expiry.as_secs() as i64) // 1 hour
119            .query(&mut *connection)
120            .map_err(|_| Error)?;
121
122        Ok(session_id)
123    }
124
125    fn end_session(&self, username: String, session_id: EncryptionContext) -> Result<(), Error> {
126        let mut connection = self.get_connection()?;
127
128        let key = format!("sessions:{}:{:?}", username, session_id);
129        let _: () = connection.del(key).expect("Failed to delete session");
130        Ok(())
131    }
132
133    fn get_sessions_for_user(&self, username: String) -> Result<Vec<EncryptionContext>, Error> {
134        let mut connection = self.get_connection()?;
135
136        let key = format!("sessions:{}:*", username);
137        let keys: Vec<String> = connection.keys(key).expect("Failed to get keys");
138        let sessions: Vec<EncryptionContext> = keys
139            .iter()
140            .map(|key| key.split(":").collect::<Vec<&str>>()[2].to_string())
141            .map(|session_id| EncryptionContext::from(&session_id))
142            .collect();
143        Ok(sessions)
144    }
145
146    fn get_all_sessions(&self) -> Result<Vec<EncryptionContext>, Error> {
147        let mut connection = self.get_connection()?;
148
149        let keys: Vec<String> = connection.keys("sessions:*:*").expect("Failed to get keys");
150        let sessions: Vec<EncryptionContext> = keys
151            .iter()
152            .map(|key| key.split(":").collect::<Vec<&str>>()[2].to_string())
153            .map(|session_id| EncryptionContext::from(&session_id))
154            .collect();
155        Ok(sessions)
156    }
157
158    fn session_exists(
159        &self,
160        username: String,
161        session_id: EncryptionContext,
162    ) -> Result<bool, Error> {
163        let mut conn = self.pool.get().map_err(|_| Error)?;
164
165        let session_id_str = session_id.to_key_string()?;
166
167        // Check if the session_id already contains the username prefix
168        let actual_session_id = if session_id_str.starts_with(&format!("{}_", username)) {
169            // If it already has the prefix, use as is
170            session_id
171        } else {
172            // Add the prefix if it doesn't already have it
173            EncryptionContext::Specific(format!("{}_{:?}", username, session_id_str))
174        };
175
176        let actual_session_id = actual_session_id.to_key_string()?;
177        let key = format!("sessions:{}:{}", username, actual_session_id);
178
179        let exists: bool = conn.exists(&key).map_err(|_| Error)?;
180        Ok(exists)
181    }
182
183    fn clone_box(&self) -> Box<dyn SessionStorage> {
184        Box::new((*self).clone())
185    }
186}
187
188#[derive(Clone)]
189pub struct InMemorySessionStorage {
190    sessions: Arc<Mutex<std::collections::HashMap<String, String>>>,
191    session_expiry: Duration,
192    new_session_length: usize,
193}
194
195impl InMemorySessionStorage {
196    pub fn new(session_expiry: Duration, new_session_length: usize) -> Self {
197        Self {
198            sessions: Arc::new(Mutex::new(std::collections::HashMap::new())),
199            session_expiry,
200            new_session_length,
201        }
202    }
203    fn is_session_expired(&self, timestamp_str: &str) -> Result<bool, Error> {
204        let timestamp = timestamp_str.parse::<i64>().map_err(|_| Error)?;
205        let session_time = Utc.timestamp_opt(timestamp, 0).single().ok_or(Error)?;
206
207        let now = Utc::now();
208        let expiry_time = session_time + self.session_expiry;
209
210        Ok(now > expiry_time)
211    }
212
213    fn clean_expired_sessions(&self) -> Result<(), Error> {
214        let mut sessions = self.sessions.lock().map_err(|_| Error)?;
215        let mut expired_keys = Vec::new();
216
217        for (key, time_str) in sessions.iter() {
218            if self.is_session_expired(time_str)? {
219                expired_keys.push(key.clone());
220            }
221        }
222
223        for key in expired_keys {
224            sessions.remove(&key);
225        }
226        Ok(())
227    }
228}
229
230impl SessionStorage for InMemorySessionStorage {
231    fn start_session(&self, username: String) -> Result<String, Error> {
232        let session_postfix: String = rand::rng()
233            .sample_iter(&Alphanumeric)
234            .take(self.new_session_length) // Random string length
235            .map(char::from)
236            .collect();
237
238        let session_id = format!("{}_{}", username, session_postfix);
239
240        let session_time = Utc::now().timestamp();
241        self.sessions
242            .lock()
243            .map_err(|_| Error)?
244            .insert(session_id.clone(), session_time.to_string());
245        Ok(session_id)
246    }
247
248    fn end_session(&self, username: String, session_id: EncryptionContext) -> Result<(), Error> {
249        let session_id = format!("{}_{:?}", username, session_id);
250        let mut sessions = self.sessions.lock().map_err(|_| Error)?;
251        sessions.remove(&session_id);
252        Ok(())
253    }
254
255    fn get_sessions_for_user(&self, username: String) -> Result<Vec<EncryptionContext>, Error> {
256        self.clean_expired_sessions()?;
257
258        let sessions = self.sessions.lock().map_err(|_| Error)?;
259        let sessions: Vec<EncryptionContext> = sessions
260            .iter()
261            .filter(|(session_id, _)| session_id.starts_with(&username))
262            .map(|(session_id, _)| EncryptionContext::from(session_id))
263            .collect();
264        Ok(sessions)
265    }
266
267    fn get_all_sessions(&self) -> Result<Vec<EncryptionContext>, Error> {
268        self.clean_expired_sessions()?;
269
270        let sessions = self.sessions.lock().map_err(|_| Error)?;
271        let sessions: Vec<EncryptionContext> = sessions
272            .keys()
273            .map(|session_id| EncryptionContext::from(session_id))
274            .collect();
275        Ok(sessions)
276    }
277
278    fn session_exists(
279        &self,
280        username: String,
281        session_id: EncryptionContext,
282    ) -> Result<bool, Error> {
283        self.clean_expired_sessions()?;
284
285        let session_id = session_id.to_key_string()?;
286
287        // Check if the session_id already contains the username prefix
288        let key = if session_id.starts_with(&format!("{}_", username)) {
289            session_id
290        } else {
291            // Add the prefix if it doesn't already have it
292            format!("{}_{}", username, session_id)
293        };
294
295        let sessions = self.sessions.lock().map_err(|_| Error)?;
296        Ok(sessions.contains_key(&key))
297    }
298
299    fn clone_box(&self) -> Box<dyn SessionStorage> {
300        Box::new(self.clone())
301    }
302}