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