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 let session_postfix: String = rand::rng()
104 .sample_iter(&Alphanumeric)
105 .take(self.new_session_length) .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) .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 let actual_session_id = if session_id_str.starts_with(&format!("{}_", username)) {
169 session_id
171 } else {
172 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) .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 let key = if session_id.starts_with(&format!("{}_", username)) {
289 session_id
290 } else {
291 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}