Skip to main content

lychee_lib/types/basic_auth/
credentials.rs

1use async_trait::async_trait;
2use std::str::FromStr;
3
4use headers::authorization::Credentials;
5use headers::{Authorization, authorization::Basic};
6use http::header::AUTHORIZATION;
7use reqwest::Request;
8use serde::Deserialize;
9use thiserror::Error;
10
11use crate::chain::{ChainResult, Handler};
12
13#[derive(Copy, Clone, Debug, Error, PartialEq)]
14pub enum BasicAuthCredentialsParseError {
15    #[error("Invalid basic auth credentials syntax")]
16    InvalidSyntax,
17
18    #[error("Missing basic auth password")]
19    MissingPassword,
20
21    #[error("Missing basic auth username")]
22    MissingUsername,
23
24    #[error(
25        "Too many values separated by colon. Expected 2, got {0}. Valid form is '<username>:<password>'"
26    )]
27    TooManyParts(usize),
28}
29
30/// [`BasicAuthCredentials`] contains a pair of basic auth values consisting of
31/// a username and password.
32#[derive(Debug, Clone, Deserialize, PartialEq, Eq, Hash)]
33pub struct BasicAuthCredentials {
34    /// Basic auth username
35    pub username: String,
36
37    /// Basic auth password
38    pub password: String,
39}
40
41impl FromStr for BasicAuthCredentials {
42    type Err = BasicAuthCredentialsParseError;
43
44    fn from_str(credentials: &str) -> Result<Self, Self::Err> {
45        let parts: Vec<_> = credentials.trim().split(':').collect();
46
47        if parts.len() <= 1 {
48            return Err(BasicAuthCredentialsParseError::InvalidSyntax);
49        }
50
51        if parts.len() > 2 {
52            return Err(BasicAuthCredentialsParseError::TooManyParts(parts.len()));
53        }
54
55        if parts[0].is_empty() {
56            return Err(BasicAuthCredentialsParseError::MissingUsername);
57        }
58
59        if parts[1].is_empty() {
60            return Err(BasicAuthCredentialsParseError::MissingPassword);
61        }
62
63        Ok(Self {
64            username: parts[0].to_string(),
65            password: parts[1].to_string(),
66        })
67    }
68}
69
70impl BasicAuthCredentials {
71    /// Returns the credentials as [`Authorization<Basic>`].
72    #[must_use]
73    pub fn to_authorization(&self) -> Authorization<Basic> {
74        Authorization::basic(&self.username, &self.password)
75    }
76
77    /// Append the credentials as headers to a `Request`
78    pub fn append_to_request(&self, request: &mut Request) {
79        request
80            .headers_mut()
81            .append(AUTHORIZATION, self.to_authorization().0.encode());
82    }
83}
84
85#[async_trait]
86impl<Response> Handler<Request, Response> for Option<BasicAuthCredentials> {
87    async fn handle(&mut self, mut request: Request) -> ChainResult<Request, Response> {
88        if let Some(credentials) = self {
89            credentials.append_to_request(&mut request);
90        }
91
92        ChainResult::Next(request)
93    }
94}