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