use std::{sync::Arc, time::Duration};
use bon::Builder;
use http::Uri;
use crate::{
client_auth::{AuthenticationParams, ClientAuthentication},
crypto::signer::JwsSigningKey,
jwt::{JwsSerializationError, Jwt},
};
#[derive(Debug, Clone, Builder)]
pub struct JwtBearer<Sgn: JwsSigningKey> {
signer: Sgn,
#[builder(into)]
subject: Option<String>,
audience: Audience,
#[builder(default = Duration::from_secs(60))]
expires_after: Duration,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum Audience {
PreferIssuer,
PreferTokenEndpoint,
Custom(Arc<str>),
}
impl<Sgn: JwsSigningKey> ClientAuthentication for JwtBearer<Sgn> {
type Error = JwsSerializationError<Sgn::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)
.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).await?
})
.build())
}
}