this repo has no description
1use crate::api::proxy_client::proxy_client; 2use crate::state::AppState; 3use axum::{ 4 Json, 5 body::Bytes, 6 extract::{Path, RawQuery, State}, 7 http::{HeaderMap, Method, StatusCode}, 8 response::{IntoResponse, Response}, 9}; 10use serde_json::json; 11use tracing::{error, info, warn}; 12 13const PROTECTED_METHODS: &[&str] = &[ 14 "com.atproto.admin.sendEmail", 15 "com.atproto.identity.requestPlcOperationSignature", 16 "com.atproto.identity.signPlcOperation", 17 "com.atproto.identity.updateHandle", 18 "com.atproto.server.activateAccount", 19 "com.atproto.server.confirmEmail", 20 "com.atproto.server.createAppPassword", 21 "com.atproto.server.deactivateAccount", 22 "com.atproto.server.getAccountInviteCodes", 23 "com.atproto.server.getSession", 24 "com.atproto.server.listAppPasswords", 25 "com.atproto.server.requestAccountDelete", 26 "com.atproto.server.requestEmailConfirmation", 27 "com.atproto.server.requestEmailUpdate", 28 "com.atproto.server.revokeAppPassword", 29 "com.atproto.server.updateEmail", 30]; 31 32fn is_protected_method(method: &str) -> bool { 33 PROTECTED_METHODS.contains(&method) 34} 35 36pub async fn proxy_handler( 37 State(state): State<AppState>, 38 Path(method): Path<String>, 39 method_verb: Method, 40 headers: HeaderMap, 41 RawQuery(query): RawQuery, 42 body: Bytes, 43) -> Response { 44 if is_protected_method(&method) { 45 warn!(method = %method, "Attempted to proxy protected method"); 46 return ( 47 StatusCode::BAD_REQUEST, 48 Json(json!({ 49 "error": "InvalidRequest", 50 "message": format!("Cannot proxy protected method: {}", method) 51 })), 52 ) 53 .into_response(); 54 } 55 56 let proxy_header = match headers.get("atproto-proxy").and_then(|h| h.to_str().ok()) { 57 Some(h) => h.to_string(), 58 None => { 59 return ( 60 StatusCode::BAD_REQUEST, 61 Json(json!({ 62 "error": "InvalidRequest", 63 "message": "Missing required atproto-proxy header" 64 })), 65 ) 66 .into_response(); 67 } 68 }; 69 70 let did = proxy_header.split('#').next().unwrap_or(&proxy_header); 71 let resolved = match state.did_resolver.resolve_did(did).await { 72 Some(r) => r, 73 None => { 74 error!(did = %did, "Could not resolve service DID"); 75 return ( 76 StatusCode::BAD_GATEWAY, 77 Json(json!({ 78 "error": "UpstreamFailure", 79 "message": "Could not resolve service DID" 80 })), 81 ) 82 .into_response(); 83 } 84 }; 85 86 let target_url = match &query { 87 Some(q) => format!("{}/xrpc/{}?{}", resolved.url, method, q), 88 None => format!("{}/xrpc/{}", resolved.url, method), 89 }; 90 info!("Proxying {} request to {}", method_verb, target_url); 91 92 let client = proxy_client(); 93 let mut request_builder = client.request(method_verb, &target_url); 94 95 let mut auth_header_val = headers.get("Authorization").cloned(); 96 if let Some(token) = crate::auth::extract_bearer_token_from_header( 97 headers.get("Authorization").and_then(|h| h.to_str().ok()), 98 ) { 99 match crate::auth::validate_bearer_token(&state.db, &token).await { 100 Ok(auth_user) => { 101 if let Err(e) = crate::auth::scope_check::check_rpc_scope( 102 auth_user.is_oauth, 103 auth_user.scope.as_deref(), 104 &resolved.did, 105 &method, 106 ) { 107 return e; 108 } 109 110 if let Some(key_bytes) = auth_user.key_bytes { 111 match crate::auth::create_service_token( 112 &auth_user.did, 113 &resolved.did, 114 &method, 115 &key_bytes, 116 ) { 117 Ok(new_token) => { 118 if let Ok(val) = 119 axum::http::HeaderValue::from_str(&format!("Bearer {}", new_token)) 120 { 121 auth_header_val = Some(val); 122 } 123 } 124 Err(e) => { 125 warn!("Failed to create service token: {:?}", e); 126 } 127 } 128 } 129 } 130 Err(e) => { 131 warn!("Token validation failed: {:?}", e); 132 if matches!(e, crate::auth::TokenValidationError::TokenExpired) { 133 return ( 134 StatusCode::BAD_REQUEST, 135 Json(json!({ 136 "error": "ExpiredToken", 137 "message": "Token has expired" 138 })), 139 ) 140 .into_response(); 141 } 142 } 143 } 144 } 145 146 if let Some(val) = auth_header_val { 147 request_builder = request_builder.header("Authorization", val); 148 } 149 for header_name in crate::api::proxy_client::HEADERS_TO_FORWARD { 150 if let Some(val) = headers.get(*header_name) { 151 request_builder = request_builder.header(*header_name, val); 152 } 153 } 154 if !body.is_empty() { 155 request_builder = request_builder.body(body); 156 } 157 158 match request_builder.send().await { 159 Ok(resp) => { 160 let status = resp.status(); 161 let headers = resp.headers().clone(); 162 let body = match resp.bytes().await { 163 Ok(b) => b, 164 Err(e) => { 165 error!("Error reading proxy response body: {:?}", e); 166 return (StatusCode::BAD_GATEWAY, "Error reading upstream response") 167 .into_response(); 168 } 169 }; 170 let mut response_builder = Response::builder().status(status); 171 for header_name in crate::api::proxy_client::RESPONSE_HEADERS_TO_FORWARD { 172 if let Some(val) = headers.get(*header_name) { 173 response_builder = response_builder.header(*header_name, val); 174 } 175 } 176 match response_builder.body(axum::body::Body::from(body)) { 177 Ok(r) => r, 178 Err(e) => { 179 error!("Error building proxy response: {:?}", e); 180 (StatusCode::INTERNAL_SERVER_ERROR, "Internal Server Error").into_response() 181 } 182 } 183 } 184 Err(e) => { 185 error!("Error sending proxy request: {:?}", e); 186 if e.is_timeout() { 187 (StatusCode::GATEWAY_TIMEOUT, "Upstream Timeout").into_response() 188 } else { 189 (StatusCode::BAD_GATEWAY, "Upstream Error").into_response() 190 } 191 } 192 } 193}