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 std::collections::HashMap;
12use std::sync::Arc;
13use tokio::sync::RwLock;
14use sha2::{Digest, Sha256};
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| {
106            revoked.expires_at.map_or(true, |exp| exp > now)
107        });
108    }
109}
110
111impl Default for TokenRevocationStore {
112    fn default() -> Self {
113        Self::new()
114    }
115}
116
117/// Key rotation state
118#[derive(Debug, Clone)]
119pub struct KeyRotationState {
120    /// Active keys (kid -> key info)
121    active_keys: Arc<RwLock<HashMap<String, ActiveKey>>>,
122    /// Grace period for old keys (seconds)
123    grace_period_seconds: i64,
124}
125
126/// Active key information
127#[derive(Debug, Clone)]
128pub struct ActiveKey {
129    /// Key ID
130    pub kid: String,
131    /// When the key was created
132    pub created_at: i64,
133    /// When the key becomes inactive (after grace period)
134    pub inactive_at: Option<i64>,
135    /// Whether this is the primary key
136    pub is_primary: bool,
137}
138
139impl KeyRotationState {
140    /// Create new key rotation state
141    pub fn new(grace_period_seconds: i64) -> Self {
142        Self {
143            active_keys: Arc::new(RwLock::new(HashMap::new())),
144            grace_period_seconds,
145        }
146    }
147
148    /// Add a new key
149    pub async fn add_key(&self, kid: String, is_primary: bool) {
150        let mut keys = self.active_keys.write().await;
151        keys.insert(
152            kid.clone(),
153            ActiveKey {
154                kid,
155                created_at: Utc::now().timestamp(),
156                inactive_at: None,
157                is_primary,
158            },
159        );
160    }
161
162    /// Rotate to a new key
163    pub async fn rotate_key(&self, new_kid: String) -> Result<(), Error> {
164        let mut keys = self.active_keys.write().await;
165        
166        // Mark all existing keys as non-primary
167        for key in keys.values_mut() {
168            key.is_primary = false;
169            // Set inactive_at after grace period
170            key.inactive_at = Some(Utc::now().timestamp() + self.grace_period_seconds);
171        }
172
173        // Add new primary key
174        keys.insert(
175            new_kid.clone(),
176            ActiveKey {
177                kid: new_kid,
178                created_at: Utc::now().timestamp(),
179                inactive_at: None,
180                is_primary: true,
181            },
182        );
183
184        Ok(())
185    }
186
187    /// Get active keys (including those in grace period)
188    pub async fn get_active_keys(&self) -> Vec<ActiveKey> {
189        let now = Utc::now().timestamp();
190        let keys = self.active_keys.read().await;
191        keys.values()
192            .filter(|key| {
193                key.inactive_at.map_or(true, |inactive_at| inactive_at > now)
194            })
195            .cloned()
196            .collect()
197    }
198
199    /// Get primary key
200    pub async fn get_primary_key(&self) -> Option<ActiveKey> {
201        let keys = self.active_keys.read().await;
202        keys.values()
203            .find(|key| key.is_primary)
204            .cloned()
205    }
206
207    /// Remove old keys (after grace period)
208    pub async fn cleanup_old_keys(&self) {
209        let now = Utc::now().timestamp();
210        let mut keys = self.active_keys.write().await;
211        keys.retain(|_, key| {
212            key.inactive_at.map_or(true, |inactive_at| inactive_at > now)
213        });
214    }
215}
216
217/// Clock skew configuration
218#[derive(Debug, Clone)]
219pub struct ClockSkewState {
220    /// Clock skew in seconds (positive = server ahead, negative = server behind)
221    skew_seconds: Arc<RwLock<i64>>,
222    /// Whether to apply skew to token issuance
223    apply_to_issuance: bool,
224    /// Whether to apply skew to token validation
225    apply_to_validation: bool,
226}
227
228impl ClockSkewState {
229    /// Create new clock skew state
230    pub fn new() -> Self {
231        Self {
232            skew_seconds: Arc::new(RwLock::new(0)),
233            apply_to_issuance: true,
234            apply_to_validation: true,
235        }
236    }
237
238    /// Set clock skew
239    pub async fn set_skew(&self, skew_seconds: i64) {
240        let mut skew = self.skew_seconds.write().await;
241        *skew = skew_seconds;
242    }
243
244    /// Get current clock skew
245    pub async fn get_skew(&self) -> i64 {
246        let skew = self.skew_seconds.read().await;
247        *skew
248    }
249
250    /// Get adjusted time (current time + skew)
251    pub async fn get_adjusted_time(&self) -> i64 {
252        let skew = self.skew_seconds.read().await;
253        Utc::now().timestamp() + *skew
254    }
255
256    /// Apply skew to a timestamp (for issuance)
257    pub async fn apply_issuance_skew(&self, timestamp: i64) -> i64 {
258        if self.apply_to_issuance {
259            let skew = self.skew_seconds.read().await;
260            timestamp + *skew
261        } else {
262            timestamp
263        }
264    }
265
266    /// Apply skew to a timestamp (for validation)
267    pub async fn apply_validation_skew(&self, timestamp: i64) -> i64 {
268        if self.apply_to_validation {
269            let skew = self.skew_seconds.read().await;
270            timestamp - *skew
271        } else {
272            timestamp
273        }
274    }
275}
276
277impl Default for ClockSkewState {
278    fn default() -> Self {
279        Self::new()
280    }
281}
282
283/// Token lifecycle manager combining all lifecycle features
284#[derive(Debug, Clone)]
285pub struct TokenLifecycleManager {
286    /// Token revocation store
287    pub revocation: TokenRevocationStore,
288    /// Key rotation state
289    pub key_rotation: KeyRotationState,
290    /// Clock skew state
291    pub clock_skew: ClockSkewState,
292}
293
294impl TokenLifecycleManager {
295    /// Create new token lifecycle manager
296    pub fn new(grace_period_seconds: i64) -> Self {
297        Self {
298            revocation: TokenRevocationStore::new(),
299            key_rotation: KeyRotationState::new(grace_period_seconds),
300            clock_skew: ClockSkewState::new(),
301        }
302    }
303}
304
305impl Default for TokenLifecycleManager {
306    fn default() -> Self {
307        Self::new(3600) // 1 hour default grace period
308    }
309}
310
311/// Extract token ID from JWT (using jti claim or token hash)
312pub fn extract_token_id(token: &str) -> String {
313    // For now, use a hash of the token as ID
314    // In production, prefer jti claim from decoded token
315    let mut hasher = Sha256::new();
316    hasher.update(token.as_bytes());
317    format!("{:x}", hasher.finalize())
318}
319