huskarl 0.6.0

A modern OAuth2 client library.
Documentation
use http::Method;
use huskarl::token::AccessToken;
use huskarl::{
    core::{
        client_auth::ClientSecret,
        dpop::{AuthorizationServerDPoP, DPoP, ResourceServerDPoP},
        secrets::{EnvVarSecret, encodings::StringEncoding},
        server_metadata::AuthorizationServerMetadata,
    },
    grant::client_credentials::{ClientCredentialsGrant, ClientCredentialsGrantParameters},
    prelude::*,
};
use huskarl_crypto_native::asymmetric::signer::{GenerateAlgorithm, PrivateKey};
use huskarl_reqwest::ReqwestClient;
use snafu::prelude::*;

#[snafu::report]
#[tokio::main]
pub async fn main() -> Result<(), snafu::Whatever> {
    let issuer = std::env::var("ISSUER").whatever_context("Failed to get ISSUER")?;
    let client_id = std::env::var("CLIENT_ID").whatever_context("Failed to get CLIENT_ID")?;
    let client_secret = EnvVarSecret::new("CLIENT_SECRET", &StringEncoding)
        .whatever_context("Failed to get CLIENT_SECRET")?;

    let http_client = ReqwestClient::builder()
        .mtls(huskarl_reqwest::mtls::NoMtls)
        .build()
        .await
        .whatever_context("Failed to build client")?;

    let metadata = AuthorizationServerMetadata::builder()
        .issuer(issuer)
        .http_client(&http_client)
        .build()
        .await
        .whatever_context("Failed to get authorization server metadata")?;

    let grant = ClientCredentialsGrant::builder_from_metadata(&metadata)
        .client_id(client_id)
        .client_auth(ClientSecret::new(client_secret))
        .dpop(
            DPoP::builder()
                .signer(PrivateKey::generate(GenerateAlgorithm::Ed25519))
                .build(),
        )
        .build();

    let token_response = grant
        .exchange(
            &http_client,
            ClientCredentialsGrantParameters::builder()
                .scopes(vec!["test"])
                .build(),
        )
        .await
        .whatever_context("Failed to get token")?;

    let access_token = token_response.access_token();

    println!("Access token: {}", access_token.token().expose_secret());

    let resource_server_dpop = grant.dpop().to_resource_server_dpop();

    let AccessToken::Dpop(dpop_token) = access_token else {
        println!("Expected response to be a DPoP token");
        return Ok(());
    };

    let dpop_proof = resource_server_dpop
        .proof(
            &Method::GET,
            &"https://blah/".parse().unwrap(),
            dpop_token.token(),
            dpop_token.jkt(),
        )
        .await
        .whatever_context("Failed to create DPoP proof")?;

    if let Some(dpop_proof) = dpop_proof {
        println!("DPoP header: {}", dpop_proof.expose_secret());
    }

    Ok(())
}