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 let create_body: Value = create_res.json().await.unwrap();
62 let did = create_body["did"].as_str().unwrap();
63
64 let _ = verify_new_account(&client, did).await;
65
66 let login_payload = json!({
67 "identifier": handle,
68 "password": password
69 });
70
71 let session1_res = client
72 .post(format!(
73 "{}/xrpc/com.atproto.server.createSession",
74 base_url().await
75 ))
76 .json(&login_payload)
77 .send()
78 .await
79 .expect("Failed session 1");
80 assert_eq!(session1_res.status(), StatusCode::OK);
81 let session1: Value = session1_res.json().await.unwrap();
82 let jwt1 = session1["accessJwt"].as_str().unwrap();
83
84 let session2_res = client
85 .post(format!(
86 "{}/xrpc/com.atproto.server.createSession",
87 base_url().await
88 ))
89 .json(&login_payload)
90 .send()
91 .await
92 .expect("Failed session 2");
93 assert_eq!(session2_res.status(), StatusCode::OK);
94 let session2: Value = session2_res.json().await.unwrap();
95 let jwt2 = session2["accessJwt"].as_str().unwrap();
96
97 assert_ne!(jwt1, jwt2, "Sessions should have different tokens");
98
99 let get1 = client
100 .get(format!(
101 "{}/xrpc/com.atproto.server.getSession",
102 base_url().await
103 ))
104 .bearer_auth(jwt1)
105 .send()
106 .await
107 .expect("Failed getSession 1");
108 assert_eq!(get1.status(), StatusCode::OK);
109
110 let get2 = client
111 .get(format!(
112 "{}/xrpc/com.atproto.server.getSession",
113 base_url().await
114 ))
115 .bearer_auth(jwt2)
116 .send()
117 .await
118 .expect("Failed getSession 2");
119 assert_eq!(get2.status(), StatusCode::OK);
120}
121
122#[tokio::test]
123async fn test_session_lifecycle_refresh_invalidates_old() {
124 let client = client();
125 let ts = Utc::now().timestamp_millis();
126 let handle = format!("refresh-inv-{}.test", ts);
127 let email = format!("refresh-inv-{}@test.com", ts);
128 let password = "refresh-inv-pw";
129
130 let create_payload = json!({
131 "handle": handle,
132 "email": email,
133 "password": password
134 });
135 let create_res = client
136 .post(format!(
137 "{}/xrpc/com.atproto.server.createAccount",
138 base_url().await
139 ))
140 .json(&create_payload)
141 .send()
142 .await
143 .expect("Failed to create account");
144 let create_body: Value = create_res.json().await.unwrap();
145 let did = create_body["did"].as_str().unwrap();
146
147 let _ = verify_new_account(&client, did).await;
148
149 let login_payload = json!({
150 "identifier": handle,
151 "password": password
152 });
153 let login_res = client
154 .post(format!(
155 "{}/xrpc/com.atproto.server.createSession",
156 base_url().await
157 ))
158 .json(&login_payload)
159 .send()
160 .await
161 .expect("Failed login");
162 let login_body: Value = login_res.json().await.unwrap();
163 let refresh_jwt = login_body["refreshJwt"].as_str().unwrap().to_string();
164
165 let refresh_res = client
166 .post(format!(
167 "{}/xrpc/com.atproto.server.refreshSession",
168 base_url().await
169 ))
170 .bearer_auth(&refresh_jwt)
171 .send()
172 .await
173 .expect("Failed first refresh");
174 assert_eq!(refresh_res.status(), StatusCode::OK);
175 let refresh_body: Value = refresh_res.json().await.unwrap();
176 let new_refresh_jwt = refresh_body["refreshJwt"].as_str().unwrap();
177
178 assert_ne!(refresh_jwt, new_refresh_jwt, "Refresh tokens should differ");
179
180 let reuse_res = client
181 .post(format!(
182 "{}/xrpc/com.atproto.server.refreshSession",
183 base_url().await
184 ))
185 .bearer_auth(&refresh_jwt)
186 .send()
187 .await
188 .expect("Failed reuse attempt");
189
190 assert!(
191 reuse_res.status() == StatusCode::UNAUTHORIZED || reuse_res.status() == StatusCode::BAD_REQUEST,
192 "Old refresh token should be invalid after use"
193 );
194}
195
196#[tokio::test]
197async fn test_app_password_lifecycle() {
198 let client = client();
199 let ts = Utc::now().timestamp_millis();
200 let handle = format!("apppass-{}.test", ts);
201 let email = format!("apppass-{}@test.com", ts);
202 let password = "apppass-password";
203
204 let create_res = client
205 .post(format!(
206 "{}/xrpc/com.atproto.server.createAccount",
207 base_url().await
208 ))
209 .json(&json!({
210 "handle": handle,
211 "email": email,
212 "password": password
213 }))
214 .send()
215 .await
216 .expect("Failed to create account");
217
218 assert_eq!(create_res.status(), StatusCode::OK);
219 let account: Value = create_res.json().await.unwrap();
220 let did = account["did"].as_str().unwrap();
221
222 let jwt = verify_new_account(&client, did).await;
223
224 let create_app_pass_res = client
225 .post(format!(
226 "{}/xrpc/com.atproto.server.createAppPassword",
227 base_url().await
228 ))
229 .bearer_auth(&jwt)
230 .json(&json!({ "name": "Test App" }))
231 .send()
232 .await
233 .expect("Failed to create app password");
234
235 assert_eq!(create_app_pass_res.status(), StatusCode::OK);
236 let app_pass: Value = create_app_pass_res.json().await.unwrap();
237 let app_password = app_pass["password"].as_str().unwrap().to_string();
238 assert_eq!(app_pass["name"], "Test App");
239
240 let list_res = client
241 .get(format!(
242 "{}/xrpc/com.atproto.server.listAppPasswords",
243 base_url().await
244 ))
245 .bearer_auth(&jwt)
246 .send()
247 .await
248 .expect("Failed to list app passwords");
249
250 assert_eq!(list_res.status(), StatusCode::OK);
251 let list_body: Value = list_res.json().await.unwrap();
252 let passwords = list_body["passwords"].as_array().unwrap();
253 assert_eq!(passwords.len(), 1);
254 assert_eq!(passwords[0]["name"], "Test App");
255
256 let login_res = client
257 .post(format!(
258 "{}/xrpc/com.atproto.server.createSession",
259 base_url().await
260 ))
261 .json(&json!({
262 "identifier": handle,
263 "password": app_password
264 }))
265 .send()
266 .await
267 .expect("Failed to login with app password");
268
269 assert_eq!(login_res.status(), StatusCode::OK, "App password login should work");
270
271 let revoke_res = client
272 .post(format!(
273 "{}/xrpc/com.atproto.server.revokeAppPassword",
274 base_url().await
275 ))
276 .bearer_auth(&jwt)
277 .json(&json!({ "name": "Test App" }))
278 .send()
279 .await
280 .expect("Failed to revoke app password");
281
282 assert_eq!(revoke_res.status(), StatusCode::OK);
283
284 let login_after_revoke = client
285 .post(format!(
286 "{}/xrpc/com.atproto.server.createSession",
287 base_url().await
288 ))
289 .json(&json!({
290 "identifier": handle,
291 "password": app_password
292 }))
293 .send()
294 .await
295 .expect("Failed to attempt login after revoke");
296
297 assert!(
298 login_after_revoke.status() == StatusCode::UNAUTHORIZED
299 || login_after_revoke.status() == StatusCode::BAD_REQUEST,
300 "Revoked app password should not work"
301 );
302
303 let list_after_revoke = client
304 .get(format!(
305 "{}/xrpc/com.atproto.server.listAppPasswords",
306 base_url().await
307 ))
308 .bearer_auth(&jwt)
309 .send()
310 .await
311 .expect("Failed to list after revoke");
312
313 let list_after: Value = list_after_revoke.json().await.unwrap();
314 let passwords_after = list_after["passwords"].as_array().unwrap();
315 assert_eq!(passwords_after.len(), 0, "No app passwords should remain");
316}
317
318#[tokio::test]
319async fn test_account_deactivation_lifecycle() {
320 let client = client();
321 let ts = Utc::now().timestamp_millis();
322 let handle = format!("deactivate-{}.test", ts);
323 let email = format!("deactivate-{}@test.com", ts);
324 let password = "deactivate-password";
325
326 let create_res = client
327 .post(format!(
328 "{}/xrpc/com.atproto.server.createAccount",
329 base_url().await
330 ))
331 .json(&json!({
332 "handle": handle,
333 "email": email,
334 "password": password
335 }))
336 .send()
337 .await
338 .expect("Failed to create account");
339
340 assert_eq!(create_res.status(), StatusCode::OK);
341 let account: Value = create_res.json().await.unwrap();
342 let did = account["did"].as_str().unwrap().to_string();
343
344 let jwt = verify_new_account(&client, &did).await;
345
346 let (post_uri, _) = create_post(&client, &did, &jwt, "Post before deactivation").await;
347 let post_rkey = post_uri.split('/').last().unwrap();
348
349 let status_before = client
350 .get(format!(
351 "{}/xrpc/com.atproto.server.checkAccountStatus",
352 base_url().await
353 ))
354 .bearer_auth(&jwt)
355 .send()
356 .await
357 .expect("Failed to check status");
358
359 assert_eq!(status_before.status(), StatusCode::OK);
360 let status_body: Value = status_before.json().await.unwrap();
361 assert_eq!(status_body["activated"], true);
362
363 let deactivate_res = client
364 .post(format!(
365 "{}/xrpc/com.atproto.server.deactivateAccount",
366 base_url().await
367 ))
368 .bearer_auth(&jwt)
369 .json(&json!({}))
370 .send()
371 .await
372 .expect("Failed to deactivate");
373
374 assert_eq!(deactivate_res.status(), StatusCode::OK);
375
376 let get_post_res = client
377 .get(format!(
378 "{}/xrpc/com.atproto.repo.getRecord",
379 base_url().await
380 ))
381 .query(&[
382 ("repo", did.as_str()),
383 ("collection", "app.bsky.feed.post"),
384 ("rkey", post_rkey),
385 ])
386 .send()
387 .await
388 .expect("Failed to get post while deactivated");
389
390 assert_eq!(get_post_res.status(), StatusCode::OK, "Records should still be readable");
391
392 let activate_res = client
393 .post(format!(
394 "{}/xrpc/com.atproto.server.activateAccount",
395 base_url().await
396 ))
397 .bearer_auth(&jwt)
398 .json(&json!({}))
399 .send()
400 .await
401 .expect("Failed to reactivate");
402
403 assert_eq!(activate_res.status(), StatusCode::OK);
404
405 let status_after_activate = client
406 .get(format!(
407 "{}/xrpc/com.atproto.server.checkAccountStatus",
408 base_url().await
409 ))
410 .bearer_auth(&jwt)
411 .send()
412 .await
413 .expect("Failed to check status after activate");
414
415 assert_eq!(status_after_activate.status(), StatusCode::OK);
416
417 let (new_post_uri, _) = create_post(&client, &did, &jwt, "Post after reactivation").await;
418 assert!(!new_post_uri.is_empty(), "Should be able to post after reactivation");
419}
420
421#[tokio::test]
422async fn test_service_auth_lifecycle() {
423 let client = client();
424 let (did, jwt) = setup_new_user("service-auth-test").await;
425
426 let service_auth_res = client
427 .get(format!(
428 "{}/xrpc/com.atproto.server.getServiceAuth",
429 base_url().await
430 ))
431 .query(&[
432 ("aud", "did:web:api.bsky.app"),
433 ("lxm", "com.atproto.repo.uploadBlob"),
434 ])
435 .bearer_auth(&jwt)
436 .send()
437 .await
438 .expect("Failed to get service auth");
439
440 assert_eq!(service_auth_res.status(), StatusCode::OK);
441 let auth_body: Value = service_auth_res.json().await.unwrap();
442 let service_token = auth_body["token"].as_str().expect("No token in response");
443
444 let parts: Vec<&str> = service_token.split('.').collect();
445 assert_eq!(parts.len(), 3, "Service token should be a valid JWT");
446
447 use base64::Engine;
448 let payload_bytes = base64::engine::general_purpose::URL_SAFE_NO_PAD
449 .decode(parts[1])
450 .expect("Failed to decode JWT payload");
451 let claims: Value = serde_json::from_slice(&payload_bytes).expect("Invalid JWT payload");
452
453 assert_eq!(claims["iss"], did);
454 assert_eq!(claims["aud"], "did:web:api.bsky.app");
455 assert_eq!(claims["lxm"], "com.atproto.repo.uploadBlob");
456}
457
458#[tokio::test]
459async fn test_request_account_delete() {
460 let client = client();
461 let (did, jwt) = setup_new_user("request-delete-test").await;
462
463 let res = client
464 .post(format!(
465 "{}/xrpc/com.atproto.server.requestAccountDelete",
466 base_url().await
467 ))
468 .bearer_auth(&jwt)
469 .send()
470 .await
471 .expect("Failed to request account deletion");
472
473 assert_eq!(res.status(), StatusCode::OK);
474
475 let db_url = get_db_connection_string().await;
476 let pool = sqlx::PgPool::connect(&db_url).await.expect("Failed to connect to test DB");
477
478 let row = sqlx::query!("SELECT token, expires_at FROM account_deletion_requests WHERE did = $1", did)
479 .fetch_optional(&pool)
480 .await
481 .expect("Failed to query DB");
482
483 assert!(row.is_some(), "Deletion token should exist in DB");
484 let row = row.unwrap();
485 assert!(!row.token.is_empty(), "Token should not be empty");
486 assert!(row.expires_at > Utc::now(), "Token should not be expired");
487}