iota_grpc_client/
interceptors.rs

1// Copyright (c) Mysten Labs, Inc.
2// Modifications Copyright (c) 2025 IOTA Stiftung
3// SPDX-License-Identifier: Apache-2.0
4
5/// Interceptor used to add additional headers to a Request
6#[derive(Clone, Debug, Default)]
7pub struct HeadersInterceptor {
8    headers: tonic::metadata::MetadataMap,
9}
10
11impl HeadersInterceptor {
12    /// Create a new, empty `HeadersInterceptor`.
13    pub fn new() -> Self {
14        Self::default()
15    }
16
17    /// Return reference to the internal `MetadataMap`.
18    pub fn headers(&self) -> &tonic::metadata::MetadataMap {
19        &self.headers
20    }
21
22    /// Get mutable access to the internal `MetadataMap` for modification.
23    pub fn headers_mut(&mut self) -> &mut tonic::metadata::MetadataMap {
24        &mut self.headers
25    }
26
27    /// Enable HTTP basic authentication with a username and optional password.
28    pub fn basic_auth<U, P>(&mut self, username: U, password: Option<P>)
29    where
30        U: std::fmt::Display,
31        P: std::fmt::Display,
32    {
33        use std::io::Write;
34
35        use base64::{prelude::BASE64_STANDARD, write::EncoderWriter};
36
37        let mut buf = b"Basic ".to_vec();
38        {
39            let mut encoder = EncoderWriter::new(&mut buf, &BASE64_STANDARD);
40            let _ = write!(encoder, "{username}:");
41            if let Some(password) = password {
42                let _ = write!(encoder, "{password}");
43            }
44        }
45        let mut header = tonic::metadata::MetadataValue::try_from(buf)
46            .expect("base64 is always valid HeaderValue");
47        header.set_sensitive(true);
48
49        self.headers
50            .insert(http::header::AUTHORIZATION.as_str(), header);
51    }
52
53    /// Enable HTTP bearer authentication.
54    pub fn bearer_auth<T>(&mut self, token: T)
55    where
56        T: std::fmt::Display,
57    {
58        let header_value = format!("Bearer {token}");
59        let mut header = tonic::metadata::MetadataValue::try_from(header_value)
60            .expect("token is always valid HeaderValue");
61        header.set_sensitive(true);
62
63        self.headers
64            .insert(http::header::AUTHORIZATION.as_str(), header);
65    }
66}
67
68impl tonic::service::Interceptor for &HeadersInterceptor {
69    fn call(
70        &mut self,
71        mut request: tonic::Request<()>,
72    ) -> std::result::Result<tonic::Request<()>, tonic::Status> {
73        if !self.headers.is_empty() {
74            request
75                .metadata_mut()
76                .as_mut()
77                .extend(self.headers.clone().into_headers());
78        }
79        Ok(request)
80    }
81}
82
83impl tonic::service::Interceptor for HeadersInterceptor {
84    fn call(
85        &mut self,
86        request: tonic::Request<()>,
87    ) -> std::result::Result<tonic::Request<()>, tonic::Status> {
88        (&*self).call(request)
89    }
90}