Skip to main content

zagens_runtime_api/
auth.rs

1//! Runtime API bearer-token authentication middleware.
2
3use axum::Json;
4use axum::extract::{Request, State};
5use axum::http::{StatusCode, header};
6use axum::middleware::Next;
7use axum::response::{IntoResponse, Response};
8use serde_json::json;
9
10use crate::state::RuntimeApiAuthState;
11
12fn constant_time_eq(left: &str, right: &str) -> bool {
13    if left.len() != right.len() {
14        return false;
15    }
16    left.bytes()
17        .zip(right.bytes())
18        .fold(0u8, |acc, (a, b)| acc | (a ^ b))
19        == 0
20}
21
22fn bearer_token_from_header(value: &str) -> Option<&str> {
23    value.strip_prefix("Bearer ")
24}
25
26pub async fn require_runtime_token<S>(State(state): State<S>, req: Request, next: Next) -> Response
27where
28    S: RuntimeApiAuthState,
29{
30    let Some(expected) = state.runtime_token() else {
31        return next.run(req).await;
32    };
33    let authorized = req
34        .headers()
35        .get(header::AUTHORIZATION)
36        .and_then(|value| value.to_str().ok())
37        .and_then(bearer_token_from_header)
38        .is_some_and(|token| constant_time_eq(token, expected))
39        || req
40            .headers()
41            .get("x-deepseek-runtime-token")
42            .and_then(|value| value.to_str().ok())
43            .is_some_and(|token| constant_time_eq(token, expected));
44
45    if authorized {
46        next.run(req).await
47    } else {
48        (
49            StatusCode::UNAUTHORIZED,
50            Json(json!({
51                "error": {
52                    "message": "runtime API bearer token required",
53                    "status": StatusCode::UNAUTHORIZED.as_u16(),
54                }
55            })),
56        )
57            .into_response()
58    }
59}
60
61#[cfg(test)]
62mod tests {
63    use super::*;
64
65    #[test]
66    fn constant_time_eq_matches_equal_strings() {
67        assert!(constant_time_eq("abc", "abc"));
68    }
69
70    #[test]
71    fn constant_time_eq_rejects_different_strings() {
72        assert!(!constant_time_eq("abc", "abd"));
73        assert!(!constant_time_eq("abc", "ab"));
74    }
75}