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