this repo has no description
1use crate::api::ApiError; 2use crate::api::proxy_client::{ 3 MAX_RESPONSE_SIZE, is_ssrf_safe, proxy_client, validate_at_uri, validate_limit, 4}; 5use crate::state::AppState; 6use axum::{ 7 extract::{Query, State}, 8 http::StatusCode, 9 response::{IntoResponse, Response}, 10}; 11use serde::Deserialize; 12use std::collections::HashMap; 13use tracing::{error, info}; 14 15#[derive(Deserialize)] 16pub struct GetFeedParams { 17 pub feed: String, 18 pub limit: Option<u32>, 19 pub cursor: Option<String>, 20} 21 22pub async fn get_feed( 23 State(state): State<AppState>, 24 headers: axum::http::HeaderMap, 25 Query(params): Query<GetFeedParams>, 26) -> Response { 27 let token = match crate::auth::extract_bearer_token_from_header( 28 headers.get("Authorization").and_then(|h| h.to_str().ok()), 29 ) { 30 Some(t) => t, 31 None => return ApiError::AuthenticationRequired.into_response(), 32 }; 33 let auth_user = match crate::auth::validate_bearer_token(&state.db, &token).await { 34 Ok(user) => user, 35 Err(e) => return ApiError::from(e).into_response(), 36 }; 37 if let Err(e) = validate_at_uri(&params.feed) { 38 return ApiError::InvalidRequest(format!("Invalid feed URI: {}", e)).into_response(); 39 } 40 let appview_url = match std::env::var("APPVIEW_URL") { 41 Ok(url) => url, 42 Err(_) => { 43 return ApiError::UpstreamUnavailable("No upstream AppView configured".to_string()) 44 .into_response(); 45 } 46 }; 47 if let Err(e) = is_ssrf_safe(&appview_url) { 48 error!("SSRF check failed for appview URL: {}", e); 49 return ApiError::UpstreamUnavailable(format!("Invalid upstream URL: {}", e)) 50 .into_response(); 51 } 52 let limit = validate_limit(params.limit, 50, 100); 53 let mut query_params = HashMap::new(); 54 query_params.insert("feed".to_string(), params.feed.clone()); 55 query_params.insert("limit".to_string(), limit.to_string()); 56 if let Some(cursor) = &params.cursor { 57 query_params.insert("cursor".to_string(), cursor.clone()); 58 } 59 let target_url = format!("{}/xrpc/app.bsky.feed.getFeed", appview_url); 60 info!(target = %target_url, feed = %params.feed, "Proxying getFeed request"); 61 let client = proxy_client(); 62 let mut request_builder = client.get(&target_url).query(&query_params); 63 if let Some(key_bytes) = auth_user.key_bytes.as_ref() { 64 let appview_did = 65 std::env::var("APPVIEW_DID").unwrap_or_else(|_| "did:web:api.bsky.app".to_string()); 66 match crate::auth::create_service_token( 67 &auth_user.did, 68 &appview_did, 69 "app.bsky.feed.getFeed", 70 key_bytes, 71 ) { 72 Ok(service_token) => { 73 request_builder = 74 request_builder.header("Authorization", format!("Bearer {}", service_token)); 75 } 76 Err(e) => { 77 error!(error = ?e, "Failed to create service token for getFeed"); 78 return ApiError::InternalError.into_response(); 79 } 80 } 81 } 82 match request_builder.send().await { 83 Ok(resp) => { 84 let status = 85 StatusCode::from_u16(resp.status().as_u16()).unwrap_or(StatusCode::BAD_GATEWAY); 86 let content_length = resp.content_length().unwrap_or(0); 87 if content_length > MAX_RESPONSE_SIZE { 88 error!( 89 content_length, 90 max = MAX_RESPONSE_SIZE, 91 "getFeed response too large" 92 ); 93 return ApiError::UpstreamFailure.into_response(); 94 } 95 let resp_headers = resp.headers().clone(); 96 let body = match resp.bytes().await { 97 Ok(b) => { 98 if b.len() as u64 > MAX_RESPONSE_SIZE { 99 error!(len = b.len(), "getFeed response body exceeded limit"); 100 return ApiError::UpstreamFailure.into_response(); 101 } 102 b 103 } 104 Err(e) => { 105 error!(error = ?e, "Error reading getFeed response"); 106 return ApiError::UpstreamFailure.into_response(); 107 } 108 }; 109 let mut response_builder = axum::response::Response::builder().status(status); 110 if let Some(ct) = resp_headers.get("content-type") { 111 response_builder = response_builder.header("content-type", ct); 112 } 113 match response_builder.body(axum::body::Body::from(body)) { 114 Ok(r) => r, 115 Err(e) => { 116 error!(error = ?e, "Error building getFeed response"); 117 ApiError::UpstreamFailure.into_response() 118 } 119 } 120 } 121 Err(e) => { 122 error!(error = ?e, "Error proxying getFeed"); 123 if e.is_timeout() { 124 ApiError::UpstreamTimeout.into_response() 125 } else if e.is_connect() { 126 ApiError::UpstreamUnavailable("Failed to connect to upstream".to_string()) 127 .into_response() 128 } else { 129 ApiError::UpstreamFailure.into_response() 130 } 131 } 132 } 133}