use rusty_gasket::auth::backend::{AuthBackend, AuthBackendHandle};
use rusty_gasket::auth::error::AuthError;
use rusty_gasket::auth::identity::Identity;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[non_exhaustive]
pub enum UnauthenticatedPolicy {
Reject,
AllowAnonymous,
}
pub struct AuthChain {
backends: Vec<AuthBackendHandle>,
fallback: UnauthenticatedPolicy,
}
impl std::fmt::Debug for AuthChain {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("AuthChain")
.field(
"backends",
&self.backends.iter().map(|b| b.name()).collect::<Vec<_>>(),
)
.field("fallback", &self.fallback)
.finish()
}
}
impl AuthChain {
#[must_use]
pub fn new() -> Self {
Self {
backends: Vec::new(),
fallback: UnauthenticatedPolicy::Reject,
}
}
#[must_use]
pub fn from_backends(backends: Vec<AuthBackendHandle>) -> Self {
Self::new().backends(backends)
}
#[must_use]
pub fn backend(mut self, backend: impl AuthBackend) -> Self {
self.backends.push(AuthBackendHandle::new(backend));
self
}
#[must_use]
pub fn backend_handle(mut self, backend: AuthBackendHandle) -> Self {
self.backends.push(backend);
self
}
#[must_use]
pub fn boxed_backend(self, backend: AuthBackendHandle) -> Self {
self.backend_handle(backend)
}
#[must_use]
pub fn backends(mut self, backends: impl IntoIterator<Item = AuthBackendHandle>) -> Self {
self.backends.extend(backends);
self
}
#[must_use]
pub const fn with_fallback(mut self, policy: UnauthenticatedPolicy) -> Self {
self.fallback = policy;
self
}
pub async fn authenticate(
&self,
headers: &http::HeaderMap,
uri: &http::Uri,
) -> Result<Option<Identity>, AuthError> {
for backend in &self.backends {
match backend.authenticate(headers, uri).await {
Ok(Some(identity)) => {
tracing::debug!(
backend = backend.name(),
subject = %identity.subject(),
"Authentication succeeded"
);
return Ok(Some(identity));
}
Ok(None) => continue,
Err(e) => {
let request_id =
rusty_gasket::observability::current_request_id().unwrap_or_default();
tracing::warn!(
request_id = %request_id,
backend = backend.name(),
error = %e,
"Authentication backend returned error"
);
return Err(e);
}
}
}
match self.fallback {
UnauthenticatedPolicy::AllowAnonymous => Ok(None),
UnauthenticatedPolicy::Reject => Err(AuthError::MissingCredentials(
"No valid credentials provided".to_string(),
)),
}
}
#[must_use]
pub const fn fallback_policy(&self) -> UnauthenticatedPolicy {
self.fallback
}
}
impl Default for AuthChain {
fn default() -> Self {
Self::new()
}
}