arcly_http_identity/
session.rs1use std::collections::HashMap;
6use std::sync::RwLock;
7use std::time::{SystemTime, UNIX_EPOCH};
8
9use async_trait::async_trait;
10use serde::{Deserialize, Serialize};
11
12use crate::error::Result;
13
14fn unix_now() -> u64 {
15 SystemTime::now()
16 .duration_since(UNIX_EPOCH)
17 .map(|d| d.as_secs())
18 .unwrap_or(0)
19}
20
21#[async_trait]
26pub trait RefreshStore: Send + Sync + 'static {
27 async fn save(&self, jti: &str, user_id: &str, ttl_secs: u64) -> Result<()>;
29 async fn take(&self, jti: &str) -> Result<Option<String>>;
32 async fn revoke(&self, jti: &str) -> Result<()>;
34 async fn revoke_all(&self, user_id: &str) -> Result<()>;
36}
37
38#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
43pub struct PendingChallenge {
44 pub user_id: String,
45 pub expires_at: u64,
46}
47
48#[async_trait]
49pub trait ChallengeStore: Send + Sync + 'static {
50 async fn put(&self, challenge_id: &str, user_id: &str, ttl_secs: u64) -> Result<()>;
51 async fn take(&self, challenge_id: &str) -> Result<Option<PendingChallenge>>;
52}
53
54#[derive(Debug, Clone, Serialize, Deserialize)]
58pub struct SessionRecord {
59 pub session_id: String,
60 pub user_id: String,
61 #[serde(default, skip_serializing_if = "Option::is_none")]
62 pub device: Option<String>,
63 #[serde(default, skip_serializing_if = "Option::is_none")]
64 pub ip: Option<String>,
65 pub created_at: u64,
66 pub last_seen: u64,
67}
68
69#[async_trait]
71pub trait SessionIndex: Send + Sync + 'static {
72 async fn register(&self, record: SessionRecord) -> Result<()>;
73 async fn list(&self, user_id: &str) -> Result<Vec<SessionRecord>>;
74 async fn revoke(&self, session_id: &str) -> Result<()>;
75 async fn revoke_all(&self, user_id: &str) -> Result<()>;
76}
77
78#[derive(Default)]
81pub struct MemoryRefreshStore {
82 inner: RwLock<HashMap<String, (String, u64)>>,
84}
85impl MemoryRefreshStore {
86 pub fn new() -> Self {
87 Self::default()
88 }
89}
90#[async_trait]
91impl RefreshStore for MemoryRefreshStore {
92 async fn save(&self, jti: &str, user_id: &str, ttl_secs: u64) -> Result<()> {
93 self.inner
94 .write()
95 .unwrap()
96 .insert(jti.to_owned(), (user_id.to_owned(), unix_now() + ttl_secs));
97 Ok(())
98 }
99 async fn take(&self, jti: &str) -> Result<Option<String>> {
100 let removed = self.inner.write().unwrap().remove(jti);
101 Ok(removed.and_then(|(uid, exp)| (unix_now() < exp).then_some(uid)))
102 }
103 async fn revoke(&self, jti: &str) -> Result<()> {
104 self.inner.write().unwrap().remove(jti);
105 Ok(())
106 }
107 async fn revoke_all(&self, user_id: &str) -> Result<()> {
108 self.inner
109 .write()
110 .unwrap()
111 .retain(|_, (uid, _)| uid != user_id);
112 Ok(())
113 }
114}
115
116#[derive(Default)]
117pub struct MemoryChallengeStore {
118 inner: RwLock<HashMap<String, PendingChallenge>>,
119}
120impl MemoryChallengeStore {
121 pub fn new() -> Self {
122 Self::default()
123 }
124}
125#[async_trait]
126impl ChallengeStore for MemoryChallengeStore {
127 async fn put(&self, challenge_id: &str, user_id: &str, ttl_secs: u64) -> Result<()> {
128 self.inner.write().unwrap().insert(
129 challenge_id.to_owned(),
130 PendingChallenge {
131 user_id: user_id.to_owned(),
132 expires_at: unix_now() + ttl_secs,
133 },
134 );
135 Ok(())
136 }
137 async fn take(&self, challenge_id: &str) -> Result<Option<PendingChallenge>> {
138 let taken = self.inner.write().unwrap().remove(challenge_id);
139 Ok(taken.filter(|c| unix_now() < c.expires_at))
140 }
141}
142
143#[derive(Default)]
144pub struct MemorySessionIndex {
145 inner: RwLock<HashMap<String, SessionRecord>>,
146}
147impl MemorySessionIndex {
148 pub fn new() -> Self {
149 Self::default()
150 }
151}
152#[async_trait]
153impl SessionIndex for MemorySessionIndex {
154 async fn register(&self, record: SessionRecord) -> Result<()> {
155 self.inner
156 .write()
157 .unwrap()
158 .insert(record.session_id.clone(), record);
159 Ok(())
160 }
161 async fn list(&self, user_id: &str) -> Result<Vec<SessionRecord>> {
162 Ok(self
163 .inner
164 .read()
165 .unwrap()
166 .values()
167 .filter(|r| r.user_id == user_id)
168 .cloned()
169 .collect())
170 }
171 async fn revoke(&self, session_id: &str) -> Result<()> {
172 self.inner.write().unwrap().remove(session_id);
173 Ok(())
174 }
175 async fn revoke_all(&self, user_id: &str) -> Result<()> {
176 self.inner
177 .write()
178 .unwrap()
179 .retain(|_, r| r.user_id != user_id);
180 Ok(())
181 }
182}