this repo has no description
1use axum::{Form, Json}; 2use axum::extract::State; 3use axum::http::{HeaderMap, StatusCode}; 4use chrono::Utc; 5use serde::{Deserialize, Serialize}; 6 7use crate::state::AppState; 8use crate::oauth::{OAuthError, db}; 9 10use super::helpers::extract_token_claims; 11 12#[derive(Debug, Deserialize)] 13pub struct RevokeRequest { 14 pub token: Option<String>, 15 #[serde(default)] 16 pub token_type_hint: Option<String>, 17} 18 19pub async fn revoke_token( 20 State(state): State<AppState>, 21 headers: HeaderMap, 22 Form(request): Form<RevokeRequest>, 23) -> Result<StatusCode, OAuthError> { 24 let client_ip = crate::rate_limit::extract_client_ip(&headers, None); 25 if !state.distributed_rate_limiter.check_rate_limit( 26 &format!("oauth_revoke:{}", client_ip), 27 30, 28 60_000, 29 ).await { 30 if state.rate_limiters.oauth_introspect.check_key(&client_ip).is_err() { 31 tracing::warn!(ip = %client_ip, "OAuth revoke rate limit exceeded"); 32 return Err(OAuthError::RateLimited); 33 } 34 } 35 36 if let Some(token) = &request.token { 37 if let Some((db_id, _)) = db::get_token_by_refresh_token(&state.db, token).await? { 38 db::delete_token_family(&state.db, db_id).await?; 39 } else { 40 db::delete_token(&state.db, token).await?; 41 } 42 } 43 44 Ok(StatusCode::OK) 45} 46 47#[derive(Debug, Deserialize)] 48pub struct IntrospectRequest { 49 pub token: String, 50 #[serde(default)] 51 pub token_type_hint: Option<String>, 52} 53 54#[derive(Debug, Serialize)] 55pub struct IntrospectResponse { 56 pub active: bool, 57 #[serde(skip_serializing_if = "Option::is_none")] 58 pub scope: Option<String>, 59 #[serde(skip_serializing_if = "Option::is_none")] 60 pub client_id: Option<String>, 61 #[serde(skip_serializing_if = "Option::is_none")] 62 pub username: Option<String>, 63 #[serde(skip_serializing_if = "Option::is_none")] 64 pub token_type: Option<String>, 65 #[serde(skip_serializing_if = "Option::is_none")] 66 pub exp: Option<i64>, 67 #[serde(skip_serializing_if = "Option::is_none")] 68 pub iat: Option<i64>, 69 #[serde(skip_serializing_if = "Option::is_none")] 70 pub nbf: Option<i64>, 71 #[serde(skip_serializing_if = "Option::is_none")] 72 pub sub: Option<String>, 73 #[serde(skip_serializing_if = "Option::is_none")] 74 pub aud: Option<String>, 75 #[serde(skip_serializing_if = "Option::is_none")] 76 pub iss: Option<String>, 77 #[serde(skip_serializing_if = "Option::is_none")] 78 pub jti: Option<String>, 79} 80 81pub async fn introspect_token( 82 State(state): State<AppState>, 83 headers: HeaderMap, 84 Form(request): Form<IntrospectRequest>, 85) -> Result<Json<IntrospectResponse>, OAuthError> { 86 let client_ip = crate::rate_limit::extract_client_ip(&headers, None); 87 if !state.distributed_rate_limiter.check_rate_limit( 88 &format!("oauth_introspect:{}", client_ip), 89 30, 90 60_000, 91 ).await { 92 if state.rate_limiters.oauth_introspect.check_key(&client_ip).is_err() { 93 tracing::warn!(ip = %client_ip, "OAuth introspect rate limit exceeded"); 94 return Err(OAuthError::RateLimited); 95 } 96 } 97 98 let inactive_response = IntrospectResponse { 99 active: false, 100 scope: None, 101 client_id: None, 102 username: None, 103 token_type: None, 104 exp: None, 105 iat: None, 106 nbf: None, 107 sub: None, 108 aud: None, 109 iss: None, 110 jti: None, 111 }; 112 113 let token_info = match extract_token_claims(&request.token) { 114 Ok(info) => info, 115 Err(_) => return Ok(Json(inactive_response)), 116 }; 117 118 let token_data = match db::get_token_by_id(&state.db, &token_info.jti).await { 119 Ok(Some(data)) => data, 120 _ => return Ok(Json(inactive_response)), 121 }; 122 123 if token_data.expires_at < Utc::now() { 124 return Ok(Json(inactive_response)); 125 } 126 127 let pds_hostname = std::env::var("PDS_HOSTNAME").unwrap_or_else(|_| "localhost".to_string()); 128 let issuer = format!("https://{}", pds_hostname); 129 130 Ok(Json(IntrospectResponse { 131 active: true, 132 scope: token_data.scope, 133 client_id: Some(token_data.client_id), 134 username: None, 135 token_type: if token_data.parameters.dpop_jkt.is_some() { 136 Some("DPoP".to_string()) 137 } else { 138 Some("Bearer".to_string()) 139 }, 140 exp: Some(token_info.exp), 141 iat: Some(token_info.iat), 142 nbf: Some(token_info.iat), 143 sub: Some(token_data.did), 144 aud: Some(issuer.clone()), 145 iss: Some(issuer), 146 jti: Some(token_info.jti), 147 })) 148}