use headers::{Authorization, Header};
use http_auth::{PasswordClient, PasswordParams};
use crate::credentials::Credentials;
use crate::error::{Error, Result};
use crate::request::RequestContext;
enum SchemeState {
Password(PasswordClient),
Bearer,
}
pub struct Authenticator {
credentials: Credentials,
state: SchemeState,
}
impl Authenticator {
pub fn from_challenge(www_authenticate: &str, credentials: Credentials) -> Result<Self> {
let state = match &credentials {
Credentials::Bearer { .. } => SchemeState::Bearer,
Credentials::Basic { .. } | Credentials::Digest { .. } => {
let client = PasswordClient::try_from(www_authenticate)
.map_err(|e| Error::ChallengeParse(e.to_string()))?;
SchemeState::Password(client)
}
};
Ok(Authenticator { credentials, state })
}
pub fn authorization(&mut self, ctx: &RequestContext<'_>) -> Result<String> {
match &mut self.state {
SchemeState::Bearer => {
let Credentials::Bearer { token } = &self.credentials else {
unreachable!("SchemeState::Bearer only paired with Credentials::Bearer")
};
let typed = Authorization::bearer(token).map_err(|_| Error::InvalidBearerToken)?;
let mut values: Vec<headers::HeaderValue> = Vec::with_capacity(1);
typed.encode(&mut values);
values
.first()
.and_then(|v| v.to_str().ok())
.map(str::to_owned)
.ok_or(Error::InvalidBearerToken)
}
SchemeState::Password(client) => {
let (username, password) = self
.credentials
.username_password()
.expect("SchemeState::Password only paired with Basic/Digest credentials");
client
.respond(&PasswordParams {
username,
password,
uri: ctx.uri,
method: ctx.method,
body: ctx.body,
})
.map_err(|e| Error::ResponseCompute(e.to_string()))
}
}
}
}
impl core::fmt::Debug for Authenticator {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
let scheme = match &self.credentials {
Credentials::Basic { .. } => "Basic",
Credentials::Digest { .. } => "Digest",
Credentials::Bearer { .. } => "Bearer",
};
f.debug_struct("Authenticator")
.field("scheme", &scheme)
.finish_non_exhaustive()
}
}
pub fn respond(
www_authenticate: &str,
ctx: &RequestContext<'_>,
credentials: Credentials,
) -> Result<String> {
Authenticator::from_challenge(www_authenticate, credentials)?.authorization(ctx)
}