Skip to main content

better_auth_core/
session.rs

1use chrono::Utc;
2use std::sync::Arc;
3
4use crate::adapters::DatabaseAdapter;
5use crate::config::AuthConfig;
6use crate::error::AuthResult;
7use crate::types::{CreateSession, Session, User};
8
9/// Session manager handles session creation, validation, and cleanup
10pub struct SessionManager {
11    config: Arc<AuthConfig>,
12    database: Arc<dyn DatabaseAdapter>,
13}
14
15impl SessionManager {
16    pub fn new(config: Arc<AuthConfig>, database: Arc<dyn DatabaseAdapter>) -> Self {
17        Self { config, database }
18    }
19
20    /// Create a new session for a user
21    pub async fn create_session(
22        &self,
23        user: &User,
24        ip_address: Option<String>,
25        user_agent: Option<String>,
26    ) -> AuthResult<Session> {
27        let expires_at = Utc::now() + self.config.session.expires_in;
28
29        let create_session = CreateSession {
30            user_id: user.id.clone(),
31            expires_at,
32            ip_address,
33            user_agent,
34            impersonated_by: None,
35            active_organization_id: None,
36        };
37
38        let session = self.database.create_session(create_session).await?;
39        Ok(session)
40    }
41
42    /// Get session by token
43    pub async fn get_session(&self, token: &str) -> AuthResult<Option<Session>> {
44        let session = self.database.get_session(token).await?;
45
46        // Check if session exists and is not expired
47        if let Some(ref session) = session {
48            if session.expires_at < Utc::now() || !session.active {
49                // Session expired or inactive - delete it
50                self.database.delete_session(token).await?;
51                return Ok(None);
52            }
53
54            // Update session if configured to do so
55            if self.config.session.update_age {
56                let new_expires_at = Utc::now() + self.config.session.expires_in;
57                let _ = self
58                    .database
59                    .update_session_expiry(token, new_expires_at)
60                    .await;
61            }
62        }
63
64        Ok(session)
65    }
66
67    /// Delete a session
68    pub async fn delete_session(&self, token: &str) -> AuthResult<()> {
69        self.database.delete_session(token).await?;
70        Ok(())
71    }
72
73    /// Delete all sessions for a user
74    pub async fn delete_user_sessions(&self, user_id: &str) -> AuthResult<()> {
75        self.database.delete_user_sessions(user_id).await?;
76        Ok(())
77    }
78
79    /// Get all active sessions for a user
80    pub async fn list_user_sessions(&self, user_id: &str) -> AuthResult<Vec<Session>> {
81        let sessions = self.database.get_user_sessions(user_id).await?;
82        let now = Utc::now();
83
84        // Filter out expired sessions
85        let active_sessions: Vec<Session> = sessions
86            .into_iter()
87            .filter(|session| session.expires_at > now && session.active)
88            .collect();
89
90        Ok(active_sessions)
91    }
92
93    /// Revoke a specific session by token
94    pub async fn revoke_session(&self, token: &str) -> AuthResult<bool> {
95        // Check if session exists before trying to delete
96        let session_exists = self.get_session(token).await?.is_some();
97
98        if session_exists {
99            self.delete_session(token).await?;
100            Ok(true)
101        } else {
102            Ok(false)
103        }
104    }
105
106    /// Revoke all sessions for a user
107    pub async fn revoke_all_user_sessions(&self, user_id: &str) -> AuthResult<usize> {
108        // Get count of sessions before deletion for return value
109        let sessions = self.list_user_sessions(user_id).await?;
110        let count = sessions.len();
111
112        self.delete_user_sessions(user_id).await?;
113        Ok(count)
114    }
115
116    /// Revoke all sessions for a user except the current one
117    pub async fn revoke_other_user_sessions(
118        &self,
119        user_id: &str,
120        current_token: &str,
121    ) -> AuthResult<usize> {
122        let sessions = self.list_user_sessions(user_id).await?;
123        let mut count = 0;
124
125        for session in sessions {
126            if session.token != current_token {
127                self.delete_session(&session.token).await?;
128                count += 1;
129            }
130        }
131
132        Ok(count)
133    }
134
135    /// Cleanup expired sessions
136    pub async fn cleanup_expired_sessions(&self) -> AuthResult<usize> {
137        let count = self.database.delete_expired_sessions().await?;
138        Ok(count)
139    }
140
141    /// Validate session token format
142    pub fn validate_token_format(&self, token: &str) -> bool {
143        token.starts_with("session_") && token.len() > 40
144    }
145
146    /// Extract session token from a request.
147    ///
148    /// Tries Bearer token from Authorization header first, then falls back
149    /// to parsing the configured cookie from the Cookie header.
150    pub fn extract_session_token(&self, req: &crate::types::AuthRequest) -> Option<String> {
151        // Try Bearer token first
152        if let Some(auth_header) = req.headers.get("authorization")
153            && let Some(token) = auth_header.strip_prefix("Bearer ")
154        {
155            return Some(token.to_string());
156        }
157
158        // Fall back to cookie
159        if let Some(cookie_header) = req.headers.get("cookie") {
160            let cookie_name = &self.config.session.cookie_name;
161            for part in cookie_header.split(';') {
162                let part = part.trim();
163                if let Some(value) = part.strip_prefix(&format!("{}=", cookie_name))
164                    && !value.is_empty()
165                {
166                    return Some(value.to_string());
167                }
168            }
169        }
170
171        None
172    }
173}