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