1use std::{collections::HashMap, fmt::Display, fs::read_to_string, io, sync::Arc};
9
10pub type AuthResult<T> = Result<T, AuthError>;
12
13#[derive(Debug, Clone, PartialEq, Eq)]
15pub enum AuthError {
16 InvalidCredentials,
18 UserNotFound,
20 EngineError(String),
22}
23
24impl Display for AuthError {
25 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
26 match self {
27 AuthError::InvalidCredentials => write!(f, "Invalid credentials"),
28 AuthError::UserNotFound => write!(f, "User not found"),
29 AuthError::EngineError(msg) => write!(f, "Engine error: {msg}"),
30 }
31 }
32}
33
34impl std::error::Error for AuthError {}
35
36pub trait AuthEngine: Send + Sync + Default {
41 fn authenticate(&self, username: &str, password: &str) -> AuthResult<()>;
47
48 fn user_exists(&self, username: &str) -> AuthResult<bool>;
50}
51
52#[derive(Debug, Clone)]
57pub struct MemoryAuthEngine {
58 credentials: Arc<HashMap<String, String>>,
59}
60
61impl MemoryAuthEngine {
62 pub fn new() -> Self {
64 Self {
65 credentials: Arc::new(HashMap::new()),
66 }
67 }
68
69 pub fn from_map(credentials: HashMap<String, String>) -> Self {
71 Self {
72 credentials: Arc::new(credentials),
73 }
74 }
75
76 pub fn from_arc(credentials: Arc<HashMap<String, String>>) -> Self {
78 Self { credentials }
79 }
80
81 pub fn from_file(path: &str) -> io::Result<Self> {
91 let content = read_to_string(path)?;
92 let mut creds = HashMap::new();
93 for line in content.lines() {
94 if let Some((user, pass)) = line.split_once(':') {
95 creds.insert(user.trim().to_string(), pass.trim().to_string());
96 }
97 }
98 Ok(Self::from_map(creds))
99 }
100
101 pub fn add_user(&mut self, username: String, password: String) {
105 let mut creds = (*self.credentials).clone();
106 creds.insert(username, password);
107 self.credentials = Arc::new(creds);
108 }
109
110 pub fn len(&self) -> usize {
112 self.credentials.len()
113 }
114
115 pub fn is_empty(&self) -> bool {
117 self.credentials.is_empty()
118 }
119}
120
121impl Default for MemoryAuthEngine {
122 fn default() -> Self {
123 Self::new()
124 }
125}
126
127impl AuthEngine for MemoryAuthEngine {
128 fn authenticate(&self, username: &str, password: &str) -> AuthResult<()> {
129 match self.credentials.get(username) {
130 Some(stored_password) if stored_password == password => Ok(()),
131 Some(_) => Err(AuthError::InvalidCredentials),
132 None => Err(AuthError::UserNotFound),
133 }
134 }
135
136 fn user_exists(&self, username: &str) -> AuthResult<bool> {
137 Ok(self.credentials.contains_key(username))
138 }
139}
140
141#[cfg(test)]
142mod tests {
143 use super::*;
144
145 #[test]
146 fn test_memory_engine_new() {
147 let engine = MemoryAuthEngine::new();
148 assert!(engine.is_empty());
149 }
150
151 #[test]
152 fn test_memory_engine_from_map() {
153 let mut map = HashMap::new();
154 map.insert("user1".to_string(), "pass1".to_string());
155 map.insert("user2".to_string(), "pass2".to_string());
156
157 let engine = MemoryAuthEngine::from_map(map);
158 assert_eq!(engine.len(), 2);
159 }
160
161 #[test]
162 fn test_memory_engine_authenticate_success() {
163 let mut map = HashMap::new();
164 map.insert("testuser".to_string(), "testpass".to_string());
165
166 let engine = MemoryAuthEngine::from_map(map);
167 assert!(engine.authenticate("testuser", "testpass").is_ok());
168 }
169
170 #[test]
171 fn test_memory_engine_authenticate_wrong_password() {
172 let mut map = HashMap::new();
173 map.insert("testuser".to_string(), "testpass".to_string());
174
175 let engine = MemoryAuthEngine::from_map(map);
176 assert_eq!(
177 engine.authenticate("testuser", "wrongpass"),
178 Err(AuthError::InvalidCredentials)
179 );
180 }
181
182 #[test]
183 fn test_memory_engine_authenticate_user_not_found() {
184 let engine = MemoryAuthEngine::new();
185 assert_eq!(
186 engine.authenticate("nonexistent", "pass"),
187 Err(AuthError::UserNotFound)
188 );
189 }
190
191 #[test]
192 fn test_memory_engine_user_exists() {
193 let mut map = HashMap::new();
194 map.insert("testuser".to_string(), "testpass".to_string());
195
196 let engine = MemoryAuthEngine::from_map(map);
197 assert!(engine.user_exists("testuser").unwrap());
198 assert!(!engine.user_exists("nonexistent").unwrap());
199 }
200
201 #[test]
202 fn test_memory_engine_add_user() {
203 let mut engine = MemoryAuthEngine::new();
204 engine.add_user("newuser".to_string(), "newpass".to_string());
205
206 assert!(engine.authenticate("newuser", "newpass").is_ok());
207 assert_eq!(engine.len(), 1);
208 }
209
210 #[test]
211 fn test_auth_error_display() {
212 assert_eq!(
213 AuthError::InvalidCredentials.to_string(),
214 "Invalid credentials"
215 );
216 assert_eq!(AuthError::UserNotFound.to_string(), "User not found");
217 assert_eq!(
218 AuthError::EngineError("test error".to_string()).to_string(),
219 "Engine error: test error"
220 );
221 }
222
223 #[test]
224 fn test_memory_engine_from_arc() {
225 let mut map = HashMap::new();
226 map.insert("user".to_string(), "pass".to_string());
227 let arc = Arc::new(map);
228
229 let engine = MemoryAuthEngine::from_arc(arc.clone());
230 assert_eq!(engine.len(), 1);
231 assert!(engine.authenticate("user", "pass").is_ok());
232 }
233
234 #[test]
235 fn test_memory_engine_from_file() {
236 let temp_dir = tempfile::TempDir::new().unwrap();
237 let file_path = temp_dir.path().join("credentials.txt");
238 std::fs::write(&file_path, "alice:secret\nbob:password123\n").unwrap();
239
240 let engine = MemoryAuthEngine::from_file(file_path.to_str().unwrap()).unwrap();
241 assert_eq!(engine.len(), 2);
242 assert!(engine.authenticate("alice", "secret").is_ok());
243 assert!(engine.authenticate("bob", "password123").is_ok());
244 }
245
246 #[test]
247 fn test_memory_engine_from_file_not_found() {
248 let result = MemoryAuthEngine::from_file("/nonexistent/credentials.txt");
249 assert!(result.is_err());
250 }
251
252 #[test]
253 fn test_memory_engine_from_file_with_whitespace() {
254 let temp_dir = tempfile::TempDir::new().unwrap();
255 let file_path = temp_dir.path().join("creds.txt");
256 std::fs::write(&file_path, " alice : secret \n bob : pass \n").unwrap();
257
258 let engine = MemoryAuthEngine::from_file(file_path.to_str().unwrap()).unwrap();
259 assert!(engine.authenticate("alice", "secret").is_ok());
260 assert!(engine.authenticate("bob", "pass").is_ok());
261 }
262
263 #[test]
264 fn test_memory_engine_from_file_empty() {
265 let temp_dir = tempfile::TempDir::new().unwrap();
266 let file_path = temp_dir.path().join("empty.txt");
267 std::fs::write(&file_path, "").unwrap();
268
269 let engine = MemoryAuthEngine::from_file(file_path.to_str().unwrap()).unwrap();
270 assert!(engine.is_empty());
271 }
272
273 #[test]
274 fn test_memory_engine_default() {
275 let engine = MemoryAuthEngine::default();
276 assert!(engine.is_empty());
277 assert_eq!(engine.len(), 0);
278 }
279
280 #[test]
281 fn test_memory_engine_len_and_is_empty() {
282 let mut engine = MemoryAuthEngine::new();
283 assert!(engine.is_empty());
284 assert_eq!(engine.len(), 0);
285
286 engine.add_user("user".to_string(), "pass".to_string());
287 assert!(!engine.is_empty());
288 assert_eq!(engine.len(), 1);
289 }
290}