1use axum::http::HeaderMap;
2use serde::{Deserialize, Serialize};
3use tonic::metadata::MetadataMap;
4
5#[derive(Clone, Debug, Default, Deserialize, Serialize)]
7#[serde(tag = "type", rename_all = "snake_case")]
8pub enum AuthMode {
9 #[default]
11 None,
12 Dev { api_key: String },
14}
15
16#[derive(Debug, thiserror::Error)]
18pub enum AuthError {
19 #[error("missing credentials")]
20 Missing,
21 #[error("invalid credentials")]
22 Invalid,
23}
24
25#[derive(Clone)]
27pub struct AuthMiddleware {
28 mode: AuthMode,
29}
30
31impl AuthMiddleware {
32 pub fn new(mode: AuthMode) -> Self {
34 Self { mode }
35 }
36
37 pub fn validate_http(&self, headers: &HeaderMap) -> Result<Option<String>, AuthError> {
39 match &self.mode {
40 AuthMode::None => Ok(None),
41 AuthMode::Dev { api_key } => {
42 let provided = extract_api_key(headers);
43 if provided.as_deref() == Some(api_key.as_str()) {
44 Ok(Some("dev".to_string()))
45 } else if provided.is_none() {
46 Err(AuthError::Missing)
47 } else {
48 Err(AuthError::Invalid)
49 }
50 }
51 }
52 }
53
54 pub fn validate_grpc(&self, metadata: &MetadataMap) -> Result<Option<String>, AuthError> {
56 match &self.mode {
57 AuthMode::None => Ok(None),
58 AuthMode::Dev { api_key } => {
59 let provided = extract_api_key_from_metadata(metadata);
60 if provided.as_deref() == Some(api_key.as_str()) {
61 Ok(Some("dev".to_string()))
62 } else if provided.is_none() {
63 Err(AuthError::Missing)
64 } else {
65 Err(AuthError::Invalid)
66 }
67 }
68 }
69 }
70
71 pub fn mode(&self) -> &AuthMode {
72 &self.mode
73 }
74}
75
76fn extract_api_key(headers: &HeaderMap) -> Option<String> {
77 if let Some(value) = headers.get("x-api-key").and_then(|v| v.to_str().ok()) {
78 return Some(value.to_string());
79 }
80 headers
81 .get(axum::http::header::AUTHORIZATION)
82 .and_then(|v| v.to_str().ok())
83 .and_then(|raw| raw.strip_prefix("Bearer "))
84 .map(|v| v.to_string())
85}
86
87fn extract_api_key_from_metadata(metadata: &MetadataMap) -> Option<String> {
88 if let Some(value) = metadata.get("x-api-key").and_then(|v| v.to_str().ok()) {
89 return Some(value.to_string());
90 }
91 metadata
92 .get("authorization")
93 .and_then(|v| v.to_str().ok())
94 .and_then(|raw| raw.strip_prefix("Bearer "))
95 .map(|v| v.to_string())
96}