Refactor sessions management
This commit is contained in:
		@@ -7,3 +7,9 @@ pub const DEFAULT_ADMIN_PASSWORD: &str = "admin";
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
/// App name
 | 
					/// App name
 | 
				
			||||||
pub const APP_NAME: &str = "Basic OIDC";
 | 
					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;
 | 
				
			||||||
@@ -52,7 +52,7 @@ pub async fn login_route(users: web::Data<Addr<UsersActor>>,
 | 
				
			|||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    // Check if user is already authenticated
 | 
					    // Check if user is already authenticated
 | 
				
			||||||
    if SessionIdentity::is_authenticated(&id) {
 | 
					    if SessionIdentity(&id).is_authenticated() {
 | 
				
			||||||
        return redirect_user("/");
 | 
					        return redirect_user("/");
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -68,7 +68,7 @@ pub async fn login_route(users: web::Data<Addr<UsersActor>>,
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
        match response {
 | 
					        match response {
 | 
				
			||||||
            LoginResult::Success(user) => {
 | 
					            LoginResult::Success(user) => {
 | 
				
			||||||
                id.remember(SessionIdentity::from_user(&user).serialize());
 | 
					                SessionIdentity(&id).set_user(&user);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                return redirect_user("/");
 | 
					                return redirect_user("/");
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,40 +1,71 @@
 | 
				
			|||||||
use std::fmt::Display;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
use actix_identity::Identity;
 | 
					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::data::user::User;
 | 
				
			||||||
 | 
					use crate::utils::time::time;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
pub struct SessionIdentity {
 | 
					#[derive(Debug, Serialize, Deserialize, Eq, PartialEq)]
 | 
				
			||||||
 | 
					enum SessionStatus {
 | 
				
			||||||
 | 
					    SignedIn,
 | 
				
			||||||
 | 
					    NeedNewPassword,
 | 
				
			||||||
 | 
					    NeedMFA,
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#[derive(Debug, Serialize, Deserialize)]
 | 
				
			||||||
 | 
					struct SessionIdentityData {
 | 
				
			||||||
    pub id: String,
 | 
					    pub id: String,
 | 
				
			||||||
    pub is_admin: bool,
 | 
					    pub is_admin: bool,
 | 
				
			||||||
 | 
					    last_access: u64,
 | 
				
			||||||
 | 
					    pub status: SessionStatus,
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
impl SessionIdentity {
 | 
					pub struct SessionIdentity<'a>(pub &'a Identity);
 | 
				
			||||||
    pub fn from_user(user: &User) -> Self {
 | 
					
 | 
				
			||||||
        Self {
 | 
					impl<'a> SessionIdentity<'a> {
 | 
				
			||||||
 | 
					    pub fn set_user(&self, user: &User) {
 | 
				
			||||||
 | 
					        self.set_session_data(&SessionIdentityData {
 | 
				
			||||||
            id: user.uid.clone(),
 | 
					            id: user.uid.clone(),
 | 
				
			||||||
            is_admin: user.admin,
 | 
					            is_admin: user.admin,
 | 
				
			||||||
        }
 | 
					            last_access: time(),
 | 
				
			||||||
 | 
					            status: SessionStatus::SignedIn,
 | 
				
			||||||
 | 
					        });
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    pub fn deserialize<D: Display>(input: D) -> Self {
 | 
					    fn get_session_data(&self) -> Option<SessionIdentityData> {
 | 
				
			||||||
        let input = input.to_string();
 | 
					        let mut res: Option<SessionIdentityData> = self.0.identity()
 | 
				
			||||||
        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()
 | 
					 | 
				
			||||||
            .as_ref()
 | 
					            .as_ref()
 | 
				
			||||||
            .map(Self::deserialize)
 | 
					            .map(String::as_str)
 | 
				
			||||||
            .map(|s| !s.id.is_empty())
 | 
					            .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)
 | 
					            .unwrap_or(false)
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
@@ -1 +1,2 @@
 | 
				
			|||||||
pub mod err;
 | 
					pub mod err;
 | 
				
			||||||
 | 
					pub mod time;
 | 
				
			||||||
							
								
								
									
										6
									
								
								src/utils/time.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										6
									
								
								src/utils/time.rs
									
									
									
									
									
										Normal file
									
								
							@@ -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()
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
		Reference in New Issue
	
	Block a user