alloy_transport/
common.rs

1use base64::{engine::general_purpose, Engine};
2use std::{fmt, net::SocketAddr};
3
4/// Basic, bearer or raw authentication in http or websocket transport.
5///
6/// Use to inject username and password or an auth token into requests.
7#[derive(Clone, Debug, PartialEq, Eq)]
8pub enum Authorization {
9    /// [RFC7617](https://datatracker.ietf.org/doc/html/rfc7617) HTTP Basic Auth.
10    Basic(String),
11    /// [RFC6750](https://datatracker.ietf.org/doc/html/rfc6750) Bearer Auth.
12    Bearer(String),
13    /// Raw auth string.
14    Raw(String),
15}
16
17impl Authorization {
18    /// Extract the auth info from a URL.
19    pub fn extract_from_url(url: &url::Url) -> Option<Self> {
20        let username = url.username();
21        let password = url.password().unwrap_or_default();
22
23        // eliminates false positives on the authority
24        if username.contains("localhost") || username.parse::<SocketAddr>().is_ok() {
25            return None;
26        }
27
28        (!username.is_empty() || !password.is_empty()).then(|| Self::basic(username, password))
29    }
30
31    /// Instantiate a new basic auth from an authority string.
32    pub fn authority(auth: impl AsRef<str>) -> Self {
33        let auth_secret = general_purpose::STANDARD.encode(auth.as_ref());
34        Self::Basic(auth_secret)
35    }
36
37    /// Instantiate a new basic auth from a username and password.
38    pub fn basic(username: impl AsRef<str>, password: impl AsRef<str>) -> Self {
39        let username = username.as_ref();
40        let password = password.as_ref();
41        Self::authority(format!("{username}:{password}"))
42    }
43
44    /// Instantiate a new bearer auth from the given token.
45    pub fn bearer(token: impl Into<String>) -> Self {
46        Self::Bearer(token.into())
47    }
48
49    /// Instantiate a new raw auth from the given token.
50    pub fn raw(token: impl Into<String>) -> Self {
51        Self::Raw(token.into())
52    }
53}
54
55impl fmt::Display for Authorization {
56    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
57        match self {
58            Self::Basic(auth) => write!(f, "Basic {auth}"),
59            Self::Bearer(auth) => write!(f, "Bearer {auth}"),
60            Self::Raw(auth) => write!(f, "{auth}"),
61        }
62    }
63}
64
65#[cfg(test)]
66mod tests {
67    use super::*;
68    use url::Url;
69
70    #[test]
71    fn test_extract_from_url_with_basic_auth() {
72        let url = Url::parse("http://username:password@domain.com").unwrap();
73        let auth = Authorization::extract_from_url(&url).unwrap();
74
75        // Expected Basic auth encoded in base64
76        assert_eq!(
77            auth,
78            Authorization::Basic(general_purpose::STANDARD.encode("username:password"))
79        );
80    }
81
82    #[test]
83    fn test_extract_from_url_no_auth() {
84        let url = Url::parse("http://domain.com").unwrap();
85        assert!(Authorization::extract_from_url(&url).is_none());
86    }
87
88    #[test]
89    fn test_extract_from_url_with_localhost() {
90        let url = Url::parse("http://localhost:password@domain.com").unwrap();
91        assert!(Authorization::extract_from_url(&url).is_none());
92    }
93
94    #[test]
95    fn test_extract_from_url_with_socket_address() {
96        let url = Url::parse("http://127.0.0.1:8080").unwrap();
97        assert!(Authorization::extract_from_url(&url).is_none());
98    }
99
100    #[test]
101    fn test_authority() {
102        let auth = Authorization::authority("user:pass");
103        assert_eq!(auth, Authorization::Basic(general_purpose::STANDARD.encode("user:pass")));
104    }
105
106    #[test]
107    fn test_basic() {
108        let auth = Authorization::basic("user", "pass");
109        assert_eq!(auth, Authorization::Basic(general_purpose::STANDARD.encode("user:pass")));
110    }
111
112    #[test]
113    fn test_raw() {
114        let auth = Authorization::raw("raw_token");
115        assert_eq!(auth, Authorization::Raw("raw_token".to_string()));
116    }
117}