this repo has no description
1mod common;
2use common::*;
3use reqwest::StatusCode;
4use serde_json::json;
5use sqlx::PgPool;
6
7#[tokio::test]
8async fn test_plc_operation_auth() {
9 let client = client();
10 let res = client
11 .post(format!(
12 "{}/xrpc/com.atproto.identity.requestPlcOperationSignature",
13 base_url().await
14 ))
15 .send()
16 .await
17 .unwrap();
18 assert_eq!(res.status(), StatusCode::UNAUTHORIZED);
19 let res = client
20 .post(format!(
21 "{}/xrpc/com.atproto.identity.signPlcOperation",
22 base_url().await
23 ))
24 .json(&json!({}))
25 .send()
26 .await
27 .unwrap();
28 assert_eq!(res.status(), StatusCode::UNAUTHORIZED);
29 let res = client
30 .post(format!(
31 "{}/xrpc/com.atproto.identity.submitPlcOperation",
32 base_url().await
33 ))
34 .json(&json!({ "operation": {} }))
35 .send()
36 .await
37 .unwrap();
38 assert_eq!(res.status(), StatusCode::UNAUTHORIZED);
39 let (token, _) = create_account_and_login(&client).await;
40 let res = client
41 .post(format!(
42 "{}/xrpc/com.atproto.identity.requestPlcOperationSignature",
43 base_url().await
44 ))
45 .bearer_auth(&token)
46 .send()
47 .await
48 .unwrap();
49 assert_eq!(res.status(), StatusCode::OK);
50}
51
52#[tokio::test]
53async fn test_sign_plc_operation_validation() {
54 let client = client();
55 let (token, _) = create_account_and_login(&client).await;
56 let res = client
57 .post(format!(
58 "{}/xrpc/com.atproto.identity.signPlcOperation",
59 base_url().await
60 ))
61 .bearer_auth(&token)
62 .json(&json!({}))
63 .send()
64 .await
65 .unwrap();
66 assert_eq!(res.status(), StatusCode::BAD_REQUEST);
67 let body: serde_json::Value = res.json().await.unwrap();
68 assert_eq!(body["error"], "InvalidRequest");
69 let res = client
70 .post(format!(
71 "{}/xrpc/com.atproto.identity.signPlcOperation",
72 base_url().await
73 ))
74 .bearer_auth(&token)
75 .json(&json!({ "token": "invalid-token-12345" }))
76 .send()
77 .await
78 .unwrap();
79 assert_eq!(res.status(), StatusCode::UNAUTHORIZED);
80 let body: serde_json::Value = res.json().await.unwrap();
81 assert!(body["error"] == "InvalidToken" || body["error"] == "ExpiredToken");
82}
83
84#[tokio::test]
85async fn test_submit_plc_operation_validation() {
86 let client = client();
87 let (token, did) = create_account_and_login(&client).await;
88 let hostname =
89 std::env::var("PDS_HOSTNAME").unwrap_or_else(|_| format!("127.0.0.1:{}", app_port()));
90 let res = client
91 .post(format!(
92 "{}/xrpc/com.atproto.identity.submitPlcOperation",
93 base_url().await
94 ))
95 .bearer_auth(&token)
96 .json(&json!({ "operation": { "type": "invalid_type" } }))
97 .send()
98 .await
99 .unwrap();
100 assert_eq!(res.status(), StatusCode::BAD_REQUEST);
101 let body: serde_json::Value = res.json().await.unwrap();
102 assert_eq!(body["error"], "InvalidRequest");
103 let res = client
104 .post(format!(
105 "{}/xrpc/com.atproto.identity.submitPlcOperation",
106 base_url().await
107 ))
108 .bearer_auth(&token)
109 .json(&json!({
110 "operation": { "type": "plc_operation", "rotationKeys": [], "verificationMethods": {},
111 "alsoKnownAs": [], "services": {}, "prev": null }
112 }))
113 .send()
114 .await
115 .unwrap();
116 assert_eq!(res.status(), StatusCode::BAD_REQUEST);
117 let handle = did.split(':').next_back().unwrap_or("user");
118 let res = client.post(format!("{}/xrpc/com.atproto.identity.submitPlcOperation", base_url().await))
119 .bearer_auth(&token).json(&json!({
120 "operation": { "type": "plc_operation", "rotationKeys": ["did:key:z123"],
121 "verificationMethods": { "atproto": "did:key:z456" },
122 "alsoKnownAs": [format!("at://{}", handle)],
123 "services": { "atproto_pds": { "type": "AtprotoPersonalDataServer", "endpoint": "https://wrong.example.com" } },
124 "prev": null, "sig": "fake_signature" }
125 })).send().await.unwrap();
126 assert_eq!(res.status(), StatusCode::BAD_REQUEST);
127 let res = client.post(format!("{}/xrpc/com.atproto.identity.submitPlcOperation", base_url().await))
128 .bearer_auth(&token).json(&json!({
129 "operation": { "type": "plc_operation", "rotationKeys": ["did:key:zWrongRotationKey123"],
130 "verificationMethods": { "atproto": "did:key:zWrongVerificationKey456" },
131 "alsoKnownAs": [format!("at://{}", handle)],
132 "services": { "atproto_pds": { "type": "AtprotoPersonalDataServer", "endpoint": format!("https://{}", hostname) } },
133 "prev": null, "sig": "fake_signature" }
134 })).send().await.unwrap();
135 assert_eq!(res.status(), StatusCode::BAD_REQUEST);
136 let body: serde_json::Value = res.json().await.unwrap();
137 assert_eq!(body["error"], "InvalidRequest");
138 assert!(
139 body["message"]
140 .as_str()
141 .unwrap_or("")
142 .contains("signing key")
143 || body["message"].as_str().unwrap_or("").contains("rotation")
144 );
145 let res = client.post(format!("{}/xrpc/com.atproto.identity.submitPlcOperation", base_url().await))
146 .bearer_auth(&token).json(&json!({
147 "operation": { "type": "plc_operation", "rotationKeys": ["did:key:z123"],
148 "verificationMethods": { "atproto": "did:key:z456" },
149 "alsoKnownAs": ["at://totally.wrong.handle"],
150 "services": { "atproto_pds": { "type": "AtprotoPersonalDataServer", "endpoint": format!("https://{}", hostname) } },
151 "prev": null, "sig": "fake_signature" }
152 })).send().await.unwrap();
153 assert_eq!(res.status(), StatusCode::BAD_REQUEST);
154 let res = client.post(format!("{}/xrpc/com.atproto.identity.submitPlcOperation", base_url().await))
155 .bearer_auth(&token).json(&json!({
156 "operation": { "type": "plc_operation", "rotationKeys": ["did:key:z123"],
157 "verificationMethods": { "atproto": "did:key:z456" },
158 "alsoKnownAs": ["at://user"],
159 "services": { "atproto_pds": { "type": "WrongServiceType", "endpoint": format!("https://{}", hostname) } },
160 "prev": null, "sig": "fake_signature" }
161 })).send().await.unwrap();
162 assert_eq!(res.status(), StatusCode::BAD_REQUEST);
163}
164
165#[tokio::test]
166async fn test_plc_token_lifecycle() {
167 let client = client();
168 let (token, did) = create_account_and_login(&client).await;
169 let res = client
170 .post(format!(
171 "{}/xrpc/com.atproto.identity.requestPlcOperationSignature",
172 base_url().await
173 ))
174 .bearer_auth(&token)
175 .send()
176 .await
177 .unwrap();
178 assert_eq!(res.status(), StatusCode::OK);
179 let db_url = get_db_connection_string().await;
180 let pool = PgPool::connect(&db_url).await.unwrap();
181 let row = sqlx::query!(
182 "SELECT t.token, t.expires_at FROM plc_operation_tokens t JOIN users u ON t.user_id = u.id WHERE u.did = $1",
183 did
184 ).fetch_optional(&pool).await.unwrap();
185 assert!(row.is_some(), "PLC token should be created in database");
186 let row = row.unwrap();
187 assert_eq!(row.token.len(), 11, "Token should be in format xxxxx-xxxxx");
188 assert!(row.token.contains('-'), "Token should contain hyphen");
189 assert!(
190 row.expires_at > chrono::Utc::now(),
191 "Token should not be expired"
192 );
193 let diff = row.expires_at - chrono::Utc::now();
194 assert!(
195 diff.num_minutes() >= 9 && diff.num_minutes() <= 11,
196 "Token should expire in ~10 minutes"
197 );
198 let token1 = row.token.clone();
199 let res = client
200 .post(format!(
201 "{}/xrpc/com.atproto.identity.requestPlcOperationSignature",
202 base_url().await
203 ))
204 .bearer_auth(&token)
205 .send()
206 .await
207 .unwrap();
208 assert_eq!(res.status(), StatusCode::OK);
209 let token2 = sqlx::query_scalar!(
210 "SELECT t.token FROM plc_operation_tokens t JOIN users u ON t.user_id = u.id WHERE u.did = $1", did
211 ).fetch_one(&pool).await.unwrap();
212 assert_ne!(token1, token2, "Second request should generate a new token");
213 let count: i64 = sqlx::query_scalar!(
214 "SELECT COUNT(*) as \"count!\" FROM plc_operation_tokens t JOIN users u ON t.user_id = u.id WHERE u.did = $1", did
215 ).fetch_one(&pool).await.unwrap();
216 assert_eq!(count, 1, "Should only have one token per user");
217}