this repo has no description
1mod common; 2mod helpers; 3use helpers::verify_new_account; 4use reqwest::StatusCode; 5use serde_json::{Value, json}; 6use sqlx::PgPool; 7 8async fn get_pool() -> PgPool { 9 let conn_str = common::get_db_connection_string().await; 10 sqlx::postgres::PgPoolOptions::new() 11 .max_connections(5) 12 .connect(&conn_str) 13 .await 14 .expect("Failed to connect to test database") 15} 16 17#[tokio::test] 18async fn test_request_password_reset_creates_code() { 19 let client = common::client(); 20 let base_url = common::base_url().await; 21 let pool = get_pool().await; 22 let handle = format!("pwreset-{}", uuid::Uuid::new_v4()); 23 let email = format!("{}@example.com", handle); 24 let payload = json!({ 25 "handle": handle, 26 "email": email, 27 "password": "Oldpass123!" 28 }); 29 let res = client 30 .post(format!( 31 "{}/xrpc/com.atproto.server.createAccount", 32 base_url 33 )) 34 .json(&payload) 35 .send() 36 .await 37 .expect("Failed to create account"); 38 assert_eq!(res.status(), StatusCode::OK); 39 let res = client 40 .post(format!( 41 "{}/xrpc/com.atproto.server.requestPasswordReset", 42 base_url 43 )) 44 .json(&json!({"email": email})) 45 .send() 46 .await 47 .expect("Failed to request password reset"); 48 assert_eq!(res.status(), StatusCode::OK); 49 let user = sqlx::query!( 50 "SELECT password_reset_code, password_reset_code_expires_at FROM users WHERE email = $1", 51 email 52 ) 53 .fetch_one(&pool) 54 .await 55 .expect("User not found"); 56 assert!(user.password_reset_code.is_some()); 57 assert!(user.password_reset_code_expires_at.is_some()); 58 let code = user.password_reset_code.unwrap(); 59 assert!(code.contains('-')); 60 assert_eq!(code.len(), 11); 61} 62 63#[tokio::test] 64async fn test_request_password_reset_unknown_email_returns_ok() { 65 let client = common::client(); 66 let base_url = common::base_url().await; 67 let res = client 68 .post(format!( 69 "{}/xrpc/com.atproto.server.requestPasswordReset", 70 base_url 71 )) 72 .json(&json!({"email": "nonexistent@example.com"})) 73 .send() 74 .await 75 .expect("Failed to request password reset"); 76 assert_eq!(res.status(), StatusCode::OK); 77} 78 79#[tokio::test] 80async fn test_reset_password_with_valid_token() { 81 let client = common::client(); 82 let base_url = common::base_url().await; 83 let pool = get_pool().await; 84 let handle = format!("pwreset2-{}", uuid::Uuid::new_v4()); 85 let email = format!("{}@example.com", handle); 86 let old_password = "Oldpass123!"; 87 let new_password = "Newpass456!"; 88 let payload = json!({ 89 "handle": handle, 90 "email": email, 91 "password": old_password 92 }); 93 let res = client 94 .post(format!( 95 "{}/xrpc/com.atproto.server.createAccount", 96 base_url 97 )) 98 .json(&payload) 99 .send() 100 .await 101 .expect("Failed to create account"); 102 assert_eq!(res.status(), StatusCode::OK); 103 let body: Value = res.json().await.unwrap(); 104 let did = body["did"].as_str().unwrap(); 105 let _ = verify_new_account(&client, did).await; 106 let res = client 107 .post(format!( 108 "{}/xrpc/com.atproto.server.requestPasswordReset", 109 base_url 110 )) 111 .json(&json!({"email": email})) 112 .send() 113 .await 114 .expect("Failed to request password reset"); 115 assert_eq!(res.status(), StatusCode::OK); 116 let user = sqlx::query!( 117 "SELECT password_reset_code FROM users WHERE email = $1", 118 email 119 ) 120 .fetch_one(&pool) 121 .await 122 .expect("User not found"); 123 let token = user.password_reset_code.expect("No reset code"); 124 let res = client 125 .post(format!( 126 "{}/xrpc/com.atproto.server.resetPassword", 127 base_url 128 )) 129 .json(&json!({ 130 "token": token, 131 "password": new_password 132 })) 133 .send() 134 .await 135 .expect("Failed to reset password"); 136 assert_eq!(res.status(), StatusCode::OK); 137 let user = sqlx::query!( 138 "SELECT password_reset_code, password_reset_code_expires_at FROM users WHERE email = $1", 139 email 140 ) 141 .fetch_one(&pool) 142 .await 143 .expect("User not found"); 144 assert!(user.password_reset_code.is_none()); 145 assert!(user.password_reset_code_expires_at.is_none()); 146 let res = client 147 .post(format!( 148 "{}/xrpc/com.atproto.server.createSession", 149 base_url 150 )) 151 .json(&json!({ 152 "identifier": handle, 153 "password": new_password 154 })) 155 .send() 156 .await 157 .expect("Failed to login"); 158 assert_eq!(res.status(), StatusCode::OK); 159 let res = client 160 .post(format!( 161 "{}/xrpc/com.atproto.server.createSession", 162 base_url 163 )) 164 .json(&json!({ 165 "identifier": handle, 166 "password": old_password 167 })) 168 .send() 169 .await 170 .expect("Failed to login attempt"); 171 assert_eq!(res.status(), StatusCode::UNAUTHORIZED); 172} 173 174#[tokio::test] 175async fn test_reset_password_with_invalid_token() { 176 let client = common::client(); 177 let base_url = common::base_url().await; 178 let res = client 179 .post(format!( 180 "{}/xrpc/com.atproto.server.resetPassword", 181 base_url 182 )) 183 .json(&json!({ 184 "token": "invalid-token", 185 "password": "Newpass123!" 186 })) 187 .send() 188 .await 189 .expect("Failed to reset password"); 190 assert_eq!(res.status(), StatusCode::BAD_REQUEST); 191 let body: Value = res.json().await.expect("Invalid JSON"); 192 assert_eq!(body["error"], "InvalidToken"); 193} 194 195#[tokio::test] 196async fn test_reset_password_with_expired_token() { 197 let client = common::client(); 198 let base_url = common::base_url().await; 199 let pool = get_pool().await; 200 let handle = format!("pwreset3-{}", uuid::Uuid::new_v4()); 201 let email = format!("{}@example.com", handle); 202 let payload = json!({ 203 "handle": handle, 204 "email": email, 205 "password": "Oldpass123!" 206 }); 207 let res = client 208 .post(format!( 209 "{}/xrpc/com.atproto.server.createAccount", 210 base_url 211 )) 212 .json(&payload) 213 .send() 214 .await 215 .expect("Failed to create account"); 216 assert_eq!(res.status(), StatusCode::OK); 217 let res = client 218 .post(format!( 219 "{}/xrpc/com.atproto.server.requestPasswordReset", 220 base_url 221 )) 222 .json(&json!({"email": email})) 223 .send() 224 .await 225 .expect("Failed to request password reset"); 226 assert_eq!(res.status(), StatusCode::OK); 227 let user = sqlx::query!( 228 "SELECT password_reset_code FROM users WHERE email = $1", 229 email 230 ) 231 .fetch_one(&pool) 232 .await 233 .expect("User not found"); 234 let token = user.password_reset_code.expect("No reset code"); 235 sqlx::query!( 236 "UPDATE users SET password_reset_code_expires_at = NOW() - INTERVAL '1 hour' WHERE email = $1", 237 email 238 ) 239 .execute(&pool) 240 .await 241 .expect("Failed to expire token"); 242 let res = client 243 .post(format!( 244 "{}/xrpc/com.atproto.server.resetPassword", 245 base_url 246 )) 247 .json(&json!({ 248 "token": token, 249 "password": "Newpass123!" 250 })) 251 .send() 252 .await 253 .expect("Failed to reset password"); 254 assert_eq!(res.status(), StatusCode::BAD_REQUEST); 255 let body: Value = res.json().await.expect("Invalid JSON"); 256 assert_eq!(body["error"], "ExpiredToken"); 257} 258 259#[tokio::test] 260async fn test_reset_password_invalidates_sessions() { 261 let client = common::client(); 262 let base_url = common::base_url().await; 263 let pool = get_pool().await; 264 let handle = format!("pwreset4-{}", uuid::Uuid::new_v4()); 265 let email = format!("{}@example.com", handle); 266 let payload = json!({ 267 "handle": handle, 268 "email": email, 269 "password": "Oldpass123!" 270 }); 271 let res = client 272 .post(format!( 273 "{}/xrpc/com.atproto.server.createAccount", 274 base_url 275 )) 276 .json(&payload) 277 .send() 278 .await 279 .expect("Failed to create account"); 280 assert_eq!(res.status(), StatusCode::OK); 281 let body: Value = res.json().await.expect("Invalid JSON"); 282 let did = body["did"].as_str().expect("No did"); 283 let original_token = verify_new_account(&client, did).await; 284 let res = client 285 .get(format!("{}/xrpc/com.atproto.server.getSession", base_url)) 286 .bearer_auth(&original_token) 287 .send() 288 .await 289 .expect("Failed to get session"); 290 assert_eq!(res.status(), StatusCode::OK); 291 let res = client 292 .post(format!( 293 "{}/xrpc/com.atproto.server.requestPasswordReset", 294 base_url 295 )) 296 .json(&json!({"email": email})) 297 .send() 298 .await 299 .expect("Failed to request password reset"); 300 assert_eq!(res.status(), StatusCode::OK); 301 let user = sqlx::query!( 302 "SELECT password_reset_code FROM users WHERE email = $1", 303 email 304 ) 305 .fetch_one(&pool) 306 .await 307 .expect("User not found"); 308 let token = user.password_reset_code.expect("No reset code"); 309 let res = client 310 .post(format!( 311 "{}/xrpc/com.atproto.server.resetPassword", 312 base_url 313 )) 314 .json(&json!({ 315 "token": token, 316 "password": "Newpass123!" 317 })) 318 .send() 319 .await 320 .expect("Failed to reset password"); 321 assert_eq!(res.status(), StatusCode::OK); 322 let res = client 323 .get(format!("{}/xrpc/com.atproto.server.getSession", base_url)) 324 .bearer_auth(&original_token) 325 .send() 326 .await 327 .expect("Failed to get session"); 328 assert_eq!(res.status(), StatusCode::UNAUTHORIZED); 329} 330 331#[tokio::test] 332async fn test_request_password_reset_empty_email() { 333 let client = common::client(); 334 let base_url = common::base_url().await; 335 let res = client 336 .post(format!( 337 "{}/xrpc/com.atproto.server.requestPasswordReset", 338 base_url 339 )) 340 .json(&json!({"email": ""})) 341 .send() 342 .await 343 .expect("Failed to request password reset"); 344 assert_eq!(res.status(), StatusCode::BAD_REQUEST); 345 let body: Value = res.json().await.expect("Invalid JSON"); 346 assert_eq!(body["error"], "InvalidRequest"); 347} 348 349#[tokio::test] 350async fn test_reset_password_creates_notification() { 351 let pool = get_pool().await; 352 let client = common::client(); 353 let base_url = common::base_url().await; 354 let handle = format!("pwreset5-{}", uuid::Uuid::new_v4()); 355 let email = format!("{}@example.com", handle); 356 let payload = json!({ 357 "handle": handle, 358 "email": email, 359 "password": "Oldpass123!" 360 }); 361 let res = client 362 .post(format!( 363 "{}/xrpc/com.atproto.server.createAccount", 364 base_url 365 )) 366 .json(&payload) 367 .send() 368 .await 369 .expect("Failed to create account"); 370 assert_eq!(res.status(), StatusCode::OK); 371 let user = sqlx::query!("SELECT id FROM users WHERE email = $1", email) 372 .fetch_one(&pool) 373 .await 374 .expect("User not found"); 375 let initial_count: i64 = sqlx::query_scalar!( 376 "SELECT COUNT(*) FROM comms_queue WHERE user_id = $1 AND comms_type = 'password_reset'", 377 user.id 378 ) 379 .fetch_one(&pool) 380 .await 381 .expect("Failed to count") 382 .unwrap_or(0); 383 let res = client 384 .post(format!( 385 "{}/xrpc/com.atproto.server.requestPasswordReset", 386 base_url 387 )) 388 .json(&json!({"email": email})) 389 .send() 390 .await 391 .expect("Failed to request password reset"); 392 assert_eq!(res.status(), StatusCode::OK); 393 let final_count: i64 = sqlx::query_scalar!( 394 "SELECT COUNT(*) FROM comms_queue WHERE user_id = $1 AND comms_type = 'password_reset'", 395 user.id 396 ) 397 .fetch_one(&pool) 398 .await 399 .expect("Failed to count") 400 .unwrap_or(0); 401 assert_eq!(final_count - initial_count, 1); 402}