use std::sync::Arc;
use axum::Router;
use axum::body::Body;
use axum::extract::{Request, State};
use axum::http::{StatusCode, header};
use axum::middleware::{self, Next};
use axum::response::{IntoResponse, Response};
use broadcast_auth::{AuthResult, Credentials, RequestContext, Verifier};
pub(crate) enum MockAuthScheme {
Basic { username: String, password: String },
Digest {
username: String,
password: String,
realm: String,
},
Bearer { token: String },
}
impl MockAuthScheme {
fn into_verifier(self) -> Verifier {
match self {
MockAuthScheme::Basic { username, password } => {
Verifier::new(Credentials::Basic { username, password }, "mock")
}
MockAuthScheme::Digest {
username,
password,
realm,
} => Verifier::new(Credentials::Digest { username, password }, realm),
MockAuthScheme::Bearer { token } => Verifier::new(Credentials::bearer(token), "mock"),
}
}
}
pub(crate) fn require_auth(router: Router, scheme: MockAuthScheme) -> Router {
let verifier = Arc::new(scheme.into_verifier());
router.layer(middleware::from_fn_with_state(verifier, auth_gate))
}
async fn auth_gate(State(verifier): State<Arc<Verifier>>, req: Request, next: Next) -> Response {
let method = req.method().as_str().to_string();
let uri = req
.uri()
.path_and_query()
.map(|pq| pq.as_str().to_string())
.unwrap_or_else(|| req.uri().path().to_string());
let headers: Vec<(&str, &str)> = req
.headers()
.iter()
.filter_map(|(name, value)| value.to_str().ok().map(|v| (name.as_str(), v)))
.collect();
let ctx = RequestContext::new(&method, &uri).with_headers(&headers);
if verifier.verify(&ctx) == AuthResult::Ok {
return next.run(req).await;
}
(
StatusCode::UNAUTHORIZED,
[(header::WWW_AUTHENTICATE, verifier.challenge())],
Body::empty(),
)
.into_response()
}