1use tonic::metadata::MetadataValue;
9use tonic::service::Interceptor;
10use tonic::service::interceptor::InterceptedService;
11use tonic::transport::Channel;
12use tonic::{Request, Status};
13
14use crate::Result;
15use crate::config::Auth;
16
17pub type Intercepted = InterceptedService<Channel, AuthInterceptor>;
19
20#[derive(Clone, Debug, Default)]
22pub struct AuthInterceptor {
23 token: Option<String>,
24}
25
26impl AuthInterceptor {
27 #[must_use]
29 pub fn new(token: Option<String>) -> Self {
30 Self { token }
31 }
32}
33
34impl Interceptor for AuthInterceptor {
35 fn call(&mut self, mut request: Request<()>) -> Result<Request<()>, Status> {
36 if let Some(token) = &self.token {
37 let value: MetadataValue<_> = format!("Bearer {token}")
38 .parse()
39 .map_err(|_| Status::unauthenticated("invalid bearer token"))?;
40 request.metadata_mut().insert("authorization", value);
41 }
42 #[cfg(feature = "otel")]
44 crate::telemetry::otel::inject_trace_context_metadata(request.metadata_mut());
45 Ok(request)
46 }
47}
48
49pub async fn intercepted(channel: &Channel, auth: &Auth) -> Result<Intercepted> {
55 let token = auth.bearer().await?;
56 Ok(InterceptedService::new(
57 channel.clone(),
58 AuthInterceptor::new(token),
59 ))
60}
61
62#[cfg(test)]
63#[allow(clippy::unwrap_used)]
64mod tests {
65 use super::*;
66
67 #[test]
68 fn injects_bearer_header_when_token_present() {
69 let mut interceptor = AuthInterceptor::new(Some("tok-123".to_string()));
70 let req = interceptor.call(Request::new(())).unwrap();
71 let value = req.metadata().get("authorization").unwrap();
72 assert_eq!(value.to_str().unwrap(), "Bearer tok-123");
73 }
74
75 #[test]
76 fn no_header_when_token_absent() {
77 let mut interceptor = AuthInterceptor::new(None);
78 let req = interceptor.call(Request::new(())).unwrap();
79 assert!(req.metadata().get("authorization").is_none());
80 }
81
82 #[test]
83 fn rejects_a_token_with_illegal_header_bytes() {
84 let mut interceptor = AuthInterceptor::new(Some("bad\ntoken".to_string()));
85 let result = interceptor.call(Request::new(()));
86 assert!(result.is_err(), "a token with a newline must be rejected");
87 }
88}