better_auth_core/
session.rs1use 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
9pub 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 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 pub async fn get_session(&self, token: &str) -> AuthResult<Option<Session>> {
44 let session = self.database.get_session(token).await?;
45
46 if let Some(ref session) = session {
48 if session.expires_at < Utc::now() || !session.active {
49 self.database.delete_session(token).await?;
51 return Ok(None);
52 }
53
54 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 pub async fn delete_session(&self, token: &str) -> AuthResult<()> {
69 self.database.delete_session(token).await?;
70 Ok(())
71 }
72
73 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 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 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 pub async fn revoke_session(&self, token: &str) -> AuthResult<bool> {
95 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 pub async fn revoke_all_user_sessions(&self, user_id: &str) -> AuthResult<usize> {
108 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 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 pub async fn cleanup_expired_sessions(&self) -> AuthResult<usize> {
137 let count = self.database.delete_expired_sessions().await?;
138 Ok(count)
139 }
140
141 pub fn validate_token_format(&self, token: &str) -> bool {
143 token.starts_with("session_") && token.len() > 40
144 }
145
146 pub fn extract_session_token(&self, req: &crate::types::AuthRequest) -> Option<String> {
151 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 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}