rust_serv/basic_auth/
authenticator.rs1use super::credentials::Credentials;
4use super::validator::AuthValidator;
5
6pub struct BasicAuthenticator {
8 validator: AuthValidator,
9 realm: String,
10}
11
12impl BasicAuthenticator {
13 pub fn new(validator: AuthValidator) -> Self {
15 Self {
16 validator,
17 realm: "Protected Area".to_string(),
18 }
19 }
20
21 pub fn with_realm(validator: AuthValidator, realm: impl Into<String>) -> Self {
23 Self {
24 validator,
25 realm: realm.into(),
26 }
27 }
28
29 pub fn realm(&self) -> &str {
31 &self.realm
32 }
33
34 pub fn requires_auth(&self, path: &str) -> bool {
36 self.validator.requires_auth(path)
37 }
38
39 pub fn authenticate(&self, auth_header: Option<&str>) -> AuthResult {
41 match auth_header {
42 Some(header) => {
43 match Credentials::from_header(header) {
44 Some(creds) => {
45 if self.validator.validate(&creds) {
46 AuthResult::Success(creds.username)
47 } else {
48 AuthResult::InvalidCredentials
49 }
50 }
51 None => AuthResult::InvalidHeader,
52 }
53 }
54 None => AuthResult::MissingHeader,
55 }
56 }
57
58 pub fn www_authenticate_header(&self) -> String {
60 format!("Basic realm=\"{}\"", self.realm)
61 }
62
63 pub fn validator(&self) -> &AuthValidator {
65 &self.validator
66 }
67
68 pub fn validator_mut(&mut self) -> &mut AuthValidator {
70 &mut self.validator
71 }
72}
73
74#[derive(Debug, Clone, PartialEq)]
76pub enum AuthResult {
77 Success(String),
79 MissingHeader,
81 InvalidHeader,
83 InvalidCredentials,
85}
86
87impl AuthResult {
88 pub fn is_success(&self) -> bool {
90 matches!(self, AuthResult::Success(_))
91 }
92
93 pub fn username(&self) -> Option<&str> {
95 match self {
96 AuthResult::Success(username) => Some(username),
97 _ => None,
98 }
99 }
100
101 pub fn status_code(&self) -> u16 {
103 match self {
104 AuthResult::Success(_) => 200,
105 _ => 401,
106 }
107 }
108}
109
110#[cfg(test)]
111mod tests {
112 use super::*;
113
114 fn create_test_authenticator() -> BasicAuthenticator {
115 let mut validator = AuthValidator::new();
116 validator.add_user("admin", "secret");
117 validator.add_protected_path("/admin");
118 BasicAuthenticator::new(validator)
119 }
120
121 #[test]
122 fn test_authenticator_creation() {
123 let auth = create_test_authenticator();
124 assert_eq!(auth.realm(), "Protected Area");
125 }
126
127 #[test]
128 fn test_authenticator_custom_realm() {
129 let validator = AuthValidator::new();
130 let auth = BasicAuthenticator::with_realm(validator, "My App");
131 assert_eq!(auth.realm(), "My App");
132 }
133
134 #[test]
135 fn test_requires_auth() {
136 let auth = create_test_authenticator();
137
138 assert!(auth.requires_auth("/admin"));
139 assert!(auth.requires_auth("/admin/settings"));
140 assert!(!auth.requires_auth("/public"));
141 }
142
143 #[test]
144 fn test_authenticate_success() {
145 let auth = create_test_authenticator();
146 let header = "Basic YWRtaW46c2VjcmV0"; let result = auth.authenticate(Some(header));
149
150 assert!(result.is_success());
151 assert_eq!(result.username(), Some("admin"));
152 }
153
154 #[test]
155 fn test_authenticate_wrong_password() {
156 let auth = create_test_authenticator();
157 let header = "Basic YWRtaW46d3Jvbmc="; let result = auth.authenticate(Some(header));
160
161 assert_eq!(result, AuthResult::InvalidCredentials);
162 assert!(!result.is_success());
163 }
164
165 #[test]
166 fn test_authenticate_wrong_user() {
167 let auth = create_test_authenticator();
168 let header = "Basic dXNlcjpwYXNz"; let result = auth.authenticate(Some(header));
171
172 assert_eq!(result, AuthResult::InvalidCredentials);
173 }
174
175 #[test]
176 fn test_authenticate_missing_header() {
177 let auth = create_test_authenticator();
178
179 let result = auth.authenticate(None);
180
181 assert_eq!(result, AuthResult::MissingHeader);
182 }
183
184 #[test]
185 fn test_authenticate_invalid_header_format() {
186 let auth = create_test_authenticator();
187
188 let result = auth.authenticate(Some("Bearer token"));
189 assert_eq!(result, AuthResult::InvalidHeader);
190
191 let result = auth.authenticate(Some("Basic invalid!!!"));
192 assert_eq!(result, AuthResult::InvalidHeader);
193 }
194
195 #[test]
196 fn test_www_authenticate_header() {
197 let auth = create_test_authenticator();
198 let header = auth.www_authenticate_header();
199
200 assert_eq!(header, "Basic realm=\"Protected Area\"");
201 }
202
203 #[test]
204 fn test_www_authenticate_custom_realm() {
205 let validator = AuthValidator::new();
206 let auth = BasicAuthenticator::with_realm(validator, "My App");
207 let header = auth.www_authenticate_header();
208
209 assert_eq!(header, "Basic realm=\"My App\"");
210 }
211
212 #[test]
213 fn test_auth_result_is_success() {
214 let success = AuthResult::Success("user".to_string());
215 assert!(success.is_success());
216
217 let missing = AuthResult::MissingHeader;
218 assert!(!missing.is_success());
219
220 let invalid_header = AuthResult::InvalidHeader;
221 assert!(!invalid_header.is_success());
222
223 let invalid_creds = AuthResult::InvalidCredentials;
224 assert!(!invalid_creds.is_success());
225 }
226
227 #[test]
228 fn test_auth_result_username() {
229 let success = AuthResult::Success("admin".to_string());
230 assert_eq!(success.username(), Some("admin"));
231
232 let missing = AuthResult::MissingHeader;
233 assert!(missing.username().is_none());
234 }
235
236 #[test]
237 fn test_auth_result_status_code() {
238 let success = AuthResult::Success("user".to_string());
239 assert_eq!(success.status_code(), 200);
240
241 let missing = AuthResult::MissingHeader;
242 assert_eq!(missing.status_code(), 401);
243
244 let invalid = AuthResult::InvalidCredentials;
245 assert_eq!(invalid.status_code(), 401);
246 }
247
248 #[test]
249 fn test_validator_access() {
250 let mut auth = create_test_authenticator();
251
252 assert_eq!(auth.validator().user_count(), 1);
254
255 auth.validator_mut().add_user("newuser", "pass");
257 assert_eq!(auth.validator().user_count(), 2);
258 }
259
260 #[test]
261 fn test_authenticate_empty_password() {
262 let mut validator = AuthValidator::new();
263 validator.add_user("guest", "");
264 let auth = BasicAuthenticator::new(validator);
265
266 let header = "Basic Z3Vlc3Q6"; let result = auth.authenticate(Some(header));
268
269 assert!(result.is_success());
270 }
271
272 #[test]
273 fn test_authenticate_empty_username() {
274 let mut validator = AuthValidator::new();
275 validator.add_user("", "password");
276 let auth = BasicAuthenticator::new(validator);
277
278 let header = "Basic OnBhc3N3b3Jk"; let result = auth.authenticate(Some(header));
280
281 assert!(result.is_success());
282 }
283}