Fix bundle lookup: normalize fingerprints, handle 404 gracefully

Server: normalize fingerprints by stripping colons and lowercasing
before storing/looking up in sled. Adds tracing for register/lookup.

Client: check HTTP status before parsing JSON response body.
Shows clear error when user is not registered instead of parse error.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
Siavash Sameni
2026-03-26 22:37:41 +04:00
parent cf7e935250
commit de118371de
2 changed files with 31 additions and 6 deletions

View File

@@ -59,7 +59,7 @@ impl ServerClient {
/// Fetch a user's pre-key bundle from the server. /// Fetch a user's pre-key bundle from the server.
pub async fn fetch_bundle(&self, fingerprint: &str) -> Result<PreKeyBundle> { pub async fn fetch_bundle(&self, fingerprint: &str) -> Result<PreKeyBundle> {
let resp: BundleResponse = self let response = self
.client .client
.get(format!( .get(format!(
"{}/v1/keys/{}", "{}/v1/keys/{}",
@@ -67,7 +67,17 @@ impl ServerClient {
)) ))
.send() .send()
.await .await
.context("failed to fetch bundle")? .context("failed to fetch bundle")?;
if !response.status().is_success() {
anyhow::bail!(
"server returned {} — user {} may not be registered",
response.status(),
fingerprint
);
}
let resp: BundleResponse = response
.json() .json()
.await .await
.context("failed to parse bundle response")?; .context("failed to parse bundle response")?;

View File

@@ -13,10 +13,18 @@ pub fn routes() -> Router<AppState> {
.route("/keys/{fingerprint}", get(get_bundle)) .route("/keys/{fingerprint}", get(get_bundle))
} }
/// Normalize fingerprint: strip colons, lowercase.
fn normalize_fp(fp: &str) -> String {
fp.chars()
.filter(|c| c.is_ascii_hexdigit())
.collect::<String>()
.to_lowercase()
}
#[derive(Deserialize)] #[derive(Deserialize)]
struct RegisterRequest { struct RegisterRequest {
fingerprint: String, fingerprint: String,
bundle: Vec<u8>, // bincode-serialized PreKeyBundle bundle: Vec<u8>,
} }
#[derive(Serialize)] #[derive(Serialize)]
@@ -28,7 +36,9 @@ async fn register_keys(
State(state): State<AppState>, State(state): State<AppState>,
Json(req): Json<RegisterRequest>, Json(req): Json<RegisterRequest>,
) -> Json<RegisterResponse> { ) -> Json<RegisterResponse> {
let _ = state.db.keys.insert(req.fingerprint.as_bytes(), req.bundle); let key = normalize_fp(&req.fingerprint);
tracing::info!("Registering bundle for {}", key);
let _ = state.db.keys.insert(key.as_bytes(), req.bundle);
Json(RegisterResponse { ok: true }) Json(RegisterResponse { ok: true })
} }
@@ -36,11 +46,16 @@ async fn get_bundle(
State(state): State<AppState>, State(state): State<AppState>,
Path(fingerprint): Path<String>, Path(fingerprint): Path<String>,
) -> Result<Json<serde_json::Value>, axum::http::StatusCode> { ) -> Result<Json<serde_json::Value>, axum::http::StatusCode> {
match state.db.keys.get(fingerprint.as_bytes()) { let key = normalize_fp(&fingerprint);
tracing::info!("Looking up bundle for {}", key);
match state.db.keys.get(key.as_bytes()) {
Ok(Some(data)) => Ok(Json(serde_json::json!({ Ok(Some(data)) => Ok(Json(serde_json::json!({
"fingerprint": fingerprint, "fingerprint": fingerprint,
"bundle": base64::Engine::encode(&base64::engine::general_purpose::STANDARD, &data), "bundle": base64::Engine::encode(&base64::engine::general_purpose::STANDARD, &data),
}))), }))),
_ => Err(axum::http::StatusCode::NOT_FOUND), _ => {
tracing::warn!("Bundle not found for {}", key);
Err(axum::http::StatusCode::NOT_FOUND)
}
} }
} }