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> = sqlx::query_scalar!("SELECT email FROM users WHERE did = $1", did) 116 .fetch_one(pool) 117 .await 118 .expect("User not found"); 119 assert_eq!(user_email, Some(new_email)); 120} 121 122#[tokio::test] 123async fn test_update_email_requires_token_when_verified() { 124 let client = common::client(); 125 let base_url = common::base_url().await; 126 let handle = format!("ed{}", &uuid::Uuid::new_v4().simple().to_string()[..12]); 127 let email = format!("{}@example.com", handle); 128 let (access_jwt, _) = create_verified_account(&client, &base_url, &handle, &email).await; 129 let new_email = format!("direct_{}@example.com", handle); 130 131 let res = client 132 .post(format!("{}/xrpc/com.atproto.server.updateEmail", base_url)) 133 .bearer_auth(&access_jwt) 134 .json(&json!({ "email": new_email })) 135 .send() 136 .await 137 .expect("Failed to update email"); 138 assert_eq!(res.status(), StatusCode::BAD_REQUEST); 139 let body: Value = res.json().await.expect("Invalid JSON"); 140 assert_eq!(body["error"], "TokenRequired"); 141} 142 143#[tokio::test] 144async fn test_update_email_same_email_noop() { 145 let client = common::client(); 146 let base_url = common::base_url().await; 147 let handle = format!("es{}", &uuid::Uuid::new_v4().simple().to_string()[..12]); 148 let email = format!("{}@example.com", handle); 149 let (access_jwt, _) = create_verified_account(&client, &base_url, &handle, &email).await; 150 151 let res = client 152 .post(format!("{}/xrpc/com.atproto.server.updateEmail", base_url)) 153 .bearer_auth(&access_jwt) 154 .json(&json!({ "email": email })) 155 .send() 156 .await 157 .expect("Failed to update email"); 158 assert_eq!( 159 res.status(), 160 StatusCode::OK, 161 "Updating to same email should succeed as no-op" 162 ); 163} 164 165#[tokio::test] 166async fn test_update_email_invalid_token() { 167 let client = common::client(); 168 let base_url = common::base_url().await; 169 let handle = format!("eb{}", &uuid::Uuid::new_v4().simple().to_string()[..12]); 170 let email = format!("{}@example.com", handle); 171 let (access_jwt, _) = create_verified_account(&client, &base_url, &handle, &email).await; 172 let new_email = format!("badtok_{}@example.com", handle); 173 174 let res = client 175 .post(format!( 176 "{}/xrpc/com.atproto.server.requestEmailUpdate", 177 base_url 178 )) 179 .bearer_auth(&access_jwt) 180 .send() 181 .await 182 .expect("Failed to request email update"); 183 assert_eq!(res.status(), StatusCode::OK); 184 185 let res = client 186 .post(format!("{}/xrpc/com.atproto.server.updateEmail", base_url)) 187 .bearer_auth(&access_jwt) 188 .json(&json!({ 189 "email": new_email, 190 "token": "wrong-token-12345" 191 })) 192 .send() 193 .await 194 .expect("Failed to attempt email update"); 195 assert_eq!(res.status(), StatusCode::BAD_REQUEST); 196 let body: Value = res.json().await.expect("Invalid JSON"); 197 assert_eq!(body["error"], "InvalidToken"); 198} 199 200#[tokio::test] 201async fn test_update_email_no_auth() { 202 let client = common::client(); 203 let base_url = common::base_url().await; 204 205 let res = client 206 .post(format!("{}/xrpc/com.atproto.server.updateEmail", base_url)) 207 .json(&json!({ "email": "test@example.com" })) 208 .send() 209 .await 210 .expect("Failed to send request"); 211 assert_eq!(res.status(), StatusCode::UNAUTHORIZED); 212 let body: Value = res.json().await.expect("Invalid JSON"); 213 assert_eq!(body["error"], "AuthenticationRequired"); 214} 215 216#[tokio::test] 217async fn test_update_email_invalid_format() { 218 let client = common::client(); 219 let base_url = common::base_url().await; 220 let handle = format!("ef{}", &uuid::Uuid::new_v4().simple().to_string()[..12]); 221 let email = format!("{}@example.com", handle); 222 let (access_jwt, _) = create_verified_account(&client, &base_url, &handle, &email).await; 223 224 let res = client 225 .post(format!("{}/xrpc/com.atproto.server.updateEmail", base_url)) 226 .bearer_auth(&access_jwt) 227 .json(&json!({ "email": "not-an-email" })) 228 .send() 229 .await 230 .expect("Failed to send request"); 231 assert_eq!(res.status(), StatusCode::BAD_REQUEST); 232} 233 234#[tokio::test] 235async fn test_confirm_email_confirms_existing_email() { 236 let client = common::client(); 237 let base_url = common::base_url().await; 238 let pool = common::get_test_db_pool().await; 239 let handle = format!("ec{}", &uuid::Uuid::new_v4().simple().to_string()[..12]); 240 let email = format!("{}@example.com", handle); 241 242 let res = client 243 .post(format!( 244 "{}/xrpc/com.atproto.server.createAccount", 245 base_url 246 )) 247 .json(&json!({ 248 "handle": handle, 249 "email": email, 250 "password": "Testpass123!" 251 })) 252 .send() 253 .await 254 .expect("Failed to create account"); 255 assert_eq!(res.status(), StatusCode::OK); 256 let body: Value = res.json().await.expect("Invalid JSON"); 257 let did = body["did"].as_str().expect("No did").to_string(); 258 let access_jwt = body["accessJwt"].as_str().expect("No accessJwt").to_string(); 259 260 let body_text: String = sqlx::query_scalar!( 261 "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", 262 did 263 ) 264 .fetch_one(pool) 265 .await 266 .expect("Verification email not found"); 267 268 let code = body_text 269 .lines() 270 .find(|line| line.trim().starts_with("MX") && line.contains('-')) 271 .map(|s| s.trim().to_string()) 272 .unwrap_or_default(); 273 274 let res = client 275 .post(format!("{}/xrpc/com.atproto.server.confirmEmail", base_url)) 276 .bearer_auth(&access_jwt) 277 .json(&json!({ 278 "email": email, 279 "token": code 280 })) 281 .send() 282 .await 283 .expect("Failed to confirm email"); 284 assert_eq!(res.status(), StatusCode::OK); 285 286 let verified: bool = sqlx::query_scalar!( 287 "SELECT email_verified FROM users WHERE did = $1", 288 did 289 ) 290 .fetch_one(pool) 291 .await 292 .expect("User not found"); 293 assert!(verified); 294} 295 296#[tokio::test] 297async fn test_confirm_email_rejects_wrong_email() { 298 let client = common::client(); 299 let base_url = common::base_url().await; 300 let pool = common::get_test_db_pool().await; 301 let handle = format!("ew{}", &uuid::Uuid::new_v4().simple().to_string()[..12]); 302 let email = format!("{}@example.com", handle); 303 304 let res = client 305 .post(format!( 306 "{}/xrpc/com.atproto.server.createAccount", 307 base_url 308 )) 309 .json(&json!({ 310 "handle": handle, 311 "email": email, 312 "password": "Testpass123!" 313 })) 314 .send() 315 .await 316 .expect("Failed to create account"); 317 assert_eq!(res.status(), StatusCode::OK); 318 let body: Value = res.json().await.expect("Invalid JSON"); 319 let did = body["did"].as_str().expect("No did").to_string(); 320 let access_jwt = body["accessJwt"].as_str().expect("No accessJwt").to_string(); 321 322 let body_text: String = sqlx::query_scalar!( 323 "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", 324 did 325 ) 326 .fetch_one(pool) 327 .await 328 .expect("Verification email not found"); 329 330 let code = body_text 331 .lines() 332 .find(|line| line.trim().starts_with("MX") && line.contains('-')) 333 .map(|s| s.trim().to_string()) 334 .unwrap_or_default(); 335 336 let res = client 337 .post(format!("{}/xrpc/com.atproto.server.confirmEmail", base_url)) 338 .bearer_auth(&access_jwt) 339 .json(&json!({ 340 "email": "different@example.com", 341 "token": code 342 })) 343 .send() 344 .await 345 .expect("Failed to confirm email"); 346 assert_eq!(res.status(), StatusCode::BAD_REQUEST); 347 let body: Value = res.json().await.expect("Invalid JSON"); 348 assert_eq!(body["error"], "InvalidEmail"); 349} 350 351#[tokio::test] 352async fn test_confirm_email_invalid_token() { 353 let client = common::client(); 354 let base_url = common::base_url().await; 355 let handle = format!("ei{}", &uuid::Uuid::new_v4().simple().to_string()[..12]); 356 let email = format!("{}@example.com", handle); 357 358 let res = client 359 .post(format!( 360 "{}/xrpc/com.atproto.server.createAccount", 361 base_url 362 )) 363 .json(&json!({ 364 "handle": handle, 365 "email": email, 366 "password": "Testpass123!" 367 })) 368 .send() 369 .await 370 .expect("Failed to create account"); 371 assert_eq!(res.status(), StatusCode::OK); 372 let body: Value = res.json().await.expect("Invalid JSON"); 373 let access_jwt = body["accessJwt"].as_str().expect("No accessJwt").to_string(); 374 375 let res = client 376 .post(format!("{}/xrpc/com.atproto.server.confirmEmail", base_url)) 377 .bearer_auth(&access_jwt) 378 .json(&json!({ 379 "email": email, 380 "token": "wrong-token" 381 })) 382 .send() 383 .await 384 .expect("Failed to confirm email"); 385 assert_eq!(res.status(), StatusCode::BAD_REQUEST); 386 let body: Value = res.json().await.expect("Invalid JSON"); 387 assert_eq!(body["error"], "InvalidToken"); 388} 389 390#[tokio::test] 391async fn test_unverified_account_can_update_email_without_token() { 392 let client = common::client(); 393 let base_url = common::base_url().await; 394 let pool = common::get_test_db_pool().await; 395 let handle = format!("ev{}", &uuid::Uuid::new_v4().simple().to_string()[..12]); 396 let email = format!("{}@example.com", handle); 397 398 let res = client 399 .post(format!( 400 "{}/xrpc/com.atproto.server.createAccount", 401 base_url 402 )) 403 .json(&json!({ 404 "handle": handle, 405 "email": email, 406 "password": "Testpass123!" 407 })) 408 .send() 409 .await 410 .expect("Failed to create account"); 411 assert_eq!(res.status(), StatusCode::OK); 412 let body: Value = res.json().await.expect("Invalid JSON"); 413 let did = body["did"].as_str().expect("No did").to_string(); 414 let access_jwt = body["accessJwt"].as_str().expect("No accessJwt").to_string(); 415 416 let res = client 417 .post(format!( 418 "{}/xrpc/com.atproto.server.requestEmailUpdate", 419 base_url 420 )) 421 .bearer_auth(&access_jwt) 422 .send() 423 .await 424 .expect("Failed to request email update"); 425 assert_eq!(res.status(), StatusCode::OK); 426 let body: Value = res.json().await.expect("Invalid JSON"); 427 assert_eq!( 428 body["tokenRequired"], false, 429 "Unverified account should not require token" 430 ); 431 432 let new_email = format!("new_{}@example.com", handle); 433 let res = client 434 .post(format!("{}/xrpc/com.atproto.server.updateEmail", base_url)) 435 .bearer_auth(&access_jwt) 436 .json(&json!({ "email": new_email })) 437 .send() 438 .await 439 .expect("Failed to update email"); 440 assert_eq!( 441 res.status(), 442 StatusCode::OK, 443 "Unverified account should be able to update email without token" 444 ); 445 446 let user_email: Option<String> = 447 sqlx::query_scalar!("SELECT email FROM users WHERE did = $1", did) 448 .fetch_one(pool) 449 .await 450 .expect("User not found"); 451 assert_eq!(user_email, Some(new_email)); 452} 453 454#[tokio::test] 455async fn test_update_email_taken_by_another_user() { 456 let client = common::client(); 457 let base_url = common::base_url().await; 458 let pool = common::get_test_db_pool().await; 459 460 let handle1 = format!("d1{}", &uuid::Uuid::new_v4().simple().to_string()[..12]); 461 let email1 = format!("{}@example.com", handle1); 462 let (_, _) = create_verified_account(&client, &base_url, &handle1, &email1).await; 463 464 let handle2 = format!("d2{}", &uuid::Uuid::new_v4().simple().to_string()[..12]); 465 let email2 = format!("{}@example.com", handle2); 466 let (access_jwt2, did2) = create_verified_account(&client, &base_url, &handle2, &email2).await; 467 468 let res = client 469 .post(format!( 470 "{}/xrpc/com.atproto.server.requestEmailUpdate", 471 base_url 472 )) 473 .bearer_auth(&access_jwt2) 474 .send() 475 .await 476 .expect("Failed to request email update"); 477 assert_eq!(res.status(), StatusCode::OK); 478 479 let code = get_email_update_token(pool, &did2).await; 480 481 let res = client 482 .post(format!("{}/xrpc/com.atproto.server.updateEmail", base_url)) 483 .bearer_auth(&access_jwt2) 484 .json(&json!({ 485 "email": email1, 486 "token": code 487 })) 488 .send() 489 .await 490 .expect("Failed to update email"); 491 assert_eq!(res.status(), StatusCode::BAD_REQUEST); 492 let body: Value = res.json().await.expect("Invalid JSON"); 493 assert_eq!(body["error"], "InvalidRequest"); 494 assert!(body["message"] 495 .as_str() 496 .unwrap_or("") 497 .contains("already in use")); 498}