lmrc_http_common/auth/
session.rs

1//! Session management types and utilities
2
3use chrono::{DateTime, Utc};
4use serde::{Deserialize, Serialize};
5use uuid::Uuid;
6
7/// Session information
8#[derive(Debug, Clone, Serialize, Deserialize)]
9pub struct SessionInfo {
10    /// Session token/ID
11    pub token: String,
12    /// User ID associated with this session
13    pub user_id: String,
14    /// User email
15    pub email: String,
16    /// Session creation time
17    pub created_at: DateTime<Utc>,
18    /// Session expiration time
19    pub expires_at: DateTime<Utc>,
20    /// Optional user metadata
21    #[serde(skip_serializing_if = "Option::is_none")]
22    pub metadata: Option<serde_json::Value>,
23}
24
25impl SessionInfo {
26    /// Create a new session
27    pub fn new(user_id: impl Into<String>, email: impl Into<String>, expires_at: DateTime<Utc>) -> Self {
28        Self {
29            token: Uuid::new_v4().to_string(),
30            user_id: user_id.into(),
31            email: email.into(),
32            created_at: Utc::now(),
33            expires_at,
34            metadata: None,
35        }
36    }
37
38    /// Create with specific token
39    pub fn with_token(
40        token: impl Into<String>,
41        user_id: impl Into<String>,
42        email: impl Into<String>,
43        expires_at: DateTime<Utc>,
44    ) -> Self {
45        Self {
46            token: token.into(),
47            user_id: user_id.into(),
48            email: email.into(),
49            created_at: Utc::now(),
50            expires_at,
51            metadata: None,
52        }
53    }
54
55    /// Add metadata to session
56    pub fn with_metadata(mut self, metadata: serde_json::Value) -> Self {
57        self.metadata = Some(metadata);
58        self
59    }
60
61    /// Check if session is expired
62    pub fn is_expired(&self) -> bool {
63        Utc::now() > self.expires_at
64    }
65
66    /// Get remaining session time in seconds
67    pub fn remaining_seconds(&self) -> i64 {
68        (self.expires_at - Utc::now()).num_seconds().max(0)
69    }
70}
71
72/// Trait for session storage backends
73#[async_trait::async_trait]
74pub trait SessionStore: Send + Sync {
75    /// Store a new session
76    async fn create(&self, session: &SessionInfo) -> Result<(), SessionStoreError>;
77
78    /// Get a session by token
79    async fn get(&self, token: &str) -> Result<Option<SessionInfo>, SessionStoreError>;
80
81    /// Update an existing session
82    async fn update(&self, session: &SessionInfo) -> Result<(), SessionStoreError>;
83
84    /// Delete a session by token
85    async fn delete(&self, token: &str) -> Result<(), SessionStoreError>;
86
87    /// Delete all sessions for a user
88    async fn delete_user_sessions(&self, user_id: &str) -> Result<(), SessionStoreError>;
89
90    /// Clean up expired sessions
91    async fn cleanup_expired(&self) -> Result<u64, SessionStoreError>;
92}
93
94/// Session storage errors
95#[derive(Debug, thiserror::Error)]
96pub enum SessionStoreError {
97    #[error("Session not found")]
98    NotFound,
99
100    #[error("Session expired")]
101    Expired,
102
103    #[error("Database error: {0}")]
104    Database(String),
105
106    #[error("Serialization error: {0}")]
107    Serialization(String),
108
109    #[error("Internal error: {0}")]
110    Internal(String),
111}
112
113#[cfg(test)]
114mod tests {
115    use super::*;
116    use chrono::Duration;
117
118    #[test]
119    fn test_session_creation() {
120        let expires_at = Utc::now() + Duration::hours(1);
121        let session = SessionInfo::new("user_123", "user@example.com", expires_at);
122
123        assert_eq!(session.user_id, "user_123");
124        assert_eq!(session.email, "user@example.com");
125        assert!(!session.is_expired());
126    }
127
128    #[test]
129    fn test_session_expiration() {
130        let expires_at = Utc::now() - Duration::hours(1);
131        let session = SessionInfo::new("user_123", "user@example.com", expires_at);
132
133        assert!(session.is_expired());
134        assert_eq!(session.remaining_seconds(), 0);
135    }
136
137    #[test]
138    fn test_session_with_metadata() {
139        let expires_at = Utc::now() + Duration::hours(1);
140        let session = SessionInfo::new("user_123", "user@example.com", expires_at)
141            .with_metadata(serde_json::json!({"role": "admin"}));
142
143        assert!(session.metadata.is_some());
144    }
145}