better_auth_core/
session.rs1use 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
10pub struct SessionManager<DB: DatabaseAdapter> {
12 config: Arc<AuthConfig>,
13 database: Arc<DB>,
14}
15
16impl<DB: DatabaseAdapter> Clone for SessionManager<DB> {
17 fn clone(&self) -> Self {
18 Self {
19 config: self.config.clone(),
20 database: self.database.clone(),
21 }
22 }
23}
24
25impl<DB: DatabaseAdapter> SessionManager<DB> {
26 pub fn new(config: Arc<AuthConfig>, database: Arc<DB>) -> Self {
27 Self { config, database }
28 }
29
30 pub async fn create_session(
32 &self,
33 user: &impl AuthUser,
34 ip_address: Option<String>,
35 user_agent: Option<String>,
36 ) -> AuthResult<DB::Session> {
37 let expires_at = Utc::now() + self.config.session.expires_in;
38
39 let create_session = CreateSession {
40 user_id: user.id().to_string(),
41 expires_at,
42 ip_address,
43 user_agent,
44 impersonated_by: None,
45 active_organization_id: None,
46 };
47
48 let session = self.database.create_session(create_session).await?;
49 Ok(session)
50 }
51
52 pub async fn get_session(&self, token: &str) -> AuthResult<Option<DB::Session>> {
54 let session = self.database.get_session(token).await?;
55
56 if let Some(ref session) = session {
58 let now = Utc::now();
59
60 if session.expires_at() < now || !session.active() {
61 self.database.delete_session(token).await?;
63 return Ok(None);
64 }
65
66 if !self.config.session.disable_session_refresh {
68 let should_refresh = match self.config.session.update_age {
69 Some(age) => {
70 let updated = session.updated_at();
73 Utc::now() - updated >= age
74 }
75 None => true,
77 };
78
79 if should_refresh {
80 let new_expires_at = Utc::now() + self.config.session.expires_in;
81 let _ = self
82 .database
83 .update_session_expiry(token, new_expires_at)
84 .await;
85 }
86 }
87 }
88
89 Ok(session)
90 }
91
92 pub async fn delete_session(&self, token: &str) -> AuthResult<()> {
94 self.database.delete_session(token).await?;
95 Ok(())
96 }
97
98 pub async fn delete_user_sessions(&self, user_id: &str) -> AuthResult<()> {
100 self.database.delete_user_sessions(user_id).await?;
101 Ok(())
102 }
103
104 pub async fn list_user_sessions(&self, user_id: &str) -> AuthResult<Vec<DB::Session>> {
106 let sessions = self.database.get_user_sessions(user_id).await?;
107 let now = Utc::now();
108
109 let active_sessions: Vec<DB::Session> = sessions
111 .into_iter()
112 .filter(|session| session.expires_at() > now && session.active())
113 .collect();
114
115 Ok(active_sessions)
116 }
117
118 pub async fn revoke_session(&self, token: &str) -> AuthResult<bool> {
120 let session_exists = self.get_session(token).await?.is_some();
122
123 if session_exists {
124 self.delete_session(token).await?;
125 Ok(true)
126 } else {
127 Ok(false)
128 }
129 }
130
131 pub async fn revoke_all_user_sessions(&self, user_id: &str) -> AuthResult<usize> {
133 let sessions = self.list_user_sessions(user_id).await?;
135 let count = sessions.len();
136
137 self.delete_user_sessions(user_id).await?;
138 Ok(count)
139 }
140
141 pub async fn revoke_other_user_sessions(
143 &self,
144 user_id: &str,
145 current_token: &str,
146 ) -> AuthResult<usize> {
147 let sessions = self.list_user_sessions(user_id).await?;
148 let mut count = 0;
149
150 for session in sessions {
151 if session.token() != current_token {
152 self.delete_session(session.token()).await?;
153 count += 1;
154 }
155 }
156
157 Ok(count)
158 }
159
160 pub async fn cleanup_expired_sessions(&self) -> AuthResult<usize> {
162 let count = self.database.delete_expired_sessions().await?;
163 Ok(count)
164 }
165
166 pub fn is_session_fresh(&self, session: &impl AuthSession) -> bool {
173 match self.config.session.fresh_age {
174 Some(fresh_age) => session.created_at() + fresh_age > Utc::now(),
175 None => false,
176 }
177 }
178
179 pub fn validate_token_format(&self, token: &str) -> bool {
181 token.starts_with("session_") && token.len() > 40
182 }
183
184 pub fn extract_session_token(&self, req: &crate::types::AuthRequest) -> Option<String> {
189 if let Some(auth_header) = req.headers.get("authorization")
191 && let Some(token) = auth_header.strip_prefix("Bearer ")
192 {
193 return Some(token.to_string());
194 }
195
196 if let Some(cookie_header) = req.headers.get("cookie") {
198 let cookie_name = &self.config.session.cookie_name;
199 for c in cookie::Cookie::split_parse(cookie_header).flatten() {
200 if c.name() == cookie_name && !c.value().is_empty() {
201 return Some(c.value().to_string());
202 }
203 }
204 }
205
206 None
207 }
208}