tsa_auth_session/
manager.rs1use chrono::{Duration, Utc};
2use std::sync::Arc;
3use tsa_auth_core::{Result, Session, SessionRepository, TsaError};
4use tsa_auth_token::OpaqueToken;
5use uuid::Uuid;
6
7#[derive(Debug, Clone)]
8pub struct SessionConfig {
9 pub session_expiry: Duration,
10 pub token_length: usize,
11}
12
13impl Default for SessionConfig {
14 fn default() -> Self {
15 Self {
16 session_expiry: Duration::days(30),
17 token_length: 32,
18 }
19 }
20}
21
22pub struct SessionManager<R: SessionRepository> {
23 repository: Arc<R>,
24 config: SessionConfig,
25}
26
27impl<R: SessionRepository> SessionManager<R> {
28 pub fn new(repository: Arc<R>, config: SessionConfig) -> Self {
29 Self { repository, config }
30 }
31
32 pub async fn create_session(
33 &self,
34 user_id: Uuid,
35 ip_address: Option<String>,
36 user_agent: Option<String>,
37 ) -> Result<(Session, String)> {
38 let (token, token_hash) = OpaqueToken::generate_with_hash(self.config.token_length)?;
39
40 let now = Utc::now();
41 let session = Session {
42 id: Uuid::new_v4(),
43 user_id,
44 token_hash,
45 expires_at: now + self.config.session_expiry,
46 created_at: now,
47 ip_address,
48 user_agent,
49 };
50
51 let created = self.repository.create(&session).await?;
52 Ok((created, token))
53 }
54
55 pub async fn validate_session(&self, token: &str) -> Result<Session> {
56 let token_hash = OpaqueToken::hash(token);
57 let session = self
58 .repository
59 .find_by_token_hash(&token_hash)
60 .await?
61 .ok_or(TsaError::SessionNotFound)?;
62
63 if session.expires_at < Utc::now() {
64 self.repository.delete(session.id).await?;
65 return Err(TsaError::SessionExpired);
66 }
67
68 Ok(session)
69 }
70
71 pub async fn invalidate_session(&self, session_id: Uuid) -> Result<()> {
72 self.repository.delete(session_id).await
73 }
74
75 pub async fn invalidate_all_sessions(&self, user_id: Uuid) -> Result<()> {
76 self.repository.delete_by_user_id(user_id).await
77 }
78
79 pub async fn get_user_sessions(&self, user_id: Uuid) -> Result<Vec<Session>> {
80 self.repository.find_by_user_id(user_id).await
81 }
82
83 pub async fn cleanup_expired(&self) -> Result<u64> {
84 self.repository.delete_expired().await
85 }
86}
87
88#[cfg(test)]
89mod tests {
90 use super::*;
91
92 #[test]
93 fn test_session_config_defaults() {
94 let config = SessionConfig::default();
95 assert_eq!(config.session_expiry, Duration::days(30));
96 assert_eq!(config.token_length, 32);
97 }
98
99 #[test]
100 fn test_session_config_custom() {
101 let config = SessionConfig {
102 session_expiry: Duration::hours(1),
103 token_length: 64,
104 };
105 assert_eq!(config.session_expiry, Duration::hours(1));
106 assert_eq!(config.token_length, 64);
107 }
108}