this repo has no description
1use axum::http::HeaderMap; 2use axum::Json; 3use chrono::{Duration, Utc}; 4use crate::config::AuthConfig; 5use crate::state::AppState; 6use crate::oauth::{ 7 ClientAuth, OAuthError, RefreshToken, TokenData, TokenId, 8 client::{ClientMetadataCache, verify_client_auth}, 9 db, 10 dpop::DPoPVerifier, 11}; 12use super::types::{TokenRequest, TokenResponse}; 13use super::helpers::{create_access_token, verify_pkce}; 14const ACCESS_TOKEN_EXPIRY_SECONDS: i64 = 3600; 15const REFRESH_TOKEN_EXPIRY_DAYS: i64 = 60; 16pub async fn handle_authorization_code_grant( 17 state: AppState, 18 _headers: HeaderMap, 19 request: TokenRequest, 20 dpop_proof: Option<String>, 21) -> Result<(HeaderMap, Json<TokenResponse>), OAuthError> { 22 let code = request 23 .code 24 .ok_or_else(|| OAuthError::InvalidRequest("code is required".to_string()))?; 25 let code_verifier = request 26 .code_verifier 27 .ok_or_else(|| OAuthError::InvalidRequest("code_verifier is required".to_string()))?; 28 let auth_request = db::consume_authorization_request_by_code(&state.db, &code) 29 .await? 30 .ok_or_else(|| OAuthError::InvalidGrant("Invalid or expired code".to_string()))?; 31 if auth_request.expires_at < Utc::now() { 32 return Err(OAuthError::InvalidGrant("Authorization code has expired".to_string())); 33 } 34 if let Some(request_client_id) = &request.client_id { 35 if request_client_id != &auth_request.client_id { 36 return Err(OAuthError::InvalidGrant("client_id mismatch".to_string())); 37 } 38 } 39 let did = auth_request 40 .did 41 .ok_or_else(|| OAuthError::InvalidGrant("Authorization not completed".to_string()))?; 42 let client_metadata_cache = ClientMetadataCache::new(3600); 43 let client_metadata = client_metadata_cache 44 .get(&auth_request.client_id) 45 .await?; 46 let client_auth = auth_request.client_auth.clone().unwrap_or(ClientAuth::None); 47 verify_client_auth(&client_metadata_cache, &client_metadata, &client_auth).await?; 48 verify_pkce(&auth_request.parameters.code_challenge, &code_verifier)?; 49 if let Some(redirect_uri) = &request.redirect_uri { 50 if redirect_uri != &auth_request.parameters.redirect_uri { 51 return Err(OAuthError::InvalidGrant("redirect_uri mismatch".to_string())); 52 } 53 } 54 let dpop_jkt = if let Some(proof) = &dpop_proof { 55 let config = AuthConfig::get(); 56 let verifier = DPoPVerifier::new(config.dpop_secret().as_bytes()); 57 let pds_hostname = std::env::var("PDS_HOSTNAME").unwrap_or_else(|_| "localhost".to_string()); 58 let token_endpoint = format!("https://{}/oauth/token", pds_hostname); 59 let result = verifier.verify_proof(proof, "POST", &token_endpoint, None)?; 60 if !db::check_and_record_dpop_jti(&state.db, &result.jti).await? { 61 return Err(OAuthError::InvalidDpopProof( 62 "DPoP proof has already been used".to_string(), 63 )); 64 } 65 if let Some(expected_jkt) = &auth_request.parameters.dpop_jkt { 66 if &result.jkt != expected_jkt { 67 return Err(OAuthError::InvalidDpopProof( 68 "DPoP key binding mismatch".to_string(), 69 )); 70 } 71 } 72 Some(result.jkt) 73 } else if auth_request.parameters.dpop_jkt.is_some() { 74 return Err(OAuthError::InvalidRequest( 75 "DPoP proof required for this authorization".to_string(), 76 )); 77 } else { 78 None 79 }; 80 let token_id = TokenId::generate(); 81 let refresh_token = RefreshToken::generate(); 82 let now = Utc::now(); 83 let access_token = create_access_token(&token_id.0, &did, dpop_jkt.as_deref())?; 84 let token_data = TokenData { 85 did: did.clone(), 86 token_id: token_id.0.clone(), 87 created_at: now, 88 updated_at: now, 89 expires_at: now + Duration::days(REFRESH_TOKEN_EXPIRY_DAYS), 90 client_id: auth_request.client_id.clone(), 91 client_auth: auth_request.client_auth.unwrap_or(ClientAuth::None), 92 device_id: auth_request.device_id, 93 parameters: auth_request.parameters.clone(), 94 details: None, 95 code: None, 96 current_refresh_token: Some(refresh_token.0.clone()), 97 scope: auth_request.parameters.scope.clone(), 98 }; 99 db::create_token(&state.db, &token_data).await?; 100 tokio::spawn({ 101 let pool = state.db.clone(); 102 let did_clone = did.clone(); 103 async move { 104 if let Err(e) = db::enforce_token_limit_for_user(&pool, &did_clone).await { 105 tracing::warn!("Failed to enforce token limit for user: {:?}", e); 106 } 107 } 108 }); 109 let mut response_headers = HeaderMap::new(); 110 let config = AuthConfig::get(); 111 let verifier = DPoPVerifier::new(config.dpop_secret().as_bytes()); 112 response_headers.insert( 113 "DPoP-Nonce", 114 verifier.generate_nonce().parse().unwrap(), 115 ); 116 Ok(( 117 response_headers, 118 Json(TokenResponse { 119 access_token, 120 token_type: if dpop_jkt.is_some() { "DPoP" } else { "Bearer" }.to_string(), 121 expires_in: ACCESS_TOKEN_EXPIRY_SECONDS as u64, 122 refresh_token: Some(refresh_token.0), 123 scope: auth_request.parameters.scope, 124 sub: Some(did), 125 }), 126 )) 127} 128pub async fn handle_refresh_token_grant( 129 state: AppState, 130 _headers: HeaderMap, 131 request: TokenRequest, 132 dpop_proof: Option<String>, 133) -> Result<(HeaderMap, Json<TokenResponse>), OAuthError> { 134 let refresh_token_str = request 135 .refresh_token 136 .ok_or_else(|| OAuthError::InvalidRequest("refresh_token is required".to_string()))?; 137 if let Some(token_id) = db::check_refresh_token_used(&state.db, &refresh_token_str).await? { 138 db::delete_token_family(&state.db, token_id).await?; 139 return Err(OAuthError::InvalidGrant( 140 "Refresh token reuse detected, token family revoked".to_string(), 141 )); 142 } 143 let (db_id, token_data) = db::get_token_by_refresh_token(&state.db, &refresh_token_str) 144 .await? 145 .ok_or_else(|| OAuthError::InvalidGrant("Invalid refresh token".to_string()))?; 146 if token_data.expires_at < Utc::now() { 147 db::delete_token_family(&state.db, db_id).await?; 148 return Err(OAuthError::InvalidGrant("Refresh token has expired".to_string())); 149 } 150 let dpop_jkt = if let Some(proof) = &dpop_proof { 151 let config = AuthConfig::get(); 152 let verifier = DPoPVerifier::new(config.dpop_secret().as_bytes()); 153 let pds_hostname = std::env::var("PDS_HOSTNAME").unwrap_or_else(|_| "localhost".to_string()); 154 let token_endpoint = format!("https://{}/oauth/token", pds_hostname); 155 let result = verifier.verify_proof(proof, "POST", &token_endpoint, None)?; 156 if !db::check_and_record_dpop_jti(&state.db, &result.jti).await? { 157 return Err(OAuthError::InvalidDpopProof( 158 "DPoP proof has already been used".to_string(), 159 )); 160 } 161 if let Some(expected_jkt) = &token_data.parameters.dpop_jkt { 162 if &result.jkt != expected_jkt { 163 return Err(OAuthError::InvalidDpopProof( 164 "DPoP key binding mismatch".to_string(), 165 )); 166 } 167 } 168 Some(result.jkt) 169 } else if token_data.parameters.dpop_jkt.is_some() { 170 return Err(OAuthError::InvalidRequest( 171 "DPoP proof required".to_string(), 172 )); 173 } else { 174 None 175 }; 176 let new_token_id = TokenId::generate(); 177 let new_refresh_token = RefreshToken::generate(); 178 let new_expires_at = Utc::now() + Duration::days(REFRESH_TOKEN_EXPIRY_DAYS); 179 db::rotate_token( 180 &state.db, 181 db_id, 182 &new_token_id.0, 183 &new_refresh_token.0, 184 new_expires_at, 185 ) 186 .await?; 187 let access_token = create_access_token(&new_token_id.0, &token_data.did, dpop_jkt.as_deref())?; 188 let mut response_headers = HeaderMap::new(); 189 let config = AuthConfig::get(); 190 let verifier = DPoPVerifier::new(config.dpop_secret().as_bytes()); 191 response_headers.insert( 192 "DPoP-Nonce", 193 verifier.generate_nonce().parse().unwrap(), 194 ); 195 Ok(( 196 response_headers, 197 Json(TokenResponse { 198 access_token, 199 token_type: if dpop_jkt.is_some() { "DPoP" } else { "Bearer" }.to_string(), 200 expires_in: ACCESS_TOKEN_EXPIRY_SECONDS as u64, 201 refresh_token: Some(new_refresh_token.0), 202 scope: token_data.scope, 203 sub: Some(token_data.did), 204 }), 205 )) 206}