use base64::{prelude::BASE64_STANDARD, write::EncoderWriter};
use http::{request::Builder, HeaderValue};
use std::io::Write;
#[non_exhaustive]
#[derive(Debug)]
pub enum Auth {
None,
Basic {
username: String,
password: Option<String>,
},
}
#[derive(thiserror::Error, Debug)]
#[error(transparent)]
pub struct AuthError(pub Box<dyn std::error::Error + Sync + Send>);
impl AuthError {
fn from<E: std::error::Error + Sync + Send + 'static>(err: E) -> Self {
Self(Box::from(err))
}
}
pub(crate) trait AuthExt: Sized {
fn authenticate(self, auth: &Auth) -> Result<Self, AuthError>;
}
impl AuthExt for Builder {
fn authenticate(self, auth: &Auth) -> Result<Builder, AuthError> {
match auth {
Auth::None => Ok(self),
Auth::Basic { username, password } => {
let mut sequence = b"Basic ".to_vec();
let mut encoder = EncoderWriter::new(sequence, &BASE64_STANDARD);
if let Some(pwd) = password {
write!(encoder, "{username}:{pwd}").map_err(AuthError::from)?;
} else {
write!(encoder, "{username}:").map_err(AuthError::from)?;
}
sequence = encoder.finish().map_err(AuthError::from)?;
let mut header = HeaderValue::from_bytes(&sequence)
.expect("base64 string contains only ascii characters");
header.set_sensitive(true);
Ok(self.header(hyper::header::AUTHORIZATION, header.clone()))
}
}
}
}