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