http_types/auth/
basic_auth.rs

1use crate::auth::{AuthenticationScheme, Authorization};
2use crate::headers::{HeaderName, HeaderValue, Headers, AUTHORIZATION};
3use crate::Status;
4use crate::{bail_status as bail, ensure_status as ensure};
5
6/// HTTP Basic authorization.
7///
8/// # Specifications
9///
10/// - [RFC7617](https://tools.ietf.org/html/rfc7617)
11///
12/// # Examples
13///
14/// ```
15/// # fn main() -> http_types::Result<()> {
16/// #
17/// use http_types::Response;
18/// use http_types::auth::{AuthenticationScheme, BasicAuth};
19///
20/// let username = "nori";
21/// let password = "secret_fish!!";
22/// let authz = BasicAuth::new(username, password);
23///
24/// let mut res = Response::new(200);
25/// authz.apply(&mut res);
26///
27/// let authz = BasicAuth::from_headers(res)?.unwrap();
28///
29/// assert_eq!(authz.username(), username);
30/// assert_eq!(authz.password(), password);
31/// #
32/// # Ok(()) }
33/// ```
34#[derive(Debug)]
35pub struct BasicAuth {
36    username: String,
37    password: String,
38}
39
40impl BasicAuth {
41    /// Create a new instance of `BasicAuth`.
42    pub fn new<U, P>(username: U, password: P) -> Self
43    where
44        U: AsRef<str>,
45        P: AsRef<str>,
46    {
47        let username = username.as_ref().to_owned();
48        let password = password.as_ref().to_owned();
49        Self { username, password }
50    }
51
52    /// Create a new instance from headers.
53    pub fn from_headers(headers: impl AsRef<Headers>) -> crate::Result<Option<Self>> {
54        let auth = match Authorization::from_headers(headers)? {
55            Some(auth) => auth,
56            None => return Ok(None),
57        };
58
59        let scheme = auth.scheme();
60        ensure!(
61            matches!(scheme, AuthenticationScheme::Basic),
62            400,
63            "Expected basic auth scheme found `{}`",
64            scheme
65        );
66        Self::from_credentials(auth.credentials()).map(Some)
67    }
68
69    /// Create a new instance from the base64 encoded credentials.
70    pub fn from_credentials(credentials: impl AsRef<[u8]>) -> crate::Result<Self> {
71        let bytes = base64::decode(credentials).status(400)?;
72        let credentials = String::from_utf8(bytes).status(400)?;
73
74        let mut iter = credentials.splitn(2, ':');
75        let username = iter.next();
76        let password = iter.next();
77
78        let (username, password) = match (username, password) {
79            (Some(username), Some(password)) => (username.to_string(), password.to_string()),
80            (Some(_), None) => bail!(400, "Expected basic auth to contain a password"),
81            (None, _) => bail!(400, "Expected basic auth to contain a username"),
82        };
83
84        Ok(Self { username, password })
85    }
86
87    /// Sets the header.
88    pub fn apply(&self, mut headers: impl AsMut<Headers>) {
89        headers.as_mut().insert(self.name(), self.value());
90    }
91
92    /// Get the `HeaderName`.
93    pub fn name(&self) -> HeaderName {
94        AUTHORIZATION
95    }
96
97    /// Get the `HeaderValue`.
98    pub fn value(&self) -> HeaderValue {
99        let scheme = AuthenticationScheme::Basic;
100        let credentials = base64::encode(format!("{}:{}", self.username, self.password));
101        let auth = Authorization::new(scheme, credentials);
102        auth.value()
103    }
104
105    /// Get the username.
106    pub fn username(&self) -> &str {
107        self.username.as_str()
108    }
109
110    /// Get the password.
111    pub fn password(&self) -> &str {
112        self.password.as_str()
113    }
114}
115
116#[cfg(test)]
117mod test {
118    use super::*;
119    use crate::headers::Headers;
120
121    #[test]
122    fn smoke() -> crate::Result<()> {
123        let username = "nori";
124        let password = "secret_fish!!";
125        let authz = BasicAuth::new(username, password);
126
127        let mut headers = Headers::new();
128        authz.apply(&mut headers);
129
130        let authz = BasicAuth::from_headers(headers)?.unwrap();
131
132        assert_eq!(authz.username(), username);
133        assert_eq!(authz.password(), password);
134        Ok(())
135    }
136
137    #[test]
138    fn bad_request_on_parse_error() {
139        let mut headers = Headers::new();
140        headers.insert(AUTHORIZATION, "<nori ate the tag. yum.>");
141        let err = BasicAuth::from_headers(headers).unwrap_err();
142        assert_eq!(err.status(), 400);
143    }
144}