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