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": "Testpass123!", 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 101 let reserve_res = client 102 .post(format!( 103 "{}/xrpc/com.atproto.server.reserveSigningKey", 104 base_url().await 105 )) 106 .json(&json!({ "did": did })) 107 .send() 108 .await 109 .expect("Failed to reserve signing key"); 110 assert_eq!(reserve_res.status(), StatusCode::OK); 111 let reserve_body: Value = reserve_res.json().await.expect("Response was not JSON"); 112 let signing_key = reserve_body["signingKey"] 113 .as_str() 114 .expect("No signingKey returned"); 115 let public_key_multibase = signing_key 116 .strip_prefix("did:key:") 117 .expect("signingKey should start with did:key:"); 118 119 let did_doc = json!({ 120 "@context": ["https://www.w3.org/ns/did/v1"], 121 "id": did, 122 "verificationMethod": [{ 123 "id": format!("{}#atproto", did), 124 "type": "Multikey", 125 "controller": did, 126 "publicKeyMultibase": public_key_multibase 127 }], 128 "service": [{ 129 "id": "#atproto_pds", 130 "type": "AtprotoPersonalDataServer", 131 "serviceEndpoint": pds_endpoint 132 }] 133 }); 134 Mock::given(method("GET")) 135 .and(path("/.well-known/did.json")) 136 .respond_with(ResponseTemplate::new(200).set_body_json(did_doc)) 137 .mount(&mock_server) 138 .await; 139 let payload = json!({ 140 "handle": handle, 141 "email": format!("{}@example.com", handle), 142 "password": "Testpass123!", 143 "didType": "web-external", 144 "did": did, 145 "signingKey": signing_key 146 }); 147 let res = client 148 .post(format!( 149 "{}/xrpc/com.atproto.server.createAccount", 150 base_url().await 151 )) 152 .json(&payload) 153 .send() 154 .await 155 .expect("Failed to send request"); 156 if res.status() != StatusCode::OK { 157 let body: Value = res.json().await.unwrap_or(json!({"error": "parse failed"})); 158 panic!("createAccount failed: {:?}", body); 159 } 160 let res = client 161 .get(format!("{}/u/{}/did.json", base_url().await, handle)) 162 .send() 163 .await 164 .expect("Failed to fetch DID doc"); 165 assert_eq!( 166 res.status(), 167 StatusCode::NOT_FOUND, 168 "External did:web should NOT have DID doc served by PDS" 169 ); 170 let body: Value = res.json().await.expect("Response was not JSON"); 171 assert!( 172 body["message"].as_str().unwrap_or("").contains("External"), 173 "Error message should indicate external did:web" 174 ); 175} 176 177#[tokio::test] 178async fn test_plc_operations_blocked_for_did_web() { 179 let client = client(); 180 let handle = format!("plcblock_{}", uuid::Uuid::new_v4()); 181 let payload = json!({ 182 "handle": handle, 183 "email": format!("{}@example.com", handle), 184 "password": "Testpass123!", 185 "didType": "web" 186 }); 187 let res = client 188 .post(format!( 189 "{}/xrpc/com.atproto.server.createAccount", 190 base_url().await 191 )) 192 .json(&payload) 193 .send() 194 .await 195 .expect("Failed to send request"); 196 assert_eq!(res.status(), StatusCode::OK); 197 let body: Value = res.json().await.expect("Response was not JSON"); 198 let did = body["did"].as_str().expect("No DID").to_string(); 199 let jwt = verify_new_account(&client, &did).await; 200 let res = client 201 .post(format!( 202 "{}/xrpc/com.atproto.identity.signPlcOperation", 203 base_url().await 204 )) 205 .bearer_auth(&jwt) 206 .json(&json!({ 207 "token": "fake-token" 208 })) 209 .send() 210 .await 211 .expect("Failed to send request"); 212 assert_eq!( 213 res.status(), 214 StatusCode::BAD_REQUEST, 215 "signPlcOperation should be blocked for did:web users" 216 ); 217 let body: Value = res.json().await.expect("Response was not JSON"); 218 assert!( 219 body["message"].as_str().unwrap_or("").contains("did:plc"), 220 "Error should mention did:plc: {:?}", 221 body 222 ); 223 let res = client 224 .post(format!( 225 "{}/xrpc/com.atproto.identity.submitPlcOperation", 226 base_url().await 227 )) 228 .bearer_auth(&jwt) 229 .json(&json!({ 230 "operation": {} 231 })) 232 .send() 233 .await 234 .expect("Failed to send request"); 235 assert_eq!( 236 res.status(), 237 StatusCode::BAD_REQUEST, 238 "submitPlcOperation should be blocked for did:web users" 239 ); 240} 241 242#[tokio::test] 243async fn test_get_recommended_did_credentials_no_rotation_keys_for_did_web() { 244 let client = client(); 245 let handle = format!("creds_{}", uuid::Uuid::new_v4()); 246 let payload = json!({ 247 "handle": handle, 248 "email": format!("{}@example.com", handle), 249 "password": "Testpass123!", 250 "didType": "web" 251 }); 252 let res = client 253 .post(format!( 254 "{}/xrpc/com.atproto.server.createAccount", 255 base_url().await 256 )) 257 .json(&payload) 258 .send() 259 .await 260 .expect("Failed to send request"); 261 assert_eq!(res.status(), StatusCode::OK); 262 let body: Value = res.json().await.expect("Response was not JSON"); 263 let did = body["did"].as_str().expect("No DID").to_string(); 264 let jwt = verify_new_account(&client, &did).await; 265 let res = client 266 .get(format!( 267 "{}/xrpc/com.atproto.identity.getRecommendedDidCredentials", 268 base_url().await 269 )) 270 .bearer_auth(&jwt) 271 .send() 272 .await 273 .expect("Failed to send request"); 274 assert_eq!(res.status(), StatusCode::OK); 275 let body: Value = res.json().await.expect("Response was not JSON"); 276 let rotation_keys = body["rotationKeys"] 277 .as_array() 278 .expect("rotationKeys should be an array"); 279 assert!( 280 rotation_keys.is_empty(), 281 "did:web should have no rotation keys, got: {:?}", 282 rotation_keys 283 ); 284 assert!( 285 body["verificationMethods"].is_object(), 286 "verificationMethods should be present" 287 ); 288 assert!(body["services"].is_object(), "services should be present"); 289} 290 291#[tokio::test] 292async fn test_did_plc_still_works_with_did_type_param() { 293 let client = client(); 294 let handle = format!("plctype_{}", uuid::Uuid::new_v4()); 295 let payload = json!({ 296 "handle": handle, 297 "email": format!("{}@example.com", handle), 298 "password": "Testpass123!", 299 "didType": "plc" 300 }); 301 let res = client 302 .post(format!( 303 "{}/xrpc/com.atproto.server.createAccount", 304 base_url().await 305 )) 306 .json(&payload) 307 .send() 308 .await 309 .expect("Failed to send request"); 310 assert_eq!(res.status(), StatusCode::OK); 311 let body: Value = res.json().await.expect("Response was not JSON"); 312 let did = body["did"].as_str().expect("No DID").to_string(); 313 assert!( 314 did.starts_with("did:plc:"), 315 "DID with didType=plc should be did:plc:, got: {}", 316 did 317 ); 318} 319 320#[tokio::test] 321async fn test_external_did_web_requires_did_field() { 322 let client = client(); 323 let handle = format!("nodid_{}", uuid::Uuid::new_v4()); 324 let payload = json!({ 325 "handle": handle, 326 "email": format!("{}@example.com", handle), 327 "password": "Testpass123!", 328 "didType": "web-external" 329 }); 330 let res = client 331 .post(format!( 332 "{}/xrpc/com.atproto.server.createAccount", 333 base_url().await 334 )) 335 .json(&payload) 336 .send() 337 .await 338 .expect("Failed to send request"); 339 assert_eq!( 340 res.status(), 341 StatusCode::BAD_REQUEST, 342 "web-external without did should fail" 343 ); 344 let body: Value = res.json().await.expect("Response was not JSON"); 345 assert!( 346 body["message"].as_str().unwrap_or("").contains("did"), 347 "Error should mention did field is required: {:?}", 348 body 349 ); 350}