this repo has no description
1mod common;
2use common::*;
3use reqwest::StatusCode;
4use serde_json::{Value, json};
5use wiremock::matchers::{method, path};
6use wiremock::{Mock, MockServer, ResponseTemplate};
7
8#[tokio::test]
9async fn test_create_self_hosted_did_web() {
10 let client = client();
11 let handle = format!("selfweb_{}", uuid::Uuid::new_v4());
12 let payload = json!({
13 "handle": handle,
14 "email": format!("{}@example.com", handle),
15 "password": "password",
16 "didType": "web"
17 });
18 let res = client
19 .post(format!(
20 "{}/xrpc/com.atproto.server.createAccount",
21 base_url().await
22 ))
23 .json(&payload)
24 .send()
25 .await
26 .expect("Failed to send request");
27 if res.status() != StatusCode::OK {
28 let body: Value = res.json().await.unwrap_or(json!({"error": "parse failed"}));
29 panic!("createAccount failed: {:?}", body);
30 }
31 let body: Value = res.json().await.expect("Response was not JSON");
32 let did = body["did"].as_str().expect("No DID in response");
33 assert!(
34 did.starts_with("did:web:"),
35 "DID should start with did:web:, got: {}",
36 did
37 );
38 assert!(
39 did.contains(&handle),
40 "DID should contain handle {}, got: {}",
41 handle,
42 did
43 );
44 assert!(
45 !did.contains(":u:"),
46 "Self-hosted did:web should use subdomain format (no :u:), got: {}",
47 did
48 );
49 let jwt = verify_new_account(&client, did).await;
50 let res = client
51 .get(format!("{}/u/{}/did.json", base_url().await, handle))
52 .send()
53 .await
54 .expect("Failed to fetch DID doc via path");
55 assert_eq!(
56 res.status(),
57 StatusCode::OK,
58 "Self-hosted did:web should have DID doc served by PDS (via path for backwards compat)"
59 );
60 let doc: Value = res.json().await.expect("DID doc was not JSON");
61 assert_eq!(doc["id"], did);
62 assert!(
63 doc["verificationMethod"][0]["publicKeyMultibase"].is_string(),
64 "DID doc should have publicKeyMultibase"
65 );
66 let res = client
67 .post(format!(
68 "{}/xrpc/com.atproto.repo.createRecord",
69 base_url().await
70 ))
71 .bearer_auth(&jwt)
72 .json(&json!({
73 "repo": did,
74 "collection": "app.bsky.feed.post",
75 "record": {
76 "$type": "app.bsky.feed.post",
77 "text": "Hello from did:web!",
78 "createdAt": chrono::Utc::now().to_rfc3339()
79 }
80 }))
81 .send()
82 .await
83 .expect("Failed to create post");
84 assert_eq!(
85 res.status(),
86 StatusCode::OK,
87 "Self-hosted did:web account should be able to create records"
88 );
89}
90
91#[tokio::test]
92async fn test_external_did_web_no_local_doc() {
93 let client = client();
94 let mock_server = MockServer::start().await;
95 let mock_uri = mock_server.uri();
96 let mock_addr = mock_uri.trim_start_matches("http://");
97 let did = format!("did:web:{}", mock_addr.replace(":", "%3A"));
98 let handle = format!("extweb_{}", uuid::Uuid::new_v4());
99 let pds_endpoint = base_url().await.replace("http://", "https://");
100 let did_doc = json!({
101 "@context": ["https://www.w3.org/ns/did/v1"],
102 "id": did,
103 "service": [{
104 "id": "#atproto_pds",
105 "type": "AtprotoPersonalDataServer",
106 "serviceEndpoint": pds_endpoint
107 }]
108 });
109 Mock::given(method("GET"))
110 .and(path("/.well-known/did.json"))
111 .respond_with(ResponseTemplate::new(200).set_body_json(did_doc))
112 .mount(&mock_server)
113 .await;
114 let payload = json!({
115 "handle": handle,
116 "email": format!("{}@example.com", handle),
117 "password": "password",
118 "didType": "web-external",
119 "did": did
120 });
121 let res = client
122 .post(format!(
123 "{}/xrpc/com.atproto.server.createAccount",
124 base_url().await
125 ))
126 .json(&payload)
127 .send()
128 .await
129 .expect("Failed to send request");
130 if res.status() != StatusCode::OK {
131 let body: Value = res.json().await.unwrap_or(json!({"error": "parse failed"}));
132 panic!("createAccount failed: {:?}", body);
133 }
134 let res = client
135 .get(format!("{}/u/{}/did.json", base_url().await, handle))
136 .send()
137 .await
138 .expect("Failed to fetch DID doc");
139 assert_eq!(
140 res.status(),
141 StatusCode::NOT_FOUND,
142 "External did:web should NOT have DID doc served by PDS"
143 );
144 let body: Value = res.json().await.expect("Response was not JSON");
145 assert!(
146 body["message"].as_str().unwrap_or("").contains("External"),
147 "Error message should indicate external did:web"
148 );
149}
150
151#[tokio::test]
152async fn test_plc_operations_blocked_for_did_web() {
153 let client = client();
154 let handle = format!("plcblock_{}", uuid::Uuid::new_v4());
155 let payload = json!({
156 "handle": handle,
157 "email": format!("{}@example.com", handle),
158 "password": "password",
159 "didType": "web"
160 });
161 let res = client
162 .post(format!(
163 "{}/xrpc/com.atproto.server.createAccount",
164 base_url().await
165 ))
166 .json(&payload)
167 .send()
168 .await
169 .expect("Failed to send request");
170 assert_eq!(res.status(), StatusCode::OK);
171 let body: Value = res.json().await.expect("Response was not JSON");
172 let did = body["did"].as_str().expect("No DID").to_string();
173 let jwt = verify_new_account(&client, &did).await;
174 let res = client
175 .post(format!(
176 "{}/xrpc/com.atproto.identity.signPlcOperation",
177 base_url().await
178 ))
179 .bearer_auth(&jwt)
180 .json(&json!({
181 "token": "fake-token"
182 }))
183 .send()
184 .await
185 .expect("Failed to send request");
186 assert_eq!(
187 res.status(),
188 StatusCode::BAD_REQUEST,
189 "signPlcOperation should be blocked for did:web users"
190 );
191 let body: Value = res.json().await.expect("Response was not JSON");
192 assert!(
193 body["message"].as_str().unwrap_or("").contains("did:plc"),
194 "Error should mention did:plc: {:?}",
195 body
196 );
197 let res = client
198 .post(format!(
199 "{}/xrpc/com.atproto.identity.submitPlcOperation",
200 base_url().await
201 ))
202 .bearer_auth(&jwt)
203 .json(&json!({
204 "operation": {}
205 }))
206 .send()
207 .await
208 .expect("Failed to send request");
209 assert_eq!(
210 res.status(),
211 StatusCode::BAD_REQUEST,
212 "submitPlcOperation should be blocked for did:web users"
213 );
214}
215
216#[tokio::test]
217async fn test_get_recommended_did_credentials_no_rotation_keys_for_did_web() {
218 let client = client();
219 let handle = format!("creds_{}", uuid::Uuid::new_v4());
220 let payload = json!({
221 "handle": handle,
222 "email": format!("{}@example.com", handle),
223 "password": "password",
224 "didType": "web"
225 });
226 let res = client
227 .post(format!(
228 "{}/xrpc/com.atproto.server.createAccount",
229 base_url().await
230 ))
231 .json(&payload)
232 .send()
233 .await
234 .expect("Failed to send request");
235 assert_eq!(res.status(), StatusCode::OK);
236 let body: Value = res.json().await.expect("Response was not JSON");
237 let did = body["did"].as_str().expect("No DID").to_string();
238 let jwt = verify_new_account(&client, &did).await;
239 let res = client
240 .get(format!(
241 "{}/xrpc/com.atproto.identity.getRecommendedDidCredentials",
242 base_url().await
243 ))
244 .bearer_auth(&jwt)
245 .send()
246 .await
247 .expect("Failed to send request");
248 assert_eq!(res.status(), StatusCode::OK);
249 let body: Value = res.json().await.expect("Response was not JSON");
250 let rotation_keys = body["rotationKeys"]
251 .as_array()
252 .expect("rotationKeys should be an array");
253 assert!(
254 rotation_keys.is_empty(),
255 "did:web should have no rotation keys, got: {:?}",
256 rotation_keys
257 );
258 assert!(
259 body["verificationMethods"].is_object(),
260 "verificationMethods should be present"
261 );
262 assert!(body["services"].is_object(), "services should be present");
263}
264
265#[tokio::test]
266async fn test_did_plc_still_works_with_did_type_param() {
267 let client = client();
268 let handle = format!("plctype_{}", uuid::Uuid::new_v4());
269 let payload = json!({
270 "handle": handle,
271 "email": format!("{}@example.com", handle),
272 "password": "password",
273 "didType": "plc"
274 });
275 let res = client
276 .post(format!(
277 "{}/xrpc/com.atproto.server.createAccount",
278 base_url().await
279 ))
280 .json(&payload)
281 .send()
282 .await
283 .expect("Failed to send request");
284 assert_eq!(res.status(), StatusCode::OK);
285 let body: Value = res.json().await.expect("Response was not JSON");
286 let did = body["did"].as_str().expect("No DID").to_string();
287 assert!(
288 did.starts_with("did:plc:"),
289 "DID with didType=plc should be did:plc:, got: {}",
290 did
291 );
292}
293
294#[tokio::test]
295async fn test_external_did_web_requires_did_field() {
296 let client = client();
297 let handle = format!("nodid_{}", uuid::Uuid::new_v4());
298 let payload = json!({
299 "handle": handle,
300 "email": format!("{}@example.com", handle),
301 "password": "password",
302 "didType": "web-external"
303 });
304 let res = client
305 .post(format!(
306 "{}/xrpc/com.atproto.server.createAccount",
307 base_url().await
308 ))
309 .json(&payload)
310 .send()
311 .await
312 .expect("Failed to send request");
313 assert_eq!(
314 res.status(),
315 StatusCode::BAD_REQUEST,
316 "web-external without did should fail"
317 );
318 let body: Value = res.json().await.expect("Response was not JSON");
319 assert!(
320 body["message"].as_str().unwrap_or("").contains("did"),
321 "Error should mention did field is required: {:?}",
322 body
323 );
324}