use chrono::{TimeZone, Utc};
use libpep::factors::EncryptionContext;
use r2d2::{Pool, PooledConnection};
use rand::distr::Alphanumeric;
use rand::RngExt;
use redis::{Client, Commands};
use redis::{IntoConnectionInfo, RedisError};
use std::fmt::Error;
use std::io::Error as ioError;
use std::sync::{Arc, Mutex};
use std::time::Duration;
pub trait SessionStorage: Send + Sync {
fn start_session(&self, username: String) -> Result<String, Error>;
fn end_session(&self, username: String, session_id: EncryptionContext) -> Result<(), Error>;
fn get_sessions_for_user(&self, username: String) -> Result<Vec<EncryptionContext>, Error>;
fn get_all_sessions(&self) -> Result<Vec<EncryptionContext>, Error>;
fn session_exists(
&self,
username: String,
session_id: EncryptionContext,
) -> Result<bool, Error>;
fn clone_box(&self) -> Box<dyn SessionStorage>;
}
impl Clone for Box<dyn SessionStorage> {
fn clone(&self) -> Self {
self.clone_box()
}
}
pub trait ToSessionKey {
fn to_key_string(&self) -> Result<String, Error>;
}
impl ToSessionKey for EncryptionContext {
fn to_key_string(&self) -> Result<String, Error> {
match self {
EncryptionContext::Specific(s) => Ok(s.clone()),
EncryptionContext::Global => Err(Error),
}
}
}
#[derive(Clone)]
pub struct RedisOptions {
pub max_pool_size: u32,
pub min_idle: Option<u32>,
pub max_lifetime: Option<Duration>,
pub connection_timeout: Option<Duration>,
}
impl Default for RedisOptions {
fn default() -> Self {
Self {
max_pool_size: 15,
min_idle: Some(2),
max_lifetime: Some(Duration::from_secs(300)),
connection_timeout: Some(Duration::from_secs(60)),
}
}
}
#[derive(Clone)]
pub struct RedisSessionStorage {
pool: Pool<Client>,
session_expiry: Duration,
new_session_length: usize,
}
impl RedisSessionStorage {
pub fn new<T: IntoConnectionInfo>(
connection_info: T,
session_expiry: Duration,
new_session_length: usize,
options: RedisOptions,
) -> Result<Self, RedisError> {
let client = Client::open(connection_info)?;
let pool = Pool::builder()
.max_size(options.max_pool_size)
.min_idle(options.min_idle)
.max_lifetime(options.max_lifetime)
.idle_timeout(options.connection_timeout)
.build(client)
.map_err(|e| RedisError::from(ioError::other(e.to_string())))?;
Ok(Self {
pool,
session_expiry,
new_session_length,
})
}
fn get_connection(&self) -> Result<PooledConnection<Client>, Error> {
self.pool.get().map_err(|_| Error)
}
}
impl SessionStorage for RedisSessionStorage {
fn start_session(&self, username: String) -> Result<String, Error> {
let session_postfix: String = rand::rng()
.sample_iter(&Alphanumeric)
.take(self.new_session_length) .map(char::from)
.collect();
let session_time = Utc::now().timestamp();
let session_id = format!("{}_{}", username, session_postfix);
let key = format!("sessions:{}:{}", username, session_id);
let mut connection = self.get_connection()?;
let _: () = redis::pipe()
.set(&key, session_time)
.expire(&key, self.session_expiry.as_secs() as i64) .query(&mut *connection)
.map_err(|_| Error)?;
Ok(session_id)
}
fn end_session(&self, username: String, session_id: EncryptionContext) -> Result<(), Error> {
let mut connection = self.get_connection()?;
let key = format!("sessions:{}:{:?}", username, session_id);
let _: () = connection.del(key).expect("Failed to delete session");
Ok(())
}
fn get_sessions_for_user(&self, username: String) -> Result<Vec<EncryptionContext>, Error> {
let mut connection = self.get_connection()?;
let key = format!("sessions:{}:*", username);
let keys: Vec<String> = connection.keys(key).expect("Failed to get keys");
let sessions: Vec<EncryptionContext> = keys
.iter()
.map(|key| key.split(":").collect::<Vec<&str>>()[2].to_string())
.map(|session_id| EncryptionContext::from(&session_id))
.collect();
Ok(sessions)
}
fn get_all_sessions(&self) -> Result<Vec<EncryptionContext>, Error> {
let mut connection = self.get_connection()?;
let keys: Vec<String> = connection.keys("sessions:*:*").expect("Failed to get keys");
let sessions: Vec<EncryptionContext> = keys
.iter()
.map(|key| key.split(":").collect::<Vec<&str>>()[2].to_string())
.map(|session_id| EncryptionContext::from(&session_id))
.collect();
Ok(sessions)
}
fn session_exists(
&self,
username: String,
session_id: EncryptionContext,
) -> Result<bool, Error> {
let mut conn = self.pool.get().map_err(|_| Error)?;
let session_id_str = session_id.to_key_string()?;
let actual_session_id = if session_id_str.starts_with(&format!("{}_", username)) {
session_id
} else {
EncryptionContext::Specific(format!("{}_{:?}", username, session_id_str))
};
let actual_session_id = actual_session_id.to_key_string()?;
let key = format!("sessions:{}:{}", username, actual_session_id);
let exists: bool = conn.exists(&key).map_err(|_| Error)?;
Ok(exists)
}
fn clone_box(&self) -> Box<dyn SessionStorage> {
Box::new((*self).clone())
}
}
#[derive(Clone)]
pub struct InMemorySessionStorage {
sessions: Arc<Mutex<std::collections::HashMap<String, String>>>,
session_expiry: Duration,
new_session_length: usize,
}
impl InMemorySessionStorage {
pub fn new(session_expiry: Duration, new_session_length: usize) -> Self {
Self {
sessions: Arc::new(Mutex::new(std::collections::HashMap::new())),
session_expiry,
new_session_length,
}
}
fn is_session_expired(&self, timestamp_str: &str) -> Result<bool, Error> {
let timestamp = timestamp_str.parse::<i64>().map_err(|_| Error)?;
let session_time = Utc.timestamp_opt(timestamp, 0).single().ok_or(Error)?;
let now = Utc::now();
let expiry_time = session_time + self.session_expiry;
Ok(now > expiry_time)
}
fn clean_expired_sessions(&self) -> Result<(), Error> {
let mut sessions = self.sessions.lock().map_err(|_| Error)?;
let mut expired_keys = Vec::new();
for (key, time_str) in sessions.iter() {
if self.is_session_expired(time_str)? {
expired_keys.push(key.clone());
}
}
for key in expired_keys {
sessions.remove(&key);
}
Ok(())
}
}
impl SessionStorage for InMemorySessionStorage {
fn start_session(&self, username: String) -> Result<String, Error> {
let session_postfix: String = rand::rng()
.sample_iter(&Alphanumeric)
.take(self.new_session_length) .map(char::from)
.collect();
let session_id = format!("{}_{}", username, session_postfix);
let session_time = Utc::now().timestamp();
self.sessions
.lock()
.map_err(|_| Error)?
.insert(session_id.clone(), session_time.to_string());
Ok(session_id)
}
fn end_session(&self, username: String, session_id: EncryptionContext) -> Result<(), Error> {
let session_id = format!("{}_{:?}", username, session_id);
let mut sessions = self.sessions.lock().map_err(|_| Error)?;
sessions.remove(&session_id);
Ok(())
}
fn get_sessions_for_user(&self, username: String) -> Result<Vec<EncryptionContext>, Error> {
self.clean_expired_sessions()?;
let sessions = self.sessions.lock().map_err(|_| Error)?;
let sessions: Vec<EncryptionContext> = sessions
.iter()
.filter(|(session_id, _)| session_id.starts_with(&username))
.map(|(session_id, _)| EncryptionContext::from(session_id))
.collect();
Ok(sessions)
}
fn get_all_sessions(&self) -> Result<Vec<EncryptionContext>, Error> {
self.clean_expired_sessions()?;
let sessions = self.sessions.lock().map_err(|_| Error)?;
let sessions: Vec<EncryptionContext> = sessions
.keys()
.map(|session_id| EncryptionContext::from(session_id))
.collect();
Ok(sessions)
}
fn session_exists(
&self,
username: String,
session_id: EncryptionContext,
) -> Result<bool, Error> {
self.clean_expired_sessions()?;
let session_id = session_id.to_key_string()?;
let key = if session_id.starts_with(&format!("{}_", username)) {
session_id
} else {
format!("{}_{}", username, session_id)
};
let sessions = self.sessions.lock().map_err(|_| Error)?;
Ok(sessions.contains_key(&key))
}
fn clone_box(&self) -> Box<dyn SessionStorage> {
Box::new(self.clone())
}
}