klieo-auth-oauth 3.4.0

OAuth 2.0 bearer-token Authenticator for klieo HTTP transports
Documentation
//! Shared timed `reqwest::Client` builder for the OAuth HTTP paths (JWKS,
//! discovery, introspection). A client without timeouts can hang the auth path
//! indefinitely against a slow or hostile IdP, so every default client this
//! crate constructs goes through here.

use crate::error::OAuthError;
use std::time::Duration;

/// Whole-request timeout for OAuth IdP calls.
pub(crate) const OAUTH_HTTP_TIMEOUT: Duration = Duration::from_secs(10);
/// TCP connect timeout for OAuth IdP calls.
pub(crate) const OAUTH_HTTP_CONNECT_TIMEOUT: Duration = Duration::from_secs(5);

/// Build a `reqwest::Client` with bounded request + connect timeouts.
pub(crate) fn build_http_client(
    timeout: Duration,
    connect_timeout: Duration,
) -> Result<reqwest::Client, OAuthError> {
    reqwest::Client::builder()
        .timeout(timeout)
        .connect_timeout(connect_timeout)
        .build()
        .map_err(|e| OAuthError::Internal {
            message: "failed to build OAuth HTTP client".into(),
            source: Box::new(e),
        })
}

/// The default OAuth HTTP client, used when the caller does not bring their own.
pub(crate) fn default_http_client() -> Result<reqwest::Client, OAuthError> {
    build_http_client(OAUTH_HTTP_TIMEOUT, OAUTH_HTTP_CONNECT_TIMEOUT)
}

#[cfg(test)]
mod tests {
    use super::*;
    use wiremock::matchers::{method, path};
    use wiremock::{Mock, MockServer, ResponseTemplate};

    #[tokio::test]
    async fn enforces_request_timeout_instead_of_hanging() {
        let server = MockServer::start().await;
        Mock::given(method("GET"))
            .and(path("/slow"))
            .respond_with(ResponseTemplate::new(200).set_delay(Duration::from_secs(2)))
            .mount(&server)
            .await;

        let client = build_http_client(Duration::from_millis(50), Duration::from_millis(50))
            .expect("client builds");
        let err = client
            .get(format!("{}/slow", server.uri()))
            .send()
            .await
            .expect_err("a slow IdP must time out, not hang");
        assert!(err.is_timeout(), "expected a timeout error, got {err:?}");
    }
}