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}
307
308#[tokio::test]
309async fn test_account_deactivation_lifecycle() {
310 let client = client();
311 let ts = Utc::now().timestamp_millis();
312 let handle = format!("deactivate-{}.test", ts);
313 let email = format!("deactivate-{}@test.com", ts);
314 let password = "deactivate-password";
315
316 let create_res = client
317 .post(format!(
318 "{}/xrpc/com.atproto.server.createAccount",
319 base_url().await
320 ))
321 .json(&json!({
322 "handle": handle,
323 "email": email,
324 "password": password
325 }))
326 .send()
327 .await
328 .expect("Failed to create account");
329
330 assert_eq!(create_res.status(), StatusCode::OK);
331 let account: Value = create_res.json().await.unwrap();
332 let did = account["did"].as_str().unwrap().to_string();
333 let jwt = account["accessJwt"].as_str().unwrap().to_string();
334
335 let (post_uri, _) = create_post(&client, &did, &jwt, "Post before deactivation").await;
336 let post_rkey = post_uri.split('/').last().unwrap();
337
338 let status_before = client
339 .get(format!(
340 "{}/xrpc/com.atproto.server.checkAccountStatus",
341 base_url().await
342 ))
343 .bearer_auth(&jwt)
344 .send()
345 .await
346 .expect("Failed to check status");
347
348 assert_eq!(status_before.status(), StatusCode::OK);
349 let status_body: Value = status_before.json().await.unwrap();
350 assert_eq!(status_body["activated"], true);
351
352 let deactivate_res = client
353 .post(format!(
354 "{}/xrpc/com.atproto.server.deactivateAccount",
355 base_url().await
356 ))
357 .bearer_auth(&jwt)
358 .json(&json!({}))
359 .send()
360 .await
361 .expect("Failed to deactivate");
362
363 assert_eq!(deactivate_res.status(), StatusCode::OK);
364
365 let get_post_res = client
366 .get(format!(
367 "{}/xrpc/com.atproto.repo.getRecord",
368 base_url().await
369 ))
370 .query(&[
371 ("repo", did.as_str()),
372 ("collection", "app.bsky.feed.post"),
373 ("rkey", post_rkey),
374 ])
375 .send()
376 .await
377 .expect("Failed to get post while deactivated");
378
379 assert_eq!(get_post_res.status(), StatusCode::OK, "Records should still be readable");
380
381 let activate_res = client
382 .post(format!(
383 "{}/xrpc/com.atproto.server.activateAccount",
384 base_url().await
385 ))
386 .bearer_auth(&jwt)
387 .json(&json!({}))
388 .send()
389 .await
390 .expect("Failed to reactivate");
391
392 assert_eq!(activate_res.status(), StatusCode::OK);
393
394 let status_after_activate = client
395 .get(format!(
396 "{}/xrpc/com.atproto.server.checkAccountStatus",
397 base_url().await
398 ))
399 .bearer_auth(&jwt)
400 .send()
401 .await
402 .expect("Failed to check status after activate");
403
404 assert_eq!(status_after_activate.status(), StatusCode::OK);
405
406 let (new_post_uri, _) = create_post(&client, &did, &jwt, "Post after reactivation").await;
407 assert!(!new_post_uri.is_empty(), "Should be able to post after reactivation");
408}
409
410#[tokio::test]
411async fn test_service_auth_lifecycle() {
412 let client = client();
413 let (did, jwt) = setup_new_user("service-auth-test").await;
414
415 let service_auth_res = client
416 .get(format!(
417 "{}/xrpc/com.atproto.server.getServiceAuth",
418 base_url().await
419 ))
420 .query(&[
421 ("aud", "did:web:api.bsky.app"),
422 ("lxm", "com.atproto.repo.uploadBlob"),
423 ])
424 .bearer_auth(&jwt)
425 .send()
426 .await
427 .expect("Failed to get service auth");
428
429 assert_eq!(service_auth_res.status(), StatusCode::OK);
430 let auth_body: Value = service_auth_res.json().await.unwrap();
431 let service_token = auth_body["token"].as_str().expect("No token in response");
432
433 let parts: Vec<&str> = service_token.split('.').collect();
434 assert_eq!(parts.len(), 3, "Service token should be a valid JWT");
435
436 use base64::Engine;
437 let payload_bytes = base64::engine::general_purpose::URL_SAFE_NO_PAD
438 .decode(parts[1])
439 .expect("Failed to decode JWT payload");
440 let claims: Value = serde_json::from_slice(&payload_bytes).expect("Invalid JWT payload");
441
442 assert_eq!(claims["iss"], did);
443 assert_eq!(claims["aud"], "did:web:api.bsky.app");
444 assert_eq!(claims["lxm"], "com.atproto.repo.uploadBlob");
445}
446
447#[tokio::test]
448async fn test_request_account_delete() {
449 let client = client();
450 let (did, jwt) = setup_new_user("request-delete-test").await;
451
452 let res = client
453 .post(format!(
454 "{}/xrpc/com.atproto.server.requestAccountDelete",
455 base_url().await
456 ))
457 .bearer_auth(&jwt)
458 .send()
459 .await
460 .expect("Failed to request account deletion");
461
462 assert_eq!(res.status(), StatusCode::OK);
463
464 let db_url = get_db_connection_string().await;
465 let pool = sqlx::PgPool::connect(&db_url).await.expect("Failed to connect to test DB");
466
467 let row = sqlx::query!("SELECT token, expires_at FROM account_deletion_requests WHERE did = $1", did)
468 .fetch_optional(&pool)
469 .await
470 .expect("Failed to query DB");
471
472 assert!(row.is_some(), "Deletion token should exist in DB");
473 let row = row.unwrap();
474 assert!(!row.token.is_empty(), "Token should not be empty");
475 assert!(row.expires_at > Utc::now(), "Token should not be expired");
476}