    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)
                    }),
            )
        }
    }