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}