use std::{sync::Arc, time::Duration};
use bon::Builder;
use http::Uri;
use crate::{
client_auth::{AuthenticationParams, ClientAuthentication},
crypto::signer::{JwsSigner, JwsSignerSelector},
jwt::{JwsSerializationError, Jwt},
};
#[derive(Debug, Clone, Builder)]
pub struct JwtBearer<Sgn: JwsSignerSelector> {
signer: Sgn,
#[builder(into)]
subject: Option<String>,
audience: Audience,
#[builder(default = Duration::from_mins(1))]
expires_after: Duration,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum Audience {
PreferIssuer,
PreferTokenEndpoint,
Custom(Arc<str>),
}
impl<Sgn: JwsSignerSelector> ClientAuthentication for JwtBearer<Sgn> {
type Error = JwsSerializationError<<Sgn::Signer as JwsSigner>::Error>;
async fn authentication_params<'a>(
&'a self,
client_id: &'a str,
issuer: Option<&'a str>,
token_endpoint: &'a Uri,
_allowed_methods: Option<&'a [String]>,
) -> Result<super::AuthenticationParams<'a>, Self::Error> {
let audience = match &self.audience {
Audience::PreferIssuer => {
issuer.map_or_else(|| token_endpoint.to_string(), ToString::to_string)
}
Audience::PreferTokenEndpoint => token_endpoint.to_string(),
Audience::Custom(custom) => custom.to_string(),
};
let jwt = Jwt::builder()
.audience(audience)
.issuer(client_id)
.subject(self.subject.as_deref().unwrap_or(client_id))
.issued_now_expires_after(self.expires_after)
.claims(())
.build();
Ok(AuthenticationParams::builder()
.form_params(bon::map! {
"client_id": client_id,
"client_assertion_type": "urn:ietf:params:oauth:client-assertion-type:jwt-bearer",
"client_assertion": jwt.to_jws_compact(&self.signer.select_signer()).await?
})
.build())
}
}