use std::collections::HashMap;
use async_trait::async_trait;
use crate::error::ARCPError;
use crate::messages::{AuthScheme, Capabilities, ClientIdentity, Credentials};
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum AuthOutcome {
Accept {
principal: String,
},
Challenge {
challenge: String,
},
Reject {
reason: String,
},
}
#[async_trait]
pub trait Authenticator: Send + Sync {
fn scheme(&self) -> AuthScheme;
async fn authenticate(
&self,
creds: &Credentials,
client: &ClientIdentity,
negotiated: &Capabilities,
) -> Result<AuthOutcome, ARCPError>;
async fn verify_challenge_response(
&self,
_challenge: &str,
_response: &str,
) -> Result<AuthOutcome, ARCPError> {
Ok(AuthOutcome::Reject {
reason: "this scheme does not use challenges".into(),
})
}
}
pub struct AuthRegistry {
by_scheme: HashMap<AuthSchemeKey, Box<dyn Authenticator>>,
}
impl Default for AuthRegistry {
fn default() -> Self {
Self::new()
}
}
impl std::fmt::Debug for AuthRegistry {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("AuthRegistry")
.field("schemes", &self.by_scheme.keys().collect::<Vec<_>>())
.finish()
}
}
impl AuthRegistry {
#[must_use]
pub fn new() -> Self {
Self {
by_scheme: HashMap::new(),
}
}
pub fn register(&mut self, auth: Box<dyn Authenticator>) {
self.by_scheme.insert(auth.scheme().into(), auth);
}
#[must_use]
pub fn get(&self, scheme: &AuthScheme) -> Option<&dyn Authenticator> {
self.by_scheme
.get(&AuthSchemeKey::from(scheme.clone()))
.map(AsRef::as_ref)
}
}
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
struct AuthSchemeKey(String);
impl From<AuthScheme> for AuthSchemeKey {
fn from(s: AuthScheme) -> Self {
let name = match s {
AuthScheme::Bearer => "bearer",
AuthScheme::SignedJwt => "signed_jwt",
AuthScheme::None => "none",
AuthScheme::Mtls => "mtls",
AuthScheme::Oauth2 => "oauth2",
};
Self(name.to_owned())
}
}