diff --git a/v2/crates/wifi-densepose-sensing-server/src/bearer_auth.rs b/v2/crates/wifi-densepose-sensing-server/src/bearer_auth.rs index c7acd168..be313afa 100644 --- a/v2/crates/wifi-densepose-sensing-server/src/bearer_auth.rs +++ b/v2/crates/wifi-densepose-sensing-server/src/bearer_auth.rs @@ -100,7 +100,17 @@ pub async fn require_bearer( .headers() .get(AUTHORIZATION) .and_then(|v| v.to_str().ok()) - .and_then(|s| s.strip_prefix("Bearer ")); + // RFC 6750 §2.1 / RFC 7235 §2.1: the auth-scheme ("Bearer") is + // case-insensitive. Match it as such (and tolerate extra leading + // whitespace before the token) so a correct token isn't rejected + // just because a client sent `bearer`/`BEARER`. The token compare + // below stays exact + constant-time. + .and_then(|s| { + let (scheme, token) = s.split_once(' ')?; + scheme + .eq_ignore_ascii_case("Bearer") + .then(|| token.trim_start()) + }); let ok = supplied .map(|s| ct_eq(s.as_bytes(), expected.as_bytes())) .unwrap_or(false); @@ -185,6 +195,31 @@ mod tests { ); } + #[tokio::test] + async fn accepts_case_insensitive_bearer_scheme() { + // RFC 6750 §2.1 / RFC 7235 §2.1: the auth-scheme is case-insensitive. + // A correct token must authenticate regardless of scheme casing or + // extra whitespace; a wrong token must still be rejected. + async fn req_status(auth_value: &str) -> StatusCode { + let r = wrap(AuthState::from_token("s3cr3t")); + let mut req = Request::builder() + .method("GET") + .uri("/api/v1/info") + .body(Body::empty()) + .unwrap(); + req.headers_mut() + .insert(AUTHORIZATION, auth_value.parse().unwrap()); + r.oneshot(req).await.unwrap().status() + } + assert_eq!(req_status("Bearer s3cr3t").await, StatusCode::OK); + assert_eq!(req_status("bearer s3cr3t").await, StatusCode::OK); + assert_eq!(req_status("BEARER s3cr3t").await, StatusCode::OK); + assert_eq!(req_status("Bearer s3cr3t").await, StatusCode::OK); // extra space + // Scheme leniency must NOT weaken the token check. + assert_eq!(req_status("bearer nope").await, StatusCode::UNAUTHORIZED); + assert_eq!(req_status("Basic s3cr3t").await, StatusCode::UNAUTHORIZED); + } + #[tokio::test] async fn enabled_blocks_api_with_wrong_bearer() { let r = wrap(AuthState::from_token("s3cr3t"));