klieo-auth-oauth 3.4.0

OAuth 2.0 bearer-token Authenticator for klieo HTTP transports
Documentation
//! Typed errors surfaced by `klieo-auth-oauth`.

use thiserror::Error;

/// Errors surfaced by [`crate::OAuthAuthenticator`] +
/// [`crate::OAuthAuthenticatorBuilder`]. T3 fills in the verifier
/// paths; T2 ships the variant shapes the rest of the cluster depends
/// on.
#[derive(Debug, Error)]
#[non_exhaustive]
pub enum OAuthError {
    /// JWKS endpoint unreachable at builder construction.
    #[error("jwks fetch failed: {0}")]
    JwksFetch(String),
    /// `Authorization` header missing or not Bearer-shape.
    #[error("invalid bearer token format")]
    BadBearer,
    /// JWT signature / claims validation failed after one JWKS
    /// rotation refresh attempt.
    #[error("token validation failed")]
    Validation(#[source] jsonwebtoken::errors::Error),
    /// Method-to-scope allow-list rejected the principal's scopes.
    #[error("method not authorized for principal")]
    ScopeDenied,
    /// Builder missing a required field (issuer/jwks_uri).
    #[error("missing required configuration: {0}")]
    Misconfigured(String),
    /// RFC 7662 introspection endpoint unreachable or returned
    /// an error. Per-bearer validation failures during the
    /// introspection path (e.g. active=false) surface as
    /// `AuthError::Rejected` to match the JWT-path posture.
    #[error("introspection failed: {0}")]
    IntrospectionFailed(String),
    /// OpenID Connect Discovery endpoint unreachable or returned
    /// malformed JSON.
    #[error("discovery failed: {0}")]
    DiscoveryFailed(String),
    /// An internal setup failure (e.g. building the shared HTTP client). Unlike
    /// the leaf [`OAuthError::Misconfigured`] validation errors, this wraps the
    /// underlying cause so `std::error::Error::source()` traversal reaches it.
    #[error("oauth internal error: {message}")]
    Internal {
        /// Context describing where the failure happened.
        message: String,
        /// Underlying error preserved for `std::error::Error::source()`.
        #[source]
        source: Box<dyn std::error::Error + Send + Sync + 'static>,
    },
}

#[cfg(test)]
mod tests {
    use super::*;
    use std::error::Error as StdError;

    #[test]
    fn internal_preserves_source_chain() {
        let inner = std::io::Error::other("tls backend missing");
        let e = OAuthError::Internal {
            message: "failed to build OAuth HTTP client".into(),
            source: Box::new(inner),
        };
        assert_eq!(
            e.to_string(),
            "oauth internal error: failed to build OAuth HTTP client"
        );
        let src = e.source().expect("source must be preserved");
        assert_eq!(src.to_string(), "tls backend missing");
    }
}