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::BAD_REQUEST); 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(':').last().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}