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}