use anyhow::{anyhow, Result};
use reqwest::blocking::{RequestBuilder, Response};
use std::collections::HashMap;
pub mod basic;
pub mod bearer;
pub mod digest;
use basic::BasicAuth;
use bearer::BearerAuth;
use digest::DigestAuth;
pub trait AuthPlugin: Send + Sync {
fn name(&self) -> &'static str;
fn realm(&self) -> Option<&str> {
None
}
fn apply(&self, req: RequestBuilder) -> RequestBuilder;
fn handle_401(&self, _req: RequestBuilder, _response: &Response) -> Option<RequestBuilder> {
None
}
}
pub struct AuthRegistry {
plugins: HashMap<&'static str, Box<dyn AuthPlugin>>,
}
impl Default for AuthRegistry {
fn default() -> Self {
Self::new()
}
}
impl AuthRegistry {
pub fn new() -> Self {
Self {
plugins: HashMap::new(),
}
}
pub fn with_defaults() -> Self {
let mut registry = Self::new();
registry.register(Box::new(BasicAuth::placeholder()));
registry.register(Box::new(BearerAuth::placeholder()));
registry.register(Box::new(DigestAuth::placeholder()));
registry
}
pub fn register(&mut self, plugin: Box<dyn AuthPlugin>) {
self.plugins.insert(plugin.name(), plugin);
}
pub fn get(&self, name: &str) -> Result<&dyn AuthPlugin> {
let key = name.to_ascii_lowercase();
self.plugins
.get(key.as_str())
.map(|p| p.as_ref())
.ok_or_else(|| anyhow!("unsupported auth type '{name}'"))
}
pub fn list(&self) -> Vec<&'static str> {
let mut keys = self.plugins.keys().copied().collect::<Vec<_>>();
keys.sort_unstable();
keys
}
}
pub fn build_auth(auth_type: &str, credentials: &str) -> Result<Box<dyn AuthPlugin>> {
let normalized = auth_type.trim().to_ascii_lowercase();
match normalized.as_str() {
"basic" => Ok(Box::new(BasicAuth::new(credentials)?)),
"bearer" => Ok(Box::new(BearerAuth::new(credentials)?)),
"digest" => Ok(Box::new(DigestAuth::new(credentials)?)),
_ => Err(anyhow!("unsupported auth type '{auth_type}'")),
}
}