Try to fetch a Bearer token. Returns None if the registry doesn't need one.
(client: &Client, registry: &str, repo: &str)
| 290 | |
| 291 | /// Try to fetch a Bearer token. Returns None if the registry doesn't need one. |
| 292 | async fn try_fetch_token(client: &Client, registry: &str, repo: &str) -> Option<String> { |
| 293 | // Probe the /v2/ endpoint to check if auth is needed |
| 294 | let probe = client |
| 295 | .get(format!("https://{registry}/v2/")) |
| 296 | .send() |
| 297 | .await |
| 298 | .ok()?; |
| 299 | |
| 300 | if probe.status() != reqwest::StatusCode::UNAUTHORIZED { |
| 301 | return None; |
| 302 | } |
| 303 | |
| 304 | // Parse WWW-Authenticate header for realm and service |
| 305 | let www_auth = probe |
| 306 | .headers() |
| 307 | .get("www-authenticate") |
| 308 | .and_then(|v| v.to_str().ok()) |
| 309 | .unwrap_or(""); |
| 310 | |
| 311 | let (realm, service) = parse_www_authenticate(www_auth); |
| 312 | |
| 313 | let token_url = if !realm.is_empty() { |
| 314 | format!("{realm}?service={service}&scope=repository:{repo}:pull") |
| 315 | } else { |
| 316 | format!("https://{registry}/v2/token?service={registry}&scope=repository:{repo}:pull") |
| 317 | }; |
| 318 | |
| 319 | let resp = client.get(&token_url).send().await.ok()?; |
| 320 | if !resp.status().is_success() { |
| 321 | return None; |
| 322 | } |
| 323 | |
| 324 | let token_data: TokenResponse = resp.json().await.ok()?; |
| 325 | Some(token_data.token) |
| 326 | } |
| 327 | |
| 328 | async fn fetch_token(client: &Client, registry: &str, repo: &str) -> Result<String> { |
| 329 | try_fetch_token(client, registry, repo) |
no test coverage detected