1use crate::AppState;
2use axum::{
3 Json,
4 extract::{Request, State},
5 http::StatusCode,
6 middleware::Next,
7 response::{IntoResponse, Response},
8};
9use crabtalk_core::{ApiError, Storage};
10
11#[derive(Clone, Debug)]
13pub struct KeyName(pub Option<String>);
14
15pub async fn auth<S: Storage + 'static>(
19 State(state): State<AppState<S>>,
20 mut request: Request,
21 next: Next,
22) -> Response {
23 if state.config.keys.is_empty() {
25 request.extensions_mut().insert(KeyName(None));
26 return next.run(request).await;
27 }
28
29 let auth_header = request
30 .headers()
31 .get("authorization")
32 .and_then(|v| v.to_str().ok());
33
34 let token = match auth_header.and_then(|h| h.strip_prefix("Bearer ")) {
35 Some(t) => t,
36 None => {
37 return (
38 StatusCode::UNAUTHORIZED,
39 Json(ApiError::new(
40 "missing or invalid Authorization header",
41 "authentication_error",
42 )),
43 )
44 .into_response();
45 }
46 };
47
48 let Some(key_name) = state.key_map.get(token) else {
49 return (
50 StatusCode::UNAUTHORIZED,
51 Json(ApiError::new("invalid API key", "authentication_error")),
52 )
53 .into_response();
54 };
55
56 request
57 .extensions_mut()
58 .insert(KeyName(Some(key_name.clone())));
59
60 next.run(request).await
61}