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
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
//! Extractor for the "Basic" HTTP Authentication Scheme

use std::borrow::Cow;

use kayrx::web::dev::{Payload, ServiceRequest};
use kayrx::http::header::Header;
use kayrx::web::{FromRequest, HttpRequest};
use futures::future;

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

/// [`BasicAuth`] extractor configuration,
/// used for [`WWW-Authenticate`] header later.
///
/// [`BasicAuth`]: ./struct.BasicAuth.html
/// [`WWW-Authenticate`]:
/// ../../headers/www_authenticate/struct.WwwAuthenticate.html
#[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 [RFC2617](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
    }
}

// Needs `fn main` to display complete example.
#[allow(clippy::needless_doctest_main)]
/// Extractor for HTTP Basic auth.
///
/// # Example
///
/// ```
/// use kayrx::web::Result;
/// use keclc_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.
///
/// ## Example
///
/// ```
/// use kayrx::web::{web, App};
/// use keclc_httpauth::extractors::basic::{BasicAuth, Config};
///
/// async fn index(auth: BasicAuth) -> String {
///     format!("Hello, {}!", auth.user_id())
/// }
///
/// fn main() {
///     let app = App::new()
///         .data(Config::default().realm("Restricted area"))
///         .service(web::resource("/index.html").route(web::get().to(index)));
/// }
/// ```
///
/// [`Config`]: ./struct.Config.html
#[derive(Debug, Clone)]
pub struct BasicAuth(Basic);

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

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

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

    fn from_request(
        req: &HttpRequest,
        _: &mut Payload,
    ) -> <Self as FromRequest>::Future {
        future::ready(
            Authorization::<Basic>::parse(req)
                .map(|auth| BasicAuth(auth.into_scheme()))
                .map_err(|_| {
                    // TODO: debug! the original error
                    let challenge = req
                        .app_data::<Self::Config>()
                        .map(|config| config.0.clone())
                        // TODO: Add trace! about `Default::default` call
                        .unwrap_or_else(Default::default);

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

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

    fn from_service_request(req: &ServiceRequest) -> Self::Future {
        future::ready(
            Authorization::<Basic>::parse(req)
                .map(|auth| BasicAuth(auth.into_scheme()))
                .map_err(|_| {
                    // TODO: debug! the original error
                    let challenge = req
                        .app_data::<Config>()
                        .map(|config| config.0.clone())
                        // TODO: Add trace! about `Default::default` call
                        .unwrap_or_else(Default::default);

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