diff --git a/server/src/routes/api_keys.rs b/server/src/routes/api_keys.rs index b0be5e6..86bf054 100644 --- a/server/src/routes/api_keys.rs +++ b/server/src/routes/api_keys.rs @@ -92,11 +92,19 @@ pub async fn revoke( /// Returns the user_id if the raw key matches a stored hash. pub async fn verify_key(s: &AppState, raw_key: &str) -> Option { let hash = hex::encode(Sha256::digest(raw_key.as_bytes())); - sqlx::query_scalar("SELECT user_id FROM api_keys WHERE key_hash = ?") - .bind(&hash) - .fetch_optional(&s.db) - .await - .unwrap_or(None) + match sqlx::query_scalar::<_, String>( + "SELECT user_id FROM api_keys WHERE key_hash = ?", + ) + .bind(&hash) + .fetch_optional(&s.db) + .await + { + Ok(r) => r, + Err(e) => { + tracing::error!("api_key verify db error: {e}"); + None + } + } } // ── Tiny CSPRNG using uuid entropy ─────────────────────────────────────────── diff --git a/server/src/routes/git.rs b/server/src/routes/git.rs index 0f632ba..ab04aa7 100644 --- a/server/src/routes/git.rs +++ b/server/src/routes/git.rs @@ -35,15 +35,55 @@ fn check_token(state: &AppState, headers: &HeaderMap) -> bool { /// git sends Basic Auth where the password field is the API key. /// Returns the user_id on success. async fn http_authenticate(s: &AppState, headers: &HeaderMap) -> Option { - let value = headers.get("authorization")?.to_str().ok()?; - let encoded = value.strip_prefix("Basic ")?; - let decoded = base64::engine::general_purpose::STANDARD - .decode(encoded) - .ok()?; - let credentials = std::str::from_utf8(&decoded).ok()?; - // credentials is "username:password" — the password IS the API key. - let api_key = credentials.splitn(2, ':').nth(1)?; - api_keys::verify_key(s, api_key).await + let value = headers + .get("authorization") + .and_then(|v| v.to_str().ok()) + .unwrap_or(""); + + if value.is_empty() { + tracing::debug!("git http auth: no Authorization header"); + return None; + } + + let encoded = match value.strip_prefix("Basic ") { + Some(e) => e, + None => { + tracing::debug!("git http auth: not Basic (got: {})", &value[..value.len().min(20)]); + return None; + } + }; + + let decoded = match base64::engine::general_purpose::STANDARD.decode(encoded) { + Ok(d) => d, + Err(e) => { + tracing::debug!("git http auth: base64 error: {e}"); + return None; + } + }; + + let credentials = match std::str::from_utf8(&decoded) { + Ok(c) => c, + Err(e) => { + tracing::debug!("git http auth: utf8 error: {e}"); + return None; + } + }; + + // credentials = "username:password" — the password IS the API key. + let api_key = match credentials.splitn(2, ':').nth(1) { + Some(k) => k, + None => { + tracing::debug!("git http auth: no colon in credentials"); + return None; + } + }; + + let result = api_keys::verify_key(s, api_key).await; + tracing::debug!( + "git http auth: key lookup → {}", + if result.is_some() { "ok" } else { "not found" } + ); + result } fn unauthorized() -> Response {