datasynth_server/grpc/
auth_interceptor.rs1use tonic::{Request, Status};
6
7pub type ApiKeyValidator = Box<dyn Fn(&str) -> bool + Send + Sync>;
9
10#[derive(Clone)]
12pub struct GrpcAuthConfig {
13 pub enabled: bool,
15 api_keys: Vec<String>,
17}
18
19impl GrpcAuthConfig {
20 pub fn new(api_keys: Vec<String>) -> Self {
22 Self {
23 enabled: !api_keys.is_empty(),
24 api_keys,
25 }
26 }
27
28 pub fn disabled() -> Self {
30 Self {
31 enabled: false,
32 api_keys: Vec::new(),
33 }
34 }
35
36 pub fn validate_token(&self, token: &str) -> bool {
38 if !self.enabled {
39 return true;
40 }
41 self.api_keys.iter().any(|k| k == token)
42 }
43}
44
45#[allow(clippy::result_large_err)]
49pub fn auth_interceptor(config: &GrpcAuthConfig, request: &Request<()>) -> Result<(), Status> {
50 if !config.enabled {
51 return Ok(());
52 }
53
54 let token = request
55 .metadata()
56 .get("authorization")
57 .and_then(|v| v.to_str().ok())
58 .and_then(|s| s.strip_prefix("Bearer "));
59
60 match token {
61 Some(t) if config.validate_token(t) => Ok(()),
62 Some(_) => Err(Status::unauthenticated("Invalid credentials")),
63 None => Err(Status::unauthenticated(
64 "Missing authorization metadata. Provide 'authorization: Bearer <token>'",
65 )),
66 }
67}
68
69#[cfg(test)]
70#[allow(clippy::unwrap_used)]
71mod tests {
72 use super::*;
73
74 #[test]
75 fn test_disabled_auth_passes() {
76 let config = GrpcAuthConfig::disabled();
77 let request = Request::new(());
78 assert!(auth_interceptor(&config, &request).is_ok());
79 }
80
81 #[test]
82 fn test_missing_token_fails() {
83 let config = GrpcAuthConfig::new(vec!["secret".to_string()]);
84 let request = Request::new(());
85 assert!(auth_interceptor(&config, &request).is_err());
86 }
87
88 #[test]
89 fn test_valid_token_passes() {
90 let config = GrpcAuthConfig::new(vec!["my-key".to_string()]);
91 let mut request = Request::new(());
92 request
93 .metadata_mut()
94 .insert("authorization", "Bearer my-key".parse().unwrap());
95 assert!(auth_interceptor(&config, &request).is_ok());
96 }
97
98 #[test]
99 fn test_invalid_token_fails() {
100 let config = GrpcAuthConfig::new(vec!["my-key".to_string()]);
101 let mut request = Request::new(());
102 request
103 .metadata_mut()
104 .insert("authorization", "Bearer wrong-key".parse().unwrap());
105 assert!(auth_interceptor(&config, &request).is_err());
106 }
107}