Skip to main content

crabllm_proxy/
auth.rs

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, Storage};
10
11/// Wrapper for the authenticated key name, inserted into request extensions.
12#[derive(Clone, Debug)]
13pub struct KeyName(pub Option<String>);
14
15/// Auth middleware: validates Bearer token against configured virtual keys.
16/// Skips auth only when no admin_token is configured AND key_map is empty.
17/// Inserts `KeyName` into request extensions for downstream handlers.
18pub async fn auth<S: Storage + 'static>(
19    State(state): State<AppState<S>>,
20    mut request: Request,
21    next: Next,
22) -> Response {
23    // Skip auth when key management is disabled and no keys exist.
24    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}