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