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 crate::api::proxy_client::proxy_client;
9use std::collections::HashMap;
10use tracing::error;
11
12fn resolve_service_did(did_with_fragment: &str) -> Option<(String, String)> {
13 if did_with_fragment.starts_with("did:web:") {
14 let without_prefix = &did_with_fragment[8..];
15 let host = without_prefix.split('#').next()?;
16 let url = format!("https://{}", host);
17 let did_without_fragment = format!("did:web:{}", host);
18 Some((url, did_without_fragment))
19 } else if did_with_fragment.starts_with("did:plc:") {
20 None
21 } else {
22 None
23 }
24}
25
26pub async fn proxy_handler(
27 State(state): State<AppState>,
28 Path(method): Path<String>,
29 method_verb: Method,
30 headers: HeaderMap,
31 Query(params): Query<HashMap<String, String>>,
32 body: Bytes,
33) -> Response {
34 let proxy_header = headers
35 .get("atproto-proxy")
36 .and_then(|h| h.to_str().ok())
37 .map(|s| s.to_string());
38 let (appview_url, service_aud) = match &proxy_header {
39 Some(did_str) => {
40 let (url, did_without_fragment) = match resolve_service_did(did_str) {
41 Some(resolved) => resolved,
42 None => {
43 error!(did = %did_str, "Could not resolve service DID");
44 return (StatusCode::BAD_GATEWAY, "Could not resolve service DID").into_response();
45 }
46 };
47 (url, Some(did_without_fragment))
48 }
49 None => {
50 let url = match std::env::var("APPVIEW_URL") {
51 Ok(url) => url,
52 Err(_) => {
53 return (StatusCode::BAD_GATEWAY, "No upstream AppView configured").into_response();
54 }
55 };
56 let aud = std::env::var("APPVIEW_DID").ok();
57 (url, aud)
58 }
59 };
60 let target_url = format!("{}/xrpc/{}", appview_url, method);
61 let client = proxy_client();
62 let mut request_builder = client.request(method_verb, &target_url).query(¶ms);
63 let mut auth_header_val = headers.get("Authorization").map(|h| h.clone());
64 if let Some(aud) = &service_aud {
65 if let Some(token) = crate::auth::extract_bearer_token_from_header(
66 headers.get("Authorization").and_then(|h| h.to_str().ok())
67 ) {
68 if let Ok(auth_user) = crate::auth::validate_bearer_token(&state.db, &token).await {
69 if let Some(key_bytes) = auth_user.key_bytes {
70 if let Ok(new_token) =
71 crate::auth::create_service_token(&auth_user.did, aud, &method, &key_bytes)
72 {
73 if let Ok(val) =
74 axum::http::HeaderValue::from_str(&format!("Bearer {}", new_token))
75 {
76 auth_header_val = Some(val);
77 }
78 }
79 }
80 }
81 }
82 }
83 if let Some(val) = auth_header_val {
84 request_builder = request_builder.header("Authorization", val);
85 }
86 for (key, value) in headers.iter() {
87 if key != "host" && key != "content-length" && key != "authorization" {
88 request_builder = request_builder.header(key, value);
89 }
90 }
91 request_builder = request_builder.body(body);
92 match request_builder.send().await {
93 Ok(resp) => {
94 let status = resp.status();
95 let headers = resp.headers().clone();
96 let body = match resp.bytes().await {
97 Ok(b) => b,
98 Err(e) => {
99 error!("Error reading proxy response body: {:?}", e);
100 return (StatusCode::BAD_GATEWAY, "Error reading upstream response")
101 .into_response();
102 }
103 };
104 let mut response_builder = Response::builder().status(status);
105 for (key, value) in headers.iter() {
106 response_builder = response_builder.header(key, value);
107 }
108 match response_builder.body(axum::body::Body::from(body)) {
109 Ok(r) => r,
110 Err(e) => {
111 error!("Error building proxy response: {:?}", e);
112 (StatusCode::INTERNAL_SERVER_ERROR, "Internal Server Error").into_response()
113 }
114 }
115 }
116 Err(e) => {
117 error!("Error sending proxy request: {:?}", e);
118 if e.is_timeout() {
119 (StatusCode::GATEWAY_TIMEOUT, "Upstream Timeout").into_response()
120 } else {
121 (StatusCode::BAD_GATEWAY, "Upstream Error").into_response()
122 }
123 }
124 }
125}