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