spiral-auth 0.1.12

Spiral credentials: bearer tokens and the providers that obtain them
Documentation
use std::env;
use std::fmt::Debug;

use reqwest_middleware::ClientWithMiddleware as Client;
use tracing::debug;
use url::Url;

use super::jwt_bearer;
use crate::{CredentialResult, Token, TokenSource};

/// Authenticates a workload with a token from any OpenID Connect issuer, read from
/// `SPIRAL_OIDC_PROVIDER_TOKEN`, as a JWT-bearer grant at the authorization server. The
/// token's audience must be the authorization server's token endpoint.
#[derive(Debug)]
pub struct OidcAuthProvider {
    client: Client,
    token_endpoint: Url,
    workload_id: String,
    assertion_provider: Option<TokenSource>,
}

impl OidcAuthProvider {
    /// Obtain the workload's tokens at `token_endpoint`, the authorization server's.
    pub fn new(client: impl Into<Client>, token_endpoint: Url, workload_id: String) -> Self {
        OidcAuthProvider {
            client: client.into(),
            token_endpoint,
            workload_id,
            assertion_provider: None,
        }
    }
    /// Obtain a fresh platform assertion from this source before each exchange. Use the
    /// installation's ID as the client ID when composing installation credentials.
    pub fn with_assertion_provider(mut self, source: TokenSource) -> Self {
        self.assertion_provider = Some(source);
        self
    }
}

impl OidcAuthProvider {
    /// A usable token, or `None` when this provider has no credentials to offer.
    pub async fn token(&self) -> CredentialResult<Option<Token>> {
        let assertion = match &self.assertion_provider {
            Some(provider) => provider
                .token()
                .await?
                .map(|token| token.expose_secret().to_owned()),
            None => env::var("SPIRAL_OIDC_PROVIDER_TOKEN").ok(),
        };
        let Some(oidc_token) = assertion else {
            return Ok(None);
        };

        let token = jwt_bearer(
            &self.client,
            &self.token_endpoint,
            self.workload_id.clone(),
            oidc_token,
            None,
        )
        .await?;

        debug!("OidcAuthProvider obtained a workload token");
        Ok(Some(token))
    }
}

impl_token_source!(OidcAuthProvider);