server/auth/token_cache.rs
1use super::types::CachedToken;
2use std::collections::HashMap;
3use std::sync::Arc;
4use tokio::sync::RwLock;
5
6/// Thread-safe cache for storing authentication tokens with expiration tracking.
7///
8/// Provides a simple key-value store for authentication tokens that automatically
9/// handles expiration checking and cleanup. The cache is designed to be shared
10/// across multiple threads and async tasks safely.
11///
12/// # Thread Safety
13///
14/// All operations are thread-safe and can be called concurrently from multiple
15/// async tasks. The cache uses RwLock for efficient concurrent read access.
16///
17/// # Examples
18///
19/// ```no_run
20/// use quetty_server::auth::{TokenCache, CachedToken};
21/// use std::time::{Duration, Instant};
22///
23/// let cache = TokenCache::new();
24///
25/// // Store a token
26/// let token = CachedToken {
27/// token: "access_token_123".to_string(),
28/// expires_at: Instant::now() + Duration::from_secs(3600),
29/// };
30/// cache.set("user_123".to_string(), token).await;
31///
32/// // Retrieve a token (returns None if expired)
33/// if let Some(token) = cache.get("user_123").await {
34/// println!("Token: {}", token);
35/// }
36/// ```
37#[derive(Clone)]
38pub struct TokenCache {
39 cache: Arc<RwLock<HashMap<String, CachedToken>>>,
40}
41
42impl TokenCache {
43 /// Creates a new empty token cache.
44 ///
45 /// # Returns
46 ///
47 /// A new TokenCache instance ready for use
48 pub fn new() -> Self {
49 Self {
50 cache: Arc::new(RwLock::new(HashMap::new())),
51 }
52 }
53
54 /// Retrieves a valid token from the cache.
55 ///
56 /// Returns the token only if it exists and has not expired. Expired tokens
57 /// are automatically filtered out.
58 ///
59 /// # Arguments
60 ///
61 /// * `key` - The cache key to look up
62 ///
63 /// # Returns
64 ///
65 /// `Some(token)` if a valid token exists, `None` if no token exists or it has expired
66 pub async fn get(&self, key: &str) -> Option<String> {
67 let cache = self.cache.read().await;
68 cache
69 .get(key)
70 .filter(|token| !token.is_expired())
71 .map(|token| token.token.clone())
72 }
73
74 /// Stores a token in the cache.
75 ///
76 /// Overwrites any existing token with the same key.
77 ///
78 /// # Arguments
79 ///
80 /// * `key` - The cache key to store the token under
81 /// * `token` - The cached token with expiration information
82 pub async fn set(&self, key: String, token: CachedToken) {
83 let mut cache = self.cache.write().await;
84 cache.insert(key, token);
85 }
86
87 /// Removes a specific token from the cache.
88 ///
89 /// # Arguments
90 ///
91 /// * `key` - The cache key to remove
92 pub async fn invalidate(&self, key: &str) {
93 let mut cache = self.cache.write().await;
94 cache.remove(key);
95 }
96
97 /// Clears all tokens from the cache.
98 ///
99 /// This is useful for logout operations or when switching authentication contexts.
100 pub async fn clear(&self) {
101 let mut cache = self.cache.write().await;
102 cache.clear();
103 }
104
105 /// Checks if a token needs refresh based on its expiration time.
106 ///
107 /// Returns `true` if the token doesn't exist, has expired, or is close to expiring.
108 ///
109 /// # Arguments
110 ///
111 /// * `key` - The cache key to check
112 ///
113 /// # Returns
114 ///
115 /// `true` if the token needs refresh, `false` if it's still valid
116 pub async fn needs_refresh(&self, key: &str) -> bool {
117 let cache = self.cache.read().await;
118 cache
119 .get(key)
120 .map(|token| token.needs_refresh())
121 .unwrap_or(true)
122 }
123}
124
125impl Default for TokenCache {
126 fn default() -> Self {
127 Self::new()
128 }
129}