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 37 let auth_user = match crate::auth::validate_bearer_token(&state.db, &token).await { 38 Ok(user) => user, 39 Err(e) => return ApiError::from(e).into_response(), 40 }; 41 42 if let Err(e) = validate_did(&input.service_did) { 43 return ApiError::InvalidRequest(format!("Invalid serviceDid: {}", e)).into_response(); 44 } 45 46 if input.token.is_empty() || input.token.len() > 4096 { 47 return ApiError::InvalidRequest("Invalid push token".to_string()).into_response(); 48 } 49 50 if !VALID_PLATFORMS.contains(&input.platform.as_str()) { 51 return ApiError::InvalidRequest(format!( 52 "Invalid platform. Must be one of: {}", 53 VALID_PLATFORMS.join(", ") 54 )) 55 .into_response(); 56 } 57 58 if input.app_id.is_empty() || input.app_id.len() > 256 { 59 return ApiError::InvalidRequest("Invalid appId".to_string()).into_response(); 60 } 61 62 let appview_url = match std::env::var("APPVIEW_URL") { 63 Ok(url) => url, 64 Err(_) => { 65 return ApiError::UpstreamUnavailable("No upstream AppView configured".to_string()) 66 .into_response(); 67 } 68 }; 69 70 if let Err(e) = is_ssrf_safe(&appview_url) { 71 error!("SSRF check failed for appview URL: {}", e); 72 return ApiError::UpstreamUnavailable(format!("Invalid upstream URL: {}", e)) 73 .into_response(); 74 } 75 76 let key_row = match sqlx::query!( 77 "SELECT key_bytes, encryption_version FROM user_keys k JOIN users u ON k.user_id = u.id WHERE u.did = $1", 78 auth_user.did 79 ) 80 .fetch_optional(&state.db) 81 .await 82 { 83 Ok(Some(row)) => row, 84 Ok(None) => { 85 error!(did = %auth_user.did, "No signing key found for user"); 86 return ApiError::InternalError.into_response(); 87 } 88 Err(e) => { 89 error!(error = ?e, "Database error fetching signing key"); 90 return ApiError::DatabaseError.into_response(); 91 } 92 }; 93 94 let decrypted_key = 95 match crate::config::decrypt_key(&key_row.key_bytes, key_row.encryption_version) { 96 Ok(k) => k, 97 Err(e) => { 98 error!(error = ?e, "Failed to decrypt signing key"); 99 return ApiError::InternalError.into_response(); 100 } 101 }; 102 103 let service_token = match crate::auth::create_service_token( 104 &auth_user.did, 105 &input.service_did, 106 "app.bsky.notification.registerPush", 107 &decrypted_key, 108 ) { 109 Ok(t) => t, 110 Err(e) => { 111 error!(error = ?e, "Failed to create service token"); 112 return ApiError::InternalError.into_response(); 113 } 114 }; 115 116 let target_url = format!("{}/xrpc/app.bsky.notification.registerPush", appview_url); 117 info!( 118 target = %target_url, 119 service_did = %input.service_did, 120 platform = %input.platform, 121 "Proxying registerPush request" 122 ); 123 124 let client = proxy_client(); 125 let request_body = json!({ 126 "serviceDid": input.service_did, 127 "token": input.token, 128 "platform": input.platform, 129 "appId": input.app_id 130 }); 131 132 match client 133 .post(&target_url) 134 .header("Authorization", format!("Bearer {}", service_token)) 135 .header("Content-Type", "application/json") 136 .json(&request_body) 137 .send() 138 .await 139 { 140 Ok(resp) => { 141 let status = 142 StatusCode::from_u16(resp.status().as_u16()).unwrap_or(StatusCode::BAD_GATEWAY); 143 if status.is_success() { 144 StatusCode::OK.into_response() 145 } else { 146 let body = resp.bytes().await.unwrap_or_default(); 147 error!( 148 status = %status, 149 "registerPush upstream error" 150 ); 151 ApiError::from_upstream_response(status.as_u16(), &body).into_response() 152 } 153 } 154 Err(e) => { 155 error!(error = ?e, "Error proxying registerPush"); 156 if e.is_timeout() { 157 ApiError::UpstreamTimeout.into_response() 158 } else if e.is_connect() { 159 ApiError::UpstreamUnavailable("Failed to connect to upstream".to_string()) 160 .into_response() 161 } else { 162 ApiError::UpstreamFailure.into_response() 163 } 164 } 165 } 166}