1use crate::AppState;
2use axum::{
3 Json,
4 extract::{Request, State},
5 http::StatusCode,
6 middleware::Next,
7 response::{IntoResponse, Response},
8};
9use crabllm_core::{ApiError, Provider, Storage};
10
11#[derive(Clone, Debug)]
17pub struct Principal(pub Option<String>);
18
19pub async fn auth<S: Storage + 'static, P: Provider + 'static>(
23 State(state): State<AppState<S, P>>,
24 mut request: Request,
25 next: Next,
26) -> Response {
27 if state.config.admin_token.is_none()
29 && state
30 .key_map
31 .read()
32 .unwrap_or_else(|e| e.into_inner())
33 .is_empty()
34 {
35 request.extensions_mut().insert(Principal(None));
36 return next.run(request).await;
37 }
38
39 let headers = request.headers();
42 let bearer = headers
43 .get("authorization")
44 .and_then(|v| v.to_str().ok())
45 .and_then(|h| h.strip_prefix("Bearer "));
46 let x_api_key = headers.get("x-api-key").and_then(|v| v.to_str().ok());
47
48 let token = match bearer.or(x_api_key) {
49 Some(t) => t,
50 None => {
51 return (
52 StatusCode::UNAUTHORIZED,
53 Json(ApiError::new(
54 "missing Authorization or x-api-key header",
55 "authentication_error",
56 )),
57 )
58 .into_response();
59 }
60 };
61
62 let principal = state
63 .key_map
64 .read()
65 .unwrap_or_else(|e| e.into_inner())
66 .get(token)
67 .cloned();
68
69 let Some(principal) = principal else {
70 return (
71 StatusCode::UNAUTHORIZED,
72 Json(ApiError::new("invalid API key", "authentication_error")),
73 )
74 .into_response();
75 };
76
77 request.extensions_mut().insert(Principal(Some(principal)));
78
79 next.run(request).await
80}