Skip to main content

tako_rs_plugins/middleware/session/
store.rs

1//! In-memory session store, stored entry type, expiry policy, and the
2//! programmatic revocation handle.
3
4use std::sync::Arc;
5use std::time::Duration;
6use std::time::Instant;
7
8use scc::HashMap as SccHashMap;
9
10/// Session expiration policy.
11#[derive(Clone, Copy)]
12pub struct SessionTtl {
13  /// Seconds of inactivity before the session is invalidated.
14  pub idle_secs: u64,
15  /// Hard cap on total session lifetime regardless of activity. `None` means
16  /// only the idle timeout applies.
17  pub absolute_secs: Option<u64>,
18}
19
20impl Default for SessionTtl {
21  fn default() -> Self {
22    Self {
23      idle_secs: 3_600,
24      absolute_secs: Some(86_400),
25    }
26  }
27}
28
29#[derive(Clone)]
30pub(crate) struct SessionEntry {
31  pub(crate) data: serde_json::Map<String, serde_json::Value>,
32  pub(crate) created_at: Instant,
33  pub(crate) last_seen_at: Instant,
34}
35
36/// Internal session store wrapper. Cloneable handle to the same `SccHashMap`.
37#[derive(Clone)]
38pub(crate) struct Store(Arc<SccHashMap<String, SessionEntry>>);
39
40impl Store {
41  pub(crate) fn new() -> Self {
42    Self(Arc::new(SccHashMap::new()))
43  }
44
45  pub(crate) fn get(&self, id: &str) -> Option<SessionEntry> {
46    self.0.get_sync(id).map(|e| e.clone())
47  }
48
49  pub(crate) fn upsert(&self, id: String, entry: SessionEntry) {
50    let _ = self.0.upsert_sync(id, entry);
51  }
52
53  pub(crate) fn remove(&self, id: &str) {
54    let _ = self.0.remove_sync(id);
55  }
56
57  fn revoke_all(&self) {
58    self.0.clear_sync();
59  }
60
61  fn revoke_predicate(&self, mut keep: impl FnMut(&str, &SessionEntry) -> bool) {
62    self.0.retain_sync(|k, v| keep(k, v));
63  }
64
65  pub(crate) fn retain_expired(&self, ttl: SessionTtl) {
66    let now = Instant::now();
67    let idle = Duration::from_secs(ttl.idle_secs);
68    let absolute = ttl.absolute_secs.map(Duration::from_secs);
69    self.0.retain_sync(|_, v| {
70      if now.duration_since(v.last_seen_at) > idle {
71        return false;
72      }
73      if let Some(abs) = absolute
74        && now.duration_since(v.created_at) > abs
75      {
76        return false;
77      }
78      true
79    });
80  }
81}
82
83/// Programmatic store handle returned by [`SessionMiddleware::handle`](super::layer::SessionMiddleware::handle).
84#[derive(Clone)]
85pub struct SessionStoreHandle {
86  pub(crate) store: Store,
87}
88
89impl SessionStoreHandle {
90  /// Drops every session.
91  pub fn revoke_all(&self) {
92    self.store.revoke_all();
93  }
94
95  /// Drops sessions matching the predicate (returns false to drop).
96  pub fn revoke_where<F>(&self, mut pred: F)
97  where
98    F: FnMut(&str, &serde_json::Map<String, serde_json::Value>) -> bool,
99  {
100    self.store.revoke_predicate(|k, v| !pred(k, &v.data));
101  }
102}