fix: explicit SQLx type + debug tracing for HTTP git auth
Fixes potential silent failure where sqlx::query_scalar couldn't infer the return type at runtime. Also adds step-by-step tracing so the exact failure point (no header / bad base64 / key not found / db error) is visible in `docker compose logs server`. https://claude.ai/code/session_01FKCW3FDjNFj6jve4niMFXH
This commit is contained in:
parent
a2627a3e2f
commit
4504c22af8
2 changed files with 62 additions and 14 deletions
|
|
@ -92,11 +92,19 @@ pub async fn revoke(
|
||||||
/// Returns the user_id if the raw key matches a stored hash.
|
/// Returns the user_id if the raw key matches a stored hash.
|
||||||
pub async fn verify_key(s: &AppState, raw_key: &str) -> Option<String> {
|
pub async fn verify_key(s: &AppState, raw_key: &str) -> Option<String> {
|
||||||
let hash = hex::encode(Sha256::digest(raw_key.as_bytes()));
|
let hash = hex::encode(Sha256::digest(raw_key.as_bytes()));
|
||||||
sqlx::query_scalar("SELECT user_id FROM api_keys WHERE key_hash = ?")
|
match sqlx::query_scalar::<_, String>(
|
||||||
.bind(&hash)
|
"SELECT user_id FROM api_keys WHERE key_hash = ?",
|
||||||
.fetch_optional(&s.db)
|
)
|
||||||
.await
|
.bind(&hash)
|
||||||
.unwrap_or(None)
|
.fetch_optional(&s.db)
|
||||||
|
.await
|
||||||
|
{
|
||||||
|
Ok(r) => r,
|
||||||
|
Err(e) => {
|
||||||
|
tracing::error!("api_key verify db error: {e}");
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// ── Tiny CSPRNG using uuid entropy ───────────────────────────────────────────
|
// ── Tiny CSPRNG using uuid entropy ───────────────────────────────────────────
|
||||||
|
|
|
||||||
|
|
@ -35,15 +35,55 @@ fn check_token(state: &AppState, headers: &HeaderMap) -> bool {
|
||||||
/// git sends Basic Auth where the password field is the API key.
|
/// git sends Basic Auth where the password field is the API key.
|
||||||
/// Returns the user_id on success.
|
/// Returns the user_id on success.
|
||||||
async fn http_authenticate(s: &AppState, headers: &HeaderMap) -> Option<String> {
|
async fn http_authenticate(s: &AppState, headers: &HeaderMap) -> Option<String> {
|
||||||
let value = headers.get("authorization")?.to_str().ok()?;
|
let value = headers
|
||||||
let encoded = value.strip_prefix("Basic ")?;
|
.get("authorization")
|
||||||
let decoded = base64::engine::general_purpose::STANDARD
|
.and_then(|v| v.to_str().ok())
|
||||||
.decode(encoded)
|
.unwrap_or("");
|
||||||
.ok()?;
|
|
||||||
let credentials = std::str::from_utf8(&decoded).ok()?;
|
if value.is_empty() {
|
||||||
// credentials is "username:password" — the password IS the API key.
|
tracing::debug!("git http auth: no Authorization header");
|
||||||
let api_key = credentials.splitn(2, ':').nth(1)?;
|
return None;
|
||||||
api_keys::verify_key(s, api_key).await
|
}
|
||||||
|
|
||||||
|
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<Body> {
|
fn unauthorized() -> Response<Body> {
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue