better_auth/core/
session.rs1use std::sync::Arc;
2use chrono::{DateTime, Utc, Duration};
3use uuid::Uuid;
4use rand::Rng;
5
6use crate::types::{Session, CreateSession, User};
7use crate::error::{AuthError, AuthResult};
8use crate::adapters::DatabaseAdapter;
9use crate::core::config::AuthConfig;
10
11pub struct SessionManager {
13 config: Arc<AuthConfig>,
14 database: Arc<dyn DatabaseAdapter>,
15}
16
17impl SessionManager {
18 pub fn new(config: Arc<AuthConfig>, database: Arc<dyn DatabaseAdapter>) -> Self {
19 Self { config, database }
20 }
21
22 pub async fn create_session(&self, user: &User, ip_address: Option<String>, user_agent: Option<String>) -> AuthResult<Session> {
24 let token = self.generate_session_token();
25 let expires_at = Utc::now() + self.config.session.expires_in;
26
27 let create_session = CreateSession {
28 user_id: user.id.clone(),
29 expires_at,
30 ip_address,
31 user_agent,
32 impersonated_by: None,
33 active_organization_id: None,
34 };
35
36 let session = self.database.create_session(create_session).await?;
37 Ok(session)
38 }
39
40 pub async fn get_session(&self, token: &str) -> AuthResult<Option<Session>> {
42 let session = self.database.get_session(token).await?;
43
44 if let Some(ref session) = session {
46 if session.expires_at < Utc::now() || !session.active {
47 self.database.delete_session(token).await?;
49 return Ok(None);
50 }
51
52 if self.config.session.update_age {
54 let new_expires_at = Utc::now() + self.config.session.expires_in;
55 let _ = self.database.update_session_expiry(token, new_expires_at).await;
56 }
57 }
58
59 Ok(session)
60 }
61
62 pub async fn delete_session(&self, token: &str) -> AuthResult<()> {
64 self.database.delete_session(token).await?;
65 Ok(())
66 }
67
68 pub async fn delete_user_sessions(&self, user_id: &str) -> AuthResult<()> {
70 self.database.delete_user_sessions(user_id).await?;
71 Ok(())
72 }
73
74 pub async fn list_user_sessions(&self, user_id: &str) -> AuthResult<Vec<Session>> {
76 let sessions = self.database.get_user_sessions(user_id).await?;
77 let now = Utc::now();
78
79 let active_sessions: Vec<Session> = sessions
81 .into_iter()
82 .filter(|session| session.expires_at > now && session.active)
83 .collect();
84
85 Ok(active_sessions)
86 }
87
88 pub async fn revoke_session(&self, token: &str) -> AuthResult<bool> {
90 let session_exists = self.get_session(token).await?.is_some();
92
93 if session_exists {
94 self.delete_session(token).await?;
95 Ok(true)
96 } else {
97 Ok(false)
98 }
99 }
100
101 pub async fn revoke_all_user_sessions(&self, user_id: &str) -> AuthResult<usize> {
103 let sessions = self.list_user_sessions(user_id).await?;
105 let count = sessions.len();
106
107 self.delete_user_sessions(user_id).await?;
108 Ok(count)
109 }
110
111 pub async fn revoke_other_user_sessions(&self, user_id: &str, current_token: &str) -> AuthResult<usize> {
113 let sessions = self.list_user_sessions(user_id).await?;
114 let mut count = 0;
115
116 for session in sessions {
117 if session.token != current_token {
118 self.delete_session(&session.token).await?;
119 count += 1;
120 }
121 }
122
123 Ok(count)
124 }
125
126 pub async fn cleanup_expired_sessions(&self) -> AuthResult<usize> {
128 let count = self.database.delete_expired_sessions().await?;
129 Ok(count)
130 }
131
132 fn generate_session_token(&self) -> String {
134 use rand::RngCore;
135 use base64::Engine;
136 let mut rng = rand::thread_rng();
137 let mut bytes = [0u8; 32];
138 rng.fill_bytes(&mut bytes);
139 format!("session_{}", base64::engine::general_purpose::URL_SAFE_NO_PAD.encode(bytes))
140 }
141
142 pub fn validate_token_format(&self, token: &str) -> bool {
144 token.starts_with("session_") && token.len() > 40
145 }
146}