use crate::SessionConfig; use chrono::{DateTime, Duration, Utc}; use serde::{Deserialize, Serialize}; use std::{collections::HashMap, fmt::Debug}; /// The Store and Configured Data for a Session. /// /// # Examples /// ```rust ignore /// use session::{SessionConfig, SessionData}; /// use uuid::Uuid; /// /// let config = SessionConfig::default(); /// let token = Uuid::new_v4(); /// let session_data = SessionData::new(token.to_string(), true, &config); /// ``` /// #[derive(Debug, Serialize, Deserialize, Clone)] pub struct SessionData { #[serde(skip)] pub(crate) id: String, pub(crate) data: HashMap, #[serde(default = "serde_expires_default")] pub(crate) expires: DateTime, #[serde(default = "serde_autoremove_default")] pub(crate) autoremove: DateTime, #[serde(skip)] pub(crate) destroy: bool, #[serde(skip)] pub(crate) renew: bool, pub(crate) longterm: bool, #[serde(skip)] pub(crate) store: bool, #[serde(skip)] pub(crate) update: bool, #[serde(skip)] pub(crate) requests: usize, #[serde(skip)] pub(crate) last_db_update: DateTime, } /// Fallback for sessions persisted before `expires` was serialized (or written /// by an older release): treat them as still valid for the configured lifespan /// instead of instantly expiring, which would wipe the stored session data. fn serde_expires_default() -> DateTime { Utc::now() + Duration::days(7) } /// Same reasoning as [`serde_expires_default`]; an unloaded session should not /// be treated as immediately eligible for memory eviction. fn serde_autoremove_default() -> DateTime { Utc::now() + Duration::minutes(60) } impl SessionData { /// Constructs a new SessionData. /// /// # Examples /// ```rust ignore /// use session::{SessionConfig, SessionData}; /// use uuid::Uuid; /// /// let config = SessionConfig::default(); /// let token = Uuid::new_v4(); /// let session_data = SessionData::new(token.to_string(), true, &config); /// ``` /// #[inline] pub(crate) fn new(id: String, storable: bool, config: &SessionConfig) -> Self { Self { id, data: HashMap::new(), expires: Utc::now() + config.lifespan, destroy: false, renew: false, autoremove: Utc::now() + config.memory.memory_lifespan, longterm: false, store: storable, update: true, requests: 1, last_db_update: Utc::now(), } } /// Determines whether the session has expired. /// /// # Examples /// ```rust ignore /// use session::{SessionConfig, SessionData}; /// use uuid::Uuid; /// /// let config = SessionConfig::default(); /// let token = Uuid::new_v4(); /// let session_data = SessionData::new(token.to_string(), true, &config); /// let expired = session_data.expired(); /// ``` /// #[inline] pub(crate) fn expired(&self) -> bool { self.expires < Utc::now() } /// Validates and checks if the Session is to be destroyed. /// If so the Sessions Data is Cleared. /// autoremove is then updated for the session regardless. /// /// # Examples /// ```rust ignore /// use session::{SessionConfig, SessionData}; /// use uuid::Uuid; /// /// let config = SessionConfig::default(); /// let token = Uuid::new_v4(); /// let mut session_data = SessionData::new(token.to_string(), true, &config); /// let expired = session_data.service_clear(Duration::days(5)); /// ``` /// #[inline] pub(crate) fn service_clear(&mut self, memory_lifespan: Duration, clear_check: bool) { if clear_check && self.autoremove < Utc::now() { self.update = true; if self.expired() { self.data.clear(); } } self.autoremove = Utc::now() + memory_lifespan; } /// Set session flags to renew/regenerate the ID. /// This deletes data from the database keyed with the old ID. /// This helps to enhance security when logging into secure /// areas on a website. The current session's data will be /// stored with the new ID. /// /// # Examples /// ```rust ignore /// session.renew(); /// ``` /// #[inline] pub fn renew(&mut self) { self.renew = true; self.update = true; } /// Sets the Session to force update the database. /// This will increase the Timer on the sessions store /// making the session live longer in the persistent database. /// /// # Examples /// ```rust ignore /// session.update(); /// ``` /// #[inline] pub fn update(&mut self) { self.update = true; } /// Sets the Current Session to be Destroyed. /// This will Deleted the Session and Cookies upon Response Phase. /// /// # Examples /// ```rust ignore /// session.destroy(); /// ``` /// #[inline] pub fn destroy(&mut self) { self.destroy = true; } /// Sets the Current Session to a long term expiration. Useful for Remember Me setups. /// This will also update the database on Response Phase. /// /// # Examples /// ```rust ignore /// session.set_longterm(true); /// ``` /// #[inline] pub fn set_longterm(&mut self, longterm: bool) { self.longterm = longterm; self.update = true; } /// Sets the Current Session to be storable. /// This will also update the database on Response Phase. /// /// This is only used when `SessionMode` is Manual or Storable. /// This will allow the Session to be stored if true. /// This will delete and not allow a session to be stored if false. /// /// # Examples /// ```rust ignore /// session.set_store(true); /// ``` /// #[inline] pub fn set_store(&mut self, can_store: bool) { self.store = can_store; self.update = true; } /// Gets data from the Session's HashMap /// /// Provides an Option that returns the requested data from the Sessions store. /// Returns None if Key does not exist or if serde_json failed to deserialize. /// /// # Examples /// ```rust ignore /// let id = session.get("user-id").unwrap_or(0); /// ``` /// ///Used to get data stored within SessionDatas hashmap from a key value. /// #[inline] pub fn get(&self, key: &str) -> Option { let string = self.data.get(key)?; serde_json::from_str(string).ok() } /// Removes a Key from the Current Session's HashMap returning it. /// This will also update the database on Response Phase. /// /// Provides an Option that returns the requested data from the Sessions store. /// Returns None if Key does not exist or if serde_json failed to deserialize. /// /// # Examples /// ```rust ignore /// let id = session.get_remove("user-id").unwrap_or(0); /// ``` /// /// Used to get data stored within SessionDatas hashmap from a key value. /// #[inline] pub fn get_remove(&mut self, key: &str) -> Option { let string = self.data.remove(key)?; self.update = true; serde_json::from_str(&string).ok() } /// Sets data to the Current Session's HashMap. /// This will also update the database on Response Phase. /// /// # Examples /// ```rust ignore /// session.set("user-id", 1); /// ``` /// #[inline] pub fn set(&mut self, key: &str, value: impl Serialize) { let value = serde_json::to_string(&value).unwrap_or_else(|_| "".to_string()); let _ = self.data.insert(key.to_string(), value); self.update = true; } /// Removes a Key from the Current Session's HashMap. /// Does not process the String into a Type, Just removes it. /// This will also update the database on Response Phase. /// /// # Examples /// ```rust ignore /// let _ = session.remove("user-id"); /// ``` /// #[inline] pub fn remove(&mut self, key: &str) { let _ = self.data.remove(key); self.update = true; } /// Clears all data from the Current Session's HashMap. /// This will also update the database on Response Phase. /// /// # Examples /// ```rust ignore /// session.clear(); /// ``` /// #[inline] pub fn clear(&mut self) { self.data.clear(); self.update = true; } /// Removes a Request from the request counter /// used to determine if parallel requests exist. /// prevents data deletion until requests == 0. /// /// # Examples /// ```rust ignore /// session.remove_request(); /// ``` /// #[inline] pub(crate) fn remove_request(&mut self) { self.requests = self.requests.saturating_sub(1); } /// Removes a Request from the request counter /// used to determine if parallel requests exist. /// prevents data deletion until requests == 0. /// /// # Examples /// ```rust ignore /// session.set_request(); /// ``` /// #[inline] pub(crate) fn set_request(&mut self) { self.requests = self.requests.saturating_add(1); } /// checks if a session has a request still. /// /// # Examples /// ```rust ignore /// session.is_parallel(); /// ``` /// #[inline] pub(crate) fn is_parallel(&self) -> bool { self.requests >= 1 } } /// Internal Timers /// /// used to keep track of the last ran expiration check for both database and memory session data. /// #[derive(Debug)] pub(crate) struct SessionTimers { pub(crate) last_expiry_sweep: DateTime, pub(crate) last_database_expiry_sweep: DateTime, }