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")
};
Ok(format!("Bearer {token}"))
}
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)
}