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!("{}/xrpc/com.atproto.server.createAppPassword", base)) 295 .bearer_auth(&jwt) 296 .json(&json!({ "name": "My App" })) 297 .send() 298 .await 299 .expect("Failed to create app password"); 300 assert_eq!(create_res.status(), StatusCode::OK); 301 let duplicate_res = client 302 .post(format!("{}/xrpc/com.atproto.server.createAppPassword", base)) 303 .bearer_auth(&jwt) 304 .json(&json!({ "name": "My App" })) 305 .send() 306 .await 307 .expect("Failed to attempt duplicate"); 308 assert_eq!( 309 duplicate_res.status(), 310 StatusCode::BAD_REQUEST, 311 "Duplicate app password name should fail" 312 ); 313 let body: Value = duplicate_res.json().await.unwrap(); 314 assert_eq!(body["error"], "DuplicateAppPassword"); 315} 316 317#[tokio::test] 318async fn test_app_password_revoke_nonexistent() { 319 let client = client(); 320 let base = base_url().await; 321 let (jwt, _did) = create_account_and_login(&client).await; 322 let revoke_res = client 323 .post(format!("{}/xrpc/com.atproto.server.revokeAppPassword", base)) 324 .bearer_auth(&jwt) 325 .json(&json!({ "name": "Does Not Exist" })) 326 .send() 327 .await 328 .expect("Failed to revoke"); 329 assert_eq!( 330 revoke_res.status(), 331 StatusCode::OK, 332 "Revoking non-existent app password should succeed silently" 333 ); 334} 335 336#[tokio::test] 337async fn test_app_password_revoke_invalidates_sessions() { 338 let client = client(); 339 let base = base_url().await; 340 let ts = Utc::now().timestamp_millis(); 341 let handle = format!("apppass-inv-{}.test", ts); 342 let email = format!("apppass-inv-{}@test.com", ts); 343 let password = "ApppassInv123!"; 344 let create_res = client 345 .post(format!("{}/xrpc/com.atproto.server.createAccount", base)) 346 .json(&json!({ 347 "handle": handle, 348 "email": email, 349 "password": password 350 })) 351 .send() 352 .await 353 .expect("Failed to create account"); 354 assert_eq!(create_res.status(), StatusCode::OK); 355 let account: Value = create_res.json().await.unwrap(); 356 let did = account["did"].as_str().unwrap(); 357 let main_jwt = verify_new_account(&client, did).await; 358 let create_app_res = client 359 .post(format!("{}/xrpc/com.atproto.server.createAppPassword", base)) 360 .bearer_auth(&main_jwt) 361 .json(&json!({ "name": "Session Test App" })) 362 .send() 363 .await 364 .expect("Failed to create app password"); 365 assert_eq!(create_app_res.status(), StatusCode::OK); 366 let app_pass: Value = create_app_res.json().await.unwrap(); 367 let app_password = app_pass["password"].as_str().unwrap(); 368 let app_session_res = client 369 .post(format!("{}/xrpc/com.atproto.server.createSession", base)) 370 .json(&json!({ 371 "identifier": handle, 372 "password": app_password 373 })) 374 .send() 375 .await 376 .expect("Failed to login with app password"); 377 assert_eq!(app_session_res.status(), StatusCode::OK); 378 let app_session: Value = app_session_res.json().await.unwrap(); 379 let app_jwt = app_session["accessJwt"].as_str().unwrap(); 380 let get_session_res = client 381 .get(format!("{}/xrpc/com.atproto.server.getSession", base)) 382 .bearer_auth(app_jwt) 383 .send() 384 .await 385 .expect("Failed to get session"); 386 assert_eq!( 387 get_session_res.status(), 388 StatusCode::OK, 389 "App password session should be valid before revocation" 390 ); 391 let revoke_res = client 392 .post(format!("{}/xrpc/com.atproto.server.revokeAppPassword", base)) 393 .bearer_auth(&main_jwt) 394 .json(&json!({ "name": "Session Test App" })) 395 .send() 396 .await 397 .expect("Failed to revoke app password"); 398 assert_eq!(revoke_res.status(), StatusCode::OK); 399 let get_session_after = client 400 .get(format!("{}/xrpc/com.atproto.server.getSession", base)) 401 .bearer_auth(app_jwt) 402 .send() 403 .await 404 .expect("Failed to check session after revoke"); 405 assert!( 406 get_session_after.status() == StatusCode::UNAUTHORIZED 407 || get_session_after.status() == StatusCode::BAD_REQUEST, 408 "Session created with revoked app password should be invalid, got {}", 409 get_session_after.status() 410 ); 411 let main_session_res = client 412 .get(format!("{}/xrpc/com.atproto.server.getSession", base)) 413 .bearer_auth(&main_jwt) 414 .send() 415 .await 416 .expect("Failed to check main session"); 417 assert_eq!( 418 main_session_res.status(), 419 StatusCode::OK, 420 "Main session should still be valid after revoking app password" 421 ); 422} 423 424#[tokio::test] 425async fn test_account_deactivation_lifecycle() { 426 let client = client(); 427 let ts = Utc::now().timestamp_millis(); 428 let handle = format!("deactivate-{}.test", ts); 429 let email = format!("deactivate-{}@test.com", ts); 430 let password = "Deactivate123!"; 431 let create_res = client 432 .post(format!( 433 "{}/xrpc/com.atproto.server.createAccount", 434 base_url().await 435 )) 436 .json(&json!({ 437 "handle": handle, 438 "email": email, 439 "password": password 440 })) 441 .send() 442 .await 443 .expect("Failed to create account"); 444 assert_eq!(create_res.status(), StatusCode::OK); 445 let account: Value = create_res.json().await.unwrap(); 446 let did = account["did"].as_str().unwrap().to_string(); 447 let jwt = verify_new_account(&client, &did).await; 448 let (post_uri, _) = create_post(&client, &did, &jwt, "Post before deactivation").await; 449 let post_rkey = post_uri.split('/').last().unwrap(); 450 let status_before = client 451 .get(format!( 452 "{}/xrpc/com.atproto.server.checkAccountStatus", 453 base_url().await 454 )) 455 .bearer_auth(&jwt) 456 .send() 457 .await 458 .expect("Failed to check status"); 459 assert_eq!(status_before.status(), StatusCode::OK); 460 let status_body: Value = status_before.json().await.unwrap(); 461 assert_eq!(status_body["activated"], true); 462 let deactivate_res = client 463 .post(format!( 464 "{}/xrpc/com.atproto.server.deactivateAccount", 465 base_url().await 466 )) 467 .bearer_auth(&jwt) 468 .json(&json!({})) 469 .send() 470 .await 471 .expect("Failed to deactivate"); 472 assert_eq!(deactivate_res.status(), StatusCode::OK); 473 let get_post_res = client 474 .get(format!( 475 "{}/xrpc/com.atproto.repo.getRecord", 476 base_url().await 477 )) 478 .query(&[ 479 ("repo", did.as_str()), 480 ("collection", "app.bsky.feed.post"), 481 ("rkey", post_rkey), 482 ]) 483 .send() 484 .await 485 .expect("Failed to get post while deactivated"); 486 assert_eq!( 487 get_post_res.status(), 488 StatusCode::OK, 489 "Records should still be readable" 490 ); 491 let activate_res = client 492 .post(format!( 493 "{}/xrpc/com.atproto.server.activateAccount", 494 base_url().await 495 )) 496 .bearer_auth(&jwt) 497 .json(&json!({})) 498 .send() 499 .await 500 .expect("Failed to reactivate"); 501 assert_eq!(activate_res.status(), StatusCode::OK); 502 let status_after_activate = client 503 .get(format!( 504 "{}/xrpc/com.atproto.server.checkAccountStatus", 505 base_url().await 506 )) 507 .bearer_auth(&jwt) 508 .send() 509 .await 510 .expect("Failed to check status after activate"); 511 assert_eq!(status_after_activate.status(), StatusCode::OK); 512 let (new_post_uri, _) = create_post(&client, &did, &jwt, "Post after reactivation").await; 513 assert!( 514 !new_post_uri.is_empty(), 515 "Should be able to post after reactivation" 516 ); 517} 518 519#[tokio::test] 520async fn test_service_auth_lifecycle() { 521 let client = client(); 522 let (did, jwt) = setup_new_user("service-auth-test").await; 523 let service_auth_res = client 524 .get(format!( 525 "{}/xrpc/com.atproto.server.getServiceAuth", 526 base_url().await 527 )) 528 .query(&[ 529 ("aud", "did:web:api.bsky.app"), 530 ("lxm", "com.atproto.repo.uploadBlob"), 531 ]) 532 .bearer_auth(&jwt) 533 .send() 534 .await 535 .expect("Failed to get service auth"); 536 assert_eq!(service_auth_res.status(), StatusCode::OK); 537 let auth_body: Value = service_auth_res.json().await.unwrap(); 538 let service_token = auth_body["token"].as_str().expect("No token in response"); 539 let parts: Vec<&str> = service_token.split('.').collect(); 540 assert_eq!(parts.len(), 3, "Service token should be a valid JWT"); 541 use base64::Engine; 542 let payload_bytes = base64::engine::general_purpose::URL_SAFE_NO_PAD 543 .decode(parts[1]) 544 .expect("Failed to decode JWT payload"); 545 let claims: Value = serde_json::from_slice(&payload_bytes).expect("Invalid JWT payload"); 546 assert_eq!(claims["iss"], did); 547 assert_eq!(claims["aud"], "did:web:api.bsky.app"); 548 assert_eq!(claims["lxm"], "com.atproto.repo.uploadBlob"); 549} 550 551#[tokio::test] 552async fn test_request_account_delete() { 553 let client = client(); 554 let (did, jwt) = setup_new_user("request-delete-test").await; 555 let res = client 556 .post(format!( 557 "{}/xrpc/com.atproto.server.requestAccountDelete", 558 base_url().await 559 )) 560 .bearer_auth(&jwt) 561 .send() 562 .await 563 .expect("Failed to request account deletion"); 564 assert_eq!(res.status(), StatusCode::OK); 565 let db_url = get_db_connection_string().await; 566 let pool = sqlx::PgPool::connect(&db_url) 567 .await 568 .expect("Failed to connect to test DB"); 569 let row = sqlx::query!( 570 "SELECT token, expires_at FROM account_deletion_requests WHERE did = $1", 571 did 572 ) 573 .fetch_optional(&pool) 574 .await 575 .expect("Failed to query DB"); 576 assert!(row.is_some(), "Deletion token should exist in DB"); 577 let row = row.unwrap(); 578 assert!(!row.token.is_empty(), "Token should not be empty"); 579 assert!(row.expires_at > Utc::now(), "Token should not be expired"); 580}