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