Skip to main content

tsa_auth_session/
manager.rs

1use 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}