mod client_secret;
mod form_value;
mod jwt_bearer;
mod no_auth;
use std::sync::Arc;
use bon::Builder;
pub use client_secret::{ClientSecret, ClientSecretBuilder};
pub use form_value::FormValue;
use http::HeaderMap;
pub use jwt_bearer::{Audience, JwtBearer, JwtBearerBuilder, MissingIssuer, MissingTokenEndpoint};
pub use no_auth::NoAuth;
use crate::{
EndpointUrl,
error::Error,
platform::{MaybeSendBoxFuture, MaybeSendSync},
};
pub trait ClientAuthentication: MaybeSendSync {
fn authentication_context<'a>(
&'a self,
ctx: AuthenticationContext<'a>,
) -> MaybeSendBoxFuture<'a, Result<AuthenticationParams<'a>, Error>>;
}
impl<T: ClientAuthentication + ?Sized> ClientAuthentication for &T {
fn authentication_context<'a>(
&'a self,
ctx: AuthenticationContext<'a>,
) -> MaybeSendBoxFuture<'a, Result<AuthenticationParams<'a>, Error>> {
(**self).authentication_context(ctx)
}
}
impl<T: ClientAuthentication + ?Sized> ClientAuthentication for Box<T> {
fn authentication_context<'a>(
&'a self,
ctx: AuthenticationContext<'a>,
) -> MaybeSendBoxFuture<'a, Result<AuthenticationParams<'a>, Error>> {
(**self).authentication_context(ctx)
}
}
impl<T: ClientAuthentication + ?Sized> ClientAuthentication for Arc<T> {
fn authentication_context<'a>(
&'a self,
ctx: AuthenticationContext<'a>,
) -> MaybeSendBoxFuture<'a, Result<AuthenticationParams<'a>, Error>> {
(**self).authentication_context(ctx)
}
}
#[non_exhaustive]
#[derive(Debug, Clone, Copy, Builder)]
pub struct AuthenticationContext<'a> {
pub client_id: &'a str,
pub target_endpoint: &'a EndpointUrl,
pub issuer: Option<&'a str>,
pub token_endpoint: Option<&'a EndpointUrl>,
pub allowed_methods: Option<&'a [String]>,
}
#[non_exhaustive]
#[derive(Debug, Clone, Builder)]
pub struct AuthenticationParams<'a> {
pub headers: Option<HeaderMap>,
pub form_params: Option<Vec<(&'static str, FormValue<'a>)>>,
}
#[cfg(test)]
mod tests {
use super::*;
#[tokio::test]
async fn erased_authentication_dispatches() {
let auth: Arc<dyn ClientAuthentication> = Arc::new(NoAuth);
let uri: EndpointUrl = "https://as.example/token".parse().unwrap();
let params = auth
.authentication_context(
AuthenticationContext::builder()
.client_id("my-client")
.target_endpoint(&uri)
.token_endpoint(&uri)
.build(),
)
.await
.expect("no_auth never fails");
let form = params.form_params.expect("client_id form param");
assert!(form.iter().any(|(k, _)| *k == "client_id"));
}
}