tide_http_auth/
lib.rs

1mod scheme;
2mod storage;
3
4pub use scheme::{BasicAuthRequest, BasicAuthScheme, BearerAuthRequest, BearerAuthScheme, Scheme};
5pub use storage::Storage;
6
7use std::marker::PhantomData;
8use tide::{Middleware, Next, Request, Response, StatusCode};
9use tracing::{error, info};
10
11/// Middleware for implementing a given [`Scheme`] (Basic, Bearer, Jwt) backed by a given
12/// [`Storage`] backend implemented by the [Tide application
13/// `State`](https://docs.rs/tide/0.9.0/tide/#state).
14pub struct Authentication<User: Send + Sync + 'static, ImplScheme: Scheme<User>> {
15    pub(crate) scheme: ImplScheme,
16    _user_t: PhantomData<User>,
17}
18
19#[doc(hidden)]
20impl<User: Send + Sync + 'static, ImplScheme: Scheme<User>> std::fmt::Debug
21    for Authentication<User, ImplScheme>
22{
23    fn fmt(
24        &self,
25        formatter: &mut std::fmt::Formatter<'_>,
26    ) -> std::result::Result<(), std::fmt::Error> {
27        write!(formatter, "Authentication<Scheme>")?;
28        Ok(())
29    }
30}
31
32impl<User: Send + Sync + 'static, ImplScheme: Scheme<User>> Authentication<User, ImplScheme> {
33    /// Create a new authentication middleware with a scheme.
34    ///
35    /// # Examples
36    ///
37    /// ```no_run
38    /// #[derive(Clone)]
39    /// struct MyUserType {
40    ///     username: String
41    /// }
42    /// # fn main() -> Result<(), std::io::Error> { async_std::task::block_on(async {
43    /// #
44    /// use tide_http_auth::{ Authentication, BasicAuthScheme };
45    /// Authentication::<MyUserType, BasicAuthScheme>::new(BasicAuthScheme::default());
46    /// # Ok(()) })}
47    /// ```
48    pub fn new(scheme: ImplScheme) -> Self {
49        Self {
50            scheme,
51
52            _user_t: PhantomData::default(),
53        }
54    }
55}
56
57#[async_trait::async_trait]
58impl<ImplScheme, State, User> Middleware<State> for Authentication<User, ImplScheme>
59where
60    ImplScheme: Scheme<User> + Send + Sync + 'static,
61    State: Storage<User, ImplScheme::Request> + Clone + Send + Sync + 'static,
62    User: Send + Sync + 'static,
63{
64    async fn handle(&self, mut req: Request<State>, next: Next<'_, State>) -> tide::Result {
65        // read the header
66        let auth_header = req.header(ImplScheme::header_name());
67        if auth_header.is_none() {
68            info!("no auth header, proceeding");
69            return Ok(next.run(req).await);
70        }
71        let value: Vec<_> = auth_header.unwrap().into_iter().collect();
72
73        if value.is_empty() {
74            info!("empty auth header, proceeding");
75            return Ok(next.run(req).await);
76        }
77
78        if value.len() > 1 && ImplScheme::should_401_on_multiple_values() {
79            error!("multiple auth headers, bailing");
80            return Ok(Response::new(StatusCode::Unauthorized));
81        }
82
83        for value in value {
84            let value = value.as_str();
85            if !value.starts_with(ImplScheme::scheme_name()) {
86                continue;
87            }
88            let auth_param = &value[ImplScheme::scheme_name().len()..];
89            let state = req.state();
90
91            info!("saw auth header, attempting to auth");
92            if let Some(user) = self.scheme.authenticate(state, auth_param).await? {
93                req.set_ext(user);
94                break;
95            } else if ImplScheme::should_403_on_bad_auth() {
96                error!("Authorization header sent but no user returned, bailing");
97                return Ok(Response::new(StatusCode::Forbidden));
98            }
99        }
100        Ok(next.run(req).await)
101    }
102}