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}