1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
use chrono::{DateTime, Utc};
use serde::{Deserialize, Serialize};
use uuid::Uuid;
/// Session model stored in Redis
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Session {
/// Session ID (used as Redis key)
pub id: Uuid,
/// User ID who owns this session
pub user_id: Uuid,
/// Session data (access token, refresh token, metadata)
pub data: SessionData,
/// When the session was created
pub created_at: DateTime<Utc>,
/// When the session expires
pub expires_at: DateTime<Utc>,
/// Last activity timestamp (for idle timeout tracking)
pub last_activity: DateTime<Utc>,
/// IP address of the client that created this session
#[serde(skip_serializing_if = "Option::is_none")]
pub client_ip: Option<String>,
/// User agent of the client that created this session
#[serde(skip_serializing_if = "Option::is_none")]
pub user_agent: Option<String>,
}
impl Session {
/// Create a new session
pub fn new(
user_id: Uuid,
data: SessionData,
expires_at: DateTime<Utc>,
) -> Self {
let now = Utc::now();
Self {
id: Uuid::new_v4(),
user_id,
data,
created_at: now,
expires_at,
last_activity: now,
client_ip: None,
user_agent: None,
}
}
/// Update last activity timestamp
pub fn touch(&mut self) {
self.last_activity = Utc::now();
}
/// Check if session is expired
pub fn is_expired(&self) -> bool {
Utc::now() > self.expires_at
}
/// Add client metadata
pub fn with_client_info(mut self, ip: Option<String>, user_agent: Option<String>) -> Self {
self.client_ip = ip;
self.user_agent = user_agent;
self
}
}
/// Session data containing tokens and metadata
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct SessionData {
/// Access token JWT
pub access_token: String,
/// Refresh token JWT
pub refresh_token: String,
/// User email (cached for convenience)
pub user_email: String,
/// Custom metadata (extensible for application-specific data)
#[serde(default)]
pub metadata: std::collections::HashMap<String, serde_json::Value>,
}
impl SessionData {
/// Create new session data
pub fn new(
access_token: String,
refresh_token: String,
user_email: String,
) -> Self {
Self {
access_token,
refresh_token,
user_email,
metadata: std::collections::HashMap::new(),
}
}
/// Add metadata to session
pub fn with_metadata(mut self, key: String, value: serde_json::Value) -> Self {
self.metadata.insert(key, value);
self
}
}