Skip to main content

rust_serv/basic_auth/
authenticator.rs

1//! Basic Authenticator
2
3use super::credentials::Credentials;
4use super::validator::AuthValidator;
5
6/// Basic authenticator
7pub struct BasicAuthenticator {
8    validator: AuthValidator,
9    realm: String,
10}
11
12impl BasicAuthenticator {
13    /// Create a new basic authenticator
14    pub fn new(validator: AuthValidator) -> Self {
15        Self {
16            validator,
17            realm: "Protected Area".to_string(),
18        }
19    }
20
21    /// Create a new basic authenticator with custom realm
22    pub fn with_realm(validator: AuthValidator, realm: impl Into<String>) -> Self {
23        Self {
24            validator,
25            realm: realm.into(),
26        }
27    }
28
29    /// Get the realm
30    pub fn realm(&self) -> &str {
31        &self.realm
32    }
33
34    /// Check if path requires authentication
35    pub fn requires_auth(&self, path: &str) -> bool {
36        self.validator.requires_auth(path)
37    }
38
39    /// Authenticate a request using Basic Auth header
40    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    /// Get the WWW-Authenticate header value for 401 responses
59    pub fn www_authenticate_header(&self) -> String {
60        format!("Basic realm=\"{}\"", self.realm)
61    }
62
63    /// Get the validator
64    pub fn validator(&self) -> &AuthValidator {
65        &self.validator
66    }
67
68    /// Get mutable validator
69    pub fn validator_mut(&mut self) -> &mut AuthValidator {
70        &mut self.validator
71    }
72}
73
74/// Authentication result
75#[derive(Debug, Clone, PartialEq)]
76pub enum AuthResult {
77    /// Authentication successful, contains username
78    Success(String),
79    /// Missing Authorization header
80    MissingHeader,
81    /// Invalid Authorization header format
82    InvalidHeader,
83    /// Invalid credentials
84    InvalidCredentials,
85}
86
87impl AuthResult {
88    /// Check if authentication was successful
89    pub fn is_success(&self) -> bool {
90        matches!(self, AuthResult::Success(_))
91    }
92
93    /// Get username if successful
94    pub fn username(&self) -> Option<&str> {
95        match self {
96            AuthResult::Success(username) => Some(username),
97            _ => None,
98        }
99    }
100
101    /// Get the HTTP status code for this result
102    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"; // admin:secret
147        
148        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="; // admin:wrong
158        
159        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"; // user:pass
169        
170        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        // Read access
253        assert_eq!(auth.validator().user_count(), 1);
254        
255        // Mutable access
256        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"; // guest:
267        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"; // :password
279        let result = auth.authenticate(Some(header));
280        
281        assert!(result.is_success());
282    }
283}