this repo has no description
1mod common; 2use reqwest::StatusCode; 3use serde_json::{Value, json}; 4use sqlx::PgPool; 5 6async fn get_pool() -> PgPool { 7 let conn_str = common::get_db_connection_string().await; 8 sqlx::postgres::PgPoolOptions::new() 9 .max_connections(5) 10 .connect(&conn_str) 11 .await 12 .expect("Failed to connect to test database") 13} 14 15async fn get_email_update_token(pool: &PgPool, did: &str) -> String { 16 let body_text: String = sqlx::query_scalar!( 17 "SELECT body FROM comms_queue WHERE user_id = (SELECT id FROM users WHERE did = $1) AND comms_type = 'email_update' ORDER BY created_at DESC LIMIT 1", 18 did 19 ) 20 .fetch_one(pool) 21 .await 22 .expect("Verification not found"); 23 24 body_text 25 .lines() 26 .skip_while(|line| !line.contains("verification code")) 27 .nth(1) 28 .map(|line| line.trim().to_string()) 29 .filter(|line| !line.is_empty() && line.contains('-')) 30 .unwrap_or_else(|| { 31 body_text 32 .lines() 33 .find(|line| line.trim().starts_with("MX") && line.contains('-')) 34 .map(|s| s.trim().to_string()) 35 .unwrap_or_default() 36 }) 37} 38 39async fn create_verified_account( 40 client: &reqwest::Client, 41 base_url: &str, 42 handle: &str, 43 email: &str, 44) -> (String, String) { 45 let res = client 46 .post(format!( 47 "{}/xrpc/com.atproto.server.createAccount", 48 base_url 49 )) 50 .json(&json!({ 51 "handle": handle, 52 "email": email, 53 "password": "Testpass123!" 54 })) 55 .send() 56 .await 57 .expect("Failed to create account"); 58 assert_eq!(res.status(), StatusCode::OK); 59 let body: Value = res.json().await.expect("Invalid JSON"); 60 let did = body["did"].as_str().expect("No did").to_string(); 61 let jwt = common::verify_new_account(client, &did).await; 62 (jwt, did) 63} 64 65#[tokio::test] 66async fn test_request_email_update_returns_token_required() { 67 let client = common::client(); 68 let base_url = common::base_url().await; 69 let handle = format!("emailreq-{}", uuid::Uuid::new_v4()); 70 let email = format!("{}@example.com", handle); 71 let (access_jwt, _) = create_verified_account(&client, &base_url, &handle, &email).await; 72 73 let res = client 74 .post(format!( 75 "{}/xrpc/com.atproto.server.requestEmailUpdate", 76 base_url 77 )) 78 .bearer_auth(&access_jwt) 79 .send() 80 .await 81 .expect("Failed to request email update"); 82 assert_eq!(res.status(), StatusCode::OK); 83 let body: Value = res.json().await.expect("Invalid JSON"); 84 assert_eq!(body["tokenRequired"], true); 85} 86 87#[tokio::test] 88async fn test_update_email_flow_success() { 89 let client = common::client(); 90 let base_url = common::base_url().await; 91 let pool = get_pool().await; 92 let handle = format!("emailup-{}", uuid::Uuid::new_v4()); 93 let email = format!("{}@example.com", handle); 94 let (access_jwt, did) = create_verified_account(&client, &base_url, &handle, &email).await; 95 let new_email = format!("new_{}@example.com", handle); 96 97 let res = client 98 .post(format!( 99 "{}/xrpc/com.atproto.server.requestEmailUpdate", 100 base_url 101 )) 102 .bearer_auth(&access_jwt) 103 .send() 104 .await 105 .expect("Failed to request email update"); 106 assert_eq!(res.status(), StatusCode::OK); 107 let body: Value = res.json().await.expect("Invalid JSON"); 108 assert_eq!(body["tokenRequired"], true); 109 110 let code = get_email_update_token(&pool, &did).await; 111 112 let res = client 113 .post(format!("{}/xrpc/com.atproto.server.updateEmail", base_url)) 114 .bearer_auth(&access_jwt) 115 .json(&json!({ 116 "email": new_email, 117 "token": code 118 })) 119 .send() 120 .await 121 .expect("Failed to update email"); 122 assert_eq!(res.status(), StatusCode::OK); 123 124 let user_email: Option<String> = sqlx::query_scalar!("SELECT email FROM users WHERE did = $1", did) 125 .fetch_one(&pool) 126 .await 127 .expect("User not found"); 128 assert_eq!(user_email, Some(new_email)); 129} 130 131#[tokio::test] 132async fn test_update_email_requires_token_when_verified() { 133 let client = common::client(); 134 let base_url = common::base_url().await; 135 let handle = format!("emailup-direct-{}", uuid::Uuid::new_v4()); 136 let email = format!("{}@example.com", handle); 137 let (access_jwt, _) = create_verified_account(&client, &base_url, &handle, &email).await; 138 let new_email = format!("direct_{}@example.com", handle); 139 140 let res = client 141 .post(format!("{}/xrpc/com.atproto.server.updateEmail", base_url)) 142 .bearer_auth(&access_jwt) 143 .json(&json!({ "email": new_email })) 144 .send() 145 .await 146 .expect("Failed to update email"); 147 assert_eq!(res.status(), StatusCode::BAD_REQUEST); 148 let body: Value = res.json().await.expect("Invalid JSON"); 149 assert_eq!(body["error"], "TokenRequired"); 150} 151 152#[tokio::test] 153async fn test_update_email_same_email_noop() { 154 let client = common::client(); 155 let base_url = common::base_url().await; 156 let handle = format!("emailup-same-{}", uuid::Uuid::new_v4()); 157 let email = format!("{}@example.com", handle); 158 let (access_jwt, _) = create_verified_account(&client, &base_url, &handle, &email).await; 159 160 let res = client 161 .post(format!("{}/xrpc/com.atproto.server.updateEmail", base_url)) 162 .bearer_auth(&access_jwt) 163 .json(&json!({ "email": email })) 164 .send() 165 .await 166 .expect("Failed to update email"); 167 assert_eq!( 168 res.status(), 169 StatusCode::OK, 170 "Updating to same email should succeed as no-op" 171 ); 172} 173 174#[tokio::test] 175async fn test_update_email_invalid_token() { 176 let client = common::client(); 177 let base_url = common::base_url().await; 178 let handle = format!("emailup-badtok-{}", uuid::Uuid::new_v4()); 179 let email = format!("{}@example.com", handle); 180 let (access_jwt, _) = create_verified_account(&client, &base_url, &handle, &email).await; 181 let new_email = format!("badtok_{}@example.com", handle); 182 183 let res = client 184 .post(format!( 185 "{}/xrpc/com.atproto.server.requestEmailUpdate", 186 base_url 187 )) 188 .bearer_auth(&access_jwt) 189 .send() 190 .await 191 .expect("Failed to request email update"); 192 assert_eq!(res.status(), StatusCode::OK); 193 194 let res = client 195 .post(format!("{}/xrpc/com.atproto.server.updateEmail", base_url)) 196 .bearer_auth(&access_jwt) 197 .json(&json!({ 198 "email": new_email, 199 "token": "wrong-token-12345" 200 })) 201 .send() 202 .await 203 .expect("Failed to attempt email update"); 204 assert_eq!(res.status(), StatusCode::BAD_REQUEST); 205 let body: Value = res.json().await.expect("Invalid JSON"); 206 assert_eq!(body["error"], "InvalidToken"); 207} 208 209#[tokio::test] 210async fn test_update_email_no_auth() { 211 let client = common::client(); 212 let base_url = common::base_url().await; 213 214 let res = client 215 .post(format!("{}/xrpc/com.atproto.server.updateEmail", base_url)) 216 .json(&json!({ "email": "test@example.com" })) 217 .send() 218 .await 219 .expect("Failed to send request"); 220 assert_eq!(res.status(), StatusCode::UNAUTHORIZED); 221 let body: Value = res.json().await.expect("Invalid JSON"); 222 assert_eq!(body["error"], "AuthenticationRequired"); 223} 224 225#[tokio::test] 226async fn test_update_email_invalid_format() { 227 let client = common::client(); 228 let base_url = common::base_url().await; 229 let handle = format!("emailup-fmt-{}", uuid::Uuid::new_v4()); 230 let email = format!("{}@example.com", handle); 231 let (access_jwt, _) = create_verified_account(&client, &base_url, &handle, &email).await; 232 233 let res = client 234 .post(format!("{}/xrpc/com.atproto.server.updateEmail", base_url)) 235 .bearer_auth(&access_jwt) 236 .json(&json!({ "email": "not-an-email" })) 237 .send() 238 .await 239 .expect("Failed to send request"); 240 assert_eq!(res.status(), StatusCode::BAD_REQUEST); 241} 242 243#[tokio::test] 244async fn test_confirm_email_confirms_existing_email() { 245 let client = common::client(); 246 let base_url = common::base_url().await; 247 let pool = get_pool().await; 248 let handle = format!("emailconfirm-{}", uuid::Uuid::new_v4()); 249 let email = format!("{}@example.com", handle); 250 251 let res = client 252 .post(format!( 253 "{}/xrpc/com.atproto.server.createAccount", 254 base_url 255 )) 256 .json(&json!({ 257 "handle": handle, 258 "email": email, 259 "password": "Testpass123!" 260 })) 261 .send() 262 .await 263 .expect("Failed to create account"); 264 assert_eq!(res.status(), StatusCode::OK); 265 let body: Value = res.json().await.expect("Invalid JSON"); 266 let did = body["did"].as_str().expect("No did").to_string(); 267 let access_jwt = body["accessJwt"].as_str().expect("No accessJwt").to_string(); 268 269 let body_text: String = sqlx::query_scalar!( 270 "SELECT body FROM comms_queue WHERE user_id = (SELECT id FROM users WHERE did = $1) AND comms_type = 'email_verification' ORDER BY created_at DESC LIMIT 1", 271 did 272 ) 273 .fetch_one(&pool) 274 .await 275 .expect("Verification email not found"); 276 277 let code = body_text 278 .lines() 279 .find(|line| line.trim().starts_with("MX") && line.contains('-')) 280 .map(|s| s.trim().to_string()) 281 .unwrap_or_default(); 282 283 let res = client 284 .post(format!("{}/xrpc/com.atproto.server.confirmEmail", base_url)) 285 .bearer_auth(&access_jwt) 286 .json(&json!({ 287 "email": email, 288 "token": code 289 })) 290 .send() 291 .await 292 .expect("Failed to confirm email"); 293 assert_eq!(res.status(), StatusCode::OK); 294 295 let verified: bool = sqlx::query_scalar!( 296 "SELECT email_verified FROM users WHERE did = $1", 297 did 298 ) 299 .fetch_one(&pool) 300 .await 301 .expect("User not found"); 302 assert!(verified); 303} 304 305#[tokio::test] 306async fn test_confirm_email_rejects_wrong_email() { 307 let client = common::client(); 308 let base_url = common::base_url().await; 309 let pool = get_pool().await; 310 let handle = format!("emailconf-wrong-{}", uuid::Uuid::new_v4()); 311 let email = format!("{}@example.com", handle); 312 313 let res = client 314 .post(format!( 315 "{}/xrpc/com.atproto.server.createAccount", 316 base_url 317 )) 318 .json(&json!({ 319 "handle": handle, 320 "email": email, 321 "password": "Testpass123!" 322 })) 323 .send() 324 .await 325 .expect("Failed to create account"); 326 assert_eq!(res.status(), StatusCode::OK); 327 let body: Value = res.json().await.expect("Invalid JSON"); 328 let did = body["did"].as_str().expect("No did").to_string(); 329 let access_jwt = body["accessJwt"].as_str().expect("No accessJwt").to_string(); 330 331 let body_text: String = sqlx::query_scalar!( 332 "SELECT body FROM comms_queue WHERE user_id = (SELECT id FROM users WHERE did = $1) AND comms_type = 'email_verification' ORDER BY created_at DESC LIMIT 1", 333 did 334 ) 335 .fetch_one(&pool) 336 .await 337 .expect("Verification email not found"); 338 339 let code = body_text 340 .lines() 341 .find(|line| line.trim().starts_with("MX") && line.contains('-')) 342 .map(|s| s.trim().to_string()) 343 .unwrap_or_default(); 344 345 let res = client 346 .post(format!("{}/xrpc/com.atproto.server.confirmEmail", base_url)) 347 .bearer_auth(&access_jwt) 348 .json(&json!({ 349 "email": "different@example.com", 350 "token": code 351 })) 352 .send() 353 .await 354 .expect("Failed to confirm email"); 355 assert_eq!(res.status(), StatusCode::BAD_REQUEST); 356 let body: Value = res.json().await.expect("Invalid JSON"); 357 assert_eq!(body["error"], "InvalidEmail"); 358} 359 360#[tokio::test] 361async fn test_confirm_email_invalid_token() { 362 let client = common::client(); 363 let base_url = common::base_url().await; 364 let handle = format!("emailconf-inv-{}", uuid::Uuid::new_v4()); 365 let email = format!("{}@example.com", handle); 366 367 let res = client 368 .post(format!( 369 "{}/xrpc/com.atproto.server.createAccount", 370 base_url 371 )) 372 .json(&json!({ 373 "handle": handle, 374 "email": email, 375 "password": "Testpass123!" 376 })) 377 .send() 378 .await 379 .expect("Failed to create account"); 380 assert_eq!(res.status(), StatusCode::OK); 381 let body: Value = res.json().await.expect("Invalid JSON"); 382 let access_jwt = body["accessJwt"].as_str().expect("No accessJwt").to_string(); 383 384 let res = client 385 .post(format!("{}/xrpc/com.atproto.server.confirmEmail", base_url)) 386 .bearer_auth(&access_jwt) 387 .json(&json!({ 388 "email": email, 389 "token": "wrong-token" 390 })) 391 .send() 392 .await 393 .expect("Failed to confirm email"); 394 assert_eq!(res.status(), StatusCode::BAD_REQUEST); 395 let body: Value = res.json().await.expect("Invalid JSON"); 396 assert_eq!(body["error"], "InvalidToken"); 397} 398 399#[tokio::test] 400async fn test_unverified_account_can_update_email_without_token() { 401 let client = common::client(); 402 let base_url = common::base_url().await; 403 let pool = get_pool().await; 404 let handle = format!("emailup-unverified-{}", uuid::Uuid::new_v4()); 405 let email = format!("{}@example.com", handle); 406 407 let res = client 408 .post(format!( 409 "{}/xrpc/com.atproto.server.createAccount", 410 base_url 411 )) 412 .json(&json!({ 413 "handle": handle, 414 "email": email, 415 "password": "Testpass123!" 416 })) 417 .send() 418 .await 419 .expect("Failed to create account"); 420 assert_eq!(res.status(), StatusCode::OK); 421 let body: Value = res.json().await.expect("Invalid JSON"); 422 let did = body["did"].as_str().expect("No did").to_string(); 423 let access_jwt = body["accessJwt"].as_str().expect("No accessJwt").to_string(); 424 425 let res = client 426 .post(format!( 427 "{}/xrpc/com.atproto.server.requestEmailUpdate", 428 base_url 429 )) 430 .bearer_auth(&access_jwt) 431 .send() 432 .await 433 .expect("Failed to request email update"); 434 assert_eq!(res.status(), StatusCode::OK); 435 let body: Value = res.json().await.expect("Invalid JSON"); 436 assert_eq!( 437 body["tokenRequired"], false, 438 "Unverified account should not require token" 439 ); 440 441 let new_email = format!("new_{}@example.com", handle); 442 let res = client 443 .post(format!("{}/xrpc/com.atproto.server.updateEmail", base_url)) 444 .bearer_auth(&access_jwt) 445 .json(&json!({ "email": new_email })) 446 .send() 447 .await 448 .expect("Failed to update email"); 449 assert_eq!( 450 res.status(), 451 StatusCode::OK, 452 "Unverified account should be able to update email without token" 453 ); 454 455 let user_email: Option<String> = 456 sqlx::query_scalar!("SELECT email FROM users WHERE did = $1", did) 457 .fetch_one(&pool) 458 .await 459 .expect("User not found"); 460 assert_eq!(user_email, Some(new_email)); 461} 462 463#[tokio::test] 464async fn test_update_email_taken_by_another_user() { 465 let client = common::client(); 466 let base_url = common::base_url().await; 467 let pool = get_pool().await; 468 469 let handle1 = format!("emailup-dup1-{}", uuid::Uuid::new_v4()); 470 let email1 = format!("{}@example.com", handle1); 471 let (_, _) = create_verified_account(&client, &base_url, &handle1, &email1).await; 472 473 let handle2 = format!("emailup-dup2-{}", uuid::Uuid::new_v4()); 474 let email2 = format!("{}@example.com", handle2); 475 let (access_jwt2, did2) = create_verified_account(&client, &base_url, &handle2, &email2).await; 476 477 let res = client 478 .post(format!( 479 "{}/xrpc/com.atproto.server.requestEmailUpdate", 480 base_url 481 )) 482 .bearer_auth(&access_jwt2) 483 .send() 484 .await 485 .expect("Failed to request email update"); 486 assert_eq!(res.status(), StatusCode::OK); 487 488 let code = get_email_update_token(&pool, &did2).await; 489 490 let res = client 491 .post(format!("{}/xrpc/com.atproto.server.updateEmail", base_url)) 492 .bearer_auth(&access_jwt2) 493 .json(&json!({ 494 "email": email1, 495 "token": code 496 })) 497 .send() 498 .await 499 .expect("Failed to update email"); 500 assert_eq!(res.status(), StatusCode::BAD_REQUEST); 501 let body: Value = res.json().await.expect("Invalid JSON"); 502 assert_eq!(body["error"], "InvalidRequest"); 503 assert!(body["message"] 504 .as_str() 505 .unwrap_or("") 506 .contains("already in use")); 507}