this repo has no description
1use crate::state::AppState;
2use axum::{
3 body::Bytes,
4 extract::{Path, Query, State},
5 http::{HeaderMap, Method, StatusCode},
6 response::{IntoResponse, Response},
7};
8use reqwest::Client;
9use std::collections::HashMap;
10use tracing::{error, info};
11
12pub async fn proxy_handler(
13 State(state): State<AppState>,
14 Path(method): Path<String>,
15 method_verb: Method,
16 headers: HeaderMap,
17 Query(params): Query<HashMap<String, String>>,
18 body: Bytes,
19) -> Response {
20 let proxy_header = headers
21 .get("atproto-proxy")
22 .and_then(|h| h.to_str().ok())
23 .map(|s| s.to_string());
24
25 let appview_url = match &proxy_header {
26 Some(url) => url.clone(),
27 None => match std::env::var("APPVIEW_URL") {
28 Ok(url) => url,
29 Err(_) => {
30 return (StatusCode::BAD_GATEWAY, "No upstream AppView configured").into_response();
31 }
32 },
33 };
34
35 let target_url = format!("{}/xrpc/{}", appview_url, method);
36
37 info!("Proxying {} request to {}", method_verb, target_url);
38
39 let client = Client::new();
40
41 let mut request_builder = client.request(method_verb, &target_url).query(¶ms);
42
43 let mut auth_header_val = headers.get("Authorization").map(|h| h.clone());
44
45 if let Some(aud) = &proxy_header {
46 if let Some(token) = crate::auth::extract_bearer_token_from_header(
47 headers.get("Authorization").and_then(|h| h.to_str().ok())
48 ) {
49 if let Ok(did) = crate::auth::get_did_from_token(&token) {
50 let key_row = sqlx::query!("SELECT k.key_bytes, k.encryption_version FROM user_keys k JOIN users u ON k.user_id = u.id WHERE u.did = $1", did)
51 .fetch_optional(&state.db)
52 .await;
53
54 if let Ok(Some(row)) = key_row {
55 if let Ok(decrypted_key) = crate::config::decrypt_key(&row.key_bytes, row.encryption_version) {
56 if let Ok(new_token) =
57 crate::auth::create_service_token(&did, aud, &method, &decrypted_key)
58 {
59 if let Ok(val) =
60 axum::http::HeaderValue::from_str(&format!("Bearer {}", new_token))
61 {
62 auth_header_val = Some(val);
63 }
64 }
65 }
66 }
67 }
68 }
69 }
70
71 if let Some(val) = auth_header_val {
72 request_builder = request_builder.header("Authorization", val);
73 }
74
75 for (key, value) in headers.iter() {
76 if key != "host" && key != "content-length" && key != "authorization" {
77 request_builder = request_builder.header(key, value);
78 }
79 }
80
81 request_builder = request_builder.body(body);
82
83 match request_builder.send().await {
84 Ok(resp) => {
85 let status = resp.status();
86 let headers = resp.headers().clone();
87 let body = match resp.bytes().await {
88 Ok(b) => b,
89 Err(e) => {
90 error!("Error reading proxy response body: {:?}", e);
91 return (StatusCode::BAD_GATEWAY, "Error reading upstream response")
92 .into_response();
93 }
94 };
95
96 let mut response_builder = Response::builder().status(status);
97
98 for (key, value) in headers.iter() {
99 response_builder = response_builder.header(key, value);
100 }
101
102 match response_builder.body(axum::body::Body::from(body)) {
103 Ok(r) => r,
104 Err(e) => {
105 error!("Error building proxy response: {:?}", e);
106 (StatusCode::INTERNAL_SERVER_ERROR, "Internal Server Error").into_response()
107 }
108 }
109 }
110 Err(e) => {
111 error!("Error sending proxy request: {:?}", e);
112 if e.is_timeout() {
113 (StatusCode::GATEWAY_TIMEOUT, "Upstream Timeout").into_response()
114 } else {
115 (StatusCode::BAD_GATEWAY, "Upstream Error").into_response()
116 }
117 }
118 }
119}