1use crate::cookies::{Cookie, SameSite};
38use crate::error::{Error, Result};
39use crate::middleware::{Middleware, Next};
40use crate::request::Request;
41use crate::response::Response;
42use crate::security::random_token;
43use async_trait::async_trait;
44use serde::de::DeserializeOwned;
45use serde::Serialize;
46use serde_json::{Map, Value};
47use std::collections::HashMap;
48use std::sync::{Arc, Mutex, MutexGuard};
49use std::time::{SystemTime, UNIX_EPOCH};
50
51const FLASH_KEY: &str = "_flash";
52const CSRF_KEY: &str = "_csrf";
53
54#[derive(Debug, Clone, Default)]
56pub struct SessionRecord {
57 pub data: Map<String, Value>,
58 pub created_at: u64,
59 pub last_seen: u64,
60}
61
62#[async_trait]
64pub trait SessionStore: Send + Sync + 'static {
65 async fn load(&self, id: &str) -> Option<SessionRecord>;
66 async fn save(&self, id: &str, record: &SessionRecord);
67 async fn delete(&self, id: &str);
68}
69
70pub struct MemoryStore {
73 sessions: Mutex<HashMap<String, SessionRecord>>,
74 max_sessions: usize,
75}
76
77impl MemoryStore {
78 pub fn new() -> Self {
80 Self::with_capacity(100_000)
81 }
82
83 pub fn with_capacity(max_sessions: usize) -> Self {
85 Self {
86 sessions: Mutex::new(HashMap::new()),
87 max_sessions: max_sessions.max(1),
88 }
89 }
90
91 pub fn len(&self) -> usize {
92 self.lock().len()
93 }
94
95 pub fn is_empty(&self) -> bool {
96 self.len() == 0
97 }
98
99 fn lock(&self) -> MutexGuard<'_, HashMap<String, SessionRecord>> {
100 self.sessions.lock().unwrap_or_else(|e| e.into_inner())
101 }
102}
103
104impl Default for MemoryStore {
105 fn default() -> Self {
106 Self::new()
107 }
108}
109
110#[async_trait]
111impl SessionStore for MemoryStore {
112 async fn load(&self, id: &str) -> Option<SessionRecord> {
113 self.lock().get(id).cloned()
114 }
115
116 async fn save(&self, id: &str, record: &SessionRecord) {
117 let mut map = self.lock();
118 if map.len() >= self.max_sessions && !map.contains_key(id) {
119 let mut by_age: Vec<(u64, String)> =
121 map.iter().map(|(k, v)| (v.last_seen, k.clone())).collect();
122 by_age.sort();
123 for (_, key) in by_age.into_iter().take((self.max_sessions / 10).max(1)) {
124 map.remove(&key);
125 }
126 }
127 map.insert(id.to_string(), record.clone());
128 }
129
130 async fn delete(&self, id: &str) {
131 self.lock().remove(id);
132 }
133}
134
135#[derive(Default)]
136struct Inner {
137 id: Option<String>,
139 data: Map<String, Value>,
140 created_at: u64,
141 last_seen: u64,
142 dirty: bool,
143 renew: bool,
144 destroy: bool,
145}
146
147#[derive(Clone)]
149pub struct Session {
150 inner: Arc<Mutex<Inner>>,
151}
152
153impl Session {
154 fn lock(&self) -> MutexGuard<'_, Inner> {
155 self.inner.lock().unwrap_or_else(|e| e.into_inner())
156 }
157
158 pub fn get<T: DeserializeOwned>(&self, key: &str) -> Option<T> {
160 let inner = self.lock();
161 inner
162 .data
163 .get(key)
164 .and_then(|v| serde_json::from_value(v.clone()).ok())
165 }
166
167 pub fn contains(&self, key: &str) -> bool {
168 self.lock().data.contains_key(key)
169 }
170
171 pub fn set<T: Serialize>(&self, key: &str, value: T) -> Result<()> {
173 let value = serde_json::to_value(value)
174 .map_err(|e| Error::internal(format!("Session value: {}", e)))?;
175 let mut inner = self.lock();
176 inner.data.insert(key.to_string(), value);
177 inner.dirty = true;
178 Ok(())
179 }
180
181 pub fn remove(&self, key: &str) {
182 let mut inner = self.lock();
183 if inner.data.remove(key).is_some() {
184 inner.dirty = true;
185 }
186 }
187
188 pub fn clear(&self) {
190 let mut inner = self.lock();
191 inner.data.clear();
192 inner.dirty = true;
193 }
194
195 pub fn renew(&self) {
199 let mut inner = self.lock();
200 inner.renew = true;
201 inner.dirty = true;
202 }
203
204 pub fn destroy(&self) {
206 let mut inner = self.lock();
207 inner.destroy = true;
208 inner.data.clear();
209 }
210
211 pub fn flash(&self, message: impl Into<String>) -> Result<()> {
213 let mut inner = self.lock();
214 let list = inner
215 .data
216 .entry(FLASH_KEY.to_string())
217 .or_insert_with(|| Value::Array(Vec::new()));
218 if let Value::Array(list) = list {
219 list.push(Value::String(message.into()));
220 }
221 inner.dirty = true;
222 Ok(())
223 }
224
225 pub fn take_flash(&self) -> Vec<String> {
227 let mut inner = self.lock();
228 match inner.data.remove(FLASH_KEY) {
229 Some(Value::Array(list)) => {
230 inner.dirty = true;
231 list.into_iter()
232 .filter_map(|v| v.as_str().map(str::to_owned))
233 .collect()
234 }
235 _ => Vec::new(),
236 }
237 }
238
239 pub fn csrf_token(&self) -> Result<String> {
242 if let Some(t) = self.get::<String>(CSRF_KEY) {
243 return Ok(t);
244 }
245 let token = random_token(32)?;
246 self.set(CSRF_KEY, &token)?;
247 Ok(token)
248 }
249
250 pub fn is_established(&self) -> bool {
252 self.lock().id.is_some()
253 }
254}
255
256impl Request {
257 pub fn session(&self) -> Result<&Session> {
260 self.extensions
261 .get::<Session>()
262 .ok_or_else(|| Error::internal("SessionLayer middleware is not installed"))
263 }
264}
265
266pub struct SessionLayer {
268 store: Arc<dyn SessionStore>,
269 cookie_name: String,
270 secure: bool,
271 idle_ttl: u64,
272 absolute_ttl: u64,
273 clock: Arc<dyn Fn() -> u64 + Send + Sync>,
274}
275
276impl SessionLayer {
277 pub fn new() -> Self {
279 Self::with_store(MemoryStore::new())
280 }
281
282 pub fn with_store(store: impl SessionStore) -> Self {
283 Self {
284 store: Arc::new(store),
285 cookie_name: "__Host-sid".into(),
286 secure: true,
287 idle_ttl: 30 * 60,
288 absolute_ttl: 12 * 3600,
289 clock: Arc::new(|| {
290 SystemTime::now()
291 .duration_since(UNIX_EPOCH)
292 .map(|d| d.as_secs())
293 .unwrap_or(0)
294 }),
295 }
296 }
297
298 pub fn cookie_name(mut self, name: impl Into<String>) -> Self {
300 self.cookie_name = name.into();
301 self
302 }
303
304 pub fn insecure_cookies(mut self) -> Self {
307 self.secure = false;
308 if self.cookie_name.starts_with("__Host-") {
309 self.cookie_name = "sid".into();
310 }
311 self
312 }
313
314 pub fn idle_timeout(mut self, secs: u64) -> Self {
316 self.idle_ttl = secs.max(1);
317 self
318 }
319
320 pub fn absolute_timeout(mut self, secs: u64) -> Self {
322 self.absolute_ttl = secs.max(1);
323 self
324 }
325
326 pub fn clock(mut self, clock: impl Fn() -> u64 + Send + Sync + 'static) -> Self {
328 self.clock = Arc::new(clock);
329 self
330 }
331
332 fn cookie(&self, value: &str, max_age: i64) -> Cookie {
333 let c = Cookie::new(&self.cookie_name, value)
334 .path("/")
335 .max_age(max_age)
336 .http_only()
337 .same_site(SameSite::Lax);
338 if self.secure {
339 c.secure()
340 } else {
341 c
342 }
343 }
344}
345
346impl Default for SessionLayer {
347 fn default() -> Self {
348 Self::new()
349 }
350}
351
352fn valid_id(id: &str) -> bool {
353 id.len() == 64
354 && id
355 .bytes()
356 .all(|b| b.is_ascii_digit() || (b'a'..=b'f').contains(&b))
357}
358
359#[async_trait]
360impl Middleware for SessionLayer {
361 async fn handle(&self, mut req: Request, next: Next) -> Result<Response> {
362 let now = (self.clock)();
363 let presented = req
364 .cookies()
365 .get(&self.cookie_name)
366 .filter(|id| valid_id(id))
367 .map(str::to_owned);
368
369 let mut inner = Inner {
370 created_at: now,
371 ..Inner::default()
372 };
373 let mut expired_cookie = false;
374 if let Some(id) = presented {
375 match self.store.load(&id).await {
376 Some(rec)
377 if now.saturating_sub(rec.last_seen) < self.idle_ttl
378 && now.saturating_sub(rec.created_at) < self.absolute_ttl =>
379 {
380 inner.id = Some(id);
381 inner.data = rec.data;
382 inner.created_at = rec.created_at;
383 inner.last_seen = rec.last_seen;
384 }
385 Some(_) => {
386 self.store.delete(&id).await;
387 expired_cookie = true;
388 }
389 None => expired_cookie = true, }
391 }
392 let session = Session {
393 inner: Arc::new(Mutex::new(inner)),
394 };
395 req.extensions.insert(session.clone());
396
397 let mut res = match next.run(req).await {
398 Ok(res) => res,
399 Err(err) => Response::from_error(err),
400 };
401
402 let (old_id, data, created_at, last_seen, dirty, renew, destroy) = {
403 let i = session.lock();
404 (
405 i.id.clone(),
406 i.data.clone(),
407 i.created_at,
408 i.last_seen,
409 i.dirty,
410 i.renew,
411 i.destroy,
412 )
413 };
414
415 if destroy {
416 if let Some(id) = &old_id {
417 self.store.delete(id).await;
418 }
419 if old_id.is_some() || expired_cookie {
420 res = res.cookie(self.cookie("", 0));
421 }
422 return Ok(res);
423 }
424
425 match (old_id, dirty) {
426 (old, true) => {
428 let id = match (&old, renew) {
429 (Some(old), false) => old.clone(),
430 (old, _) => {
431 if let Some(old) = old {
432 self.store.delete(old).await;
433 }
434 random_token(32)?
435 }
436 };
437 self.store
438 .save(
439 &id,
440 &SessionRecord {
441 data,
442 created_at: if renew { now } else { created_at },
444 last_seen: now,
445 },
446 )
447 .await;
448 let remaining = self
449 .absolute_ttl
450 .saturating_sub(if renew {
451 0
452 } else {
453 now.saturating_sub(created_at)
454 })
455 .min(self.idle_ttl);
456 res = res.cookie(self.cookie(&id, remaining as i64));
457 }
458 (Some(id), false) => {
460 if now.saturating_sub(last_seen) >= self.idle_ttl / 4 {
461 self.store
462 .save(
463 &id,
464 &SessionRecord {
465 data,
466 created_at,
467 last_seen: now,
468 },
469 )
470 .await;
471 let remaining = self
472 .absolute_ttl
473 .saturating_sub(now.saturating_sub(created_at))
474 .min(self.idle_ttl);
475 res = res.cookie(self.cookie(&id, remaining as i64));
476 }
477 }
478 (None, false) => {
479 if expired_cookie {
480 res = res.cookie(self.cookie("", 0));
481 }
482 }
483 }
484 Ok(res)
485 }
486}
487
488#[cfg(test)]
489mod tests {
490 use super::*;
491
492 #[test]
493 fn id_format() {
494 assert!(valid_id(&"a1".repeat(32)));
495 assert!(!valid_id(&"A1".repeat(32)));
496 assert!(!valid_id(&"a1".repeat(31)));
497 assert!(!valid_id(""));
498 assert!(!valid_id(&format!("{}; Path=/", "a".repeat(40))));
499 }
500
501 #[tokio::test]
502 async fn memory_store_evicts_the_least_recently_seen() {
503 let store = MemoryStore::with_capacity(3);
504 for (id, seen) in [("a", 10), ("b", 20), ("c", 30)] {
505 store
506 .save(
507 id,
508 &SessionRecord {
509 last_seen: seen,
510 ..Default::default()
511 },
512 )
513 .await;
514 }
515 store
516 .save(
517 "d",
518 &SessionRecord {
519 last_seen: 40,
520 ..Default::default()
521 },
522 )
523 .await;
524 assert_eq!(store.len(), 3);
525 assert!(store.load("a").await.is_none());
526 assert!(store.load("d").await.is_some());
527 store
529 .save(
530 "d",
531 &SessionRecord {
532 last_seen: 50,
533 ..Default::default()
534 },
535 )
536 .await;
537 assert_eq!(store.len(), 3);
538 store.delete("b").await;
539 assert_eq!(store.len(), 2);
540 }
541
542 #[test]
543 fn session_values_and_flash() {
544 let s = Session {
545 inner: Arc::new(Mutex::new(Inner::default())),
546 };
547 assert!(s.get::<String>("k").is_none());
548 s.set("k", "v").unwrap();
549 s.set("n", 5u32).unwrap();
550 assert_eq!(s.get::<String>("k").as_deref(), Some("v"));
551 assert_eq!(s.get::<u32>("n"), Some(5));
552 assert_eq!(s.get::<u32>("k"), None); s.remove("n");
554 assert!(!s.contains("n"));
555
556 s.flash("one").unwrap();
557 s.flash("two").unwrap();
558 assert_eq!(s.take_flash(), ["one", "two"]);
559 assert!(s.take_flash().is_empty());
560
561 let t1 = s.csrf_token().unwrap();
562 assert_eq!(t1.len(), 64);
563 assert_eq!(s.csrf_token().unwrap(), t1);
564 s.clear();
565 assert_ne!(s.csrf_token().unwrap(), t1);
566 }
567}