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