mockforge_http/handlers/
token_lifecycle.rs

1//! Token lifecycle test scenario handlers
2//!
3//! This module provides API endpoints for testing token lifecycle scenarios:
4//! - Token revocation
5//! - Key rotation
6//! - Clock skew
7//! - Prebuilt test scenarios
8
9use axum::{
10    extract::{Query, State},
11    http::StatusCode,
12    response::Json,
13};
14use serde::Deserialize;
15use std::collections::HashMap;
16use std::sync::Arc;
17
18use crate::auth::token_lifecycle::{extract_token_id, TokenLifecycleManager};
19
20/// State for token lifecycle handlers
21#[derive(Clone)]
22pub struct TokenLifecycleState {
23    /// Token lifecycle manager
24    pub manager: Arc<TokenLifecycleManager>,
25}
26
27/// Revoke token request
28#[derive(Debug, Deserialize)]
29pub struct RevokeTokenRequest {
30    /// Token to revoke
31    pub token: Option<String>,
32    /// Token ID (jti claim)
33    pub token_id: Option<String>,
34    /// User ID (sub claim)
35    pub user_id: Option<String>,
36    /// Reason for revocation
37    pub reason: String,
38}
39
40/// Revoke user tokens request
41#[derive(Debug, Deserialize)]
42pub struct RevokeUserTokensRequest {
43    /// User ID
44    pub user_id: String,
45    /// Reason for revocation
46    pub reason: String,
47}
48
49/// Key rotation request
50#[derive(Debug, Deserialize)]
51pub struct RotateKeyRequest {
52    /// New key ID
53    pub new_key_id: String,
54    /// Grace period in seconds
55    pub grace_period_seconds: Option<i64>,
56}
57
58/// Clock skew request
59#[derive(Debug, Deserialize)]
60pub struct ClockSkewRequest {
61    /// Skew in seconds (positive = server ahead, negative = server behind)
62    pub skew_seconds: i64,
63    /// Duration in seconds (0 = permanent)
64    pub duration_seconds: Option<u64>,
65}
66
67/// Force refresh failure request
68#[derive(Debug, Deserialize)]
69pub struct ForceRefreshFailureRequest {
70    /// User ID
71    pub user_id: String,
72    /// Failure type
73    pub failure_type: String,
74}
75
76/// Revoke mid-session request
77#[derive(Debug, Deserialize)]
78pub struct RevokeMidSessionRequest {
79    /// User ID
80    pub user_id: String,
81    /// Delay in seconds before revocation
82    pub delay_seconds: u64,
83}
84
85/// Revoke token endpoint
86pub async fn revoke_token(
87    State(state): State<TokenLifecycleState>,
88    Json(request): Json<RevokeTokenRequest>,
89) -> Result<Json<serde_json::Value>, StatusCode> {
90    let token_id = if let Some(token) = request.token {
91        extract_token_id(&token)
92    } else if let Some(tid) = request.token_id {
93        tid
94    } else {
95        return Err(StatusCode::BAD_REQUEST);
96    };
97
98    state
99        .manager
100        .revocation
101        .revoke_token(
102            token_id.clone(),
103            request.user_id,
104            request.reason,
105            None, // expires_at not provided
106        )
107        .await;
108
109    Ok(Json(serde_json::json!({
110        "success": true,
111        "token_id": token_id,
112        "message": "Token revoked successfully"
113    })))
114}
115
116/// Revoke all tokens for a user
117pub async fn revoke_user_tokens(
118    State(state): State<TokenLifecycleState>,
119    Json(request): Json<RevokeUserTokensRequest>,
120) -> Result<Json<serde_json::Value>, StatusCode> {
121    state
122        .manager
123        .revocation
124        .revoke_user_tokens(request.user_id.clone(), request.reason)
125        .await;
126
127    Ok(Json(serde_json::json!({
128        "success": true,
129        "user_id": request.user_id,
130        "message": "All user tokens revoked successfully"
131    })))
132}
133
134/// Get token revocation status
135pub async fn get_token_status(
136    State(state): State<TokenLifecycleState>,
137    Query(params): Query<HashMap<String, String>>,
138) -> Result<Json<serde_json::Value>, StatusCode> {
139    let token_id = if let Some(token) = params.get("token") {
140        extract_token_id(token)
141    } else if let Some(tid) = params.get("token_id") {
142        tid.clone()
143    } else {
144        return Err(StatusCode::BAD_REQUEST);
145    };
146
147    if let Some(revoked) = state.manager.revocation.get_revocation_status(&token_id).await {
148        Ok(Json(serde_json::json!({
149            "revoked": true,
150            "revoked_at": revoked.revoked_at,
151            "reason": revoked.reason
152        })))
153    } else {
154        Ok(Json(serde_json::json!({
155            "revoked": false
156        })))
157    }
158}
159
160/// Rotate keys
161pub async fn rotate_keys(
162    State(state): State<TokenLifecycleState>,
163    Json(request): Json<RotateKeyRequest>,
164) -> Result<Json<serde_json::Value>, StatusCode> {
165    // Update the key rotation state
166    // Note: Actual key rotation in OIDC state would require access to OidcState
167    // which is managed separately. This endpoint manages the rotation lifecycle
168    // and the OIDC state should be updated separately via configuration.
169    state
170        .manager
171        .key_rotation
172        .rotate_key(request.new_key_id.clone())
173        .await
174        .map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)?;
175
176    Ok(Json(serde_json::json!({
177        "success": true,
178        "new_key_id": request.new_key_id,
179        "message": "Key rotation initiated. Update OIDC configuration to use the new key."
180    })))
181}
182
183/// Get active keys
184pub async fn get_active_keys(State(state): State<TokenLifecycleState>) -> Json<serde_json::Value> {
185    let keys = state.manager.key_rotation.get_active_keys().await;
186    Json(serde_json::json!({
187        "keys": keys.iter().map(|k| serde_json::json!({
188            "kid": k.kid,
189            "created_at": k.created_at,
190            "inactive_at": k.inactive_at,
191            "is_primary": k.is_primary
192        })).collect::<Vec<_>>()
193    }))
194}
195
196/// Set clock skew
197pub async fn set_clock_skew(
198    State(state): State<TokenLifecycleState>,
199    Json(request): Json<ClockSkewRequest>,
200) -> Result<Json<serde_json::Value>, StatusCode> {
201    state.manager.clock_skew.set_skew(request.skew_seconds).await;
202
203    // If duration is specified, schedule reset
204    if let Some(duration) = request.duration_seconds {
205        let state_clone = state.clone();
206        let skew_value = request.skew_seconds;
207        tokio::spawn(async move {
208            tokio::time::sleep(tokio::time::Duration::from_secs(duration)).await;
209            state_clone.manager.clock_skew.set_skew(0).await;
210        });
211    }
212
213    Ok(Json(serde_json::json!({
214        "success": true,
215        "skew_seconds": request.skew_seconds,
216        "message": "Clock skew set successfully"
217    })))
218}
219
220/// Get clock skew
221pub async fn get_clock_skew(State(state): State<TokenLifecycleState>) -> Json<serde_json::Value> {
222    let skew = state.manager.clock_skew.get_skew().await;
223    let adjusted_time = state.manager.clock_skew.get_adjusted_time().await;
224    let server_time = chrono::Utc::now().timestamp();
225
226    Json(serde_json::json!({
227        "skew_seconds": skew,
228        "server_time": server_time,
229        "adjusted_time": adjusted_time
230    }))
231}
232
233/// Force refresh token failure (test scenario)
234pub async fn force_refresh_failure(
235    State(state): State<TokenLifecycleState>,
236    Json(request): Json<ForceRefreshFailureRequest>,
237) -> Result<Json<serde_json::Value>, StatusCode> {
238    // Revoke all tokens for the user to simulate refresh failure
239    let reason = format!("test_scenario:{}", request.failure_type);
240    state
241        .manager
242        .revocation
243        .revoke_user_tokens(request.user_id.clone(), reason)
244        .await;
245
246    Ok(Json(serde_json::json!({
247        "success": true,
248        "user_id": request.user_id,
249        "failure_type": request.failure_type,
250        "message": "Refresh token failure simulated"
251    })))
252}
253
254/// Revoke token mid-session (test scenario)
255pub async fn revoke_mid_session(
256    State(state): State<TokenLifecycleState>,
257    Json(request): Json<RevokeMidSessionRequest>,
258) -> Result<Json<serde_json::Value>, StatusCode> {
259    let state_clone = state.clone();
260    let user_id = request.user_id.clone();
261    let delay = request.delay_seconds;
262
263    tokio::spawn(async move {
264        tokio::time::sleep(tokio::time::Duration::from_secs(delay)).await;
265        state_clone
266            .manager
267            .revocation
268            .revoke_user_tokens(user_id, "mid_session_revocation".to_string())
269            .await;
270    });
271
272    Ok(Json(serde_json::json!({
273        "success": true,
274        "user_id": request.user_id,
275        "delay_seconds": request.delay_seconds,
276        "message": format!("Token will be revoked in {} seconds", request.delay_seconds)
277    })))
278}
279
280/// Create token lifecycle router
281pub fn token_lifecycle_router(state: TokenLifecycleState) -> axum::Router {
282    use axum::routing::{get, post};
283
284    axum::Router::new()
285        .route("/tokens/revoke", post(revoke_token))
286        .route("/tokens/revoke/user", post(revoke_user_tokens))
287        .route("/tokens/status", get(get_token_status))
288        .route("/keys/rotate", post(rotate_keys))
289        .route("/keys/active", get(get_active_keys))
290        .route("/clock/skew", post(set_clock_skew))
291        .route("/clock/skew", get(get_clock_skew))
292        .route("/test/force-refresh-failure", post(force_refresh_failure))
293        .route("/test/revoke-mid-session", post(revoke_mid_session))
294        .with_state(state)
295}