seuss 0.3.1

A batteries-included framework for implementing Redfish-compliant services.
Documentation
use axum::{http::request::Parts, response::Response};
use base64::engine::{general_purpose, Engine};
use redfish_codegen::models::redfish;
use redfish_core::auth::{unauthorized, AuthenticateRequest, AuthenticatedUser};
use std::str;

pub trait BasicAuthentication {
    fn authenticate(
        &self,
        username: String,
        password: String,
    ) -> Result<AuthenticatedUser, redfish::Error>;
}

#[derive(Clone)]
pub struct BasicAuthenticationProxy<B>
where
    B: BasicAuthentication + Clone,
{
    authenticator: B,
}

impl<'a, B> AsRef<dyn AuthenticateRequest + 'a> for BasicAuthenticationProxy<B>
where
    B: BasicAuthentication + Clone + 'a,
{
    fn as_ref(&self) -> &(dyn AuthenticateRequest + 'a) {
        self
    }
}

impl<B> BasicAuthenticationProxy<B>
where
    B: BasicAuthentication + Clone,
{
    pub fn new(authenticator: B) -> Self {
        Self { authenticator }
    }
}

impl<B> AuthenticateRequest for BasicAuthenticationProxy<B>
where
    B: BasicAuthentication + Clone,
{
    fn authenticate_request(
        &self,
        parts: &mut Parts,
    ) -> Result<Option<AuthenticatedUser>, Response> {
        let authorization = parts
            .headers
            .get("Authorization")
            .ok_or_else(|| unauthorized(&self.challenge()))?
            .to_str()
            .map_err(|_| unauthorized(&self.challenge()))?
            .strip_prefix("Basic ")
            .ok_or_else(|| unauthorized(&self.challenge()))?
            .to_string();

        let result = general_purpose::STANDARD
            .decode(authorization)
            .map_err(|_| unauthorized(&self.challenge()))?;

        let credentials: Vec<&str> = str::from_utf8(&result)
            .map_err(|_| unauthorized(&self.challenge()))?
            .split(':')
            .collect();

        if credentials.len() != 2 {
            return Err(unauthorized(&self.challenge()));
        }

        self.authenticator
            .authenticate(credentials[0].to_string(), credentials[1].to_string())
            .map(Some)
            .map_err(|_| unauthorized(&self.challenge()))
    }

    fn challenge(&self) -> Vec<&'static str> {
        vec!["Basic"]
    }
}