this repo has no description
1use crate::api::proxy_client::{is_ssrf_safe, proxy_client, validate_did}; 2use crate::api::ApiError; 3use crate::state::AppState; 4use axum::{ 5 extract::State, 6 http::{HeaderMap, StatusCode}, 7 response::{IntoResponse, Response}, 8 Json, 9}; 10use serde::Deserialize; 11use serde_json::json; 12use tracing::{error, info}; 13 14#[derive(Deserialize)] 15#[serde(rename_all = "camelCase")] 16pub struct RegisterPushInput { 17 pub service_did: String, 18 pub token: String, 19 pub platform: String, 20 pub app_id: String, 21} 22 23const VALID_PLATFORMS: &[&str] = &["ios", "android", "web"]; 24 25pub async fn register_push( 26 State(state): State<AppState>, 27 headers: HeaderMap, 28 Json(input): Json<RegisterPushInput>, 29) -> Response { 30 let token = match crate::auth::extract_bearer_token_from_header( 31 headers.get("Authorization").and_then(|h| h.to_str().ok()), 32 ) { 33 Some(t) => t, 34 None => return ApiError::AuthenticationRequired.into_response(), 35 }; 36 let auth_user = match crate::auth::validate_bearer_token(&state.db, &token).await { 37 Ok(user) => user, 38 Err(e) => return ApiError::from(e).into_response(), 39 }; 40 if let Err(e) = validate_did(&input.service_did) { 41 return ApiError::InvalidRequest(format!("Invalid serviceDid: {}", e)).into_response(); 42 } 43 if input.token.is_empty() || input.token.len() > 4096 { 44 return ApiError::InvalidRequest("Invalid push token".to_string()).into_response(); 45 } 46 if !VALID_PLATFORMS.contains(&input.platform.as_str()) { 47 return ApiError::InvalidRequest(format!( 48 "Invalid platform. Must be one of: {}", 49 VALID_PLATFORMS.join(", ") 50 )) 51 .into_response(); 52 } 53 if input.app_id.is_empty() || input.app_id.len() > 256 { 54 return ApiError::InvalidRequest("Invalid appId".to_string()).into_response(); 55 } 56 let appview_url = match std::env::var("APPVIEW_URL") { 57 Ok(url) => url, 58 Err(_) => { 59 return ApiError::UpstreamUnavailable("No upstream AppView configured".to_string()) 60 .into_response(); 61 } 62 }; 63 if let Err(e) = is_ssrf_safe(&appview_url) { 64 error!("SSRF check failed for appview URL: {}", e); 65 return ApiError::UpstreamUnavailable(format!("Invalid upstream URL: {}", e)) 66 .into_response(); 67 } 68 let key_row = match sqlx::query!( 69 "SELECT key_bytes, encryption_version FROM user_keys k JOIN users u ON k.user_id = u.id WHERE u.did = $1", 70 auth_user.did 71 ) 72 .fetch_optional(&state.db) 73 .await 74 { 75 Ok(Some(row)) => row, 76 Ok(None) => { 77 error!(did = %auth_user.did, "No signing key found for user"); 78 return ApiError::InternalError.into_response(); 79 } 80 Err(e) => { 81 error!(error = ?e, "Database error fetching signing key"); 82 return ApiError::DatabaseError.into_response(); 83 } 84 }; 85 let decrypted_key = 86 match crate::config::decrypt_key(&key_row.key_bytes, key_row.encryption_version) { 87 Ok(k) => k, 88 Err(e) => { 89 error!(error = ?e, "Failed to decrypt signing key"); 90 return ApiError::InternalError.into_response(); 91 } 92 }; 93 let service_token = match crate::auth::create_service_token( 94 &auth_user.did, 95 &input.service_did, 96 "app.bsky.notification.registerPush", 97 &decrypted_key, 98 ) { 99 Ok(t) => t, 100 Err(e) => { 101 error!(error = ?e, "Failed to create service token"); 102 return ApiError::InternalError.into_response(); 103 } 104 }; 105 let target_url = format!("{}/xrpc/app.bsky.notification.registerPush", appview_url); 106 info!( 107 target = %target_url, 108 service_did = %input.service_did, 109 platform = %input.platform, 110 "Proxying registerPush request" 111 ); 112 let client = proxy_client(); 113 let request_body = json!({ 114 "serviceDid": input.service_did, 115 "token": input.token, 116 "platform": input.platform, 117 "appId": input.app_id 118 }); 119 match client 120 .post(&target_url) 121 .header("Authorization", format!("Bearer {}", service_token)) 122 .header("Content-Type", "application/json") 123 .json(&request_body) 124 .send() 125 .await 126 { 127 Ok(resp) => { 128 let status = 129 StatusCode::from_u16(resp.status().as_u16()).unwrap_or(StatusCode::BAD_GATEWAY); 130 if status.is_success() { 131 StatusCode::OK.into_response() 132 } else { 133 let body = resp.bytes().await.unwrap_or_default(); 134 error!( 135 status = %status, 136 "registerPush upstream error" 137 ); 138 ApiError::from_upstream_response(status.as_u16(), &body).into_response() 139 } 140 } 141 Err(e) => { 142 error!(error = ?e, "Error proxying registerPush"); 143 if e.is_timeout() { 144 ApiError::UpstreamTimeout.into_response() 145 } else if e.is_connect() { 146 ApiError::UpstreamUnavailable("Failed to connect to upstream".to_string()) 147 .into_response() 148 } else { 149 ApiError::UpstreamFailure.into_response() 150 } 151 } 152 } 153}