better_auth/core/
session.rs

1use std::sync::Arc;
2use chrono::{DateTime, Utc, Duration};
3use uuid::Uuid;
4use rand::Rng;
5
6use crate::types::{Session, CreateSession, User};
7use crate::error::{AuthError, AuthResult};
8use crate::adapters::DatabaseAdapter;
9use crate::core::config::AuthConfig;
10
11/// Session manager handles session creation, validation, and cleanup
12pub struct SessionManager {
13    config: Arc<AuthConfig>,
14    database: Arc<dyn DatabaseAdapter>,
15}
16
17impl SessionManager {
18    pub fn new(config: Arc<AuthConfig>, database: Arc<dyn DatabaseAdapter>) -> Self {
19        Self { config, database }
20    }
21    
22    /// Create a new session for a user
23    pub async fn create_session(&self, user: &User, ip_address: Option<String>, user_agent: Option<String>) -> AuthResult<Session> {
24        let token = self.generate_session_token();
25        let expires_at = Utc::now() + self.config.session.expires_in;
26        
27        let create_session = CreateSession {
28            user_id: user.id.clone(),
29            expires_at,
30            ip_address,
31            user_agent,
32            impersonated_by: None,
33            active_organization_id: None,
34        };
35        
36        let session = self.database.create_session(create_session).await?;
37        Ok(session)
38    }
39    
40    /// Get session by token
41    pub async fn get_session(&self, token: &str) -> AuthResult<Option<Session>> {
42        let session = self.database.get_session(token).await?;
43        
44        // Check if session exists and is not expired
45        if let Some(ref session) = session {
46            if session.expires_at < Utc::now() || !session.active {
47                // Session expired or inactive - delete it
48                self.database.delete_session(token).await?;
49                return Ok(None);
50            }
51            
52            // Update session if configured to do so
53            if self.config.session.update_age {
54                let new_expires_at = Utc::now() + self.config.session.expires_in;
55                let _ = self.database.update_session_expiry(token, new_expires_at).await;
56            }
57        }
58        
59        Ok(session)
60    }
61    
62    /// Delete a session
63    pub async fn delete_session(&self, token: &str) -> AuthResult<()> {
64        self.database.delete_session(token).await?;
65        Ok(())
66    }
67    
68    /// Delete all sessions for a user
69    pub async fn delete_user_sessions(&self, user_id: &str) -> AuthResult<()> {
70        self.database.delete_user_sessions(user_id).await?;
71        Ok(())
72    }
73    
74    /// Get all active sessions for a user
75    pub async fn list_user_sessions(&self, user_id: &str) -> AuthResult<Vec<Session>> {
76        let sessions = self.database.get_user_sessions(user_id).await?;
77        let now = Utc::now();
78        
79        // Filter out expired sessions
80        let active_sessions: Vec<Session> = sessions
81            .into_iter()
82            .filter(|session| session.expires_at > now && session.active)
83            .collect();
84            
85        Ok(active_sessions)
86    }
87    
88    /// Revoke a specific session by token
89    pub async fn revoke_session(&self, token: &str) -> AuthResult<bool> {
90        // Check if session exists before trying to delete
91        let session_exists = self.get_session(token).await?.is_some();
92        
93        if session_exists {
94            self.delete_session(token).await?;
95            Ok(true)
96        } else {
97            Ok(false)
98        }
99    }
100    
101    /// Revoke all sessions for a user
102    pub async fn revoke_all_user_sessions(&self, user_id: &str) -> AuthResult<usize> {
103        // Get count of sessions before deletion for return value
104        let sessions = self.list_user_sessions(user_id).await?;
105        let count = sessions.len();
106        
107        self.delete_user_sessions(user_id).await?;
108        Ok(count)
109    }
110    
111    /// Revoke all sessions for a user except the current one
112    pub async fn revoke_other_user_sessions(&self, user_id: &str, current_token: &str) -> AuthResult<usize> {
113        let sessions = self.list_user_sessions(user_id).await?;
114        let mut count = 0;
115        
116        for session in sessions {
117            if session.token != current_token {
118                self.delete_session(&session.token).await?;
119                count += 1;
120            }
121        }
122        
123        Ok(count)
124    }
125    
126    /// Cleanup expired sessions
127    pub async fn cleanup_expired_sessions(&self) -> AuthResult<usize> {
128        let count = self.database.delete_expired_sessions().await?;
129        Ok(count)
130    }
131    
132    /// Generate a secure session token
133    fn generate_session_token(&self) -> String {
134        use rand::RngCore;
135        use base64::Engine;
136        let mut rng = rand::thread_rng();
137        let mut bytes = [0u8; 32];
138        rng.fill_bytes(&mut bytes);
139        format!("session_{}", base64::engine::general_purpose::URL_SAFE_NO_PAD.encode(bytes))
140    }
141    
142    /// Validate session token format
143    pub fn validate_token_format(&self, token: &str) -> bool {
144        token.starts_with("session_") && token.len() > 40
145    }
146}