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