rust_serv/basic_auth/
credentials.rs1use base64::{engine::general_purpose::STANDARD, Engine};
4
5#[derive(Debug, Clone, PartialEq)]
7pub struct Credentials {
8 pub username: String,
10 pub password: String,
12}
13
14impl Credentials {
15 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 pub fn from_header(header_value: &str) -> Option<Self> {
25 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 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 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}