Skip to main content

rama_http/layer/auth/
add_authorization.rs

1//! Add authorization to requests using the [`Authorization`] header.
2//!
3//! [`Authorization`]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Authorization
4//!
5//! # Example
6//!
7//! ```
8//! use rama_core::bytes::Bytes;
9//!
10//! use rama_http::layer::validate_request::{ValidateRequestHeader, ValidateRequestHeaderLayer};
11//! use rama_http::layer::auth::AddAuthorizationLayer;
12//! use rama_http::{Body, Request, Response, StatusCode, header::AUTHORIZATION};
13//! use rama_core::service::service_fn;
14//! use rama_core::{Service, Layer};
15//! use rama_core::error::BoxError;
16//! use rama_net::user::credentials::basic;
17//!
18//! # async fn handle(request: Request) -> Result<Response, BoxError> {
19//! #     Ok(Response::new(Body::default()))
20//! # }
21//!
22//! # #[tokio::main]
23//! # async fn main() -> Result<(), BoxError> {
24//! # let service_that_requires_auth = ValidateRequestHeader::auth(
25//! #     service_fn(handle),
26//! #     basic!("username", "password"),
27//! # );
28//! let mut client = (
29//!     // Use basic auth with the given username and password
30//!     AddAuthorizationLayer::new(basic!("username", "password")),
31//! ).layer(service_that_requires_auth);
32//!
33//! // Make a request, we don't have to add the `Authorization` header manually
34//! let response = client
35//!     .serve(Request::new(Body::default()))
36//!     .await?;
37//!
38//! assert_eq!(StatusCode::OK, response.status());
39//! # Ok(())
40//! # }
41//! ```
42
43#![expect(
44    clippy::allow_attributes,
45    reason = "macro-generated `#[allow]` attributes whose underlying lints fire only for some expansions"
46)]
47
48use crate::{HeaderValue, Request, Response};
49use rama_core::{Layer, Service};
50use rama_http_headers::authorization::Credentials;
51use rama_utils::macros::define_inner_service_accessors;
52
53/// Layer that applies [`AddAuthorization`] which adds authorization to all requests using the
54/// [`Authorization`] header.
55///
56/// See the [module docs](crate::layer::auth::add_authorization) for an example.
57///
58/// You can also use [`SetRequestHeader`] if you have a use case that isn't supported by this
59/// middleware.
60///
61/// [`Authorization`]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Authorization
62/// [`SetRequestHeader`]: crate::layer::set_header::SetRequestHeader
63#[derive(Debug, Clone)]
64pub struct AddAuthorizationLayer {
65    value: Option<HeaderValue>,
66    if_not_present: bool,
67}
68
69impl AddAuthorizationLayer {
70    /// Create a new [`AddAuthorizationLayer`] that does not add any authorization.
71    ///
72    /// Can be useful if you only want to add authorization for some branches
73    /// of your service.
74    #[must_use]
75    pub fn none() -> Self {
76        Self {
77            value: None,
78            if_not_present: false,
79        }
80    }
81
82    /// Authorize requests using the given [`Credentials`].
83    #[allow(clippy::needless_pass_by_value)]
84    pub fn new(credential: impl Credentials) -> Self {
85        Self {
86            value: credential.encode(),
87            if_not_present: false,
88        }
89    }
90
91    rama_utils::macros::generate_set_and_with! {
92        /// Mark the header as [sensitive].
93        ///
94        /// This can for example be used to hide the header value from logs.
95        ///
96        /// [sensitive]: https://docs.rs/http/latest/http/header/struct.HeaderValue.html#method.set_sensitive
97        pub fn sensitive(mut self, sensitive: bool) -> Self {
98            if let Some(value) = &mut self.value {
99                value.set_sensitive(sensitive);
100            }
101            self
102        }
103    }
104
105    rama_utils::macros::generate_set_and_with! {
106        /// Preserve the existing `Authorization` header if it exists.
107        ///
108        /// This can be useful if you want to use different authorization headers for different requests.
109        pub fn if_not_present(mut self, value: bool) -> Self {
110            self.if_not_present = value;
111            self
112        }
113    }
114}
115
116impl<S> Layer<S> for AddAuthorizationLayer {
117    type Service = AddAuthorization<S>;
118
119    fn layer(&self, inner: S) -> Self::Service {
120        AddAuthorization {
121            inner,
122            value: self.value.clone(),
123            if_not_present: self.if_not_present,
124        }
125    }
126
127    fn into_layer(self, inner: S) -> Self::Service {
128        AddAuthorization {
129            inner,
130            value: self.value,
131            if_not_present: self.if_not_present,
132        }
133    }
134}
135
136/// Middleware that adds authorization all requests using the [`Authorization`] header.
137///
138/// See the [module docs](crate::layer::auth::add_authorization) for an example.
139///
140/// You can also use [`SetRequestHeader`] if you have a use case that isn't supported by this
141/// middleware.
142///
143/// [`Authorization`]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Authorization
144/// [`SetRequestHeader`]: crate::layer::set_header::SetRequestHeader
145#[derive(Debug, Clone)]
146pub struct AddAuthorization<S> {
147    inner: S,
148    value: Option<HeaderValue>,
149    if_not_present: bool,
150}
151
152impl<S> AddAuthorization<S> {
153    /// Create a new [`AddAuthorization`] that does not add any authorization.
154    ///
155    /// Can be useful if you only want to add authorization for some branches
156    /// of your service.
157    pub fn none(inner: S) -> Self {
158        AddAuthorizationLayer::none().into_layer(inner)
159    }
160
161    /// Authorize requests using the given [`Credentials`].
162    pub fn new(inner: S, credential: impl Credentials) -> Self {
163        AddAuthorizationLayer::new(credential).into_layer(inner)
164    }
165
166    define_inner_service_accessors!();
167
168    rama_utils::macros::generate_set_and_with! {
169        /// Mark the header as [sensitive].
170        ///
171        /// This can for example be used to hide the header value from logs.
172        ///
173        /// [sensitive]: https://docs.rs/http/latest/http/header/struct.HeaderValue.html#method.set_sensitive
174        pub fn sensitive(mut self, sensitive: bool) -> Self {
175            if let Some(value) = &mut self.value {
176                value.set_sensitive(sensitive);
177            }
178            self
179        }
180    }
181
182    rama_utils::macros::generate_set_and_with! {
183        /// Preserve the existing `Authorization` header if it exists.
184        ///
185        /// This can be useful if you want to use different authorization headers for different requests.
186        pub fn if_not_present(mut self, value: bool) -> Self {
187            self.if_not_present = value;
188            self
189        }
190    }
191}
192
193impl<S, ReqBody, ResBody> Service<Request<ReqBody>> for AddAuthorization<S>
194where
195    S: Service<Request<ReqBody>, Output = Response<ResBody>>,
196    ReqBody: Send + 'static,
197    ResBody: Send + 'static,
198{
199    type Output = S::Output;
200    type Error = S::Error;
201
202    async fn serve(&self, mut req: Request<ReqBody>) -> Result<Self::Output, Self::Error> {
203        if let Some(value) = &self.value
204            && (!self.if_not_present
205                || !req
206                    .headers()
207                    .contains_key(rama_http_types::header::AUTHORIZATION))
208        {
209            req.headers_mut()
210                .insert(rama_http_types::header::AUTHORIZATION, value.clone());
211        }
212        self.inner.serve(req).await
213    }
214}
215
216#[cfg(test)]
217mod tests {
218    #[allow(unused_imports)]
219    use super::*;
220
221    use crate::layer::validate_request::ValidateRequestHeaderLayer;
222    use crate::{Body, Request, Response, StatusCode};
223    use rama_core::Service;
224    use rama_core::error::BoxError;
225    use rama_core::service::service_fn;
226    use rama_net::user::credentials::{basic, bearer};
227    use std::convert::Infallible;
228
229    #[tokio::test]
230    async fn test_basic() {
231        // service that requires auth for all requests
232        let svc =
233            ValidateRequestHeaderLayer::auth(basic!("foo", "bar")).into_layer(service_fn(echo));
234
235        // make a client that adds auth
236        let client = AddAuthorization::new(svc, basic!("foo", "bar"));
237
238        let res = client.serve(Request::new(Body::empty())).await.unwrap();
239
240        assert_eq!(res.status(), StatusCode::OK);
241    }
242
243    #[tokio::test]
244    async fn test_token() {
245        // service that requires auth for all requests
246        let svc = ValidateRequestHeaderLayer::auth(bearer!("foo")).into_layer(service_fn(echo));
247
248        // make a client that adds auth
249        let client = AddAuthorization::new(svc, bearer!("foo"));
250
251        let res = client.serve(Request::new(Body::empty())).await.unwrap();
252
253        assert_eq!(res.status(), StatusCode::OK);
254    }
255
256    #[tokio::test]
257    async fn making_header_sensitive() {
258        let svc = ValidateRequestHeaderLayer::auth(bearer!("foo")).into_layer(service_fn(
259            async |request: Request<Body>| {
260                let auth = request
261                    .headers()
262                    .get(rama_http_types::header::AUTHORIZATION)
263                    .unwrap();
264                assert!(auth.is_sensitive());
265
266                Ok::<_, Infallible>(Response::new(Body::empty()))
267            },
268        ));
269
270        let client = AddAuthorization::new(svc, bearer!("foo")).with_sensitive(true);
271
272        let res = client.serve(Request::new(Body::empty())).await.unwrap();
273
274        assert_eq!(res.status(), StatusCode::OK);
275    }
276
277    async fn echo<Body>(req: Request<Body>) -> Result<Response<Body>, BoxError> {
278        Ok(Response::new(req.into_body()))
279    }
280}