authx_plugins/password_reset/
mod.rs1mod service;
2
3pub use service::{PasswordResetRequest, PasswordResetService};
4
5#[cfg(test)]
6mod tests {
7 use super::service::{PasswordResetRequest, PasswordResetService};
8 use authx_core::{error::AuthError, events::EventBus, models::CreateUser};
9 use authx_storage::{memory::MemoryStore, ports::UserRepository};
10
11 fn setup() -> (MemoryStore, EventBus) {
12 (MemoryStore::new(), EventBus::new())
13 }
14
15 async fn make_user(store: &MemoryStore) -> uuid::Uuid {
16 UserRepository::create(
17 store,
18 CreateUser {
19 email: "reset@example.com".into(),
20 username: None,
21 metadata: None,
22 },
23 )
24 .await
25 .unwrap()
26 .id
27 }
28
29 #[tokio::test]
30 async fn request_reset_returns_none_for_unknown_email() {
31 let (store, events) = setup();
32 let svc = PasswordResetService::new(store, events);
33 let tok = svc.request_reset("nobody@x.com").await.unwrap();
34 assert!(tok.is_none());
35 }
36
37 #[tokio::test]
38 async fn request_reset_returns_token_for_known_email() {
39 let (store, events) = setup();
40 make_user(&store).await;
41 let svc = PasswordResetService::new(store, events);
42 let tok = svc.request_reset("reset@example.com").await.unwrap();
43 assert!(tok.is_some());
44 assert_eq!(tok.unwrap().len(), 64); }
46
47 #[tokio::test]
48 async fn reset_password_fails_with_bad_token() {
49 let (store, events) = setup();
50 let svc = PasswordResetService::new(store, events);
51 let err = svc
52 .reset_password(PasswordResetRequest {
53 token: "bad_token".into(),
54 new_password: "newpassword123".into(),
55 })
56 .await
57 .unwrap_err();
58 assert!(matches!(err, AuthError::InvalidToken));
59 }
60
61 #[tokio::test]
62 async fn reset_password_succeeds_with_valid_token() {
63 use authx_core::crypto::hash_password;
64 use authx_core::models::{CreateCredential, CredentialKind};
65 use authx_storage::ports::CredentialRepository;
66
67 let (store, events) = setup();
68 let uid = make_user(&store).await;
69
70 let old_hash = hash_password("oldpass123").unwrap();
72 CredentialRepository::create(
73 &store,
74 CreateCredential {
75 user_id: uid,
76 kind: CredentialKind::Password,
77 credential_hash: old_hash,
78 metadata: None,
79 },
80 )
81 .await
82 .unwrap();
83
84 let svc = PasswordResetService::new(store.clone(), events);
85 let token = svc
86 .request_reset("reset@example.com")
87 .await
88 .unwrap()
89 .unwrap();
90
91 svc.reset_password(PasswordResetRequest {
92 token,
93 new_password: "newpass456".into(),
94 })
95 .await
96 .unwrap();
97
98 let new_hash = CredentialRepository::find_password_hash(&store, uid)
100 .await
101 .unwrap()
102 .unwrap();
103 assert!(authx_core::crypto::verify_password(&new_hash, "newpass456").unwrap());
104 }
105
106 #[tokio::test]
107 async fn reset_password_rejects_same_password() {
108 use authx_core::crypto::hash_password;
109 use authx_core::models::{CreateCredential, CredentialKind};
110 use authx_storage::ports::CredentialRepository;
111
112 let (store, events) = setup();
113 let uid = make_user(&store).await;
114
115 let old_hash = hash_password("samepass123").unwrap();
116 CredentialRepository::create(
117 &store,
118 CreateCredential {
119 user_id: uid,
120 kind: CredentialKind::Password,
121 credential_hash: old_hash,
122 metadata: None,
123 },
124 )
125 .await
126 .unwrap();
127
128 let svc = PasswordResetService::new(store.clone(), events);
129 let token = svc
130 .request_reset("reset@example.com")
131 .await
132 .unwrap()
133 .unwrap();
134
135 let err = svc
136 .reset_password(PasswordResetRequest {
137 token,
138 new_password: "samepass123".into(),
139 })
140 .await
141 .unwrap_err();
142
143 assert!(matches!(err, AuthError::Internal(_)));
144 }
145}