this repo has no description
1mod common; 2use base64::Engine; 3use base64::engine::general_purpose::URL_SAFE_NO_PAD; 4use common::*; 5use k256::ecdsa::{SigningKey, signature::Signer}; 6use reqwest::StatusCode; 7use serde_json::{Value, json}; 8use wiremock::matchers::{method, path}; 9use wiremock::{Mock, MockServer, ResponseTemplate}; 10 11#[tokio::test] 12async fn test_create_self_hosted_did_web() { 13 let client = client(); 14 let handle = format!("sw{}", &uuid::Uuid::new_v4().simple().to_string()[..12]); 15 let payload = json!({ 16 "handle": handle, 17 "email": format!("{}@example.com", handle), 18 "password": "Testpass123!", 19 "didType": "web" 20 }); 21 let res = client 22 .post(format!( 23 "{}/xrpc/com.atproto.server.createAccount", 24 base_url().await 25 )) 26 .json(&payload) 27 .send() 28 .await 29 .expect("Failed to send request"); 30 if res.status() != StatusCode::OK { 31 let body: Value = res.json().await.unwrap_or(json!({"error": "parse failed"})); 32 panic!("createAccount failed: {:?}", body); 33 } 34 let body: Value = res.json().await.expect("Response was not JSON"); 35 let did = body["did"].as_str().expect("No DID in response"); 36 assert!( 37 did.starts_with("did:web:"), 38 "DID should start with did:web:, got: {}", 39 did 40 ); 41 assert!( 42 did.contains(&handle), 43 "DID should contain handle {}, got: {}", 44 handle, 45 did 46 ); 47 assert!( 48 !did.contains(":u:"), 49 "Self-hosted did:web should use subdomain format (no :u:), got: {}", 50 did 51 ); 52 let jwt = verify_new_account(&client, did).await; 53 let res = client 54 .get(format!("{}/u/{}/did.json", base_url().await, handle)) 55 .send() 56 .await 57 .expect("Failed to fetch DID doc via path"); 58 assert_eq!( 59 res.status(), 60 StatusCode::OK, 61 "Self-hosted did:web should have DID doc served by PDS (via path for backwards compat)" 62 ); 63 let doc: Value = res.json().await.expect("DID doc was not JSON"); 64 assert_eq!(doc["id"], did); 65 assert!( 66 doc["verificationMethod"][0]["publicKeyMultibase"].is_string(), 67 "DID doc should have publicKeyMultibase" 68 ); 69 let res = client 70 .post(format!( 71 "{}/xrpc/com.atproto.repo.createRecord", 72 base_url().await 73 )) 74 .bearer_auth(&jwt) 75 .json(&json!({ 76 "repo": did, 77 "collection": "app.bsky.feed.post", 78 "record": { 79 "$type": "app.bsky.feed.post", 80 "text": "Hello from did:web!", 81 "createdAt": chrono::Utc::now().to_rfc3339() 82 } 83 })) 84 .send() 85 .await 86 .expect("Failed to create post"); 87 assert_eq!( 88 res.status(), 89 StatusCode::OK, 90 "Self-hosted did:web account should be able to create records" 91 ); 92} 93 94#[tokio::test] 95async fn test_external_did_web_no_local_doc() { 96 let client = client(); 97 let mock_server = MockServer::start().await; 98 let mock_uri = mock_server.uri(); 99 let mock_addr = mock_uri.trim_start_matches("http://"); 100 let did = format!("did:web:{}", mock_addr.replace(":", "%3A")); 101 let handle = format!("xw{}", &uuid::Uuid::new_v4().simple().to_string()[..12]); 102 let pds_endpoint = base_url().await.replace("http://", "https://"); 103 104 let reserve_res = client 105 .post(format!( 106 "{}/xrpc/com.atproto.server.reserveSigningKey", 107 base_url().await 108 )) 109 .json(&json!({ "did": did })) 110 .send() 111 .await 112 .expect("Failed to reserve signing key"); 113 assert_eq!(reserve_res.status(), StatusCode::OK); 114 let reserve_body: Value = reserve_res.json().await.expect("Response was not JSON"); 115 let signing_key = reserve_body["signingKey"] 116 .as_str() 117 .expect("No signingKey returned"); 118 let public_key_multibase = signing_key 119 .strip_prefix("did:key:") 120 .expect("signingKey should start with did:key:"); 121 122 let did_doc = json!({ 123 "@context": ["https://www.w3.org/ns/did/v1"], 124 "id": did, 125 "verificationMethod": [{ 126 "id": format!("{}#atproto", did), 127 "type": "Multikey", 128 "controller": did, 129 "publicKeyMultibase": public_key_multibase 130 }], 131 "service": [{ 132 "id": "#atproto_pds", 133 "type": "AtprotoPersonalDataServer", 134 "serviceEndpoint": pds_endpoint 135 }] 136 }); 137 Mock::given(method("GET")) 138 .and(path("/.well-known/did.json")) 139 .respond_with(ResponseTemplate::new(200).set_body_json(did_doc)) 140 .mount(&mock_server) 141 .await; 142 let payload = json!({ 143 "handle": handle, 144 "email": format!("{}@example.com", handle), 145 "password": "Testpass123!", 146 "didType": "web-external", 147 "did": did, 148 "signingKey": signing_key 149 }); 150 let res = client 151 .post(format!( 152 "{}/xrpc/com.atproto.server.createAccount", 153 base_url().await 154 )) 155 .json(&payload) 156 .send() 157 .await 158 .expect("Failed to send request"); 159 if res.status() != StatusCode::OK { 160 let body: Value = res.json().await.unwrap_or(json!({"error": "parse failed"})); 161 panic!("createAccount failed: {:?}", body); 162 } 163 let res = client 164 .get(format!("{}/u/{}/did.json", base_url().await, handle)) 165 .send() 166 .await 167 .expect("Failed to fetch DID doc"); 168 assert_eq!( 169 res.status(), 170 StatusCode::NOT_FOUND, 171 "External did:web should NOT have DID doc served by PDS" 172 ); 173 let body: Value = res.json().await.expect("Response was not JSON"); 174 assert!( 175 body["message"].as_str().unwrap_or("").contains("External"), 176 "Error message should indicate external did:web" 177 ); 178} 179 180#[tokio::test] 181async fn test_plc_operations_blocked_for_did_web() { 182 let client = client(); 183 let handle = format!("pb{}", &uuid::Uuid::new_v4().simple().to_string()[..12]); 184 let payload = json!({ 185 "handle": handle, 186 "email": format!("{}@example.com", handle), 187 "password": "Testpass123!", 188 "didType": "web" 189 }); 190 let res = client 191 .post(format!( 192 "{}/xrpc/com.atproto.server.createAccount", 193 base_url().await 194 )) 195 .json(&payload) 196 .send() 197 .await 198 .expect("Failed to send request"); 199 assert_eq!(res.status(), StatusCode::OK); 200 let body: Value = res.json().await.expect("Response was not JSON"); 201 let did = body["did"].as_str().expect("No DID").to_string(); 202 let jwt = verify_new_account(&client, &did).await; 203 let res = client 204 .post(format!( 205 "{}/xrpc/com.atproto.identity.signPlcOperation", 206 base_url().await 207 )) 208 .bearer_auth(&jwt) 209 .json(&json!({ 210 "token": "fake-token" 211 })) 212 .send() 213 .await 214 .expect("Failed to send request"); 215 assert_eq!( 216 res.status(), 217 StatusCode::BAD_REQUEST, 218 "signPlcOperation should be blocked for did:web users" 219 ); 220 let body: Value = res.json().await.expect("Response was not JSON"); 221 assert!( 222 body["message"].as_str().unwrap_or("").contains("did:plc"), 223 "Error should mention did:plc: {:?}", 224 body 225 ); 226 let res = client 227 .post(format!( 228 "{}/xrpc/com.atproto.identity.submitPlcOperation", 229 base_url().await 230 )) 231 .bearer_auth(&jwt) 232 .json(&json!({ 233 "operation": {} 234 })) 235 .send() 236 .await 237 .expect("Failed to send request"); 238 assert_eq!( 239 res.status(), 240 StatusCode::BAD_REQUEST, 241 "submitPlcOperation should be blocked for did:web users" 242 ); 243} 244 245#[tokio::test] 246async fn test_get_recommended_did_credentials_no_rotation_keys_for_did_web() { 247 let client = client(); 248 let handle = format!("cr{}", &uuid::Uuid::new_v4().simple().to_string()[..12]); 249 let payload = json!({ 250 "handle": handle, 251 "email": format!("{}@example.com", handle), 252 "password": "Testpass123!", 253 "didType": "web" 254 }); 255 let res = client 256 .post(format!( 257 "{}/xrpc/com.atproto.server.createAccount", 258 base_url().await 259 )) 260 .json(&payload) 261 .send() 262 .await 263 .expect("Failed to send request"); 264 assert_eq!(res.status(), StatusCode::OK); 265 let body: Value = res.json().await.expect("Response was not JSON"); 266 let did = body["did"].as_str().expect("No DID").to_string(); 267 let jwt = verify_new_account(&client, &did).await; 268 let res = client 269 .get(format!( 270 "{}/xrpc/com.atproto.identity.getRecommendedDidCredentials", 271 base_url().await 272 )) 273 .bearer_auth(&jwt) 274 .send() 275 .await 276 .expect("Failed to send request"); 277 assert_eq!(res.status(), StatusCode::OK); 278 let body: Value = res.json().await.expect("Response was not JSON"); 279 let rotation_keys = body["rotationKeys"] 280 .as_array() 281 .expect("rotationKeys should be an array"); 282 assert!( 283 rotation_keys.is_empty(), 284 "did:web should have no rotation keys, got: {:?}", 285 rotation_keys 286 ); 287 assert!( 288 body["verificationMethods"].is_object(), 289 "verificationMethods should be present" 290 ); 291 assert!(body["services"].is_object(), "services should be present"); 292} 293 294#[tokio::test] 295async fn test_did_plc_still_works_with_did_type_param() { 296 let client = client(); 297 let handle = format!("pt{}", &uuid::Uuid::new_v4().simple().to_string()[..12]); 298 let payload = json!({ 299 "handle": handle, 300 "email": format!("{}@example.com", handle), 301 "password": "Testpass123!", 302 "didType": "plc" 303 }); 304 let res = client 305 .post(format!( 306 "{}/xrpc/com.atproto.server.createAccount", 307 base_url().await 308 )) 309 .json(&payload) 310 .send() 311 .await 312 .expect("Failed to send request"); 313 assert_eq!(res.status(), StatusCode::OK); 314 let body: Value = res.json().await.expect("Response was not JSON"); 315 let did = body["did"].as_str().expect("No DID").to_string(); 316 assert!( 317 did.starts_with("did:plc:"), 318 "DID with didType=plc should be did:plc:, got: {}", 319 did 320 ); 321} 322 323#[tokio::test] 324async fn test_external_did_web_requires_did_field() { 325 let client = client(); 326 let handle = format!("nd{}", &uuid::Uuid::new_v4().simple().to_string()[..12]); 327 let payload = json!({ 328 "handle": handle, 329 "email": format!("{}@example.com", handle), 330 "password": "Testpass123!", 331 "didType": "web-external" 332 }); 333 let res = client 334 .post(format!( 335 "{}/xrpc/com.atproto.server.createAccount", 336 base_url().await 337 )) 338 .json(&payload) 339 .send() 340 .await 341 .expect("Failed to send request"); 342 assert_eq!( 343 res.status(), 344 StatusCode::BAD_REQUEST, 345 "web-external without did should fail" 346 ); 347 let body: Value = res.json().await.expect("Response was not JSON"); 348 assert!( 349 body["message"].as_str().unwrap_or("").contains("did"), 350 "Error should mention did field is required: {:?}", 351 body 352 ); 353} 354 355fn signing_key_to_multibase(signing_key: &SigningKey) -> String { 356 let verifying_key = signing_key.verifying_key(); 357 let compressed = verifying_key.to_sec1_bytes(); 358 let mut multicodec = vec![0xe7, 0x01]; 359 multicodec.extend_from_slice(&compressed); 360 multibase::encode(multibase::Base::Base58Btc, &multicodec) 361} 362 363fn create_service_jwt(signing_key: &SigningKey, did: &str, aud: &str) -> String { 364 let header = json!({"alg": "ES256K", "typ": "jwt"}); 365 let now = chrono::Utc::now().timestamp() as usize; 366 let claims = json!({ 367 "iss": did, 368 "sub": did, 369 "aud": aud, 370 "exp": now + 300, 371 "iat": now, 372 "lxm": "com.atproto.server.createAccount", 373 "jti": uuid::Uuid::new_v4().to_string() 374 }); 375 let header_b64 = URL_SAFE_NO_PAD.encode(header.to_string()); 376 let claims_b64 = URL_SAFE_NO_PAD.encode(claims.to_string()); 377 let message = format!("{}.{}", header_b64, claims_b64); 378 let signature: k256::ecdsa::Signature = signing_key.sign(message.as_bytes()); 379 let sig_b64 = URL_SAFE_NO_PAD.encode(signature.to_bytes()); 380 format!("{}.{}", message, sig_b64) 381} 382 383#[tokio::test] 384async fn test_did_web_byod_flow() { 385 let client = client(); 386 let mock_server = MockServer::start().await; 387 let mock_uri = mock_server.uri(); 388 let mock_addr = mock_uri.trim_start_matches("http://"); 389 let unique_id = uuid::Uuid::new_v4().to_string().replace("-", ""); 390 let did = format!( 391 "did:web:{}:byod:{}", 392 mock_addr.replace(":", "%3A"), 393 unique_id 394 ); 395 let handle = format!("by{}", &uuid::Uuid::new_v4().simple().to_string()[..12]); 396 let pds_endpoint = base_url().await.replace("http://", "https://"); 397 let pds_did = format!("did:web:{}", pds_endpoint.trim_start_matches("https://")); 398 399 let temp_key = SigningKey::random(&mut rand::thread_rng()); 400 let public_key_multibase = signing_key_to_multibase(&temp_key); 401 402 let did_doc = json!({ 403 "@context": ["https://www.w3.org/ns/did/v1"], 404 "id": did, 405 "verificationMethod": [{ 406 "id": format!("{}#atproto", did), 407 "type": "Multikey", 408 "controller": did, 409 "publicKeyMultibase": public_key_multibase 410 }], 411 "service": [{ 412 "id": "#atproto_pds", 413 "type": "AtprotoPersonalDataServer", 414 "serviceEndpoint": pds_endpoint 415 }] 416 }); 417 Mock::given(method("GET")) 418 .and(path(format!("/byod/{}/did.json", unique_id))) 419 .respond_with(ResponseTemplate::new(200).set_body_json(&did_doc)) 420 .mount(&mock_server) 421 .await; 422 423 let service_jwt = create_service_jwt(&temp_key, &did, &pds_did); 424 let payload = json!({ 425 "handle": handle, 426 "email": format!("{}@example.com", handle), 427 "password": "Testpass123!", 428 "did": did 429 }); 430 let res = client 431 .post(format!( 432 "{}/xrpc/com.atproto.server.createAccount", 433 base_url().await 434 )) 435 .header("Authorization", format!("Bearer {}", service_jwt)) 436 .json(&payload) 437 .send() 438 .await 439 .expect("Failed to send request"); 440 if res.status() != StatusCode::OK { 441 let body: Value = res.json().await.unwrap_or(json!({"error": "parse failed"})); 442 panic!("createAccount BYOD failed: {:?}", body); 443 } 444 let body: Value = res.json().await.expect("Response was not JSON"); 445 let returned_did = body["did"].as_str().expect("No DID in response"); 446 assert_eq!(returned_did, did, "Returned DID should match requested DID"); 447 assert_eq!( 448 body["verificationRequired"], true, 449 "BYOD accounts should require verification" 450 ); 451 452 let access_jwt = common::verify_new_account(&client, returned_did).await; 453 454 let res = client 455 .get(format!( 456 "{}/xrpc/com.atproto.server.checkAccountStatus", 457 base_url().await 458 )) 459 .bearer_auth(&access_jwt) 460 .send() 461 .await 462 .expect("Failed to check account status"); 463 assert_eq!(res.status(), StatusCode::OK); 464 let status: Value = res.json().await.expect("Response was not JSON"); 465 assert_eq!( 466 status["activated"], false, 467 "BYOD account should be deactivated initially" 468 ); 469 470 let res = client 471 .get(format!( 472 "{}/xrpc/com.atproto.identity.getRecommendedDidCredentials", 473 base_url().await 474 )) 475 .bearer_auth(&access_jwt) 476 .send() 477 .await 478 .expect("Failed to get recommended credentials"); 479 assert_eq!(res.status(), StatusCode::OK); 480 let creds: Value = res.json().await.expect("Response was not JSON"); 481 assert!( 482 creds["verificationMethods"]["atproto"].is_string(), 483 "Should return PDS signing key" 484 ); 485 let pds_signing_key = creds["verificationMethods"]["atproto"] 486 .as_str() 487 .expect("No atproto verification method"); 488 assert!( 489 pds_signing_key.starts_with("did:key:"), 490 "PDS signing key should be did:key format" 491 ); 492 493 let res = client 494 .post(format!( 495 "{}/xrpc/com.atproto.server.activateAccount", 496 base_url().await 497 )) 498 .bearer_auth(&access_jwt) 499 .send() 500 .await 501 .expect("Failed to activate account"); 502 assert_eq!( 503 res.status(), 504 StatusCode::OK, 505 "activateAccount should succeed" 506 ); 507 508 let res = client 509 .get(format!( 510 "{}/xrpc/com.atproto.server.checkAccountStatus", 511 base_url().await 512 )) 513 .bearer_auth(&access_jwt) 514 .send() 515 .await 516 .expect("Failed to check account status"); 517 assert_eq!(res.status(), StatusCode::OK); 518 let status: Value = res.json().await.expect("Response was not JSON"); 519 assert_eq!( 520 status["activated"], true, 521 "Account should be activated after activateAccount call" 522 ); 523 524 let res = client 525 .post(format!( 526 "{}/xrpc/com.atproto.repo.createRecord", 527 base_url().await 528 )) 529 .bearer_auth(&access_jwt) 530 .json(&json!({ 531 "repo": did, 532 "collection": "app.bsky.feed.post", 533 "record": { 534 "$type": "app.bsky.feed.post", 535 "text": "Hello from BYOD did:web!", 536 "createdAt": chrono::Utc::now().to_rfc3339() 537 } 538 })) 539 .send() 540 .await 541 .expect("Failed to create post"); 542 assert_eq!( 543 res.status(), 544 StatusCode::OK, 545 "Activated BYOD account should be able to create records" 546 ); 547} 548 549#[tokio::test] 550async fn test_deactivate_with_migrating_to() { 551 let client = client(); 552 let base = base_url().await; 553 let handle = format!("mig{}", &uuid::Uuid::new_v4().simple().to_string()[..12]); 554 let payload = json!({ 555 "handle": handle, 556 "email": format!("{}@example.com", handle), 557 "password": "Testpass123!", 558 "didType": "web" 559 }); 560 let res = client 561 .post(format!("{}/xrpc/com.atproto.server.createAccount", base)) 562 .json(&payload) 563 .send() 564 .await 565 .expect("Failed to send request"); 566 assert_eq!(res.status(), StatusCode::OK); 567 let body: Value = res.json().await.expect("Response was not JSON"); 568 let did = body["did"].as_str().expect("No DID").to_string(); 569 let jwt = verify_new_account(&client, &did).await; 570 let target_pds = "https://pds2.example.com"; 571 let res = client 572 .post(format!( 573 "{}/xrpc/com.atproto.server.deactivateAccount", 574 base 575 )) 576 .bearer_auth(&jwt) 577 .json(&json!({ "migratingTo": target_pds })) 578 .send() 579 .await 580 .expect("Failed to send request"); 581 assert_eq!(res.status(), StatusCode::OK); 582 let pool = get_test_db_pool().await; 583 let row = sqlx::query!( 584 r#"SELECT migrated_to_pds, deactivated_at FROM users WHERE did = $1"#, 585 &did 586 ) 587 .fetch_one(pool) 588 .await 589 .expect("Failed to query user"); 590 assert_eq!( 591 row.migrated_to_pds.as_deref(), 592 Some(target_pds), 593 "migrated_to_pds should be set to target PDS" 594 ); 595 assert!( 596 row.deactivated_at.is_some(), 597 "deactivated_at should be set for migrated account" 598 ); 599} 600 601#[tokio::test] 602async fn test_migrated_account_blocked_from_repo_ops() { 603 let client = client(); 604 let base = base_url().await; 605 let handle = format!("blk{}", &uuid::Uuid::new_v4().simple().to_string()[..12]); 606 let payload = json!({ 607 "handle": handle, 608 "email": format!("{}@example.com", handle), 609 "password": "Testpass123!", 610 "didType": "web" 611 }); 612 let res = client 613 .post(format!("{}/xrpc/com.atproto.server.createAccount", base)) 614 .json(&payload) 615 .send() 616 .await 617 .expect("Failed to send request"); 618 assert_eq!(res.status(), StatusCode::OK); 619 let body: Value = res.json().await.expect("Response was not JSON"); 620 let did = body["did"].as_str().expect("No DID").to_string(); 621 let jwt = verify_new_account(&client, &did).await; 622 let res = client 623 .post(format!("{}/xrpc/com.atproto.repo.createRecord", base)) 624 .bearer_auth(&jwt) 625 .json(&json!({ 626 "repo": did, 627 "collection": "app.bsky.feed.post", 628 "record": { 629 "$type": "app.bsky.feed.post", 630 "text": "Pre-migration post", 631 "createdAt": chrono::Utc::now().to_rfc3339() 632 } 633 })) 634 .send() 635 .await 636 .expect("Failed to send request"); 637 assert_eq!(res.status(), StatusCode::OK); 638 let res = client 639 .post(format!( 640 "{}/xrpc/com.atproto.server.deactivateAccount", 641 base 642 )) 643 .bearer_auth(&jwt) 644 .json(&json!({ "migratingTo": "https://pds2.example.com" })) 645 .send() 646 .await 647 .expect("Failed to send request"); 648 assert_eq!(res.status(), StatusCode::OK); 649 let res = client 650 .post(format!("{}/xrpc/com.atproto.repo.createRecord", base)) 651 .bearer_auth(&jwt) 652 .json(&json!({ 653 "repo": did, 654 "collection": "app.bsky.feed.post", 655 "record": { 656 "$type": "app.bsky.feed.post", 657 "text": "Post-migration post - should fail", 658 "createdAt": chrono::Utc::now().to_rfc3339() 659 } 660 })) 661 .send() 662 .await 663 .expect("Failed to send request"); 664 assert!( 665 res.status().is_client_error(), 666 "createRecord should fail for migrated account: {}", 667 res.status() 668 ); 669 let res = client 670 .post(format!("{}/xrpc/com.atproto.repo.putRecord", base)) 671 .bearer_auth(&jwt) 672 .json(&json!({ 673 "repo": did, 674 "collection": "app.bsky.actor.profile", 675 "rkey": "self", 676 "record": { 677 "$type": "app.bsky.actor.profile", 678 "displayName": "Test" 679 } 680 })) 681 .send() 682 .await 683 .expect("Failed to send request"); 684 assert!( 685 res.status().is_client_error(), 686 "putRecord should fail for migrated account: {}", 687 res.status() 688 ); 689 let res = client 690 .post(format!("{}/xrpc/com.atproto.repo.deleteRecord", base)) 691 .bearer_auth(&jwt) 692 .json(&json!({ 693 "repo": did, 694 "collection": "app.bsky.feed.post", 695 "rkey": "test123" 696 })) 697 .send() 698 .await 699 .expect("Failed to send request"); 700 assert!( 701 res.status().is_client_error(), 702 "deleteRecord should fail for migrated account: {}", 703 res.status() 704 ); 705 let res = client 706 .post(format!("{}/xrpc/com.atproto.repo.applyWrites", base)) 707 .bearer_auth(&jwt) 708 .json(&json!({ 709 "repo": did, 710 "writes": [{ 711 "$type": "com.atproto.repo.applyWrites#create", 712 "collection": "app.bsky.feed.post", 713 "value": { 714 "$type": "app.bsky.feed.post", 715 "text": "Batch post", 716 "createdAt": chrono::Utc::now().to_rfc3339() 717 } 718 }] 719 })) 720 .send() 721 .await 722 .expect("Failed to send request"); 723 assert!( 724 res.status().is_client_error(), 725 "applyWrites should fail for migrated account: {}", 726 res.status() 727 ); 728 let res = client 729 .post(format!("{}/xrpc/com.atproto.repo.uploadBlob", base)) 730 .bearer_auth(&jwt) 731 .header("Content-Type", "text/plain") 732 .body("test blob content") 733 .send() 734 .await 735 .expect("Failed to send request"); 736 assert!( 737 res.status().is_client_error(), 738 "uploadBlob should fail for migrated account: {}", 739 res.status() 740 ); 741} 742 743#[tokio::test] 744async fn test_migrated_session_status() { 745 let client = client(); 746 let base = base_url().await; 747 let handle = format!("ses{}", &uuid::Uuid::new_v4().simple().to_string()[..12]); 748 let payload = json!({ 749 "handle": handle, 750 "email": format!("{}@example.com", handle), 751 "password": "Testpass123!", 752 "didType": "web" 753 }); 754 let res = client 755 .post(format!("{}/xrpc/com.atproto.server.createAccount", base)) 756 .json(&payload) 757 .send() 758 .await 759 .expect("Failed to send request"); 760 assert_eq!(res.status(), StatusCode::OK); 761 let body: Value = res.json().await.expect("Response was not JSON"); 762 let did = body["did"].as_str().expect("No DID").to_string(); 763 let jwt = verify_new_account(&client, &did).await; 764 let res = client 765 .get(format!("{}/xrpc/com.atproto.server.getSession", base)) 766 .bearer_auth(&jwt) 767 .send() 768 .await 769 .expect("Failed to send request"); 770 assert_eq!(res.status(), StatusCode::OK); 771 let body: Value = res.json().await.expect("Response was not JSON"); 772 assert_eq!(body["active"], true); 773 assert!( 774 body["status"].is_null() || body["status"] == "active", 775 "Status should be null or 'active' for normal accounts" 776 ); 777 let target_pds = "https://pds3.example.com"; 778 let res = client 779 .post(format!( 780 "{}/xrpc/com.atproto.server.deactivateAccount", 781 base 782 )) 783 .bearer_auth(&jwt) 784 .json(&json!({ "migratingTo": target_pds })) 785 .send() 786 .await 787 .expect("Failed to send request"); 788 assert_eq!(res.status(), StatusCode::OK); 789 let res = client 790 .get(format!("{}/xrpc/com.atproto.server.getSession", base)) 791 .bearer_auth(&jwt) 792 .send() 793 .await 794 .expect("Failed to send request"); 795 assert_eq!(res.status(), StatusCode::OK); 796 let body: Value = res.json().await.expect("Response was not JSON"); 797 assert_eq!( 798 body["active"], false, 799 "Migrated account should not be active" 800 ); 801 assert_eq!( 802 body["status"], "migrated", 803 "Status should be 'migrated' after migration" 804 ); 805 assert_eq!( 806 body["migratedToPds"], target_pds, 807 "migratedToPds should be set to target PDS" 808 ); 809} 810 811#[tokio::test] 812async fn test_migrating_to_ignored_for_did_plc() { 813 let client = client(); 814 let base = base_url().await; 815 let handle = format!("plc{}", &uuid::Uuid::new_v4().simple().to_string()[..12]); 816 let payload = json!({ 817 "handle": handle, 818 "email": format!("{}@example.com", handle), 819 "password": "Testpass123!", 820 "didType": "plc" 821 }); 822 let res = client 823 .post(format!("{}/xrpc/com.atproto.server.createAccount", base)) 824 .json(&payload) 825 .send() 826 .await 827 .expect("Failed to send request"); 828 assert_eq!(res.status(), StatusCode::OK); 829 let body: Value = res.json().await.expect("Response was not JSON"); 830 let did = body["did"].as_str().expect("No DID").to_string(); 831 assert!(did.starts_with("did:plc:"), "Should be did:plc account"); 832 let jwt = verify_new_account(&client, &did).await; 833 let res = client 834 .post(format!( 835 "{}/xrpc/com.atproto.server.deactivateAccount", 836 base 837 )) 838 .bearer_auth(&jwt) 839 .json(&json!({ "migratingTo": "https://pds2.example.com" })) 840 .send() 841 .await 842 .expect("Failed to send request"); 843 assert_eq!(res.status(), StatusCode::OK); 844 let pool = get_test_db_pool().await; 845 let row = sqlx::query!( 846 r#"SELECT migrated_to_pds, deactivated_at FROM users WHERE did = $1"#, 847 &did 848 ) 849 .fetch_one(pool) 850 .await 851 .expect("Failed to query user"); 852 assert!( 853 row.migrated_to_pds.is_none(), 854 "migrated_to_pds should NOT be set for did:plc accounts" 855 ); 856 assert!( 857 row.deactivated_at.is_some(), 858 "deactivated_at should still be set" 859 ); 860 let res = client 861 .get(format!("{}/xrpc/com.atproto.server.getSession", base)) 862 .bearer_auth(&jwt) 863 .send() 864 .await 865 .expect("Failed to send request"); 866 assert_eq!(res.status(), StatusCode::OK); 867 let body: Value = res.json().await.expect("Response was not JSON"); 868 assert_eq!(body["active"], false); 869 assert_eq!( 870 body["status"], "deactivated", 871 "Status should be 'deactivated' not 'migrated' for did:plc" 872 ); 873 assert!( 874 body["migratedToPds"].is_null(), 875 "migratedToPds should not be set for did:plc accounts" 876 ); 877}