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