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