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
11pub 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 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 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}