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