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