1use crate::serve::error::ServeError;
5use crate::serve::state::ServerState;
6use axum::extract::{Request, State};
7use axum::middleware::Next;
8use axum::response::Response;
9use subtle::ConstantTimeEq;
10
11pub(crate) fn constant_time_eq(a: &[u8], b: &[u8]) -> bool {
16 a.ct_eq(b).into()
17}
18
19pub fn authorize_header(header: Option<&str>, expected: &str) -> Result<(), ServeError> {
21 let value = header.ok_or(ServeError::Unauthorized)?;
22 let token = value
23 .strip_prefix("Bearer ")
24 .ok_or(ServeError::Unauthorized)?;
25 if constant_time_eq(token.as_bytes(), expected.as_bytes()) {
26 Ok(())
27 } else {
28 Err(ServeError::Unauthorized)
29 }
30}
31
32pub async fn require_auth(
36 State(state): State<ServerState>,
37 req: Request,
38 next: Next,
39) -> Result<Response, ServeError> {
40 if req.method() == axum::http::Method::OPTIONS {
41 return Ok(next.run(req).await);
42 }
43 if let Some(expected) = state.auth_token() {
44 let header = req
45 .headers()
46 .get(axum::http::header::AUTHORIZATION)
47 .and_then(|v| v.to_str().ok());
48 authorize_header(header, expected)?;
49 }
50 Ok(next.run(req).await)
51}
52
53#[cfg(test)]
54mod tests {
55 use super::*;
56
57 #[test]
58 fn constant_time_eq_matches_only_identical() {
59 assert!(constant_time_eq(b"abc123", b"abc123"));
60 assert!(!constant_time_eq(b"abc123", b"abc124"));
61 assert!(!constant_time_eq(b"abc", b"abc123")); }
63
64 #[test]
65 fn authorize_accepts_correct_bearer() {
66 assert!(authorize_header(Some("Bearer s3cret"), "s3cret").is_ok());
67 }
68
69 #[test]
70 fn authorize_rejects_wrong_or_missing() {
71 assert!(authorize_header(Some("Bearer nope"), "s3cret").is_err());
72 assert!(authorize_header(None, "s3cret").is_err());
73 assert!(authorize_header(Some("s3cret"), "s3cret").is_err()); assert!(authorize_header(Some("Basic s3cret"), "s3cret").is_err());
75 }
76}