this repo has no description
1mod common;
2mod helpers;
3use reqwest::StatusCode;
4use serde_json::{json, Value};
5use sqlx::PgPool;
6use helpers::verify_new_account;
7
8async fn get_pool() -> PgPool {
9 let conn_str = common::get_db_connection_string().await;
10 sqlx::postgres::PgPoolOptions::new()
11 .max_connections(5)
12 .connect(&conn_str)
13 .await
14 .expect("Failed to connect to test database")
15}
16
17#[tokio::test]
18async fn test_request_password_reset_creates_code() {
19 let client = common::client();
20 let base_url = common::base_url().await;
21 let pool = get_pool().await;
22 let handle = format!("pwreset_{}", uuid::Uuid::new_v4());
23 let email = format!("{}@example.com", handle);
24 let payload = json!({
25 "handle": handle,
26 "email": email,
27 "password": "oldpassword"
28 });
29 let res = client
30 .post(format!("{}/xrpc/com.atproto.server.createAccount", base_url))
31 .json(&payload)
32 .send()
33 .await
34 .expect("Failed to create account");
35 assert_eq!(res.status(), StatusCode::OK);
36 let res = client
37 .post(format!("{}/xrpc/com.atproto.server.requestPasswordReset", base_url))
38 .json(&json!({"email": email}))
39 .send()
40 .await
41 .expect("Failed to request password reset");
42 assert_eq!(res.status(), StatusCode::OK);
43 let user = sqlx::query!(
44 "SELECT password_reset_code, password_reset_code_expires_at FROM users WHERE email = $1",
45 email
46 )
47 .fetch_one(&pool)
48 .await
49 .expect("User not found");
50 assert!(user.password_reset_code.is_some());
51 assert!(user.password_reset_code_expires_at.is_some());
52 let code = user.password_reset_code.unwrap();
53 assert!(code.contains('-'));
54 assert_eq!(code.len(), 11);
55}
56
57#[tokio::test]
58async fn test_request_password_reset_unknown_email_returns_ok() {
59 let client = common::client();
60 let base_url = common::base_url().await;
61 let res = client
62 .post(format!("{}/xrpc/com.atproto.server.requestPasswordReset", base_url))
63 .json(&json!({"email": "nonexistent@example.com"}))
64 .send()
65 .await
66 .expect("Failed to request password reset");
67 assert_eq!(res.status(), StatusCode::OK);
68}
69
70#[tokio::test]
71async fn test_reset_password_with_valid_token() {
72 let client = common::client();
73 let base_url = common::base_url().await;
74 let pool = get_pool().await;
75 let handle = format!("pwreset2_{}", uuid::Uuid::new_v4());
76 let email = format!("{}@example.com", handle);
77 let old_password = "oldpassword";
78 let new_password = "newpassword123";
79 let payload = json!({
80 "handle": handle,
81 "email": email,
82 "password": old_password
83 });
84 let res = client
85 .post(format!("{}/xrpc/com.atproto.server.createAccount", base_url))
86 .json(&payload)
87 .send()
88 .await
89 .expect("Failed to create account");
90 assert_eq!(res.status(), StatusCode::OK);
91 let body: Value = res.json().await.unwrap();
92 let did = body["did"].as_str().unwrap();
93 let _ = verify_new_account(&client, did).await;
94 let res = client
95 .post(format!("{}/xrpc/com.atproto.server.requestPasswordReset", base_url))
96 .json(&json!({"email": email}))
97 .send()
98 .await
99 .expect("Failed to request password reset");
100 assert_eq!(res.status(), StatusCode::OK);
101 let user = sqlx::query!(
102 "SELECT password_reset_code FROM users WHERE email = $1",
103 email
104 )
105 .fetch_one(&pool)
106 .await
107 .expect("User not found");
108 let token = user.password_reset_code.expect("No reset code");
109 let res = client
110 .post(format!("{}/xrpc/com.atproto.server.resetPassword", base_url))
111 .json(&json!({
112 "token": token,
113 "password": new_password
114 }))
115 .send()
116 .await
117 .expect("Failed to reset password");
118 assert_eq!(res.status(), StatusCode::OK);
119 let user = sqlx::query!(
120 "SELECT password_reset_code, password_reset_code_expires_at FROM users WHERE email = $1",
121 email
122 )
123 .fetch_one(&pool)
124 .await
125 .expect("User not found");
126 assert!(user.password_reset_code.is_none());
127 assert!(user.password_reset_code_expires_at.is_none());
128 let res = client
129 .post(format!("{}/xrpc/com.atproto.server.createSession", base_url))
130 .json(&json!({
131 "identifier": handle,
132 "password": new_password
133 }))
134 .send()
135 .await
136 .expect("Failed to login");
137 assert_eq!(res.status(), StatusCode::OK);
138 let res = client
139 .post(format!("{}/xrpc/com.atproto.server.createSession", base_url))
140 .json(&json!({
141 "identifier": handle,
142 "password": old_password
143 }))
144 .send()
145 .await
146 .expect("Failed to login attempt");
147 assert_eq!(res.status(), StatusCode::UNAUTHORIZED);
148}
149
150#[tokio::test]
151async fn test_reset_password_with_invalid_token() {
152 let client = common::client();
153 let base_url = common::base_url().await;
154 let res = client
155 .post(format!("{}/xrpc/com.atproto.server.resetPassword", base_url))
156 .json(&json!({
157 "token": "invalid-token",
158 "password": "newpassword"
159 }))
160 .send()
161 .await
162 .expect("Failed to reset password");
163 assert_eq!(res.status(), StatusCode::BAD_REQUEST);
164 let body: Value = res.json().await.expect("Invalid JSON");
165 assert_eq!(body["error"], "InvalidToken");
166}
167
168#[tokio::test]
169async fn test_reset_password_with_expired_token() {
170 let client = common::client();
171 let base_url = common::base_url().await;
172 let pool = get_pool().await;
173 let handle = format!("pwreset3_{}", uuid::Uuid::new_v4());
174 let email = format!("{}@example.com", handle);
175 let payload = json!({
176 "handle": handle,
177 "email": email,
178 "password": "oldpassword"
179 });
180 let res = client
181 .post(format!("{}/xrpc/com.atproto.server.createAccount", base_url))
182 .json(&payload)
183 .send()
184 .await
185 .expect("Failed to create account");
186 assert_eq!(res.status(), StatusCode::OK);
187 let res = client
188 .post(format!("{}/xrpc/com.atproto.server.requestPasswordReset", base_url))
189 .json(&json!({"email": email}))
190 .send()
191 .await
192 .expect("Failed to request password reset");
193 assert_eq!(res.status(), StatusCode::OK);
194 let user = sqlx::query!(
195 "SELECT password_reset_code FROM users WHERE email = $1",
196 email
197 )
198 .fetch_one(&pool)
199 .await
200 .expect("User not found");
201 let token = user.password_reset_code.expect("No reset code");
202 sqlx::query!(
203 "UPDATE users SET password_reset_code_expires_at = NOW() - INTERVAL '1 hour' WHERE email = $1",
204 email
205 )
206 .execute(&pool)
207 .await
208 .expect("Failed to expire token");
209 let res = client
210 .post(format!("{}/xrpc/com.atproto.server.resetPassword", base_url))
211 .json(&json!({
212 "token": token,
213 "password": "newpassword"
214 }))
215 .send()
216 .await
217 .expect("Failed to reset password");
218 assert_eq!(res.status(), StatusCode::BAD_REQUEST);
219 let body: Value = res.json().await.expect("Invalid JSON");
220 assert_eq!(body["error"], "ExpiredToken");
221}
222
223#[tokio::test]
224async fn test_reset_password_invalidates_sessions() {
225 let client = common::client();
226 let base_url = common::base_url().await;
227 let pool = get_pool().await;
228 let handle = format!("pwreset4_{}", uuid::Uuid::new_v4());
229 let email = format!("{}@example.com", handle);
230 let payload = json!({
231 "handle": handle,
232 "email": email,
233 "password": "oldpassword"
234 });
235 let res = client
236 .post(format!("{}/xrpc/com.atproto.server.createAccount", base_url))
237 .json(&payload)
238 .send()
239 .await
240 .expect("Failed to create account");
241 assert_eq!(res.status(), StatusCode::OK);
242 let body: Value = res.json().await.expect("Invalid JSON");
243 let did = body["did"].as_str().expect("No did");
244 let original_token = verify_new_account(&client, did).await;
245 let res = client
246 .get(format!("{}/xrpc/com.atproto.server.getSession", base_url))
247 .bearer_auth(&original_token)
248 .send()
249 .await
250 .expect("Failed to get session");
251 assert_eq!(res.status(), StatusCode::OK);
252 let res = client
253 .post(format!("{}/xrpc/com.atproto.server.requestPasswordReset", base_url))
254 .json(&json!({"email": email}))
255 .send()
256 .await
257 .expect("Failed to request password reset");
258 assert_eq!(res.status(), StatusCode::OK);
259 let user = sqlx::query!(
260 "SELECT password_reset_code FROM users WHERE email = $1",
261 email
262 )
263 .fetch_one(&pool)
264 .await
265 .expect("User not found");
266 let token = user.password_reset_code.expect("No reset code");
267 let res = client
268 .post(format!("{}/xrpc/com.atproto.server.resetPassword", base_url))
269 .json(&json!({
270 "token": token,
271 "password": "newpassword123"
272 }))
273 .send()
274 .await
275 .expect("Failed to reset password");
276 assert_eq!(res.status(), StatusCode::OK);
277 let res = client
278 .get(format!("{}/xrpc/com.atproto.server.getSession", base_url))
279 .bearer_auth(&original_token)
280 .send()
281 .await
282 .expect("Failed to get session");
283 assert_eq!(res.status(), StatusCode::UNAUTHORIZED);
284}
285
286#[tokio::test]
287async fn test_request_password_reset_empty_email() {
288 let client = common::client();
289 let base_url = common::base_url().await;
290 let res = client
291 .post(format!("{}/xrpc/com.atproto.server.requestPasswordReset", base_url))
292 .json(&json!({"email": ""}))
293 .send()
294 .await
295 .expect("Failed to request password reset");
296 assert_eq!(res.status(), StatusCode::BAD_REQUEST);
297 let body: Value = res.json().await.expect("Invalid JSON");
298 assert_eq!(body["error"], "InvalidRequest");
299}
300
301#[tokio::test]
302async fn test_reset_password_creates_notification() {
303 let pool = get_pool().await;
304 let client = common::client();
305 let base_url = common::base_url().await;
306 let handle = format!("pwreset5_{}", uuid::Uuid::new_v4());
307 let email = format!("{}@example.com", handle);
308 let payload = json!({
309 "handle": handle,
310 "email": email,
311 "password": "oldpassword"
312 });
313 let res = client
314 .post(format!("{}/xrpc/com.atproto.server.createAccount", base_url))
315 .json(&payload)
316 .send()
317 .await
318 .expect("Failed to create account");
319 assert_eq!(res.status(), StatusCode::OK);
320 let user = sqlx::query!("SELECT id FROM users WHERE email = $1", email)
321 .fetch_one(&pool)
322 .await
323 .expect("User not found");
324 let initial_count: i64 = sqlx::query_scalar!(
325 "SELECT COUNT(*) FROM notification_queue WHERE user_id = $1 AND notification_type = 'password_reset'",
326 user.id
327 )
328 .fetch_one(&pool)
329 .await
330 .expect("Failed to count")
331 .unwrap_or(0);
332 let res = client
333 .post(format!("{}/xrpc/com.atproto.server.requestPasswordReset", base_url))
334 .json(&json!({"email": email}))
335 .send()
336 .await
337 .expect("Failed to request password reset");
338 assert_eq!(res.status(), StatusCode::OK);
339 let final_count: i64 = sqlx::query_scalar!(
340 "SELECT COUNT(*) FROM notification_queue WHERE user_id = $1 AND notification_type = 'password_reset'",
341 user.id
342 )
343 .fetch_one(&pool)
344 .await
345 .expect("Failed to count")
346 .unwrap_or(0);
347 assert_eq!(final_count - initial_count, 1);
348}