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