(
token_url: &str,
form: &[(String, String)],
max_lifetime_seconds: i64,
)
| 545 | } |
| 546 | |
| 547 | async fn request_token( |
| 548 | token_url: &str, |
| 549 | form: &[(String, String)], |
| 550 | max_lifetime_seconds: i64, |
| 551 | ) -> Result<MintedCredential, Status> { |
| 552 | let parsed = reqwest::Url::parse(token_url) |
| 553 | .map_err(|_| Status::invalid_argument("token_url must be an absolute URL"))?; |
| 554 | match parsed.scheme() { |
| 555 | "https" => {} |
| 556 | "http" if parsed.host_str().is_some_and(is_loopback_host) => {} |
| 557 | _ => { |
| 558 | return Err(Status::invalid_argument( |
| 559 | "token_url must use https, except loopback http for local tests", |
| 560 | )); |
| 561 | } |
| 562 | } |
| 563 | |
| 564 | let client = reqwest::Client::builder() |
| 565 | .timeout(Duration::from_secs(30)) |
| 566 | .build() |
| 567 | .map_err(|e| Status::internal(format!("build refresh HTTP client failed: {e}")))?; |
| 568 | let response = client |
| 569 | .post(parsed) |
| 570 | .form(form) |
| 571 | .send() |
| 572 | .await |
| 573 | .map_err(|e| Status::unavailable(format!("token endpoint request failed: {e}")))?; |
| 574 | let status = response.status(); |
| 575 | if !status.is_success() { |
| 576 | return Err(Status::failed_precondition(format!( |
| 577 | "token endpoint returned HTTP {status}" |
| 578 | ))); |
| 579 | } |
| 580 | let token = response |
| 581 | .json::<TokenResponse>() |
| 582 | .await |
| 583 | .map_err(|_| Status::failed_precondition("token endpoint returned invalid JSON"))?; |
| 584 | if token.access_token.trim().is_empty() { |
| 585 | return Err(Status::failed_precondition( |
| 586 | "token endpoint returned empty access_token", |
| 587 | )); |
| 588 | } |
| 589 | let now_ms = current_time_ms(); |
| 590 | let lifetime_cap_seconds = if max_lifetime_seconds > 0 { |
| 591 | max_lifetime_seconds |
| 592 | } else { |
| 593 | DEFAULT_MAX_LIFETIME_SECONDS |
| 594 | }; |
| 595 | let lifetime_seconds = token |
| 596 | .expires_in |
| 597 | .filter(|value| *value > 0) |
| 598 | .unwrap_or(lifetime_cap_seconds); |
| 599 | let lifetime_seconds = lifetime_seconds.min(lifetime_cap_seconds); |
| 600 | Ok(MintedCredential { |
| 601 | access_token: token.access_token, |
| 602 | expires_at_ms: now_ms.saturating_add(lifetime_seconds.saturating_mul(1000)), |
| 603 | refresh_token: token |
| 604 | .refresh_token |
no test coverage detected