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.
pub async fn fetch_bundle(&self, fingerprint: &str) -> Result<PreKeyBundle> {
let resp: BundleResponse = self
let response = self
.client
.get(format!(
"{}/v1/keys/{}",
@@ -67,7 +67,17 @@ impl ServerClient {
))
.send()
.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()
.await
.context("failed to parse bundle response")?;

View File

@@ -13,10 +13,18 @@ pub fn routes() -> Router<AppState> {
.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)]
struct RegisterRequest {
fingerprint: String,
bundle: Vec<u8>, // bincode-serialized PreKeyBundle
bundle: Vec<u8>,
}
#[derive(Serialize)]
@@ -28,7 +36,9 @@ async fn register_keys(
State(state): State<AppState>,
Json(req): Json<RegisterRequest>,
) -> 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 })
}
@@ -36,11 +46,16 @@ async fn get_bundle(
State(state): State<AppState>,
Path(fingerprint): Path<String>,
) -> 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!({
"fingerprint": fingerprint,
"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)
}
}
}