1use axum::extract::Request;
8use axum::http::StatusCode;
9use axum::middleware::Next;
10use axum::response::{IntoResponse, Response};
11use axum::Json;
12
13use crate::openai_types::ErrorResponse;
14
15const API_KEY_ENV: &str = "EMBACLE_API_KEY";
17
18pub async fn require_auth(request: Request, next: Next) -> Response {
25 let expected_key = match std::env::var(API_KEY_ENV) {
26 Ok(key) if !key.is_empty() => key,
27 _ => return next.run(request).await,
28 };
29
30 let auth_header = request
31 .headers()
32 .get("authorization")
33 .and_then(|v| v.to_str().ok());
34
35 match auth_header {
36 Some(header) if header.starts_with("Bearer ") => {
37 let token = &header["Bearer ".len()..];
38 if token == expected_key {
39 next.run(request).await
40 } else {
41 auth_error("Invalid API key")
42 }
43 }
44 Some(_) => auth_error("Authorization header must use Bearer scheme"),
45 None => auth_error("Missing Authorization header"),
46 }
47}
48
49fn auth_error(message: &str) -> Response {
51 let body = ErrorResponse::new("authentication_error", message);
52 (StatusCode::UNAUTHORIZED, Json(body)).into_response()
53}
54
55#[cfg(test)]
56mod tests {
57 use super::*;
58
59 #[test]
60 fn api_key_env_is_correct() {
61 assert_eq!(API_KEY_ENV, "EMBACLE_API_KEY");
62 }
63}