this repo has no description
1mod common; 2mod helpers; 3 4use common::*; 5use helpers::*; 6 7use chrono::Utc; 8use reqwest::StatusCode; 9use serde_json::{Value, json}; 10 11#[tokio::test] 12async fn test_session_lifecycle_wrong_password() { 13 let client = client(); 14 let (_, _) = setup_new_user("session-wrong-pw").await; 15 16 let login_payload = json!({ 17 "identifier": format!("session-wrong-pw-{}.test", Utc::now().timestamp_millis()), 18 "password": "wrong-password" 19 }); 20 21 let res = client 22 .post(format!( 23 "{}/xrpc/com.atproto.server.createSession", 24 base_url().await 25 )) 26 .json(&login_payload) 27 .send() 28 .await 29 .expect("Failed to send request"); 30 31 assert!( 32 res.status() == StatusCode::UNAUTHORIZED || res.status() == StatusCode::BAD_REQUEST, 33 "Expected 401 or 400 for wrong password, got {}", 34 res.status() 35 ); 36} 37 38#[tokio::test] 39async fn test_session_lifecycle_multiple_sessions() { 40 let client = client(); 41 let ts = Utc::now().timestamp_millis(); 42 let handle = format!("multi-session-{}.test", ts); 43 let email = format!("multi-session-{}@test.com", ts); 44 let password = "multi-session-pw"; 45 46 let create_payload = json!({ 47 "handle": handle, 48 "email": email, 49 "password": password 50 }); 51 let create_res = client 52 .post(format!( 53 "{}/xrpc/com.atproto.server.createAccount", 54 base_url().await 55 )) 56 .json(&create_payload) 57 .send() 58 .await 59 .expect("Failed to create account"); 60 assert_eq!(create_res.status(), StatusCode::OK); 61 62 let login_payload = json!({ 63 "identifier": handle, 64 "password": password 65 }); 66 67 let session1_res = client 68 .post(format!( 69 "{}/xrpc/com.atproto.server.createSession", 70 base_url().await 71 )) 72 .json(&login_payload) 73 .send() 74 .await 75 .expect("Failed session 1"); 76 assert_eq!(session1_res.status(), StatusCode::OK); 77 let session1: Value = session1_res.json().await.unwrap(); 78 let jwt1 = session1["accessJwt"].as_str().unwrap(); 79 80 let session2_res = client 81 .post(format!( 82 "{}/xrpc/com.atproto.server.createSession", 83 base_url().await 84 )) 85 .json(&login_payload) 86 .send() 87 .await 88 .expect("Failed session 2"); 89 assert_eq!(session2_res.status(), StatusCode::OK); 90 let session2: Value = session2_res.json().await.unwrap(); 91 let jwt2 = session2["accessJwt"].as_str().unwrap(); 92 93 assert_ne!(jwt1, jwt2, "Sessions should have different tokens"); 94 95 let get1 = client 96 .get(format!( 97 "{}/xrpc/com.atproto.server.getSession", 98 base_url().await 99 )) 100 .bearer_auth(jwt1) 101 .send() 102 .await 103 .expect("Failed getSession 1"); 104 assert_eq!(get1.status(), StatusCode::OK); 105 106 let get2 = client 107 .get(format!( 108 "{}/xrpc/com.atproto.server.getSession", 109 base_url().await 110 )) 111 .bearer_auth(jwt2) 112 .send() 113 .await 114 .expect("Failed getSession 2"); 115 assert_eq!(get2.status(), StatusCode::OK); 116} 117 118#[tokio::test] 119async fn test_session_lifecycle_refresh_invalidates_old() { 120 let client = client(); 121 let ts = Utc::now().timestamp_millis(); 122 let handle = format!("refresh-inv-{}.test", ts); 123 let email = format!("refresh-inv-{}@test.com", ts); 124 let password = "refresh-inv-pw"; 125 126 let create_payload = json!({ 127 "handle": handle, 128 "email": email, 129 "password": password 130 }); 131 client 132 .post(format!( 133 "{}/xrpc/com.atproto.server.createAccount", 134 base_url().await 135 )) 136 .json(&create_payload) 137 .send() 138 .await 139 .expect("Failed to create account"); 140 141 let login_payload = json!({ 142 "identifier": handle, 143 "password": password 144 }); 145 let login_res = client 146 .post(format!( 147 "{}/xrpc/com.atproto.server.createSession", 148 base_url().await 149 )) 150 .json(&login_payload) 151 .send() 152 .await 153 .expect("Failed login"); 154 let login_body: Value = login_res.json().await.unwrap(); 155 let refresh_jwt = login_body["refreshJwt"].as_str().unwrap().to_string(); 156 157 let refresh_res = client 158 .post(format!( 159 "{}/xrpc/com.atproto.server.refreshSession", 160 base_url().await 161 )) 162 .bearer_auth(&refresh_jwt) 163 .send() 164 .await 165 .expect("Failed first refresh"); 166 assert_eq!(refresh_res.status(), StatusCode::OK); 167 let refresh_body: Value = refresh_res.json().await.unwrap(); 168 let new_refresh_jwt = refresh_body["refreshJwt"].as_str().unwrap(); 169 170 assert_ne!(refresh_jwt, new_refresh_jwt, "Refresh tokens should differ"); 171 172 let reuse_res = client 173 .post(format!( 174 "{}/xrpc/com.atproto.server.refreshSession", 175 base_url().await 176 )) 177 .bearer_auth(&refresh_jwt) 178 .send() 179 .await 180 .expect("Failed reuse attempt"); 181 182 assert!( 183 reuse_res.status() == StatusCode::UNAUTHORIZED || reuse_res.status() == StatusCode::BAD_REQUEST, 184 "Old refresh token should be invalid after use" 185 ); 186} 187 188#[tokio::test] 189async fn test_app_password_lifecycle() { 190 let client = client(); 191 let ts = Utc::now().timestamp_millis(); 192 let handle = format!("apppass-{}.test", ts); 193 let email = format!("apppass-{}@test.com", ts); 194 let password = "apppass-password"; 195 196 let create_res = client 197 .post(format!( 198 "{}/xrpc/com.atproto.server.createAccount", 199 base_url().await 200 )) 201 .json(&json!({ 202 "handle": handle, 203 "email": email, 204 "password": password 205 })) 206 .send() 207 .await 208 .expect("Failed to create account"); 209 210 assert_eq!(create_res.status(), StatusCode::OK); 211 let account: Value = create_res.json().await.unwrap(); 212 let jwt = account["accessJwt"].as_str().unwrap(); 213 214 let create_app_pass_res = client 215 .post(format!( 216 "{}/xrpc/com.atproto.server.createAppPassword", 217 base_url().await 218 )) 219 .bearer_auth(jwt) 220 .json(&json!({ "name": "Test App" })) 221 .send() 222 .await 223 .expect("Failed to create app password"); 224 225 assert_eq!(create_app_pass_res.status(), StatusCode::OK); 226 let app_pass: Value = create_app_pass_res.json().await.unwrap(); 227 let app_password = app_pass["password"].as_str().unwrap().to_string(); 228 assert_eq!(app_pass["name"], "Test App"); 229 230 let list_res = client 231 .get(format!( 232 "{}/xrpc/com.atproto.server.listAppPasswords", 233 base_url().await 234 )) 235 .bearer_auth(jwt) 236 .send() 237 .await 238 .expect("Failed to list app passwords"); 239 240 assert_eq!(list_res.status(), StatusCode::OK); 241 let list_body: Value = list_res.json().await.unwrap(); 242 let passwords = list_body["passwords"].as_array().unwrap(); 243 assert_eq!(passwords.len(), 1); 244 assert_eq!(passwords[0]["name"], "Test App"); 245 246 let login_res = client 247 .post(format!( 248 "{}/xrpc/com.atproto.server.createSession", 249 base_url().await 250 )) 251 .json(&json!({ 252 "identifier": handle, 253 "password": app_password 254 })) 255 .send() 256 .await 257 .expect("Failed to login with app password"); 258 259 assert_eq!(login_res.status(), StatusCode::OK, "App password login should work"); 260 261 let revoke_res = client 262 .post(format!( 263 "{}/xrpc/com.atproto.server.revokeAppPassword", 264 base_url().await 265 )) 266 .bearer_auth(jwt) 267 .json(&json!({ "name": "Test App" })) 268 .send() 269 .await 270 .expect("Failed to revoke app password"); 271 272 assert_eq!(revoke_res.status(), StatusCode::OK); 273 274 let login_after_revoke = client 275 .post(format!( 276 "{}/xrpc/com.atproto.server.createSession", 277 base_url().await 278 )) 279 .json(&json!({ 280 "identifier": handle, 281 "password": app_password 282 })) 283 .send() 284 .await 285 .expect("Failed to attempt login after revoke"); 286 287 assert!( 288 login_after_revoke.status() == StatusCode::UNAUTHORIZED 289 || login_after_revoke.status() == StatusCode::BAD_REQUEST, 290 "Revoked app password should not work" 291 ); 292 293 let list_after_revoke = client 294 .get(format!( 295 "{}/xrpc/com.atproto.server.listAppPasswords", 296 base_url().await 297 )) 298 .bearer_auth(jwt) 299 .send() 300 .await 301 .expect("Failed to list after revoke"); 302 303 let list_after: Value = list_after_revoke.json().await.unwrap(); 304 let passwords_after = list_after["passwords"].as_array().unwrap(); 305 assert_eq!(passwords_after.len(), 0, "No app passwords should remain"); 306}