From 41ee80a077851fc884d72a5f9ab48c0dc055521d Mon Sep 17 00:00:00 2001 From: Pierre Hubert Date: Fri, 1 Apr 2022 22:51:33 +0200 Subject: [PATCH] Refactor sessions management --- src/constants.rs | 8 ++- src/controllers/login_controller.rs | 4 +- src/data/session_identity.rs | 83 ++++++++++++++++++++--------- src/utils/mod.rs | 3 +- src/utils/time.rs | 6 +++ 5 files changed, 74 insertions(+), 30 deletions(-) create mode 100644 src/utils/time.rs diff --git a/src/constants.rs b/src/constants.rs index d28a2e5..0861640 100644 --- a/src/constants.rs +++ b/src/constants.rs @@ -6,4 +6,10 @@ pub const DEFAULT_ADMIN_USERNAME: &str = "admin"; pub const DEFAULT_ADMIN_PASSWORD: &str = "admin"; /// App name -pub const APP_NAME: &str = "Basic OIDC"; \ No newline at end of file +pub const APP_NAME: &str = "Basic OIDC"; + +/// Maximum session duration after inactivity, in seconds +pub const MAX_SESSION_DURATION: u64 = 60 * 30; + +/// Minimum interval between each last activity record in session +pub const MIN_ACTIVITY_RECORD_TIME: u64 = 10; \ No newline at end of file diff --git a/src/controllers/login_controller.rs b/src/controllers/login_controller.rs index 10bb078..fda0ba8 100644 --- a/src/controllers/login_controller.rs +++ b/src/controllers/login_controller.rs @@ -52,7 +52,7 @@ pub async fn login_route(users: web::Data>, } // Check if user is already authenticated - if SessionIdentity::is_authenticated(&id) { + if SessionIdentity(&id).is_authenticated() { return redirect_user("/"); } @@ -68,7 +68,7 @@ pub async fn login_route(users: web::Data>, match response { LoginResult::Success(user) => { - id.remember(SessionIdentity::from_user(&user).serialize()); + SessionIdentity(&id).set_user(&user); return redirect_user("/"); } diff --git a/src/data/session_identity.rs b/src/data/session_identity.rs index 0cf6165..031c2bf 100644 --- a/src/data/session_identity.rs +++ b/src/data/session_identity.rs @@ -1,40 +1,71 @@ -use std::fmt::Display; - use actix_identity::Identity; +use serde::{Deserialize, Serialize}; +use crate::constants::{MAX_SESSION_DURATION, MIN_ACTIVITY_RECORD_TIME}; use crate::data::user::User; +use crate::utils::time::time; -pub struct SessionIdentity { - pub id: String, - pub is_admin: bool, +#[derive(Debug, Serialize, Deserialize, Eq, PartialEq)] +enum SessionStatus { + SignedIn, + NeedNewPassword, + NeedMFA, } -impl SessionIdentity { - pub fn from_user(user: &User) -> Self { - Self { +#[derive(Debug, Serialize, Deserialize)] +struct SessionIdentityData { + pub id: String, + pub is_admin: bool, + last_access: u64, + pub status: SessionStatus, +} + +pub struct SessionIdentity<'a>(pub &'a Identity); + +impl<'a> SessionIdentity<'a> { + pub fn set_user(&self, user: &User) { + self.set_session_data(&SessionIdentityData { id: user.uid.clone(), is_admin: user.admin, - } + last_access: time(), + status: SessionStatus::SignedIn, + }); } - pub fn deserialize(input: D) -> Self { - let input = input.to_string(); - let mut iter = input.split('-'); - Self { - id: iter.next().unwrap_or_default().to_string(), - is_admin: iter.next().unwrap_or_default() == "true", - } - } - - pub fn serialize(&self) -> String { - format!("{}-{}", self.id, self.is_admin) - } - - pub fn is_authenticated(i: &Identity) -> bool { - i.identity() + fn get_session_data(&self) -> Option { + let mut res: Option = self.0.identity() .as_ref() - .map(Self::deserialize) - .map(|s| !s.id.is_empty()) + .map(String::as_str) + .map(serde_json::from_str) + .map(|f| f.expect("Failed to deserialize session data!")); + + if let Some(session) = res.as_mut() { + if session.last_access + MAX_SESSION_DURATION < time() { + log::info!("Session is expired for {}", session.id); + self.0.forget(); + return None; + } + + if session.last_access + MIN_ACTIVITY_RECORD_TIME < time() { + log::debug!("Refresh last access for session"); + session.last_access = time(); + self.set_session_data(session); + } + } + + res + } + + fn set_session_data(&self, data: &SessionIdentityData) { + let s = serde_json::to_string(data) + .expect("Failed to serialize session data!"); + + self.0.remember(s); + } + + pub fn is_authenticated(&self) -> bool { + self.get_session_data() + .map(|s| s.status == SessionStatus::SignedIn) .unwrap_or(false) } } \ No newline at end of file diff --git a/src/utils/mod.rs b/src/utils/mod.rs index e4429f4..ab95e2b 100644 --- a/src/utils/mod.rs +++ b/src/utils/mod.rs @@ -1 +1,2 @@ -pub mod err; \ No newline at end of file +pub mod err; +pub mod time; \ No newline at end of file diff --git a/src/utils/time.rs b/src/utils/time.rs new file mode 100644 index 0000000..37badb5 --- /dev/null +++ b/src/utils/time.rs @@ -0,0 +1,6 @@ +use std::time::{SystemTime, UNIX_EPOCH}; + +/// Get the current time since epoch +pub fn time() -> u64 { + SystemTime::now().duration_since(UNIX_EPOCH).unwrap().as_secs() +} \ No newline at end of file