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