this repo has no description
1mod common;
2mod helpers;
3use chrono::Utc;
4use common::*;
5use helpers::*;
6use reqwest::StatusCode;
7use serde_json::{Value, json};
8
9#[tokio::test]
10async fn test_session_lifecycle_wrong_password() {
11 let client = client();
12 let (_, _) = setup_new_user("session-wrong-pw").await;
13 let login_payload = json!({
14 "identifier": format!("session-wrong-pw-{}.test", Utc::now().timestamp_millis()),
15 "password": "wrong-password"
16 });
17 let res = client
18 .post(format!(
19 "{}/xrpc/com.atproto.server.createSession",
20 base_url().await
21 ))
22 .json(&login_payload)
23 .send()
24 .await
25 .expect("Failed to send request");
26 assert!(
27 res.status() == StatusCode::UNAUTHORIZED || res.status() == StatusCode::BAD_REQUEST,
28 "Expected 401 or 400 for wrong password, got {}",
29 res.status()
30 );
31}
32
33#[tokio::test]
34async fn test_session_lifecycle_multiple_sessions() {
35 let client = client();
36 let ts = Utc::now().timestamp_millis();
37 let handle = format!("multi-session-{}.test", ts);
38 let email = format!("multi-session-{}@test.com", ts);
39 let password = "Multisession123!";
40 let create_payload = json!({
41 "handle": handle,
42 "email": email,
43 "password": password
44 });
45 let create_res = client
46 .post(format!(
47 "{}/xrpc/com.atproto.server.createAccount",
48 base_url().await
49 ))
50 .json(&create_payload)
51 .send()
52 .await
53 .expect("Failed to create account");
54 assert_eq!(create_res.status(), StatusCode::OK);
55 let create_body: Value = create_res.json().await.unwrap();
56 let did = create_body["did"].as_str().unwrap();
57 let _ = verify_new_account(&client, did).await;
58 let login_payload = json!({
59 "identifier": handle,
60 "password": password
61 });
62 let session1_res = client
63 .post(format!(
64 "{}/xrpc/com.atproto.server.createSession",
65 base_url().await
66 ))
67 .json(&login_payload)
68 .send()
69 .await
70 .expect("Failed session 1");
71 assert_eq!(session1_res.status(), StatusCode::OK);
72 let session1: Value = session1_res.json().await.unwrap();
73 let jwt1 = session1["accessJwt"].as_str().unwrap();
74 let session2_res = client
75 .post(format!(
76 "{}/xrpc/com.atproto.server.createSession",
77 base_url().await
78 ))
79 .json(&login_payload)
80 .send()
81 .await
82 .expect("Failed session 2");
83 assert_eq!(session2_res.status(), StatusCode::OK);
84 let session2: Value = session2_res.json().await.unwrap();
85 let jwt2 = session2["accessJwt"].as_str().unwrap();
86 assert_ne!(jwt1, jwt2, "Sessions should have different tokens");
87 let get1 = client
88 .get(format!(
89 "{}/xrpc/com.atproto.server.getSession",
90 base_url().await
91 ))
92 .bearer_auth(jwt1)
93 .send()
94 .await
95 .expect("Failed getSession 1");
96 assert_eq!(get1.status(), StatusCode::OK);
97 let get2 = client
98 .get(format!(
99 "{}/xrpc/com.atproto.server.getSession",
100 base_url().await
101 ))
102 .bearer_auth(jwt2)
103 .send()
104 .await
105 .expect("Failed getSession 2");
106 assert_eq!(get2.status(), StatusCode::OK);
107}
108
109#[tokio::test]
110async fn test_session_lifecycle_refresh_invalidates_old() {
111 let client = client();
112 let ts = Utc::now().timestamp_millis();
113 let handle = format!("refresh-inv-{}.test", ts);
114 let email = format!("refresh-inv-{}@test.com", ts);
115 let password = "Refresh123inv!";
116 let create_payload = json!({
117 "handle": handle,
118 "email": email,
119 "password": password
120 });
121 let create_res = client
122 .post(format!(
123 "{}/xrpc/com.atproto.server.createAccount",
124 base_url().await
125 ))
126 .json(&create_payload)
127 .send()
128 .await
129 .expect("Failed to create account");
130 let create_body: Value = create_res.json().await.unwrap();
131 let did = create_body["did"].as_str().unwrap();
132 let _ = verify_new_account(&client, did).await;
133 let login_payload = json!({
134 "identifier": handle,
135 "password": password
136 });
137 let login_res = client
138 .post(format!(
139 "{}/xrpc/com.atproto.server.createSession",
140 base_url().await
141 ))
142 .json(&login_payload)
143 .send()
144 .await
145 .expect("Failed login");
146 let login_body: Value = login_res.json().await.unwrap();
147 let refresh_jwt = login_body["refreshJwt"].as_str().unwrap().to_string();
148 let refresh_res = client
149 .post(format!(
150 "{}/xrpc/com.atproto.server.refreshSession",
151 base_url().await
152 ))
153 .bearer_auth(&refresh_jwt)
154 .send()
155 .await
156 .expect("Failed first refresh");
157 assert_eq!(refresh_res.status(), StatusCode::OK);
158 let refresh_body: Value = refresh_res.json().await.unwrap();
159 let new_refresh_jwt = refresh_body["refreshJwt"].as_str().unwrap();
160 assert_ne!(refresh_jwt, new_refresh_jwt, "Refresh tokens should differ");
161 let reuse_res = client
162 .post(format!(
163 "{}/xrpc/com.atproto.server.refreshSession",
164 base_url().await
165 ))
166 .bearer_auth(&refresh_jwt)
167 .send()
168 .await
169 .expect("Failed reuse attempt");
170 assert!(
171 reuse_res.status() == StatusCode::UNAUTHORIZED
172 || reuse_res.status() == StatusCode::BAD_REQUEST,
173 "Old refresh token should be invalid after use"
174 );
175}
176
177#[tokio::test]
178async fn test_app_password_lifecycle() {
179 let client = client();
180 let ts = Utc::now().timestamp_millis();
181 let handle = format!("apppass-{}.test", ts);
182 let email = format!("apppass-{}@test.com", ts);
183 let password = "Apppass123!";
184 let create_res = client
185 .post(format!(
186 "{}/xrpc/com.atproto.server.createAccount",
187 base_url().await
188 ))
189 .json(&json!({
190 "handle": handle,
191 "email": email,
192 "password": password
193 }))
194 .send()
195 .await
196 .expect("Failed to create account");
197 assert_eq!(create_res.status(), StatusCode::OK);
198 let account: Value = create_res.json().await.unwrap();
199 let did = account["did"].as_str().unwrap();
200 let jwt = verify_new_account(&client, did).await;
201 let create_app_pass_res = client
202 .post(format!(
203 "{}/xrpc/com.atproto.server.createAppPassword",
204 base_url().await
205 ))
206 .bearer_auth(&jwt)
207 .json(&json!({ "name": "Test App" }))
208 .send()
209 .await
210 .expect("Failed to create app password");
211 assert_eq!(create_app_pass_res.status(), StatusCode::OK);
212 let app_pass: Value = create_app_pass_res.json().await.unwrap();
213 let app_password = app_pass["password"].as_str().unwrap().to_string();
214 assert_eq!(app_pass["name"], "Test App");
215 let list_res = client
216 .get(format!(
217 "{}/xrpc/com.atproto.server.listAppPasswords",
218 base_url().await
219 ))
220 .bearer_auth(&jwt)
221 .send()
222 .await
223 .expect("Failed to list app passwords");
224 assert_eq!(list_res.status(), StatusCode::OK);
225 let list_body: Value = list_res.json().await.unwrap();
226 let passwords = list_body["passwords"].as_array().unwrap();
227 assert_eq!(passwords.len(), 1);
228 assert_eq!(passwords[0]["name"], "Test App");
229 let login_res = client
230 .post(format!(
231 "{}/xrpc/com.atproto.server.createSession",
232 base_url().await
233 ))
234 .json(&json!({
235 "identifier": handle,
236 "password": app_password
237 }))
238 .send()
239 .await
240 .expect("Failed to login with app password");
241 assert_eq!(
242 login_res.status(),
243 StatusCode::OK,
244 "App password login should work"
245 );
246 let revoke_res = client
247 .post(format!(
248 "{}/xrpc/com.atproto.server.revokeAppPassword",
249 base_url().await
250 ))
251 .bearer_auth(&jwt)
252 .json(&json!({ "name": "Test App" }))
253 .send()
254 .await
255 .expect("Failed to revoke app password");
256 assert_eq!(revoke_res.status(), StatusCode::OK);
257 let login_after_revoke = client
258 .post(format!(
259 "{}/xrpc/com.atproto.server.createSession",
260 base_url().await
261 ))
262 .json(&json!({
263 "identifier": handle,
264 "password": app_password
265 }))
266 .send()
267 .await
268 .expect("Failed to attempt login after revoke");
269 assert!(
270 login_after_revoke.status() == StatusCode::UNAUTHORIZED
271 || login_after_revoke.status() == StatusCode::BAD_REQUEST,
272 "Revoked app password should not work"
273 );
274 let list_after_revoke = client
275 .get(format!(
276 "{}/xrpc/com.atproto.server.listAppPasswords",
277 base_url().await
278 ))
279 .bearer_auth(&jwt)
280 .send()
281 .await
282 .expect("Failed to list after revoke");
283 let list_after: Value = list_after_revoke.json().await.unwrap();
284 let passwords_after = list_after["passwords"].as_array().unwrap();
285 assert_eq!(passwords_after.len(), 0, "No app passwords should remain");
286}
287
288#[tokio::test]
289async fn test_app_password_duplicate_name() {
290 let client = client();
291 let base = base_url().await;
292 let (jwt, _did) = create_account_and_login(&client).await;
293 let create_res = client
294 .post(format!(
295 "{}/xrpc/com.atproto.server.createAppPassword",
296 base
297 ))
298 .bearer_auth(&jwt)
299 .json(&json!({ "name": "My App" }))
300 .send()
301 .await
302 .expect("Failed to create app password");
303 assert_eq!(create_res.status(), StatusCode::OK);
304 let duplicate_res = client
305 .post(format!(
306 "{}/xrpc/com.atproto.server.createAppPassword",
307 base
308 ))
309 .bearer_auth(&jwt)
310 .json(&json!({ "name": "My App" }))
311 .send()
312 .await
313 .expect("Failed to attempt duplicate");
314 assert_eq!(
315 duplicate_res.status(),
316 StatusCode::BAD_REQUEST,
317 "Duplicate app password name should fail"
318 );
319 let body: Value = duplicate_res.json().await.unwrap();
320 assert_eq!(body["error"], "DuplicateAppPassword");
321}
322
323#[tokio::test]
324async fn test_app_password_revoke_nonexistent() {
325 let client = client();
326 let base = base_url().await;
327 let (jwt, _did) = create_account_and_login(&client).await;
328 let revoke_res = client
329 .post(format!(
330 "{}/xrpc/com.atproto.server.revokeAppPassword",
331 base
332 ))
333 .bearer_auth(&jwt)
334 .json(&json!({ "name": "Does Not Exist" }))
335 .send()
336 .await
337 .expect("Failed to revoke");
338 assert_eq!(
339 revoke_res.status(),
340 StatusCode::OK,
341 "Revoking non-existent app password should succeed silently"
342 );
343}
344
345#[tokio::test]
346async fn test_app_password_revoke_invalidates_sessions() {
347 let client = client();
348 let base = base_url().await;
349 let ts = Utc::now().timestamp_millis();
350 let handle = format!("apppass-inv-{}.test", ts);
351 let email = format!("apppass-inv-{}@test.com", ts);
352 let password = "ApppassInv123!";
353 let create_res = client
354 .post(format!("{}/xrpc/com.atproto.server.createAccount", base))
355 .json(&json!({
356 "handle": handle,
357 "email": email,
358 "password": password
359 }))
360 .send()
361 .await
362 .expect("Failed to create account");
363 assert_eq!(create_res.status(), StatusCode::OK);
364 let account: Value = create_res.json().await.unwrap();
365 let did = account["did"].as_str().unwrap();
366 let main_jwt = verify_new_account(&client, did).await;
367 let create_app_res = client
368 .post(format!(
369 "{}/xrpc/com.atproto.server.createAppPassword",
370 base
371 ))
372 .bearer_auth(&main_jwt)
373 .json(&json!({ "name": "Session Test App" }))
374 .send()
375 .await
376 .expect("Failed to create app password");
377 assert_eq!(create_app_res.status(), StatusCode::OK);
378 let app_pass: Value = create_app_res.json().await.unwrap();
379 let app_password = app_pass["password"].as_str().unwrap();
380 let app_session_res = client
381 .post(format!("{}/xrpc/com.atproto.server.createSession", base))
382 .json(&json!({
383 "identifier": handle,
384 "password": app_password
385 }))
386 .send()
387 .await
388 .expect("Failed to login with app password");
389 assert_eq!(app_session_res.status(), StatusCode::OK);
390 let app_session: Value = app_session_res.json().await.unwrap();
391 let app_jwt = app_session["accessJwt"].as_str().unwrap();
392 let get_session_res = client
393 .get(format!("{}/xrpc/com.atproto.server.getSession", base))
394 .bearer_auth(app_jwt)
395 .send()
396 .await
397 .expect("Failed to get session");
398 assert_eq!(
399 get_session_res.status(),
400 StatusCode::OK,
401 "App password session should be valid before revocation"
402 );
403 let revoke_res = client
404 .post(format!(
405 "{}/xrpc/com.atproto.server.revokeAppPassword",
406 base
407 ))
408 .bearer_auth(&main_jwt)
409 .json(&json!({ "name": "Session Test App" }))
410 .send()
411 .await
412 .expect("Failed to revoke app password");
413 assert_eq!(revoke_res.status(), StatusCode::OK);
414 let get_session_after = client
415 .get(format!("{}/xrpc/com.atproto.server.getSession", base))
416 .bearer_auth(app_jwt)
417 .send()
418 .await
419 .expect("Failed to check session after revoke");
420 assert!(
421 get_session_after.status() == StatusCode::UNAUTHORIZED
422 || get_session_after.status() == StatusCode::BAD_REQUEST,
423 "Session created with revoked app password should be invalid, got {}",
424 get_session_after.status()
425 );
426 let main_session_res = client
427 .get(format!("{}/xrpc/com.atproto.server.getSession", base))
428 .bearer_auth(&main_jwt)
429 .send()
430 .await
431 .expect("Failed to check main session");
432 assert_eq!(
433 main_session_res.status(),
434 StatusCode::OK,
435 "Main session should still be valid after revoking app password"
436 );
437}
438
439#[tokio::test]
440async fn test_account_deactivation_lifecycle() {
441 let client = client();
442 let ts = Utc::now().timestamp_millis();
443 let handle = format!("deactivate-{}.test", ts);
444 let email = format!("deactivate-{}@test.com", ts);
445 let password = "Deactivate123!";
446 let create_res = client
447 .post(format!(
448 "{}/xrpc/com.atproto.server.createAccount",
449 base_url().await
450 ))
451 .json(&json!({
452 "handle": handle,
453 "email": email,
454 "password": password
455 }))
456 .send()
457 .await
458 .expect("Failed to create account");
459 assert_eq!(create_res.status(), StatusCode::OK);
460 let account: Value = create_res.json().await.unwrap();
461 let did = account["did"].as_str().unwrap().to_string();
462 let jwt = verify_new_account(&client, &did).await;
463 let (post_uri, _) = create_post(&client, &did, &jwt, "Post before deactivation").await;
464 let post_rkey = post_uri.split('/').last().unwrap();
465 let status_before = client
466 .get(format!(
467 "{}/xrpc/com.atproto.server.checkAccountStatus",
468 base_url().await
469 ))
470 .bearer_auth(&jwt)
471 .send()
472 .await
473 .expect("Failed to check status");
474 assert_eq!(status_before.status(), StatusCode::OK);
475 let status_body: Value = status_before.json().await.unwrap();
476 assert_eq!(status_body["activated"], true);
477 let deactivate_res = client
478 .post(format!(
479 "{}/xrpc/com.atproto.server.deactivateAccount",
480 base_url().await
481 ))
482 .bearer_auth(&jwt)
483 .json(&json!({}))
484 .send()
485 .await
486 .expect("Failed to deactivate");
487 assert_eq!(deactivate_res.status(), StatusCode::OK);
488 let get_post_res = client
489 .get(format!(
490 "{}/xrpc/com.atproto.repo.getRecord",
491 base_url().await
492 ))
493 .query(&[
494 ("repo", did.as_str()),
495 ("collection", "app.bsky.feed.post"),
496 ("rkey", post_rkey),
497 ])
498 .send()
499 .await
500 .expect("Failed to get post while deactivated");
501 assert_eq!(
502 get_post_res.status(),
503 StatusCode::OK,
504 "Records should still be readable"
505 );
506 let activate_res = client
507 .post(format!(
508 "{}/xrpc/com.atproto.server.activateAccount",
509 base_url().await
510 ))
511 .bearer_auth(&jwt)
512 .json(&json!({}))
513 .send()
514 .await
515 .expect("Failed to reactivate");
516 assert_eq!(activate_res.status(), StatusCode::OK);
517 let status_after_activate = client
518 .get(format!(
519 "{}/xrpc/com.atproto.server.checkAccountStatus",
520 base_url().await
521 ))
522 .bearer_auth(&jwt)
523 .send()
524 .await
525 .expect("Failed to check status after activate");
526 assert_eq!(status_after_activate.status(), StatusCode::OK);
527 let (new_post_uri, _) = create_post(&client, &did, &jwt, "Post after reactivation").await;
528 assert!(
529 !new_post_uri.is_empty(),
530 "Should be able to post after reactivation"
531 );
532}
533
534#[tokio::test]
535async fn test_service_auth_lifecycle() {
536 let client = client();
537 let (did, jwt) = setup_new_user("service-auth-test").await;
538 let service_auth_res = client
539 .get(format!(
540 "{}/xrpc/com.atproto.server.getServiceAuth",
541 base_url().await
542 ))
543 .query(&[
544 ("aud", "did:web:api.bsky.app"),
545 ("lxm", "com.atproto.repo.uploadBlob"),
546 ])
547 .bearer_auth(&jwt)
548 .send()
549 .await
550 .expect("Failed to get service auth");
551 assert_eq!(service_auth_res.status(), StatusCode::OK);
552 let auth_body: Value = service_auth_res.json().await.unwrap();
553 let service_token = auth_body["token"].as_str().expect("No token in response");
554 let parts: Vec<&str> = service_token.split('.').collect();
555 assert_eq!(parts.len(), 3, "Service token should be a valid JWT");
556 use base64::Engine;
557 let payload_bytes = base64::engine::general_purpose::URL_SAFE_NO_PAD
558 .decode(parts[1])
559 .expect("Failed to decode JWT payload");
560 let claims: Value = serde_json::from_slice(&payload_bytes).expect("Invalid JWT payload");
561 assert_eq!(claims["iss"], did);
562 assert_eq!(claims["aud"], "did:web:api.bsky.app");
563 assert_eq!(claims["lxm"], "com.atproto.repo.uploadBlob");
564}
565
566#[tokio::test]
567async fn test_request_account_delete() {
568 let client = client();
569 let (did, jwt) = setup_new_user("request-delete-test").await;
570 let res = client
571 .post(format!(
572 "{}/xrpc/com.atproto.server.requestAccountDelete",
573 base_url().await
574 ))
575 .bearer_auth(&jwt)
576 .send()
577 .await
578 .expect("Failed to request account deletion");
579 assert_eq!(res.status(), StatusCode::OK);
580 let db_url = get_db_connection_string().await;
581 let pool = sqlx::PgPool::connect(&db_url)
582 .await
583 .expect("Failed to connect to test DB");
584 let row = sqlx::query!(
585 "SELECT token, expires_at FROM account_deletion_requests WHERE did = $1",
586 did
587 )
588 .fetch_optional(&pool)
589 .await
590 .expect("Failed to query DB");
591 assert!(row.is_some(), "Deletion token should exist in DB");
592 let row = row.unwrap();
593 assert!(!row.token.is_empty(), "Token should not be empty");
594 assert!(row.expires_at > Utc::now(), "Token should not be expired");
595}