Skip to main content

rust_serv/basic_auth/
credentials.rs

1//! Credentials for Basic Auth
2
3use base64::{engine::general_purpose::STANDARD, Engine};
4
5/// Username and password credentials
6#[derive(Debug, Clone, PartialEq)]
7pub struct Credentials {
8    /// Username
9    pub username: String,
10    /// Password (plain text or hash depending on context)
11    pub password: String,
12}
13
14impl Credentials {
15    /// Create new credentials
16    pub fn new(username: impl Into<String>, password: impl Into<String>) -> Self {
17        Self {
18            username: username.into(),
19            password: password.into(),
20        }
21    }
22
23    /// Parse from Basic Auth header value (base64 encoded)
24    pub fn from_header(header_value: &str) -> Option<Self> {
25        // Basic Auth format: "Basic <base64(username:password)>"
26        let encoded = header_value.strip_prefix("Basic ")?;
27        let decoded = STANDARD.decode(encoded).ok()?;
28        let decoded_str = String::from_utf8(decoded).ok()?;
29        
30        let (username, password) = decoded_str.split_once(':')?;
31        
32        Some(Self {
33            username: username.to_string(),
34            password: password.to_string(),
35        })
36    }
37
38    /// Convert to Basic Auth header value
39    pub fn to_header(&self) -> String {
40        let combined = format!("{}:{}", self.username, self.password);
41        let encoded = STANDARD.encode(combined.as_bytes());
42        format!("Basic {}", encoded)
43    }
44}
45
46#[cfg(test)]
47mod tests {
48    use super::*;
49
50    #[test]
51    fn test_credentials_creation() {
52        let creds = Credentials::new("admin", "password123");
53        assert_eq!(creds.username, "admin");
54        assert_eq!(creds.password, "password123");
55    }
56
57    #[test]
58    fn test_credentials_to_header() {
59        let creds = Credentials::new("admin", "secret");
60        let header = creds.to_header();
61        assert!(header.starts_with("Basic "));
62        assert_eq!(header, "Basic YWRtaW46c2VjcmV0");
63    }
64
65    #[test]
66    fn test_credentials_from_header() {
67        let creds = Credentials::from_header("Basic YWRtaW46c2VjcmV0").unwrap();
68        assert_eq!(creds.username, "admin");
69        assert_eq!(creds.password, "secret");
70    }
71
72    #[test]
73    fn test_credentials_from_header_invalid_prefix() {
74        let result = Credentials::from_header("Bearer token");
75        assert!(result.is_none());
76    }
77
78    #[test]
79    fn test_credentials_from_header_no_colon() {
80        // Valid base64 but no colon separator
81        let result = Credentials::from_header("Basic dXNlcm5hbWVvbmx5");
82        assert!(result.is_none());
83    }
84
85    #[test]
86    fn test_credentials_roundtrip() {
87        let original = Credentials::new("user@test.com", "p@ss:word");
88        let header = original.to_header();
89        let parsed = Credentials::from_header(&header).unwrap();
90        assert_eq!(original, parsed);
91    }
92
93    #[test]
94    fn test_credentials_empty_username() {
95        let creds = Credentials::new("", "password");
96        let header = creds.to_header();
97        let parsed = Credentials::from_header(&header).unwrap();
98        assert_eq!(parsed.username, "");
99        assert_eq!(parsed.password, "password");
100    }
101
102    #[test]
103    fn test_credentials_empty_password() {
104        let creds = Credentials::new("username", "");
105        let header = creds.to_header();
106        let parsed = Credentials::from_header(&header).unwrap();
107        assert_eq!(parsed.username, "username");
108        assert_eq!(parsed.password, "");
109    }
110
111    #[test]
112    fn test_credentials_special_characters() {
113        let creds = Credentials::new("用户名", "密码123");
114        let header = creds.to_header();
115        let parsed = Credentials::from_header(&header).unwrap();
116        assert_eq!(parsed.username, "用户名");
117        assert_eq!(parsed.password, "密码123");
118    }
119
120    #[test]
121    fn test_credentials_from_header_missing_basic() {
122        let result = Credentials::from_header("YWRtaW46c2VjcmV0");
123        assert!(result.is_none());
124    }
125
126    #[test]
127    fn test_credentials_from_header_empty() {
128        let result = Credentials::from_header("");
129        assert!(result.is_none());
130    }
131
132    #[test]
133    fn test_credentials_from_header_invalid_base64() {
134        let result = Credentials::from_header("Basic !!!invalid!!!");
135        assert!(result.is_none());
136    }
137
138    #[test]
139    fn test_credentials_clone() {
140        let creds = Credentials::new("admin", "secret");
141        let cloned = creds.clone();
142        assert_eq!(creds, cloned);
143    }
144
145    #[test]
146    fn test_credentials_debug() {
147        let creds = Credentials::new("admin", "secret");
148        let debug = format!("{:?}", creds);
149        assert!(debug.contains("admin"));
150        assert!(debug.contains("secret"));
151    }
152}