use serde::Serialize;
use crate::{
core::{
EndpointUrl, Error, ErrorKind,
client_auth::{AuthenticationContext, AuthenticationParams, ClientAuthentication},
dpop::AuthorizationServerDPoP,
http::HttpClient,
platform::{MaybeSend, MaybeSendSync},
},
grant::{
core::{
form::{OAuth2FormRequest, with_dpop_nonce_retry},
token_response::{RawTokenResponse, TokenResponse},
},
refresh::RefreshGrant,
},
};
pub trait OAuth2ExchangeGrant: MaybeSendSync {
type Parameters: Clone + MaybeSendSync;
type Form<'a>: MaybeSendSync + Serialize
where
Self: 'a;
fn client_id(&self) -> Option<&str>;
fn issuer(&self) -> Option<&str>;
fn client_auth(&self) -> Option<&dyn ClientAuthentication>;
fn bound_dpop_jkt(_params: &Self::Parameters) -> Option<&str> {
None
}
fn token_endpoint(&self) -> &EndpointUrl;
fn effective_token_endpoint(&self) -> &EndpointUrl {
self.token_endpoint()
}
fn dpop(&self) -> &dyn AuthorizationServerDPoP;
fn http_client(&self) -> &dyn HttpClient;
fn build_form(&self, params: Self::Parameters) -> Self::Form<'_>;
fn allowed_auth_methods(&self) -> Option<&[String]>;
fn authentication_params(
&self,
) -> impl Future<Output = Result<AuthenticationParams<'_>, Error>> + MaybeSend {
async {
let Some(client_auth) = self.client_auth() else {
return Ok(AuthenticationParams::builder().build());
};
let client_id = self.client_id().ok_or_else(|| {
Error::new(
ErrorKind::Auth,
"client authentication requires a client ID",
)
})?;
client_auth
.authentication_context(
AuthenticationContext::builder()
.client_id(client_id)
.target_endpoint(self.effective_token_endpoint())
.maybe_issuer(self.issuer())
.token_endpoint(self.token_endpoint())
.maybe_allowed_methods(self.allowed_auth_methods())
.build(),
)
.await
}
}
fn exchange(
&self,
params: Self::Parameters,
) -> impl Future<Output = Result<TokenResponse, Error>> + MaybeSend {
async move {
let dpop_jkt = match Self::bound_dpop_jkt(¶ms).map(ToString::to_string) {
Some(jkt) => Some(jkt),
None => self.dpop().get_current_thumbprint().await,
};
let http_client = self.http_client();
let endpoint = self.effective_token_endpoint();
let form = self.build_form(params);
let raw_token_response: RawTokenResponse = with_dpop_nonce_retry!({
let auth_params = self.authentication_params().await?;
OAuth2FormRequest::builder()
.auth_params(auth_params)
.dpop(self.dpop())
.maybe_dpop_jkt(dpop_jkt.as_deref())
.form(&form)
.uri(endpoint.as_uri())
.build()
.execute(http_client)
.await
})?;
raw_token_response
.into_token_response(dpop_jkt, crate::core::platform::SystemTime::now())
.map_err(|source| Error::new(ErrorKind::Protocol, source))
}
}
fn to_refresh_grant(&self) -> RefreshGrant;
}