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> SessionManager<DB> {
17 pub fn new(config: Arc<AuthConfig>, database: Arc<DB>) -> Self {
18 Self { config, database }
19 }
20
21 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 pub async fn get_session(&self, token: &str) -> AuthResult<Option<DB::Session>> {
45 let session = self.database.get_session(token).await?;
46
47 if let Some(ref session) = session {
49 let now = Utc::now();
50
51 if session.expires_at() < now || !session.active() {
52 self.database.delete_session(token).await?;
54 return Ok(None);
55 }
56
57 if !self.config.session.disable_session_refresh {
59 let should_refresh = match self.config.session.update_age {
60 Some(age) => {
61 let updated = session.updated_at();
64 Utc::now() - updated >= age
65 }
66 None => true,
68 };
69
70 if should_refresh {
71 let new_expires_at = Utc::now() + self.config.session.expires_in;
72 let _ = self
73 .database
74 .update_session_expiry(token, new_expires_at)
75 .await;
76 }
77 }
78 }
79
80 Ok(session)
81 }
82
83 pub async fn delete_session(&self, token: &str) -> AuthResult<()> {
85 self.database.delete_session(token).await?;
86 Ok(())
87 }
88
89 pub async fn delete_user_sessions(&self, user_id: &str) -> AuthResult<()> {
91 self.database.delete_user_sessions(user_id).await?;
92 Ok(())
93 }
94
95 pub async fn list_user_sessions(&self, user_id: &str) -> AuthResult<Vec<DB::Session>> {
97 let sessions = self.database.get_user_sessions(user_id).await?;
98 let now = Utc::now();
99
100 let active_sessions: Vec<DB::Session> = sessions
102 .into_iter()
103 .filter(|session| session.expires_at() > now && session.active())
104 .collect();
105
106 Ok(active_sessions)
107 }
108
109 pub async fn revoke_session(&self, token: &str) -> AuthResult<bool> {
111 let session_exists = self.get_session(token).await?.is_some();
113
114 if session_exists {
115 self.delete_session(token).await?;
116 Ok(true)
117 } else {
118 Ok(false)
119 }
120 }
121
122 pub async fn revoke_all_user_sessions(&self, user_id: &str) -> AuthResult<usize> {
124 let sessions = self.list_user_sessions(user_id).await?;
126 let count = sessions.len();
127
128 self.delete_user_sessions(user_id).await?;
129 Ok(count)
130 }
131
132 pub async fn revoke_other_user_sessions(
134 &self,
135 user_id: &str,
136 current_token: &str,
137 ) -> AuthResult<usize> {
138 let sessions = self.list_user_sessions(user_id).await?;
139 let mut count = 0;
140
141 for session in sessions {
142 if session.token() != current_token {
143 self.delete_session(session.token()).await?;
144 count += 1;
145 }
146 }
147
148 Ok(count)
149 }
150
151 pub async fn cleanup_expired_sessions(&self) -> AuthResult<usize> {
153 let count = self.database.delete_expired_sessions().await?;
154 Ok(count)
155 }
156
157 pub fn is_session_fresh(&self, session: &impl AuthSession) -> bool {
164 match self.config.session.fresh_age {
165 Some(fresh_age) => session.created_at() + fresh_age > Utc::now(),
166 None => false,
167 }
168 }
169
170 pub fn validate_token_format(&self, token: &str) -> bool {
172 token.starts_with("session_") && token.len() > 40
173 }
174
175 pub fn extract_session_token(&self, req: &crate::types::AuthRequest) -> Option<String> {
180 if let Some(auth_header) = req.headers.get("authorization")
182 && let Some(token) = auth_header.strip_prefix("Bearer ")
183 {
184 return Some(token.to_string());
185 }
186
187 if let Some(cookie_header) = req.headers.get("cookie") {
189 let cookie_name = &self.config.session.cookie_name;
190 for c in cookie::Cookie::split_parse(cookie_header).flatten() {
191 if c.name() == cookie_name && !c.value().is_empty() {
192 return Some(c.value().to_string());
193 }
194 }
195 }
196
197 None
198 }
199}