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('/').next_back().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}