mockforge_http/auth/
token_lifecycle.rs

1//! Token lifecycle management
2//!
3//! This module provides functionality for managing token lifecycle scenarios:
4//! - Token revocation tracking
5//! - Key rotation management
6//! - Clock skew simulation
7//! - Prebuilt test scenarios
8
9use chrono::Utc;
10use serde::{Deserialize, Serialize};
11use sha2::{Digest, Sha256};
12use std::collections::HashMap;
13use std::sync::Arc;
14use tokio::sync::RwLock;
15
16use mockforge_core::Error;
17
18/// Token revocation information
19#[derive(Debug, Clone, Serialize, Deserialize)]
20pub struct RevokedToken {
21    /// Token identifier (jti claim or full token hash)
22    pub token_id: String,
23    /// User ID (sub claim)
24    pub user_id: Option<String>,
25    /// When the token was revoked
26    pub revoked_at: i64,
27    /// Reason for revocation
28    pub reason: String,
29    /// Token expiration time (if known)
30    pub expires_at: Option<i64>,
31}
32
33/// Token revocation store
34#[derive(Debug, Clone)]
35pub struct TokenRevocationStore {
36    /// Map of token_id -> RevokedToken
37    revoked_tokens: Arc<RwLock<HashMap<String, RevokedToken>>>,
38    /// Map of user_id -> set of revoked token IDs
39    user_revoked_tokens: Arc<RwLock<HashMap<String, Vec<String>>>>,
40}
41
42impl TokenRevocationStore {
43    /// Create a new token revocation store
44    pub fn new() -> Self {
45        Self {
46            revoked_tokens: Arc::new(RwLock::new(HashMap::new())),
47            user_revoked_tokens: Arc::new(RwLock::new(HashMap::new())),
48        }
49    }
50
51    /// Revoke a token
52    pub async fn revoke_token(
53        &self,
54        token_id: String,
55        user_id: Option<String>,
56        reason: String,
57        expires_at: Option<i64>,
58    ) {
59        let revoked = RevokedToken {
60            token_id: token_id.clone(),
61            user_id: user_id.clone(),
62            revoked_at: Utc::now().timestamp(),
63            reason,
64            expires_at,
65        };
66
67        let mut tokens = self.revoked_tokens.write().await;
68        tokens.insert(token_id.clone(), revoked);
69
70        if let Some(uid) = user_id {
71            let mut user_tokens = self.user_revoked_tokens.write().await;
72            user_tokens.entry(uid).or_insert_with(Vec::new).push(token_id);
73        }
74    }
75
76    /// Revoke all tokens for a user
77    pub async fn revoke_user_tokens(&self, user_id: String, reason: String) {
78        let mut user_tokens = self.user_revoked_tokens.write().await;
79        if let Some(token_ids) = user_tokens.get(&user_id) {
80            let mut tokens = self.revoked_tokens.write().await;
81            for token_id in token_ids {
82                if let Some(revoked) = tokens.get_mut(token_id) {
83                    revoked.revoked_at = Utc::now().timestamp();
84                    revoked.reason = reason.clone();
85                }
86            }
87        }
88    }
89
90    /// Check if a token is revoked
91    pub async fn is_revoked(&self, token_id: &str) -> Option<RevokedToken> {
92        let tokens = self.revoked_tokens.read().await;
93        tokens.get(token_id).cloned()
94    }
95
96    /// Get revocation status
97    pub async fn get_revocation_status(&self, token_id: &str) -> Option<RevokedToken> {
98        self.is_revoked(token_id).await
99    }
100
101    /// Clean up expired revoked tokens
102    pub async fn cleanup_expired(&self) {
103        let now = Utc::now().timestamp();
104        let mut tokens = self.revoked_tokens.write().await;
105        tokens.retain(|_, revoked| revoked.expires_at.map_or(true, |exp| exp > now));
106    }
107}
108
109impl Default for TokenRevocationStore {
110    fn default() -> Self {
111        Self::new()
112    }
113}
114
115/// Key rotation state
116#[derive(Debug, Clone)]
117pub struct KeyRotationState {
118    /// Active keys (kid -> key info)
119    active_keys: Arc<RwLock<HashMap<String, ActiveKey>>>,
120    /// Grace period for old keys (seconds)
121    grace_period_seconds: i64,
122}
123
124/// Active key information
125#[derive(Debug, Clone)]
126pub struct ActiveKey {
127    /// Key ID
128    pub kid: String,
129    /// When the key was created
130    pub created_at: i64,
131    /// When the key becomes inactive (after grace period)
132    pub inactive_at: Option<i64>,
133    /// Whether this is the primary key
134    pub is_primary: bool,
135}
136
137impl KeyRotationState {
138    /// Create new key rotation state
139    pub fn new(grace_period_seconds: i64) -> Self {
140        Self {
141            active_keys: Arc::new(RwLock::new(HashMap::new())),
142            grace_period_seconds,
143        }
144    }
145
146    /// Add a new key
147    pub async fn add_key(&self, kid: String, is_primary: bool) {
148        let mut keys = self.active_keys.write().await;
149        keys.insert(
150            kid.clone(),
151            ActiveKey {
152                kid,
153                created_at: Utc::now().timestamp(),
154                inactive_at: None,
155                is_primary,
156            },
157        );
158    }
159
160    /// Rotate to a new key
161    pub async fn rotate_key(&self, new_kid: String) -> Result<(), Error> {
162        let mut keys = self.active_keys.write().await;
163
164        // Mark all existing keys as non-primary
165        for key in keys.values_mut() {
166            key.is_primary = false;
167            // Set inactive_at after grace period
168            key.inactive_at = Some(Utc::now().timestamp() + self.grace_period_seconds);
169        }
170
171        // Add new primary key
172        keys.insert(
173            new_kid.clone(),
174            ActiveKey {
175                kid: new_kid,
176                created_at: Utc::now().timestamp(),
177                inactive_at: None,
178                is_primary: true,
179            },
180        );
181
182        Ok(())
183    }
184
185    /// Get active keys (including those in grace period)
186    pub async fn get_active_keys(&self) -> Vec<ActiveKey> {
187        let now = Utc::now().timestamp();
188        let keys = self.active_keys.read().await;
189        keys.values()
190            .filter(|key| key.inactive_at.map_or(true, |inactive_at| inactive_at > now))
191            .cloned()
192            .collect()
193    }
194
195    /// Get primary key
196    pub async fn get_primary_key(&self) -> Option<ActiveKey> {
197        let keys = self.active_keys.read().await;
198        keys.values().find(|key| key.is_primary).cloned()
199    }
200
201    /// Remove old keys (after grace period)
202    pub async fn cleanup_old_keys(&self) {
203        let now = Utc::now().timestamp();
204        let mut keys = self.active_keys.write().await;
205        keys.retain(|_, key| key.inactive_at.map_or(true, |inactive_at| inactive_at > now));
206    }
207}
208
209/// Clock skew configuration
210#[derive(Debug, Clone)]
211pub struct ClockSkewState {
212    /// Clock skew in seconds (positive = server ahead, negative = server behind)
213    skew_seconds: Arc<RwLock<i64>>,
214    /// Whether to apply skew to token issuance
215    apply_to_issuance: bool,
216    /// Whether to apply skew to token validation
217    apply_to_validation: bool,
218}
219
220impl ClockSkewState {
221    /// Create new clock skew state
222    pub fn new() -> Self {
223        Self {
224            skew_seconds: Arc::new(RwLock::new(0)),
225            apply_to_issuance: true,
226            apply_to_validation: true,
227        }
228    }
229
230    /// Set clock skew
231    pub async fn set_skew(&self, skew_seconds: i64) {
232        let mut skew = self.skew_seconds.write().await;
233        *skew = skew_seconds;
234    }
235
236    /// Get current clock skew
237    pub async fn get_skew(&self) -> i64 {
238        let skew = self.skew_seconds.read().await;
239        *skew
240    }
241
242    /// Get adjusted time (current time + skew)
243    pub async fn get_adjusted_time(&self) -> i64 {
244        let skew = self.skew_seconds.read().await;
245        Utc::now().timestamp() + *skew
246    }
247
248    /// Apply skew to a timestamp (for issuance)
249    pub async fn apply_issuance_skew(&self, timestamp: i64) -> i64 {
250        if self.apply_to_issuance {
251            let skew = self.skew_seconds.read().await;
252            timestamp + *skew
253        } else {
254            timestamp
255        }
256    }
257
258    /// Apply skew to a timestamp (for validation)
259    pub async fn apply_validation_skew(&self, timestamp: i64) -> i64 {
260        if self.apply_to_validation {
261            let skew = self.skew_seconds.read().await;
262            timestamp - *skew
263        } else {
264            timestamp
265        }
266    }
267}
268
269impl Default for ClockSkewState {
270    fn default() -> Self {
271        Self::new()
272    }
273}
274
275/// Token lifecycle manager combining all lifecycle features
276#[derive(Debug, Clone)]
277pub struct TokenLifecycleManager {
278    /// Token revocation store
279    pub revocation: TokenRevocationStore,
280    /// Key rotation state
281    pub key_rotation: KeyRotationState,
282    /// Clock skew state
283    pub clock_skew: ClockSkewState,
284}
285
286impl TokenLifecycleManager {
287    /// Create new token lifecycle manager
288    pub fn new(grace_period_seconds: i64) -> Self {
289        Self {
290            revocation: TokenRevocationStore::new(),
291            key_rotation: KeyRotationState::new(grace_period_seconds),
292            clock_skew: ClockSkewState::new(),
293        }
294    }
295}
296
297impl Default for TokenLifecycleManager {
298    fn default() -> Self {
299        Self::new(3600) // 1 hour default grace period
300    }
301}
302
303/// Extract token ID from JWT (using jti claim or token hash)
304pub fn extract_token_id(token: &str) -> String {
305    // For now, use a hash of the token as ID
306    // In production, prefer jti claim from decoded token
307    let mut hasher = Sha256::new();
308    hasher.update(token.as_bytes());
309    format!("{:x}", hasher.finalize())
310}