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