Skip to main content

rama_http/layer/auth/
validate_authorization.rs

1//! Authorize requests using [`ValidateRequest`].
2
3use std::{fmt, marker::PhantomData};
4
5use rama_core::{extensions::ExtensionsRef, telemetry::tracing};
6use rama_http_headers::{Authorization, HeaderMapExt, authorization::Credentials};
7use rama_http_types::{Body, HeaderValue, Request, Response, StatusCode, header};
8use rama_net::user::{
9    Basic, Bearer, RawToken, UserId,
10    authority::{AuthorizeResult, Authorizer, StaticAuthorizer},
11};
12
13use crate::{
14    layer::validate_request::{ValidateRequest, ValidateRequestHeader, ValidateRequestHeaderLayer},
15    service::web::response::IntoResponse,
16};
17
18/// Utility type to allow you to use any [`Authorizer`]
19/// that works with [`Credentials`] to authorize the [`Authorization`] header,
20/// and return [`StatusCode::UNAUTHORIZED`] response with [`header::WWW_AUTHENTICATE`] for unauthorized request,
21/// tracing the original error for your convenience.
22pub struct HttpAuthorizer<A, C> {
23    authorizer: A,
24    allow_anonymous: bool,
25    _credentials: PhantomData<fn() -> C>,
26}
27
28impl From<Basic> for HttpAuthorizer<StaticAuthorizer<Basic>, Basic> {
29    fn from(value: Basic) -> Self {
30        Self::new(StaticAuthorizer::new(value))
31    }
32}
33
34impl From<Bearer> for HttpAuthorizer<StaticAuthorizer<Bearer>, Bearer> {
35    fn from(value: Bearer) -> Self {
36        Self::new(StaticAuthorizer::new(value))
37    }
38}
39
40impl From<RawToken> for HttpAuthorizer<StaticAuthorizer<RawToken>, RawToken> {
41    fn from(value: RawToken) -> Self {
42        Self::new(StaticAuthorizer::new(value))
43    }
44}
45
46impl<C: Credentials> From<Vec<C>> for HttpAuthorizer<Vec<C>, C> {
47    fn from(value: Vec<C>) -> Self {
48        Self::new(value)
49    }
50}
51
52impl<const N: usize, C: Credentials> From<[C; N]> for HttpAuthorizer<[C; N], C> {
53    fn from(value: [C; N]) -> Self {
54        Self::new(value)
55    }
56}
57
58impl<A, C> HttpAuthorizer<A, C> {
59    pub fn new(authorizer: A) -> Self {
60        Self {
61            authorizer,
62            allow_anonymous: false,
63            _credentials: PhantomData,
64        }
65    }
66
67    rama_utils::macros::generate_set_and_with! {
68        /// Defines whether or not to allow anonymous.
69        ///
70        /// This means that the request is will be authorized automatically,
71        /// if no [`Authorization`] header was passed in.
72        pub fn allow_anonymous(mut self, allow: bool) -> Self {
73            self.allow_anonymous = allow;
74            self
75        }
76    }
77}
78
79impl<A: fmt::Debug, C> fmt::Debug for HttpAuthorizer<A, C> {
80    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
81        f.debug_struct("HttpAuthorizer")
82            .field("authorizer", &self.authorizer)
83            .field("allow_anonymous", &self.allow_anonymous)
84            .field(
85                "_credentials",
86                &format_args!("{}", std::any::type_name::<fn() -> C>()),
87            )
88            .finish()
89    }
90}
91
92impl<A: Clone, C> Clone for HttpAuthorizer<A, C> {
93    fn clone(&self) -> Self {
94        Self {
95            authorizer: self.authorizer.clone(),
96            allow_anonymous: self.allow_anonymous,
97            _credentials: PhantomData,
98        }
99    }
100}
101
102impl<A, C> Authorizer<C> for HttpAuthorizer<A, C>
103where
104    A: Authorizer<C, Error: fmt::Debug>,
105    C: Credentials + Send + 'static,
106{
107    type Error = Response;
108
109    async fn authorize(&self, credentials: C) -> AuthorizeResult<C, Self::Error> {
110        let AuthorizeResult {
111            credentials,
112            result,
113        } = self.authorizer.authorize(credentials).await;
114
115        let result = result.map_err(|err| {
116            tracing::trace!("input credentials were not authorized: {err:?}");
117            let mut res = Response::new(Body::empty());
118            *res.status_mut() = StatusCode::UNAUTHORIZED;
119            // Scheme-less credentials (`RawToken`) have no meaningful
120            // `WWW-Authenticate` challenge to advertise — RFC 7235 §4.1
121            // requires the value to be at least a `<scheme>`, so we omit
122            // the header rather than emit an empty one.
123            if !C::SCHEME.is_empty() {
124                res.headers_mut().insert(
125                    header::WWW_AUTHENTICATE,
126                    HeaderValue::from_static(C::SCHEME),
127                );
128            }
129            res
130        });
131
132        AuthorizeResult {
133            credentials,
134            result,
135        }
136    }
137}
138
139impl<ReqBody, A, C> ValidateRequest<ReqBody> for HttpAuthorizer<A, C>
140where
141    ReqBody: Send + 'static,
142    A: Authorizer<C, Error: fmt::Debug>,
143    C: Credentials + Send + 'static,
144{
145    type ResponseBody = Body;
146
147    async fn validate(
148        &self,
149        request: Request<ReqBody>,
150    ) -> Result<Request<ReqBody>, Response<Self::ResponseBody>> {
151        match request.headers().typed_get::<Authorization<C>>() {
152            Some(auth) => {
153                let AuthorizeResult { result, .. } = self.authorize(auth.into_inner()).await;
154                match result {
155                    Ok(maybe_ext) => {
156                        if let Some(ext) = maybe_ext {
157                            request.extensions().extend(&ext);
158                        }
159                        Ok(request)
160                    }
161                    Err(response) => Err(response),
162                }
163            }
164            None => {
165                if self.allow_anonymous {
166                    request.extensions().insert(UserId::Anonymous);
167                    Ok(request)
168                } else {
169                    Err(StatusCode::UNAUTHORIZED.into_response())
170                }
171            }
172        }
173    }
174}
175
176impl<S, A, C> ValidateRequestHeader<S, HttpAuthorizer<A, C>> {
177    #[inline]
178    /// Validate the request with an [`HttpAuthorizer`].
179    pub fn auth(inner: S, authorizer: impl Into<HttpAuthorizer<A, C>>) -> Self {
180        Self::custom(inner, authorizer.into())
181    }
182}
183
184impl<A, C> ValidateRequestHeaderLayer<HttpAuthorizer<A, C>> {
185    #[inline]
186    /// Validate the request with an [`HttpAuthorizer`].
187    pub fn auth(authorizer: impl Into<HttpAuthorizer<A, C>>) -> Self {
188        Self::custom(authorizer.into())
189    }
190}