Skip to main content

alloy_transport/
common.rs

1use base64::{engine::general_purpose, Engine};
2use std::fmt;
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    ///
20    /// Returns [`Authorization::Basic`] if the URL contains userinfo (i.e.
21    /// `user:pass@host` or `user@host`).
22    pub fn extract_from_url(url: &url::Url) -> Option<Self> {
23        let username = url.username();
24        let password = url.password();
25
26        // Userinfo is present when the username is non-empty or a password was
27        // explicitly provided (even if empty, e.g. `:pass@host`).
28        let has_userinfo = !username.is_empty() || password.is_some();
29        has_userinfo.then(|| Self::basic(username, password.unwrap_or_default()))
30    }
31
32    /// Instantiate a new basic auth from an authority string.
33    pub fn authority(auth: impl AsRef<str>) -> Self {
34        let auth_secret = general_purpose::STANDARD.encode(auth.as_ref());
35        Self::Basic(auth_secret)
36    }
37
38    /// Instantiate a new basic auth from a username and password.
39    pub fn basic(username: impl AsRef<str>, password: impl AsRef<str>) -> Self {
40        let username = username.as_ref();
41        let password = password.as_ref();
42        Self::authority(format!("{username}:{password}"))
43    }
44
45    /// Instantiate a new bearer auth from the given token.
46    pub fn bearer(token: impl Into<String>) -> Self {
47        Self::Bearer(token.into())
48    }
49
50    /// Instantiate a new raw auth from the given token.
51    pub fn raw(token: impl Into<String>) -> Self {
52        Self::Raw(token.into())
53    }
54}
55
56impl fmt::Display for Authorization {
57    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
58        match self {
59            Self::Basic(auth) => write!(f, "Basic {auth}"),
60            Self::Bearer(auth) => write!(f, "Bearer {auth}"),
61            Self::Raw(auth) => write!(f, "{auth}"),
62        }
63    }
64}
65
66#[cfg(test)]
67mod tests {
68    use super::*;
69    use url::Url;
70
71    #[test]
72    fn test_extract_from_url_with_basic_auth() {
73        let url = Url::parse("http://username:password@domain.com").unwrap();
74        let auth = Authorization::extract_from_url(&url).unwrap();
75
76        // Expected Basic auth encoded in base64
77        assert_eq!(
78            auth,
79            Authorization::Basic(general_purpose::STANDARD.encode("username:password"))
80        );
81    }
82
83    #[test]
84    fn test_extract_from_url_no_auth() {
85        let url = Url::parse("http://domain.com").unwrap();
86        assert!(Authorization::extract_from_url(&url).is_none());
87    }
88
89    #[test]
90    fn test_extract_from_url_with_localhost_username() {
91        // A username of "localhost" is valid userinfo and should be extracted.
92        let url = Url::parse("http://localhost:password@domain.com").unwrap();
93        let auth = Authorization::extract_from_url(&url).unwrap();
94        assert_eq!(
95            auth,
96            Authorization::Basic(general_purpose::STANDARD.encode("localhost:password"))
97        );
98    }
99
100    #[test]
101    fn test_extract_from_url_plain_host() {
102        // No userinfo — just a host with a port.
103        let url = Url::parse("http://127.0.0.1:8080").unwrap();
104        assert!(Authorization::extract_from_url(&url).is_none());
105    }
106
107    #[test]
108    fn test_extract_from_url_password_only() {
109        let url = Url::parse("http://:secret@domain.com").unwrap();
110        let auth = Authorization::extract_from_url(&url).unwrap();
111        assert_eq!(auth, Authorization::Basic(general_purpose::STANDARD.encode(":secret")));
112    }
113
114    #[test]
115    fn test_authority() {
116        let auth = Authorization::authority("user:pass");
117        assert_eq!(auth, Authorization::Basic(general_purpose::STANDARD.encode("user:pass")));
118    }
119
120    #[test]
121    fn test_basic() {
122        let auth = Authorization::basic("user", "pass");
123        assert_eq!(auth, Authorization::Basic(general_purpose::STANDARD.encode("user:pass")));
124    }
125
126    #[test]
127    fn test_raw() {
128        let auth = Authorization::raw("raw_token");
129        assert_eq!(auth, Authorization::Raw("raw_token".to_string()));
130    }
131}