helios_auth/jti/
memory.rs1use std::time::Duration;
2
3use async_trait::async_trait;
4use chrono::{DateTime, Utc};
5use moka::future::Cache;
6
7use super::JtiCache;
8use crate::error::AuthError;
9
10pub struct InMemoryJtiCache {
15 cache: Cache<String, ()>,
16}
17
18impl InMemoryJtiCache {
19 pub fn new() -> Self {
24 let cache = Cache::builder()
25 .max_capacity(100_000)
26 .time_to_live(Duration::from_secs(3600))
27 .build();
28 Self { cache }
29 }
30}
31
32impl Default for InMemoryJtiCache {
33 fn default() -> Self {
34 Self::new()
35 }
36}
37
38#[async_trait]
39impl JtiCache for InMemoryJtiCache {
40 async fn check_and_store(
41 &self,
42 jti: &str,
43 _expires_at: DateTime<Utc>,
44 ) -> Result<bool, AuthError> {
45 if self.cache.get(jti).await.is_some() {
47 return Ok(true); }
49
50 self.cache.insert(jti.to_string(), ()).await;
52
53 Ok(false) }
55}
56
57#[cfg(test)]
58mod tests {
59 use super::*;
60 use chrono::Duration as ChronoDuration;
61
62 #[tokio::test]
63 async fn test_new_jti_not_replay() {
64 let cache = InMemoryJtiCache::new();
65 let expires = Utc::now() + ChronoDuration::hours(1);
66
67 let replay = cache.check_and_store("jti-1", expires).await.unwrap();
68 assert!(!replay);
69 }
70
71 #[tokio::test]
72 async fn test_duplicate_jti_is_replay() {
73 let cache = InMemoryJtiCache::new();
74 let expires = Utc::now() + ChronoDuration::hours(1);
75
76 let first = cache.check_and_store("jti-2", expires).await.unwrap();
77 assert!(!first);
78
79 let second = cache.check_and_store("jti-2", expires).await.unwrap();
80 assert!(second);
81 }
82
83 #[tokio::test]
84 async fn test_different_jtis_independent() {
85 let cache = InMemoryJtiCache::new();
86 let expires = Utc::now() + ChronoDuration::hours(1);
87
88 let a = cache.check_and_store("jti-a", expires).await.unwrap();
89 assert!(!a);
90
91 let b = cache.check_and_store("jti-b", expires).await.unwrap();
92 assert!(!b);
93 }
94}