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