music on atproto
plyr.fm
1//! Authentication middleware.
2
3use axum::{extract::Request, http::StatusCode, middleware::Next, response::Response};
4use tracing::warn;
5
6/// Auth middleware that checks X-Moderation-Key header for protected endpoints.
7pub async fn auth_middleware(
8 req: Request,
9 next: Next,
10 auth_token: Option<String>,
11) -> Result<Response, StatusCode> {
12 let path = req.uri().path();
13
14 // Public endpoints - no auth required
15 // Note: /admin and /admin/review/:id serve HTML, auth is handled client-side for API calls
16 // Static files must be public for admin UI CSS/JS to load
17 let is_review_page = path.starts_with("/admin/review/")
18 && !path.ends_with("/data")
19 && !path.ends_with("/submit");
20 if path == "/"
21 || path == "/health"
22 || path == "/sensitive-images"
23 || path == "/admin"
24 || is_review_page
25 || path.starts_with("/static/")
26 || path.starts_with("/xrpc/com.atproto.label.")
27 {
28 return Ok(next.run(req).await);
29 }
30
31 let Some(expected_token) = auth_token else {
32 warn!("no MODERATION_AUTH_TOKEN set - rejecting protected request");
33 return Err(StatusCode::SERVICE_UNAVAILABLE);
34 };
35
36 let token = req
37 .headers()
38 .get("X-Moderation-Key")
39 .and_then(|v| v.to_str().ok());
40
41 match token {
42 Some(t) if t == expected_token => Ok(next.run(req).await),
43 Some(_) => {
44 warn!("invalid auth token provided");
45 Err(StatusCode::UNAUTHORIZED)
46 }
47 None => {
48 warn!("missing X-Moderation-Key header");
49 Err(StatusCode::UNAUTHORIZED)
50 }
51 }
52}