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