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