1use std::sync::Arc;
52
53use chrono::{DateTime, Utc};
54use password_auth::{generate_hash, verify_password};
55use regex::Regex;
56use torii_core::{
57 Error, NewUser, Plugin, User, UserId, UserManager,
58 error::{AuthError, StorageError, ValidationError},
59 events::{Event, EventBus},
60 storage::PasswordStorage,
61};
62
63pub struct PasswordPlugin<M, S>
64where
65 M: UserManager,
66 S: PasswordStorage,
67{
68 user_manager: Arc<M>,
69 password_storage: Arc<S>,
70 event_bus: Option<EventBus>,
71}
72
73impl<M, S> PasswordPlugin<M, S>
74where
75 M: UserManager,
76 S: PasswordStorage,
77{
78 pub fn new(user_manager: Arc<M>, password_storage: Arc<S>) -> Self {
79 Self {
80 user_manager,
81 password_storage,
82 event_bus: None,
83 }
84 }
85
86 pub fn with_event_bus(mut self, event_bus: EventBus) -> Self {
87 self.event_bus = Some(event_bus);
88 self
89 }
90}
91
92impl<M, S> Plugin for PasswordPlugin<M, S>
93where
94 M: UserManager,
95 S: PasswordStorage,
96{
97 fn name(&self) -> String {
98 "password".to_string()
99 }
100}
101
102impl<M, S> PasswordPlugin<M, S>
103where
104 M: UserManager,
105 S: PasswordStorage,
106{
107 pub async fn register_user_with_password(
108 &self,
109 email: &str,
110 password: &str,
111 email_verified_at: Option<DateTime<Utc>>,
112 ) -> Result<User, Error> {
113 if !is_valid_email(email) {
114 return Err(Error::Validation(ValidationError::InvalidEmail));
115 }
116 if !is_valid_password(password) {
117 return Err(Error::Validation(ValidationError::WeakPassword));
118 }
119
120 if let Some(_user) = self.user_manager.get_user_by_email(email).await? {
121 tracing::debug!(email = %email, "User already exists");
122 return Err(Error::Auth(AuthError::UserAlreadyExists));
123 }
124
125 let new_user = NewUser::builder()
126 .email(email.to_string())
127 .email_verified_at(email_verified_at)
128 .build()
129 .unwrap();
130 let user = self.user_manager.create_user(&new_user).await?;
131
132 let hash = generate_hash(password);
133 self.password_storage
134 .set_password_hash(&user.id, &hash)
135 .await
136 .map_err(|e| Error::Storage(StorageError::Database(e.to_string())))?;
137
138 tracing::info!(
139 user.id = %user.id,
140 user.email = %user.email,
141 user.name = ?user.name,
142 "Created user",
143 );
144
145 self.emit_event(&Event::UserCreated(user.clone())).await?;
146
147 Ok(user)
148 }
149
150 pub async fn change_user_password(
151 &self,
152 user_id: &UserId,
153 old_password: &str,
154 new_password: &str,
155 ) -> Result<(), Error> {
156 if !is_valid_password(new_password) {
157 return Err(Error::Validation(ValidationError::WeakPassword));
158 }
159
160 let user = self
161 .user_manager
162 .get_user(user_id)
163 .await?
164 .ok_or(Error::Auth(AuthError::UserNotFound))?;
165
166 let stored_hash = self
167 .password_storage
168 .get_password_hash(user_id)
169 .await
170 .map_err(|e| Error::Storage(StorageError::Database(e.to_string())))?;
171
172 if stored_hash.is_none() {
173 return Err(Error::Auth(AuthError::InvalidCredentials));
174 }
175
176 verify_password(old_password, &stored_hash.unwrap())
177 .map_err(|_| Error::Auth(AuthError::InvalidCredentials))?;
178
179 let new_hash = generate_hash(new_password);
180 self.password_storage
181 .set_password_hash(user_id, &new_hash)
182 .await
183 .map_err(|e| Error::Storage(StorageError::Database(e.to_string())))?;
184
185 tracing::info!(
186 user.id = %user_id,
187 "Changed user password",
188 );
189
190 self.emit_event(&Event::UserUpdated(user)).await?;
191
192 Ok(())
193 }
194
195 pub async fn login_user_with_password(
196 &self,
197 email: &str,
198 password: &str,
199 ) -> Result<User, Error> {
200 let user = self
201 .user_manager
202 .get_user_by_email(email)
203 .await?
204 .ok_or(Error::Auth(AuthError::UserNotFound))?;
205
206 if !user.is_email_verified() {
207 return Err(Error::Auth(AuthError::EmailNotVerified));
208 }
209
210 let hash = self
211 .password_storage
212 .get_password_hash(&user.id)
213 .await
214 .map_err(|e| Error::Storage(StorageError::Database(e.to_string())))?;
215
216 if hash.is_none() {
217 return Err(Error::Auth(AuthError::InvalidCredentials));
218 }
219
220 verify_password(password, &hash.unwrap())
221 .map_err(|_| Error::Auth(AuthError::InvalidCredentials))?;
222
223 Ok(user)
224 }
225
226 async fn emit_event(&self, event: &Event) -> Result<(), Error> {
227 if let Some(event_bus) = &self.event_bus {
228 event_bus.emit(event).await?;
229 }
230 Ok(())
231 }
232}
233
234fn is_valid_email(email: &str) -> bool {
236 let email_regex = Regex::new(r"^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$").unwrap();
237 email_regex.is_match(email)
238}
239
240fn is_valid_password(password: &str) -> bool {
242 password.len() >= 8
244}
245
246#[cfg(test)]
247mod tests {
248 use super::*;
249 use async_trait::async_trait;
250 use sqlx::SqlitePool;
251 use std::sync::{
252 Arc,
253 atomic::{AtomicBool, AtomicUsize, Ordering},
254 };
255 use torii_core::{DefaultUserManager, error::EventError, events::EventHandler};
256 use torii_storage_sqlite::SqliteStorage;
257
258 async fn setup_storage() -> Result<Arc<SqliteStorage>, Error> {
259 let _ = tracing_subscriber::fmt().try_init();
260
261 let pool = SqlitePool::connect("sqlite::memory:")
262 .await
263 .expect("Failed to create pool");
264
265 let user_storage = Arc::new(SqliteStorage::new(pool.clone()));
266 user_storage.migrate().await?;
267
268 Ok(user_storage)
269 }
270
271 #[test]
272 fn test_is_valid_email() {
273 assert!(is_valid_email("test@example.com"));
275 assert!(is_valid_email("user.name@domain.co.uk"));
276 assert!(is_valid_email("user+tag@example.com"));
277 assert!(is_valid_email("123@domain.com"));
278
279 assert!(!is_valid_email(""));
281 assert!(!is_valid_email("not-an-email"));
282 assert!(!is_valid_email("@domain.com"));
283 assert!(!is_valid_email("user@"));
284 assert!(!is_valid_email("user@.com"));
285 assert!(!is_valid_email("user@domain"));
286 assert!(!is_valid_email("user name@domain.com"));
287 }
288
289 #[test]
290 fn test_is_valid_password() {
291 assert!(is_valid_password("password123"));
293 assert!(is_valid_password("12345678"));
294 assert!(is_valid_password("abcdefghijklmnop"));
295
296 assert!(!is_valid_password(""));
298 assert!(!is_valid_password("short"));
299 assert!(!is_valid_password("1234567"));
300 }
301
302 #[tokio::test]
303 async fn test_create_user_and_login_with_unverified_email() -> Result<(), Error> {
304 let storage = setup_storage().await?;
305 let user_manager = Arc::new(DefaultUserManager::new(storage.clone()));
306
307 let plugin = PasswordPlugin::new(user_manager.clone(), storage.clone())
308 .register_user_with_password("test@example.com", "password", None)
309 .await?;
310 assert_eq!(plugin.email, "test@example.com");
311
312 let result = PasswordPlugin::new(user_manager.clone(), storage.clone())
313 .login_user_with_password("test@example.com", "password")
314 .await;
315 assert!(matches!(
316 result,
317 Err(Error::Auth(AuthError::EmailNotVerified))
318 ));
319
320 Ok(())
321 }
322
323 #[tokio::test]
324 async fn test_create_user_and_login_with_verified_email() -> Result<(), Error> {
325 let storage = setup_storage().await?;
326 let user_manager = Arc::new(DefaultUserManager::new(storage.clone()));
327
328 let plugin = PasswordPlugin::new(user_manager.clone(), storage.clone())
329 .register_user_with_password("test@example.com", "password", Some(Utc::now()))
330 .await?;
331 assert_eq!(plugin.email, "test@example.com");
332
333 let plugin = PasswordPlugin::new(user_manager.clone(), storage.clone())
334 .login_user_with_password("test@example.com", "password")
335 .await?;
336 assert_eq!(plugin.email, "test@example.com");
337
338 Ok(())
339 }
340
341 #[tokio::test]
342 async fn test_create_duplicate_user() -> Result<(), Error> {
343 let storage = setup_storage().await?;
344 let user_manager = Arc::new(DefaultUserManager::new(storage.clone()));
345
346 let _ = PasswordPlugin::new(user_manager.clone(), storage.clone())
347 .register_user_with_password("test@example.com", "password", None)
348 .await?;
349
350 let result = PasswordPlugin::new(user_manager.clone(), storage.clone())
351 .register_user_with_password("test@example.com", "password", None)
352 .await;
353
354 assert!(matches!(
355 result,
356 Err(Error::Auth(AuthError::UserAlreadyExists))
357 ));
358
359 Ok(())
360 }
361
362 #[tokio::test]
363 async fn test_invalid_email_format() -> Result<(), Error> {
364 let storage = setup_storage().await?;
365 let user_manager = Arc::new(DefaultUserManager::new(storage.clone()));
366
367 let result = PasswordPlugin::new(user_manager.clone(), storage.clone())
368 .register_user_with_password("not-an-email", "password", None)
369 .await;
370
371 assert!(matches!(
372 result,
373 Err(Error::Validation(ValidationError::InvalidEmail))
374 ));
375
376 Ok(())
377 }
378
379 #[tokio::test]
380 async fn test_weak_password() -> Result<(), Error> {
381 let storage = setup_storage().await?;
382 let user_manager = Arc::new(DefaultUserManager::new(storage.clone()));
383
384 let result = PasswordPlugin::new(user_manager.clone(), storage.clone())
385 .register_user_with_password("test@example.com", "123", None)
386 .await;
387
388 assert!(matches!(
389 result,
390 Err(Error::Validation(ValidationError::WeakPassword))
391 ));
392
393 Ok(())
394 }
395
396 #[tokio::test]
397 async fn test_incorrect_password_login() -> Result<(), Error> {
398 let storage = setup_storage().await?;
399 let user_manager = Arc::new(DefaultUserManager::new(storage.clone()));
400
401 PasswordPlugin::new(user_manager.clone(), storage.clone())
402 .register_user_with_password("test@example.com", "password", Some(Utc::now()))
403 .await?;
404
405 let result = PasswordPlugin::new(user_manager.clone(), storage.clone())
406 .login_user_with_password("test@example.com", "wrong-password")
407 .await;
408
409 assert!(matches!(
410 result,
411 Err(Error::Auth(AuthError::InvalidCredentials))
412 ));
413
414 Ok(())
415 }
416
417 #[tokio::test]
418 async fn test_nonexistent_user_login() -> Result<(), Error> {
419 let storage = setup_storage().await?;
420 let user_manager = Arc::new(DefaultUserManager::new(storage.clone()));
421
422 let result = PasswordPlugin::new(user_manager.clone(), storage.clone())
423 .login_user_with_password("nonexistent@example.com", "password")
424 .await;
425
426 assert!(matches!(result, Err(Error::Auth(AuthError::UserNotFound))));
427
428 Ok(())
429 }
430
431 #[tokio::test]
432 async fn test_sql_injection_attempt() -> Result<(), Error> {
433 let storage = setup_storage().await?;
434 let user_manager = Arc::new(DefaultUserManager::new(storage.clone()));
435
436 let result = PasswordPlugin::new(user_manager.clone(), storage.clone())
437 .register_user_with_password("test@example.com'; DROP TABLE users;--", "password", None)
438 .await;
439
440 assert!(matches!(
441 result,
442 Err(Error::Validation(ValidationError::InvalidEmail))
443 ));
444
445 Ok(())
446 }
447
448 #[tokio::test]
449 async fn test_change_password() -> Result<(), Error> {
450 let storage = setup_storage().await?;
451 let user_manager = Arc::new(DefaultUserManager::new(storage.clone()));
452
453 let plugin = PasswordPlugin::new(user_manager.clone(), storage.clone());
454
455 let user = plugin
457 .register_user_with_password("test@example.com", "password", Some(Utc::now()))
458 .await?;
459
460 plugin
462 .change_user_password(&user.id, "password", "new-password")
463 .await?;
464
465 let result = plugin
467 .login_user_with_password("test@example.com", "new-password")
468 .await;
469 assert!(result.is_ok());
470
471 let result = plugin
473 .login_user_with_password("test@example.com", "password")
474 .await;
475 assert!(matches!(
476 result,
477 Err(Error::Auth(AuthError::InvalidCredentials))
478 ));
479
480 Ok(())
481 }
482
483 #[tokio::test]
484 async fn test_event_handler_emitting() -> Result<(), Error> {
485 let _ = tracing_subscriber::fmt().try_init();
486
487 let storage = setup_storage().await?;
488 let user_manager = Arc::new(DefaultUserManager::new(storage.clone()));
489
490 let event_bus = EventBus::new();
491 let event_was_emitted = Arc::new(AtomicBool::new(false));
492 let event_count = Arc::new(AtomicUsize::new(0));
493
494 let handler = TestEventHandler {
495 called: event_was_emitted.clone(),
496 call_count: event_count.clone(),
497 };
498 event_bus.register(Arc::new(handler)).await;
499
500 let plugin =
501 PasswordPlugin::new(user_manager.clone(), storage.clone()).with_event_bus(event_bus);
502
503 let user = plugin
505 .register_user_with_password("test@example.com", "password", Some(Utc::now()))
506 .await?;
507
508 assert!(
509 event_was_emitted.load(Ordering::SeqCst),
510 "Expected event to be emitted on user creation"
511 );
512 assert_eq!(event_count.load(Ordering::SeqCst), 1);
513
514 event_was_emitted.store(false, Ordering::SeqCst);
516 plugin
517 .change_user_password(&user.id, "password", "new-password")
518 .await?;
519
520 assert!(
521 event_was_emitted.load(Ordering::SeqCst),
522 "Expected event to be emitted on password change"
523 );
524 assert_eq!(event_count.load(Ordering::SeqCst), 2);
525
526 Ok(())
527 }
528
529 struct TestEventHandler {
530 called: Arc<AtomicBool>,
531 call_count: Arc<AtomicUsize>,
532 }
533
534 #[async_trait]
535 impl EventHandler for TestEventHandler {
536 async fn handle_event(&self, _event: &Event) -> Result<(), EventError> {
537 self.called.store(true, Ordering::SeqCst);
538 self.call_count.fetch_add(1, Ordering::SeqCst);
539 Ok(())
540 }
541 }
542}