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