use axum::body::Body;
use axum::extract::Request;
use axum::http::{StatusCode, header};
use axum::middleware::Next;
use axum::response::{IntoResponse, Response};
use axum::{Json, middleware};
#[derive(Debug, Clone)]
pub enum ControlPlaneAuth {
BearerToken(String),
Disabled,
}
impl ControlPlaneAuth {
pub fn bearer(token: impl Into<String>) -> Self {
Self::BearerToken(token.into())
}
pub(crate) fn apply(self, router: axum::Router) -> axum::Router {
match self {
Self::Disabled => router,
Self::BearerToken(token) => router.layer(middleware::from_fn(
move |req: Request<Body>, next: Next| {
let token = token.clone();
async move {
match bearer_of(&req) {
Some(presented) if constant_time_eq(presented, &token) => {
next.run(req).await
}
_ => unauthorized(),
}
}
},
)),
}
}
}
fn bearer_of(req: &Request<Body>) -> Option<&str> {
req.headers()
.get(header::AUTHORIZATION)?
.to_str()
.ok()?
.strip_prefix("Bearer ")
.map(str::trim)
}
fn constant_time_eq(a: &str, b: &str) -> bool {
let (a, b) = (a.as_bytes(), b.as_bytes());
if a.len() != b.len() {
return false;
}
a.iter().zip(b).fold(0u8, |acc, (x, y)| acc | (x ^ y)) == 0
}
fn unauthorized() -> Response {
(
StatusCode::UNAUTHORIZED,
Json(serde_json::json!({ "error": "missing or invalid bearer token" })),
)
.into_response()
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn constant_time_eq_matches_string_equality() {
assert!(constant_time_eq("s3cret", "s3cret"));
assert!(!constant_time_eq("s3cret", "s3crey"));
assert!(!constant_time_eq("s3cret", "s3cre"));
assert!(!constant_time_eq("", "x"));
assert!(constant_time_eq("", ""));
}
fn req_with_auth(header_value: &str) -> Request<Body> {
Request::builder()
.header(header::AUTHORIZATION, header_value)
.body(Body::empty())
.unwrap()
}
#[test]
fn bearer_is_parsed_only_from_a_well_formed_header() {
assert_eq!(bearer_of(&req_with_auth("Bearer tok")), Some("tok"));
assert_eq!(bearer_of(&req_with_auth("bearer tok")), None);
assert_eq!(bearer_of(&req_with_auth("Basic dXNlcjpwdw==")), None);
assert_eq!(bearer_of(&req_with_auth("tok")), None);
assert_eq!(
bearer_of(&Request::builder().body(Body::empty()).unwrap()),
None
);
}
}