zagens_runtime_api/
auth.rs1use 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}