1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
//! Extractor for the "Basic" HTTP Authentication Scheme.

use std::borrow::Cow;

use actix_utils::future::{ready, Ready};
use actix_web::{dev::Payload, http::header::Header, FromRequest, HttpRequest};

use super::{config::AuthExtractorConfig, errors::AuthenticationError};
use crate::headers::{
    authorization::{Authorization, Basic},
    www_authenticate::basic::Basic as Challenge,
};

/// [`BasicAuth`] extractor configuration used for [`WWW-Authenticate`] header later.
///
/// [`WWW-Authenticate`]: crate::headers::www_authenticate::WwwAuthenticate
#[derive(Debug, Clone, Default)]
pub struct Config(Challenge);

impl Config {
    /// Set challenge `realm` attribute.
    ///
    /// The "realm" attribute indicates the scope of protection in the manner described in HTTP/1.1
    /// [RFC 2617 §1.2](https://tools.ietf.org/html/rfc2617#section-1.2).
    pub fn realm<T>(mut self, value: T) -> Config
    where
        T: Into<Cow<'static, str>>,
    {
        self.0.realm = Some(value.into());
        self
    }
}

impl AsRef<Challenge> for Config {
    fn as_ref(&self) -> &Challenge {
        &self.0
    }
}

impl AuthExtractorConfig for Config {
    type Inner = Challenge;

    fn into_inner(self) -> Self::Inner {
        self.0
    }
}

/// Extractor for HTTP Basic auth.
///
/// # Examples
/// ```
/// use actix_web_httpauth::extractors::basic::BasicAuth;
///
/// async fn index(auth: BasicAuth) -> String {
///     format!("Hello, {}!", auth.user_id())
/// }
/// ```
///
/// If authentication fails, this extractor fetches the [`Config`] instance from the [app data] in
/// order to properly form the `WWW-Authenticate` response header.
///
/// # Examples
/// ```
/// use actix_web::{web, App};
/// use actix_web_httpauth::extractors::basic::{self, BasicAuth};
///
/// async fn index(auth: BasicAuth) -> String {
///     format!("Hello, {}!", auth.user_id())
/// }
///
/// App::new()
///     .app_data(basic::Config::default().realm("Restricted area"))
///     .service(web::resource("/index.html").route(web::get().to(index)));
/// ```
///
/// [app data]: https://docs.rs/actix-web/4/actix_web/struct.App.html#method.app_data
#[derive(Debug, Clone)]
pub struct BasicAuth(Basic);

impl BasicAuth {
    /// Returns client's user-ID.
    pub fn user_id(&self) -> &str {
        self.0.user_id()
    }

    /// Returns client's password.
    pub fn password(&self) -> Option<&str> {
        self.0.password()
    }
}

impl From<Basic> for BasicAuth {
    fn from(basic: Basic) -> Self {
        Self(basic)
    }
}

impl FromRequest for BasicAuth {
    type Future = Ready<Result<Self, Self::Error>>;
    type Error = AuthenticationError<Challenge>;

    fn from_request(req: &HttpRequest, _: &mut Payload) -> <Self as FromRequest>::Future {
        ready(
            Authorization::<Basic>::parse(req)
                .map(|auth| BasicAuth(auth.into_scheme()))
                .map_err(|err| {
                    log::debug!("`BasicAuth` extract error: {}", err);

                    let challenge = req
                        .app_data::<Config>()
                        .map(|config| config.0.clone())
                        .unwrap_or_default();

                    AuthenticationError::new(challenge)
                }),
        )
    }
}