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)]
13pub struct KeyName(pub Option<String>);
14
15pub async fn auth<S: Storage + 'static, P: Provider + 'static>(
19 State(state): State<AppState<S, P>>,
20 mut request: Request,
21 next: Next,
22) -> Response {
23 if state.config.admin_token.is_none()
25 && state
26 .key_map
27 .read()
28 .unwrap_or_else(|e| e.into_inner())
29 .is_empty()
30 {
31 request.extensions_mut().insert(KeyName(None));
32 return next.run(request).await;
33 }
34
35 let auth_header = request
36 .headers()
37 .get("authorization")
38 .and_then(|v| v.to_str().ok());
39
40 let token = match auth_header.and_then(|h| h.strip_prefix("Bearer ")) {
41 Some(t) => t,
42 None => {
43 return (
44 StatusCode::UNAUTHORIZED,
45 Json(ApiError::new(
46 "missing or invalid Authorization header",
47 "authentication_error",
48 )),
49 )
50 .into_response();
51 }
52 };
53
54 let key_name = state
55 .key_map
56 .read()
57 .unwrap_or_else(|e| e.into_inner())
58 .get(token)
59 .cloned();
60
61 let Some(key_name) = key_name else {
62 return (
63 StatusCode::UNAUTHORIZED,
64 Json(ApiError::new("invalid API key", "authentication_error")),
65 )
66 .into_response();
67 };
68
69 request.extensions_mut().insert(KeyName(Some(key_name)));
70
71 next.run(request).await
72}