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