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