diff --git a/e2e/src/tests/admin.rs b/e2e/src/tests/admin.rs index a50277e5..127133a4 100644 --- a/e2e/src/tests/admin.rs +++ b/e2e/src/tests/admin.rs @@ -1,5 +1,4 @@ -use pkarr::Keypair; -use pubky_testnet::pubky::{Method, PubkyHttpClient, StatusCode}; +use pubky_testnet::pubky::{Keypair, Method, PubkyHttpClient, StatusCode}; use pubky_testnet::{ pubky_homeserver::{ConfigToml, Domain, MockDataDir}, Testnet, @@ -71,7 +70,7 @@ async fn admin_info_includes_metadata() { assert_eq!(response.status(), StatusCode::OK); let body: InfoResponse = response.json().await.unwrap(); - assert_eq!(body.public_key, homeserver.public_key().to_string()); + assert_eq!(body.public_key, homeserver.public_key().z32()); assert_eq!(body.pkarr_pubky_address, Some(expected_pubky_endpoint)); assert_eq!(body.pkarr_icann_domain, Some(expected_icann_endpoint)); assert_eq!(body.version, env!("CARGO_PKG_VERSION")); diff --git a/e2e/src/tests/auth.rs b/e2e/src/tests/auth.rs index f6e0c2a3..3fe18211 100644 --- a/e2e/src/tests/auth.rs +++ b/e2e/src/tests/auth.rs @@ -39,7 +39,7 @@ async fn disabled_user() { // Create a brand-new user and session let signer = pubky.signer(Keypair::random()); - let pubky = signer.public_key().clone(); + let user_pubky = signer.public_key().clone(); let session = signer.signup(&server.public_key(), None).await.unwrap(); // Create a test file to ensure the user can write to their account @@ -69,7 +69,7 @@ async fn disabled_user() { let response = admin_client .request( Method::POST, - &format!("http://{admin_socket}/users/{pubky}/disable"), + &format!("http://{admin_socket}/users/{}/disable", user_pubky.z32()), ) .header("X-Admin-Password", "admin") .send() @@ -99,7 +99,7 @@ async fn disabled_user() { .signin() .await .expect("Signin should succeed for disabled users"); - assert_eq!(session2.info().public_key(), &pubky); + assert_eq!(session2.info().public_key(), &user_pubky); } #[tokio::test] diff --git a/e2e/src/tests/events.rs b/e2e/src/tests/events.rs index d3912452..c4cf9f95 100644 --- a/e2e/src/tests/events.rs +++ b/e2e/src/tests/events.rs @@ -21,6 +21,7 @@ async fn events_stream_basic_modes() { let testnet = EphemeralTestnet::start().await.unwrap(); let server = testnet.homeserver_app(); + let server_host = server.public_key().z32(); let pubky = testnet.sdk().unwrap(); // Create one user with 250 events - reuse for all subtests @@ -28,6 +29,7 @@ async fn events_stream_basic_modes() { let signer = pubky.signer(keypair); let session = signer.signup(&server.public_key(), None).await.unwrap(); let user_pubky = signer.public_key(); + let user_host = user_pubky.z32(); // Create 250 events (internal batch size is 100, tests pagination) for i in 0..250 { @@ -43,8 +45,7 @@ async fn events_stream_basic_modes() { // ==== Test 1: Historical auto-pagination (>100 events) ==== let stream_url = format!( "https://{}/events-stream?user={}&limit=255", - server.public_key(), - user_pubky + server_host, user_host ); let response = pubky .client() @@ -96,8 +97,7 @@ async fn events_stream_basic_modes() { // ==== Test 2: Finite limit enforcement ==== let stream_url = format!( "https://{}/events-stream?user={}&limit=50", - server.public_key(), - user_pubky + server_host, user_host ); let response = pubky .client() @@ -128,9 +128,7 @@ async fn events_stream_basic_modes() { // Reuse cursor_250 captured from Test 1 (event 250) let stream_url = format!( "https://{}/events-stream?user={}:{}&live=true", - server.public_key(), - user_pubky, - cursor_250 + server_host, user_host, cursor_250 ); let response = pubky .client() @@ -171,9 +169,7 @@ async fn events_stream_basic_modes() { // ==== Test 4: Live mode with limit - transitions from historical to live until limit reached ==== let stream_url = format!( "https://{}/events-stream?user={}:{}&live=true&limit=10", - server.public_key(), - user_pubky, - cursor_250 + server_host, user_host, cursor_250 ); let response = pubky .client() @@ -229,9 +225,7 @@ async fn events_stream_basic_modes() { // ==== Test 5: Batch mode (live=false) - connection closes after historical events ==== let stream_url = format!( "https://{}/events-stream?user={}:{}&limit=5", - server.public_key(), - user_pubky, - cursor_250 + server_host, user_host, cursor_250 ); let response = pubky .client() @@ -276,9 +270,7 @@ async fn events_stream_basic_modes() { // Fetch from cursor_250 which we know has 5 DEL events after it let stream_url = format!( "https://{}/events-stream?user={}:{}&limit=6", - server.public_key(), - user_pubky, - cursor_250 + server_host, user_host, cursor_250 ); let response = pubky .client() @@ -347,11 +339,11 @@ async fn events_stream_basic_modes() { .unwrap(); // Get the content_hash from HTTP GET headers (ETag) - let get_url = format!("https://{}/pub/hash_test.txt", server.public_key()); + let get_url = format!("https://{}/pub/hash_test.txt", server_host); let get_response = pubky .client() .request(Method::GET, &get_url) - .header("pubky-host", user_pubky.to_string()) + .header("pubky-host", user_host.to_string()) .send() .await .unwrap(); @@ -369,8 +361,7 @@ async fn events_stream_basic_modes() { // Get the content_hash from event stream let stream_url = format!( "https://{}/events-stream?user={}&path=/pub/hash_test.txt", - server.public_key(), - user_pubky + server_host, user_host ); let stream_response = pubky .client() @@ -412,8 +403,8 @@ async fn events_stream_basic_modes() { // Test 7a: Batch mode should close immediately let stream_url = format!( "https://{}/events-stream?user={}", - server.public_key(), - empty_user_pubky + server_host, + empty_user_pubky.z32() ); let response = pubky .client() @@ -432,8 +423,8 @@ async fn events_stream_basic_modes() { // Test 7b: Live mode should stay open and receive new events let stream_url = format!( "https://{}/events-stream?user={}&live=true", - server.public_key(), - empty_user_pubky + server_host, + empty_user_pubky.z32() ); let response = pubky .client() @@ -503,8 +494,8 @@ async fn events_stream_basic_modes() { // Test forward order first to establish baseline let stream_url = format!( "https://{}/events-stream?user={}&limit=5", - server.public_key(), - reverse_user_pubky + server_host, + reverse_user_pubky.z32() ); let response = pubky .client() @@ -532,8 +523,8 @@ async fn events_stream_basic_modes() { // Test reverse order let stream_url = format!( "https://{}/events-stream?user={}&reverse=true&limit=5", - server.public_key(), - reverse_user_pubky + server_host, + reverse_user_pubky.z32() ); let response = pubky .client() @@ -602,6 +593,7 @@ async fn events_stream_multiple_users() { let testnet = EphemeralTestnet::start().await.unwrap(); let server = testnet.homeserver_app(); + let server_host = server.public_key().z32(); let pubky = testnet.sdk().unwrap(); let keypair1 = Keypair::random(); @@ -638,9 +630,9 @@ async fn events_stream_multiple_users() { // Stream events for user1 and user2 (should get 5 events total) let stream_url = format!( "https://{}/events-stream?user={}&user={}", - server.public_key(), - pubky1, - pubky2 + server_host, + pubky1.z32(), + pubky2.z32() ); let response = pubky @@ -673,32 +665,23 @@ async fn events_stream_multiple_users() { // Verify we got events from both users assert_eq!(events.len(), 5, "Should receive 5 events total"); - let user1_events = events - .iter() - .filter(|e| e.contains(&pubky1.to_string())) - .count(); - let user2_events = events - .iter() - .filter(|e| e.contains(&pubky2.to_string())) - .count(); + let user1_events = events.iter().filter(|e| e.contains(&pubky1.z32())).count(); + let user2_events = events.iter().filter(|e| e.contains(&pubky2.z32())).count(); assert_eq!(user1_events, 3, "Should receive 3 events from user1"); assert_eq!(user2_events, 2, "Should receive 2 events from user2"); // Verify no events from user3 - let user3_events = events - .iter() - .filter(|e| e.contains(&pubky3.to_string())) - .count(); + let user3_events = events.iter().filter(|e| e.contains(&pubky3.z32())).count(); assert_eq!(user3_events, 0, "Should not receive events from user3"); // Now test that returned cursor values are correct with per-user cursors // Get the first 2 events and track cursor per user let stream_url_for_cursor = format!( "https://{}/events-stream?user={}&user={}&limit=2", - server.public_key(), - pubky1, - pubky2 + server_host, + pubky1.z32(), + pubky2.z32() ); let response = pubky @@ -725,9 +708,9 @@ async fn events_stream_multiple_users() { let cursor = cursor_line.strip_prefix("cursor: ").unwrap().to_string(); // Determine which user this event belongs to - if lines[0].contains(&pubky1.to_string()) { + if lines[0].contains(&pubky1.z32()) { user1_cursor = cursor; - } else if lines[0].contains(&pubky2.to_string()) { + } else if lines[0].contains(&pubky2.z32()) { user2_cursor = cursor; } } @@ -744,18 +727,18 @@ async fn events_stream_multiple_users() { // Now request the remaining events using per-user cursors // This should properly handle the case where each user has a different cursor position // Build the URL conditionally based on whether we have cursors - let mut url_parts = vec![format!("https://{}/events-stream?", server.public_key())]; + let mut url_parts = vec![format!("https://{}/events-stream?", server_host)]; if !user1_cursor.is_empty() { - url_parts.push(format!("user={}:{}", pubky1, user1_cursor)); + url_parts.push(format!("user={}:{}", pubky1.z32(), user1_cursor)); } else { - url_parts.push(format!("user={}", pubky1)); + url_parts.push(format!("user={}", pubky1.z32())); } if !user2_cursor.is_empty() { - url_parts.push(format!("&user={}:{}", pubky2, user2_cursor)); + url_parts.push(format!("&user={}:{}", pubky2.z32(), user2_cursor)); } else { - url_parts.push(format!("&user={}", pubky2)); + url_parts.push(format!("&user={}", pubky2.z32())); } let stream_url_with_cursor = url_parts.join(""); @@ -792,11 +775,11 @@ async fn events_stream_multiple_users() { let user1_remaining = remaining_events .iter() - .filter(|e| e.contains(&pubky1.to_string())) + .filter(|e| e.contains(&pubky1.z32())) .count(); let user2_remaining = remaining_events .iter() - .filter(|e| e.contains(&pubky2.to_string())) + .filter(|e| e.contains(&pubky2.z32())) .count(); // With per-user cursors, each user's position is tracked independently: @@ -824,6 +807,7 @@ async fn events_stream_validation_errors() { let testnet = EphemeralTestnet::start().await.unwrap(); let server = testnet.homeserver_app(); + let server_host = server.public_key().z32(); let pubky = testnet.sdk().unwrap(); let keypair1 = Keypair::random(); @@ -838,7 +822,7 @@ async fn events_stream_validation_errors() { let invalid_pubkey = "invalid_key_not_zbase32"; // Test 1: No user parameter - let stream_url = format!("https://{}/events-stream", server.public_key()); + let stream_url = format!("https://{}/events-stream", server_host); let response = pubky .client() .request(Method::GET, &stream_url) @@ -857,11 +841,11 @@ async fn events_stream_validation_errors() { let mut query_params = vec![]; for _i in 0..51 { let keypair = Keypair::random(); - query_params.push(format!("user={}", keypair.public_key())); + query_params.push(format!("user={}", keypair.public_key().z32())); } let stream_url = format!( "https://{}/events-stream?{}", - server.public_key(), + server_host, query_params.join("&") ); let response = pubky @@ -877,8 +861,7 @@ async fn events_stream_validation_errors() { // Test 3: Invalid public key format let stream_url = format!( "https://{}/events-stream?user={}", - server.public_key(), - invalid_pubkey + server_host, invalid_pubkey ); let response = pubky .client() @@ -897,8 +880,8 @@ async fn events_stream_validation_errors() { // Test 4: Valid key but user not registered let stream_url = format!( "https://{}/events-stream?user={}", - server.public_key(), - pubky2 + server_host, + pubky2.z32() ); let response = pubky .client() @@ -915,9 +898,9 @@ async fn events_stream_validation_errors() { // Test 5: Mix of valid registered and unregistered user let stream_url = format!( "https://{}/events-stream?user={}&user={}", - server.public_key(), - pubky1, - pubky2 + server_host, + pubky1.z32(), + pubky2.z32() ); let response = pubky .client() @@ -934,8 +917,8 @@ async fn events_stream_validation_errors() { // Test 6: Mix of valid user and invalid key format let stream_url = format!( "https://{}/events-stream?user={}&user={}", - server.public_key(), - pubky1, + server_host, + pubky1.z32(), invalid_pubkey ); let response = pubky @@ -955,9 +938,7 @@ async fn events_stream_validation_errors() { // Test 7: Multiple invalid keys let stream_url = format!( "https://{}/events-stream?user={}&user={}", - server.public_key(), - invalid_pubkey, - "another_invalid_key" + server_host, invalid_pubkey, "another_invalid_key" ); let response = pubky .client() @@ -974,8 +955,8 @@ async fn events_stream_validation_errors() { // Test 8: Incompatible live=true with reverse=true let stream_url = format!( "https://{}/events-stream?user={}&live=true&reverse=true", - server.public_key(), - pubky1 + server_host, + pubky1.z32() ); let response = pubky .client() @@ -1002,8 +983,8 @@ async fn events_stream_validation_errors() { // Test 9a: Malformed cursor (non-numeric) let stream_url = format!( "https://{}/events-stream?user={}:abc123xyz", - server.public_key(), - pubky1 + server_host, + pubky1.z32() ); let response = pubky .client() @@ -1022,8 +1003,8 @@ async fn events_stream_validation_errors() { // Test 9b: Negative cursor (technically valid i64, but no events will have negative IDs) let stream_url = format!( "https://{}/events-stream?user={}:-100&limit=10", - server.public_key(), - pubky1 + server_host, + pubky1.z32() ); let response = pubky .client() @@ -1047,8 +1028,8 @@ async fn events_stream_validation_errors() { // Test 9c: Very large cursor beyond any events (should succeed but return no events) let stream_url = format!( "https://{}/events-stream?user={}:999999999&limit=10", - server.public_key(), - pubky1 + server_host, + pubky1.z32() ); let response = pubky .client() @@ -1072,8 +1053,8 @@ async fn events_stream_validation_errors() { // Test 10a: Path without leading slash - should automatically add "/" prefix let stream_url = format!( "https://{}/events-stream?user={}&path=pub/test.txt&limit=1", - server.public_key(), - pubky1 + server_host, + pubky1.z32() ); let response = pubky .client() @@ -1093,8 +1074,8 @@ async fn events_stream_validation_errors() { // Test 10b: Empty path parameter (should be treated as no filter) let stream_url = format!( "https://{}/events-stream?user={}&path=&limit=1", - server.public_key(), - pubky1 + server_host, + pubky1.z32() ); let response = pubky .client() @@ -1122,6 +1103,7 @@ async fn events_stream_path_filter() { let testnet = EphemeralTestnet::start().await.unwrap(); let server = testnet.homeserver_app(); let pubky = testnet.sdk().unwrap(); + let server_host = server.public_key().z32(); // Create 2 users upfront with diverse directory structures let keypair1 = Keypair::random(); @@ -1170,8 +1152,8 @@ async fn events_stream_path_filter() { // ==== Test 1: Basic filtering (both specific and broad paths) ==== let stream_url = format!( "https://{}/events-stream?user={}&path=/pub/files/", - server.public_key(), - pubky1 + server_host, + pubky1.z32() ); let response = pubky .client() @@ -1213,8 +1195,8 @@ async fn events_stream_path_filter() { // Get first 5 with cursor let stream_url = format!( "https://{}/events-stream?user={}&path=/pub/files/&limit=5", - server.public_key(), - pubky2 + server_host, + pubky2.z32() ); let response = pubky .client() @@ -1249,8 +1231,8 @@ async fn events_stream_path_filter() { // Get remaining 5 with cursor let stream_url = format!( "https://{}/events-stream?user={}:{}&path=/pub/files/", - server.public_key(), - pubky2, + server_host, + pubky2.z32(), cursor ); let response = pubky @@ -1286,9 +1268,9 @@ async fn events_stream_path_filter() { // Filter both users by files directory - user1 has 6 events (5 PUT + 1 DEL), user2 has 10 let stream_url = format!( "https://{}/events-stream?user={}&user={}&path=/pub/files/", - server.public_key(), - pubky1, - pubky2 + server_host, + pubky1.z32(), + pubky2.z32() ); let response = pubky .client() @@ -1319,11 +1301,11 @@ async fn events_stream_path_filter() { ); let user1_count = multi_events .iter() - .filter(|e| e.contains(&pubky1.to_string())) + .filter(|e| e.contains(&pubky1.z32())) .count(); let user2_count = multi_events .iter() - .filter(|e| e.contains(&pubky2.to_string())) + .filter(|e| e.contains(&pubky2.z32())) .count(); assert_eq!(user1_count, 6, "Multi-user: Should get 6 from user1"); assert_eq!(user2_count, 10, "Multi-user: Should get 10 from user2"); @@ -1332,8 +1314,8 @@ async fn events_stream_path_filter() { // Use user1's /pub/files/ which has 5 PUT + 1 DEL = 6 events let stream_url = format!( "https://{}/events-stream?user={}&path=/pub/files/&reverse=true&limit=6", - server.public_key(), - pubky1 + server_host, + pubky1.z32() ); let response = pubky .client() @@ -1382,8 +1364,8 @@ async fn events_stream_path_filter() { let stream_url = format!( "https://{}/events-stream?user={}&path=/pub/my_folder/", - server.public_key(), - pubky1 + server_host, + pubky1.z32() ); let response = pubky .client() diff --git a/e2e/src/tests/http.rs b/e2e/src/tests/http.rs index 69c5a68f..9b1c02c4 100644 --- a/e2e/src/tests/http.rs +++ b/e2e/src/tests/http.rs @@ -9,7 +9,7 @@ async fn http_get_pubky() { let client = testnet.client().unwrap(); - let pubky_url = format!("https://{}/", server.public_key()); + let pubky_url = format!("https://{}/", server.public_key().z32()); let response = client .request(Method::GET, &pubky_url) .send() diff --git a/e2e/src/tests/metrics.rs b/e2e/src/tests/metrics.rs index dd22aa02..8c093864 100644 --- a/e2e/src/tests/metrics.rs +++ b/e2e/src/tests/metrics.rs @@ -63,7 +63,7 @@ async fn metrics_comprehensive() { let mock_dir = MockDataDir::new(config, Some(Keypair::from_secret_key(&[0; 32]))).unwrap(); // Extract values we need before getting SDK to avoid borrow conflicts - let (metrics_url, server_public_key) = { + let (metrics_url, server_public_key, server_public_key_z32) = { let server = testnet .create_homeserver_app_with_mock(mock_dir) .await @@ -75,8 +75,9 @@ async fn metrics_comprehensive() { let metrics_socket = metrics_server.listen_socket(); let metrics_url = format!("http://{}/metrics", metrics_socket); let public_key = server.public_key().clone(); + let public_key_z32 = public_key.z32(); - (metrics_url, public_key) + (metrics_url, public_key, public_key_z32) }; let pubky = testnet.sdk().unwrap(); @@ -129,11 +130,13 @@ async fn metrics_comprehensive() { // 3. Test concurrent stream connections to generate metrics let stream_url1 = format!( "https://{}/events-stream?user={}&live=true", - server_public_key, user_pubky1 + server_public_key_z32, + user_pubky1.z32() ); let stream_url2 = format!( "https://{}/events-stream?user={}&live=true", - server_public_key, user_pubky2 + server_public_key_z32, + user_pubky2.z32() ); let response1 = pubky diff --git a/e2e/src/tests/rate_limiting.rs b/e2e/src/tests/rate_limiting.rs index c7e50775..aab2d1e9 100644 --- a/e2e/src/tests/rate_limiting.rs +++ b/e2e/src/tests/rate_limiting.rs @@ -148,7 +148,7 @@ async fn test_limit_events() { let server = testnet.create_homeserver_app_with_mock(mock).await.unwrap(); // Events feed URL (pkarr host form) - let url = format!("https://{}/events/", server.public_key()); + let url = format!("https://{}/events/", server.public_key().z32()); // First request OK let res = client.request(Method::GET, &url).send().await.unwrap(); @@ -243,7 +243,8 @@ async fn test_concurrent_write_read() { let start = Instant::now(); { let mut tasks = Vec::with_capacity(user_count); - for session in sessions.iter().cloned() { + for session in &sessions { + let session = session.clone(); let body = body.clone(); tasks.push(tokio::spawn(async move { session.storage().put(path, body).await.unwrap(); @@ -265,7 +266,8 @@ async fn test_concurrent_write_read() { let start = Instant::now(); { let mut tasks = Vec::with_capacity(user_count); - for session in sessions.iter().cloned() { + for session in &sessions { + let session = session.clone(); tasks.push(tokio::spawn(async move { let resp = session.storage().get(path).await.unwrap(); let _ = resp.bytes().await.unwrap(); // read body to apply full 3 KB download diff --git a/e2e/src/tests/storage.rs b/e2e/src/tests/storage.rs index 0dc13ced..896684ed 100644 --- a/e2e/src/tests/storage.rs +++ b/e2e/src/tests/storage.rs @@ -33,7 +33,7 @@ async fn put_get_delete() { // Use Pubky native method to get data from homeserver let response = pubky .public_storage() - .get(format!("pubky{public_key}/{path}")) + .get(format!("{public_key}/{path}")) .await .unwrap(); @@ -48,7 +48,7 @@ async fn put_get_delete() { let regular_url = format!( "{}pub/foo.txt?pubky-host={}", server.icann_http_url(), - session.info().public_key() + session.info().public_key().z32() ); // We set `non.pubky.host` header as otherwise he client will use by default @@ -115,7 +115,7 @@ async fn put_then_get_json_roundtrip() { // Read back as strongly-typed JSON and assert equality. let got: Payload = pubky .public_storage() - .get_json(format!("pubky{}/{path}", public_key)) + .get_json(format!("{public_key}/{path}")) .await .unwrap(); assert_eq!(got, expected); @@ -229,7 +229,7 @@ async fn unauthorized_put_delete() { // Someone tries to write to owner's namespace -> 401 Unauthorized let owner_url = format!( - "pubky{}/{}", + "{}/{}", owner_session.info().public_key(), path.trim_start_matches('/') ); @@ -321,19 +321,19 @@ async fn list_deep() { assert_eq!( list, vec![ - format!("pubky{public_key}/pub/example.com/a.txt") + format!("{public_key}/pub/example.com/a.txt") .parse() .unwrap(), - format!("pubky{public_key}/pub/example.com/b.txt") + format!("{public_key}/pub/example.com/b.txt") .parse() .unwrap(), - format!("pubky{public_key}/pub/example.com/c.txt") + format!("{public_key}/pub/example.com/c.txt") .parse() .unwrap(), - format!("pubky{public_key}/pub/example.com/cc-nested/z.txt") + format!("{public_key}/pub/example.com/cc-nested/z.txt") .parse() .unwrap(), - format!("pubky{public_key}/pub/example.com/d.txt") + format!("{public_key}/pub/example.com/d.txt") .parse() .unwrap(), ], @@ -354,10 +354,10 @@ async fn list_deep() { assert_eq!( list, vec![ - format!("pubky{public_key}/pub/example.com/a.txt") + format!("{public_key}/pub/example.com/a.txt") .parse() .unwrap(), - format!("pubky{public_key}/pub/example.com/b.txt") + format!("{public_key}/pub/example.com/b.txt") .parse() .unwrap(), ], @@ -372,7 +372,7 @@ async fn list_deep() { .list(&url) .unwrap() .limit(2) - .cursor(format!("{public_key}/pub/example.com/a.txt").as_str()) + .cursor(format!("{}/pub/example.com/a.txt", public_key.z32()).as_str()) .send() .await .unwrap(); @@ -380,10 +380,10 @@ async fn list_deep() { assert_eq!( list, vec![ - format!("pubky{public_key}/pub/example.com/b.txt") + format!("{public_key}/pub/example.com/b.txt") .parse() .unwrap(), - format!("pubky{public_key}/pub/example.com/c.txt") + format!("{public_key}/pub/example.com/c.txt") .parse() .unwrap(), ], @@ -397,7 +397,7 @@ async fn list_deep() { .list(&url) .unwrap() .limit(2) - .cursor(&format!("{public_key}/pub/example.com/a.txt")) + .cursor(&format!("{}/pub/example.com/a.txt", public_key.z32())) .send() .await .unwrap(); @@ -405,10 +405,10 @@ async fn list_deep() { assert_eq!( list, vec![ - format!("pubky{public_key}/pub/example.com/b.txt") + format!("{public_key}/pub/example.com/b.txt") .parse() .unwrap(), - format!("pubky{public_key}/pub/example.com/c.txt") + format!("{public_key}/pub/example.com/c.txt") .parse() .unwrap(), ], @@ -462,19 +462,13 @@ async fn list_shallow() { assert_eq!( list, vec![ - format!("pubky{public_key}/pub/a.com/").parse().unwrap(), - format!("pubky{public_key}/pub/example.com/") - .parse() - .unwrap(), - format!("pubky{public_key}/pub/example.con") - .parse() - .unwrap(), - format!("pubky{public_key}/pub/example.con/") - .parse() - .unwrap(), - format!("pubky{public_key}/pub/file").parse().unwrap(), - format!("pubky{public_key}/pub/file2").parse().unwrap(), - format!("pubky{public_key}/pub/z.com/").parse().unwrap(), + format!("{public_key}/pub/a.com/").parse().unwrap(), + format!("{public_key}/pub/example.com/").parse().unwrap(), + format!("{public_key}/pub/example.con").parse().unwrap(), + format!("{public_key}/pub/example.con/").parse().unwrap(), + format!("{public_key}/pub/file").parse().unwrap(), + format!("{public_key}/pub/file2").parse().unwrap(), + format!("{public_key}/pub/z.com/").parse().unwrap(), ], "normal list shallow" ); @@ -495,10 +489,8 @@ async fn list_shallow() { assert_eq!( list, vec![ - format!("pubky{public_key}/pub/a.com/").parse().unwrap(), - format!("pubky{public_key}/pub/example.com/") - .parse() - .unwrap(), + format!("{public_key}/pub/a.com/").parse().unwrap(), + format!("{public_key}/pub/example.com/").parse().unwrap(), ], "normal list shallow with limit but no cursor" ); @@ -511,7 +503,7 @@ async fn list_shallow() { .unwrap() .shallow(true) .limit(2) - .cursor(format!("{public_key}/pub/example.com/").as_str()) + .cursor(format!("{}/pub/example.com/", public_key.z32()).as_str()) .send() .await .unwrap(); @@ -519,12 +511,8 @@ async fn list_shallow() { assert_eq!( list1, vec![ - format!("pubky{public_key}/pub/example.con") - .parse() - .unwrap(), - format!("pubky{public_key}/pub/example.con/") - .parse() - .unwrap(), + format!("{public_key}/pub/example.con").parse().unwrap(), + format!("{public_key}/pub/example.con/").parse().unwrap(), ], "normal list shallow with limit and a file cursor" ); @@ -535,7 +523,7 @@ async fn list_shallow() { .unwrap() .shallow(true) .limit(2) - .cursor(format!("{public_key}/pub/example.com/a.txt").as_str()) + .cursor(format!("{}/pub/example.com/a.txt", public_key.z32()).as_str()) .send() .await .unwrap(); @@ -553,7 +541,7 @@ async fn list_shallow() { .unwrap() .shallow(true) .limit(3) - .cursor(format!("{public_key}/pub/example.com/").as_str()) + .cursor(format!("{}/pub/example.com/", public_key.z32()).as_str()) .send() .await .unwrap(); @@ -561,13 +549,9 @@ async fn list_shallow() { assert_eq!( list, vec![ - format!("pubky{public_key}/pub/example.con") - .parse() - .unwrap(), - format!("pubky{public_key}/pub/example.con/") - .parse() - .unwrap(), - format!("pubky{public_key}/pub/file").parse().unwrap(), + format!("{public_key}/pub/example.con").parse().unwrap(), + format!("{public_key}/pub/example.con/").parse().unwrap(), + format!("{public_key}/pub/file").parse().unwrap(), ], "normal list shallow with limit and a directory cursor" ); @@ -584,6 +568,7 @@ async fn list_events() { // Create a user/session let signer = pubky.signer(Keypair::random()); let public_key = signer.public_key(); + let public_key_z32 = public_key.z32(); let session = signer.signup(&server.public_key(), None).await.unwrap(); // Write + delete a bunch of files to populate the event feed @@ -605,7 +590,7 @@ async fn list_events() { } // Feed is exposed under the public-key host - let feed_url = format!("https://{}/events/", server.public_key()); + let feed_url = format!("https://{}/events/", server.public_key().z32()); // Page 1 let cursor: String = { @@ -626,16 +611,16 @@ async fn list_events() { assert_eq!( lines, vec![ - format!("PUT pubky://{public_key}/pub/a.com/a.txt"), - format!("DEL pubky://{public_key}/pub/a.com/a.txt"), - format!("PUT pubky://{public_key}/pub/example.com/a.txt"), - format!("DEL pubky://{public_key}/pub/example.com/a.txt"), - format!("PUT pubky://{public_key}/pub/example.com/b.txt"), - format!("DEL pubky://{public_key}/pub/example.com/b.txt"), - format!("PUT pubky://{public_key}/pub/example.com/c.txt"), - format!("DEL pubky://{public_key}/pub/example.com/c.txt"), - format!("PUT pubky://{public_key}/pub/example.com/d.txt"), - format!("DEL pubky://{public_key}/pub/example.com/d.txt"), + format!("PUT pubky://{public_key_z32}/pub/a.com/a.txt"), + format!("DEL pubky://{public_key_z32}/pub/a.com/a.txt"), + format!("PUT pubky://{public_key_z32}/pub/example.com/a.txt"), + format!("DEL pubky://{public_key_z32}/pub/example.com/a.txt"), + format!("PUT pubky://{public_key_z32}/pub/example.com/b.txt"), + format!("DEL pubky://{public_key_z32}/pub/example.com/b.txt"), + format!("PUT pubky://{public_key_z32}/pub/example.com/c.txt"), + format!("DEL pubky://{public_key_z32}/pub/example.com/c.txt"), + format!("PUT pubky://{public_key_z32}/pub/example.com/d.txt"), + format!("DEL pubky://{public_key_z32}/pub/example.com/d.txt"), format!("cursor: {cursor}"), ] ); @@ -659,16 +644,16 @@ async fn list_events() { assert_eq!( lines, vec![ - format!("PUT pubky://{public_key}/pub/example.xyz/d.txt"), - format!("DEL pubky://{public_key}/pub/example.xyz/d.txt"), - format!("PUT pubky://{public_key}/pub/example.xyz"), - format!("DEL pubky://{public_key}/pub/example.xyz"), - format!("PUT pubky://{public_key}/pub/file"), - format!("DEL pubky://{public_key}/pub/file"), - format!("PUT pubky://{public_key}/pub/file2"), - format!("DEL pubky://{public_key}/pub/file2"), - format!("PUT pubky://{public_key}/pub/z.com/a.txt"), - format!("DEL pubky://{public_key}/pub/z.com/a.txt"), + format!("PUT pubky://{public_key_z32}/pub/example.xyz/d.txt"), + format!("DEL pubky://{public_key_z32}/pub/example.xyz/d.txt"), + format!("PUT pubky://{public_key_z32}/pub/example.xyz"), + format!("DEL pubky://{public_key_z32}/pub/example.xyz"), + format!("PUT pubky://{public_key_z32}/pub/file"), + format!("DEL pubky://{public_key_z32}/pub/file"), + format!("PUT pubky://{public_key_z32}/pub/file2"), + format!("DEL pubky://{public_key_z32}/pub/file2"), + format!("PUT pubky://{public_key_z32}/pub/z.com/a.txt"), + format!("DEL pubky://{public_key_z32}/pub/z.com/a.txt"), lines.last().unwrap().to_string(), ] ); @@ -685,10 +670,11 @@ async fn read_after_event() { // User + session let signer = pubky.signer(Keypair::random()); let public_key = signer.public_key(); + let public_key_z32 = public_key.z32(); let session = signer.signup(&server.public_key(), None).await.unwrap(); // Write one file - let url = format!("pubky://{public_key}/pub/a.com/a.txt"); + let url = format!("pubky://{public_key_z32}/pub/a.com/a.txt"); session .storage() .put("/pub/a.com/a.txt", vec![0]) @@ -696,7 +682,7 @@ async fn read_after_event() { .unwrap(); // Events page 1 - let feed_url = format!("https://{}/events/", server.public_key()); + let feed_url = format!("https://{}/events/", server.public_key().z32()); { let page_url = format!("{feed_url}?limit=10"); let resp = pubky @@ -761,7 +747,7 @@ async fn dont_delete_shared_blobs() { assert_eq!(blob, file); // Event feed should show PUT u1, PUT u2, DEL u1 (order preserved) - let feed_url = format!("https://{}/events/", homeserver.public_key()); + let feed_url = format!("https://{}/events/", homeserver.public_key().z32()); let resp = pubky .client() .request(Method::GET, &feed_url) @@ -777,9 +763,9 @@ async fn dont_delete_shared_blobs() { assert_eq!( lines, vec![ - format!("PUT pubky://{user_1_id}/pub/pubky.app/file/file_1"), - format!("PUT pubky://{user_2_id}/pub/pubky.app/file/file_1"), - format!("DEL pubky://{user_1_id}/pub/pubky.app/file/file_1"), + format!("PUT pubky://{}/pub/pubky.app/file/file_1", user_1_id.z32()), + format!("PUT pubky://{}/pub/pubky.app/file/file_1", user_2_id.z32()), + format!("DEL pubky://{}/pub/pubky.app/file/file_1", user_1_id.z32()), lines.last().unwrap().to_string(), ] ); diff --git a/examples/javascript/0-logging.mjs b/examples/javascript/0-logging.mjs index 419d5cf7..b760bb91 100644 --- a/examples/javascript/0-logging.mjs +++ b/examples/javascript/0-logging.mjs @@ -3,7 +3,7 @@ import { Pubky, Keypair, PublicKey, setLogLevel } from "@synonymdev/pubky"; import { args } from "./_cli.mjs"; -const TESTNET_HOMESERVER = "8pinxxgqs41n4aididenw5apqp1urfmzdztr8jt4abrkdn435ewo"; +const TESTNET_HOMESERVER = "pubky8pinxxgqs41n4aididenw5apqp1urfmzdztr8jt4abrkdn435ewo"; const usage = ` Usage: @@ -43,7 +43,7 @@ const homeserver = PublicKey.from(homeserverArg); const keypair = Keypair.random(); const signer = pubky.signer(keypair); -console.log("Generated ephemeral signer:", keypair.publicKey.z32()); +console.log("Generated ephemeral signer:", keypair.publicKey.toString()); console.log("Signing up to homeserver... (watch the debug logs above)"); const session = await signer.signup(homeserver, null); diff --git a/examples/javascript/1-testnet.mjs b/examples/javascript/1-testnet.mjs index f7a0567d..d9965862 100644 --- a/examples/javascript/1-testnet.mjs +++ b/examples/javascript/1-testnet.mjs @@ -4,7 +4,7 @@ import { Pubky, Keypair, PublicKey, setLogLevel } from "@synonymdev/pubky"; // This is the default testnet homeserver. It comes from the secret `00000...` (bits). const TESTNET_HOMESERVER = - "8pinxxgqs41n4aididenw5apqp1urfmzdztr8jt4abrkdn435ewo"; + "pubky8pinxxgqs41n4aididenw5apqp1urfmzdztr8jt4abrkdn435ewo"; // 1) Build Pubky SDK facade for local testnet host const pubky = Pubky.testnet(); @@ -14,7 +14,7 @@ const keypair = Keypair.random(); const signer = pubky.signer(keypair); const homeserver = PublicKey.from(TESTNET_HOMESERVER); const session = await signer.signup(homeserver, null); -console.log("Signed up succeeded for user:", session.info.publicKey.z32()); +console.log("Signed up succeeded for user:", session.info.publicKey.toString()); // 3) Write then read a file under /pub// const path = "/pub/my-cool-app/hello.txt"; diff --git a/examples/javascript/2-signup.mjs b/examples/javascript/2-signup.mjs index 670e557d..a278feab 100644 --- a/examples/javascript/2-signup.mjs +++ b/examples/javascript/2-signup.mjs @@ -8,7 +8,7 @@ Usage: npm run signup -- [signup_code] [--testnet] Example: - npm run signup -- 8pinxxg... ./alice.recovery INVITE-123 --testnet + npm run signup -- pubky8pinxxg... ./alice.recovery INVITE-123 --testnet `; const a = args(process.argv.slice(2), { usage }); @@ -32,5 +32,5 @@ const homeserver = PublicKey.from(homeserverArg); const session = await signer.signup(homeserver, signupCode ?? null); // 4) Show session owner + capabilities -console.log("\nSigned up as:", session.info.publicKey.z32()); +console.log("\nSigned up as:", session.info.publicKey.toString()); console.log("Capabilities:", session.info.capabilities); diff --git a/examples/javascript/3-authenticator.mjs b/examples/javascript/3-authenticator.mjs index 40e0e0a4..27d63f57 100644 --- a/examples/javascript/3-authenticator.mjs +++ b/examples/javascript/3-authenticator.mjs @@ -17,7 +17,7 @@ You can try this out with the example backend-less third party browser applicati const a = args(process.argv.slice(2), { usage, defaults: { - homeserver: "8pinxxgqs41n4aididenw5apqp1urfmzdztr8jt4abrkdn435ewo", + homeserver: "pubky8pinxxgqs41n4aididenw5apqp1urfmzdztr8jt4abrkdn435ewo", }, }); const [recoveryPath, authUrl] = a._; diff --git a/examples/javascript/README.md b/examples/javascript/README.md index b5bdbfbb..7dc66a86 100644 --- a/examples/javascript/README.md +++ b/examples/javascript/README.md @@ -56,7 +56,7 @@ Decrypts a recovery file, creates a `Signer`, and signs up on a homeserver. npm run signup -- [invitation_code] [--testnet] # example (testnet homeserver) -npm run signup -- 8pinxxgqs41n4aididenw5apqp1urfmzdztr8jt4abrkdn435ewo ./alice.recovery INVITE-123 --testnet +npm run signup -- pubky8pinxxgqs41n4aididenw5apqp1urfmzdztr8jt4abrkdn435ewo ./alice.recovery INVITE-123 --testnet ``` You’ll be prompted for the recovery **passphrase**. @@ -80,14 +80,14 @@ You can run a Browser 3rd party app that requires authentication with [**3rd-par ### 4) Public storage read (no auth) -Reads a public resource via the **addressed** form: `/pub/my-cool-app/path/to/file.txt`. +Reads a public resource via the **addressed** form: `pubky/pub/my-cool-app/path/to/file.txt`. ```bash npm run storage -- / [--testnet] # examples -npm run storage -- q5oo7ma.../pub/my-cool-app/hello.txt --testnet -npm run storage -- operrr8w.../pub/pubky.app/posts/0033X02JAN0SG +npm run storage -- pubkyq5oo7ma.../pub/my-cool-app/hello.txt --testnet +npm run storage -- pubkyoperrr8w.../pub/pubky.app/posts/0033X02JAN0SG ``` Shows **exists**, **stats**, and downloads the content. @@ -96,8 +96,10 @@ Shows **exists**, **stats**, and downloads the content. Low-level fetch through the Pubky client. Handy for debugging. +> Use the **raw z-base32** key (no `pubky` prefix) in the `_pubky.` host portion. Call `publicKey.z32()` to get it. + ```bash -npm run request -- [--testnet] [-H "Name: value"]... [-d DATA] + npm run request -- [--testnet] [-H "Name: value"]... [-d DATA] # pubky:// read (testnet) npm run request -- GET https://_pubky.q5oo7ma.../pub/my-cool-app/info.json --testnet diff --git a/examples/rust/2-signup/README.md b/examples/rust/2-signup/README.md index 83e0aa7f..7fa215b5 100644 --- a/examples/rust/2-signup/README.md +++ b/examples/rust/2-signup/README.md @@ -11,5 +11,5 @@ as opposed to using a the 3rd party [authorization flow](../3-auth_flow). cargo run --bin signup # or use the local testnet defaults -cargo run --bin signup -- --testnet 8pinxxgqs41n4aididenw5apqp1urfmzdztr8jt4abrkdn435ewo +cargo run --bin signup -- --testnet pubky8pinxxgqs41n4aididenw5apqp1urfmzdztr8jt4abrkdn435ewo ``` diff --git a/examples/rust/2-signup/signup.rs b/examples/rust/2-signup/signup.rs index a42ee38a..5465835a 100644 --- a/examples/rust/2-signup/signup.rs +++ b/examples/rust/2-signup/signup.rs @@ -6,7 +6,7 @@ use std::path::PathBuf; #[derive(Parser, Debug)] #[command(version, about, long_about = None)] struct Cli { - /// Homeserver Pkarr Domain (for example `5jsjx1o6fzu6aeeo697r3i5rx15zq41kikcye8wtwdqm4nb4tryo`) + /// Homeserver identifier (for example `pubky5jsjx1o6fzu6aeeo697r3i5rx15zq41kikcye8wtwdqm4nb4tryo`) homeserver: String, /// Path to a recovery_file of the Pubky you want to sign in with diff --git a/examples/rust/3-auth_flow/authenticator.rs b/examples/rust/3-auth_flow/authenticator.rs index 3d63a556..74974bc7 100644 --- a/examples/rust/3-auth_flow/authenticator.rs +++ b/examples/rust/3-auth_flow/authenticator.rs @@ -5,7 +5,7 @@ use std::path::PathBuf; use url::Url; /// local testnet HOMESERVER -const HOMESERVER: &str = "8pinxxgqs41n4aididenw5apqp1urfmzdztr8jt4abrkdn435ewo"; +const HOMESERVER: &str = "pubky8pinxxgqs41n4aididenw5apqp1urfmzdztr8jt4abrkdn435ewo"; #[derive(Parser, Debug)] #[command(version, about, long_about = None)] diff --git a/examples/rust/5-request/README.md b/examples/rust/5-request/README.md index 74a6eee6..3948958c 100644 --- a/examples/rust/5-request/README.md +++ b/examples/rust/5-request/README.md @@ -17,8 +17,8 @@ cargo run --bin request -- [--testnet] [-H "Name: value"] [-d DAT ## Examples ```bash -# HTTPS to the _pubky (homeserver) subdomain form -cargo run --bin request -- GET https://_pubky./pub/my-cool-app/info.json +# HTTPS to the _pubky (homeserver) subdomain form (use the raw z-base32 key) +cargo run --bin request -- GET https://_pubky./pub/my-cool-app/info.json # JSON POST with headers cargo run --bin request -- \ @@ -28,7 +28,7 @@ cargo run --bin request -- \ POST https://example.com/data.json # Use local testnet endpoints -cargo run --bin request -- --testnet GET https://_pubky./pub/my-cool-app/hello.txt +cargo run --bin request -- --testnet GET https://_pubky./pub/my-cool-app/hello.txt ``` For example, at the time of writing, the following command returns the content of a user's social post from his pubky homeserver. diff --git a/http-relay/src/http_relay.rs b/http-relay/src/http_relay.rs index 2b5efdd8..52d50c9b 100644 --- a/http-relay/src/http_relay.rs +++ b/http-relay/src/http_relay.rs @@ -282,8 +282,10 @@ mod tests { #[tokio::test] async fn test_request_timeout() { - let mut config = Config::default(); - config.request_timeout = Duration::from_millis(50); + let config = Config { + request_timeout: Duration::from_millis(50), + ..Config::default() + }; let (app, state) = HttpRelay::create_app(config).unwrap(); let server = axum_test::TestServer::new(app).unwrap(); diff --git a/pkarr-republisher/examples/publish_and_save.rs b/pkarr-republisher/examples/publish_and_save.rs index 3c22b9cd..850e5c02 100644 --- a/pkarr-republisher/examples/publish_and_save.rs +++ b/pkarr-republisher/examples/publish_and_save.rs @@ -140,16 +140,15 @@ async fn publish_records(num_records: usize, thread_id: usize) -> Vec { .cname(Name::new("test").unwrap(), Name::new("test2").unwrap(), 600) .build(&key) .unwrap(); - let result = rclient.publish(packet, None).await; let elapsed_time = instant.elapsed().as_millis(); - if result.is_ok() { - let info = result.unwrap(); - tracing::info!("- t{thread_id:<2} {i:>3}/{num_records} Published {} within {elapsed_time}ms to {} nodes {} attempts", key.public_key(), info.published_nodes_count, info.attempts_needed); - records.push(key); - } else { - let e = result.unwrap_err(); - tracing::error!("Failed to publish {} record: {e:?}", key.public_key()); - continue; + match rclient.publish(packet, None).await { + Ok(info) => { + tracing::info!("- t{thread_id:<2} {i:>3}/{num_records} Published {} within {elapsed_time}ms to {} nodes {} attempts", key.public_key(), info.published_nodes_count, info.attempts_needed); + records.push(key); + } + Err(e) => { + tracing::error!("Failed to publish {} record: {e:?}", key.public_key()); + } } } records diff --git a/pubky-common/src/crypto.rs b/pubky-common/src/crypto.rs index 465f281a..439fc214 100644 --- a/pubky-common/src/crypto.rs +++ b/pubky-common/src/crypto.rs @@ -6,7 +6,8 @@ use crypto_secretbox::{ }; use rand::random; -pub use pkarr::{Keypair, PublicKey}; +mod keys; +pub use keys::{is_prefixed_pubky, Keypair, PublicKey}; pub use ed25519_dalek::Signature; diff --git a/pubky-common/src/crypto/keys.rs b/pubky-common/src/crypto/keys.rs new file mode 100644 index 00000000..c9a8a824 --- /dev/null +++ b/pubky-common/src/crypto/keys.rs @@ -0,0 +1,220 @@ +use core::fmt; +use core::ops::{Deref, DerefMut}; +use core::str::FromStr; +#[cfg(not(target_arch = "wasm32"))] +use std::{io, path::Path}; + +use serde::{Deserialize, Serialize}; + +type ParseError = >::Error; + +/// Returns true if the value is in `pubky` form. +pub fn is_prefixed_pubky(value: &str) -> bool { + matches!(value.strip_prefix("pubky"), Some(stripped) if stripped.len() == 52) +} + +fn parse_public_key(value: &str) -> Result { + let raw = if is_prefixed_pubky(value) { + value.strip_prefix("pubky").unwrap_or(value) + } else { + value + }; + pkarr::PublicKey::try_from(raw.to_string()) +} + +/// Wrapper around [`pkarr::Keypair`] that customizes [`PublicKey`] rendering. +#[derive(Clone)] +pub struct Keypair(pkarr::Keypair); + +impl Keypair { + /// Generate a random keypair. + #[must_use] + pub fn random() -> Self { + Self(pkarr::Keypair::random()) + } + + /// Export the secret key bytes. + #[must_use] + pub fn secret_key(&self) -> [u8; 32] { + let mut out = [0u8; 32]; + out.copy_from_slice(self.0.secret_key().as_ref()); + out + } + + /// Construct a [`Keypair`] from a 32-byte secret key. + #[must_use] + pub fn from_secret_key(secret: &[u8; 32]) -> Self { + Self(pkarr::Keypair::from_secret_key(secret)) + } + + /// Read a keypair from a pkarr secret key file. + #[cfg(not(target_arch = "wasm32"))] + pub fn from_secret_key_file(path: &Path) -> Result { + pkarr::Keypair::from_secret_key_file(path).map(Self) + } + + /// Return the [`PublicKey`] associated with this [`Keypair`]. + /// + /// Display the returned key with `.to_string()` to get the `pubky` identifier or + /// [`PublicKey::z32()`] when you specifically need the bare z-base32 text (e.g. hostnames). + #[must_use] + pub fn public_key(&self) -> PublicKey { + PublicKey(self.0.public_key()) + } + + /// Borrow the inner [`pkarr::Keypair`]. + #[must_use] + pub const fn as_inner(&self) -> &pkarr::Keypair { + &self.0 + } + + /// Persist the secret key to disk using the pkarr format. + #[cfg(not(target_arch = "wasm32"))] + pub fn write_secret_key_file(&self, path: &Path) -> Result<(), io::Error> { + self.0.write_secret_key_file(path) + } + + /// Extract the inner [`pkarr::Keypair`]. + #[must_use] + pub fn into_inner(self) -> pkarr::Keypair { + self.0 + } +} + +impl fmt::Debug for Keypair { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + self.0.fmt(f) + } +} + +impl Deref for Keypair { + type Target = pkarr::Keypair; + + fn deref(&self) -> &Self::Target { + &self.0 + } +} + +impl DerefMut for Keypair { + fn deref_mut(&mut self) -> &mut Self::Target { + &mut self.0 + } +} + +impl From for Keypair { + fn from(keypair: pkarr::Keypair) -> Self { + Self(keypair) + } +} + +impl From for pkarr::Keypair { + fn from(value: Keypair) -> Self { + value.0 + } +} + +/// Wrapper around [`pkarr::PublicKey`] that renders with the `pubky` prefix. +#[derive(Clone, PartialEq, Eq, Hash, Serialize, Deserialize)] +#[serde(transparent)] +pub struct PublicKey(pkarr::PublicKey); + +impl PublicKey { + /// Borrow the inner [`pkarr::PublicKey`]. + #[must_use] + pub const fn as_inner(&self) -> &pkarr::PublicKey { + &self.0 + } + + /// Extract the inner [`pkarr::PublicKey`]. + #[must_use] + pub fn into_inner(self) -> pkarr::PublicKey { + self.0 + } + + /// Return the raw z-base32 representation without the `pubky` prefix. + #[must_use] + pub fn z32(&self) -> String { + self.0.to_string() + } + + /// Parse a public key from raw z-base32 text (without the `pubky` prefix). + pub fn try_from_z32(value: &str) -> Result { + pkarr::PublicKey::try_from(value.to_string()).map(Self) + } +} + +impl fmt::Display for PublicKey { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "pubky{}", self.z32()) + } +} + +impl fmt::Debug for PublicKey { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.debug_tuple("PublicKey").field(&self.to_string()).finish() + } +} + +impl Deref for PublicKey { + type Target = pkarr::PublicKey; + + fn deref(&self) -> &Self::Target { + &self.0 + } +} + +impl From for PublicKey { + fn from(value: pkarr::PublicKey) -> Self { + Self(value) + } +} + +impl From<&pkarr::PublicKey> for PublicKey { + fn from(value: &pkarr::PublicKey) -> Self { + Self(value.clone()) + } +} + +impl From for pkarr::PublicKey { + fn from(value: PublicKey) -> Self { + value.0 + } +} + +impl From<&PublicKey> for pkarr::PublicKey { + fn from(value: &PublicKey) -> Self { + value.0.clone() + } +} + +impl TryFrom<&str> for PublicKey { + type Error = ParseError; + + fn try_from(value: &str) -> Result { + parse_public_key(value).map(Self) + } +} + +impl TryFrom<&String> for PublicKey { + type Error = ParseError; + + fn try_from(value: &String) -> Result { + parse_public_key(value).map(Self) + } +} + +impl TryFrom for PublicKey { + type Error = ParseError; + + fn try_from(value: String) -> Result { + parse_public_key(&value).map(Self) + } +} + +impl FromStr for PublicKey { + type Err = ParseError; + + fn from_str(s: &str) -> Result { + parse_public_key(s).map(Self) + } +} diff --git a/pubky-common/src/recovery_file.rs b/pubky-common/src/recovery_file.rs index 3e889e8c..848b9c8b 100644 --- a/pubky-common/src/recovery_file.rs +++ b/pubky-common/src/recovery_file.rs @@ -1,9 +1,8 @@ //! Tools for encrypting and decrypting a recovery file storing user's root key's secret. use argon2::Argon2; -use pkarr::Keypair; -use crate::crypto::{decrypt, encrypt}; +use crate::crypto::{decrypt, encrypt, Keypair}; static SPEC_NAME: &str = "recovery"; static SPEC_LINE: &str = "pubky.org/recovery"; diff --git a/pubky-common/src/session.rs b/pubky-common/src/session.rs index 0024981b..1c202d56 100644 --- a/pubky-common/src/session.rs +++ b/pubky-common/src/session.rs @@ -1,6 +1,5 @@ //! Pubky homeserver session struct. -use pkarr::PublicKey; use postcard::{from_bytes, to_allocvec}; use serde::{Deserialize, Serialize}; @@ -9,6 +8,7 @@ use alloc::vec::Vec; use crate::{ capabilities::{Capabilities, Capability}, + crypto::PublicKey, timestamp::Timestamp, }; diff --git a/pubky-homeserver/src/admin_server/app.rs b/pubky-homeserver/src/admin_server/app.rs index 15a40b92..902b6da2 100644 --- a/pubky-homeserver/src/admin_server/app.rs +++ b/pubky-homeserver/src/admin_server/app.rs @@ -110,7 +110,7 @@ impl AdminServer { &password, ) .with_metadata_from_config( - context.keypair.public_key().to_string(), + context.keypair.public_key().z32(), &context.config_toml, env!("CARGO_PKG_VERSION"), ); diff --git a/pubky-homeserver/src/admin_server/routes/delete_entry.rs b/pubky-homeserver/src/admin_server/routes/delete_entry.rs index c73963cb..48fdd003 100644 --- a/pubky-homeserver/src/admin_server/routes/delete_entry.rs +++ b/pubky-homeserver/src/admin_server/routes/delete_entry.rs @@ -29,7 +29,7 @@ mod tests { use crate::AppContext; use axum::{routing::delete, Router}; use opendal::Buffer; - use pkarr::Keypair; + use pubky_common::crypto::Keypair; async fn write_test_file(file_service: &FileService, entry_path: &EntryPath) { let buffer = Buffer::from(vec![0; 10]); @@ -63,7 +63,7 @@ mod tests { // Delete the file let server = axum_test::TestServer::new(router).unwrap(); let response = server - .delete(format!("/webdav/{}{}", pubkey, entry_path.path().as_str()).as_str()) + .delete(format!("/webdav/{}", entry_path.as_str()).as_str()) .await; assert_eq!(response.status_code(), StatusCode::NO_CONTENT); @@ -102,7 +102,7 @@ mod tests { .with_state(app_state); // Delete the file - let url = format!("/webdav/{}/pub/{}", pubkey, file_path); + let url = format!("/webdav/{}/pub/{}", pubkey.z32(), file_path); let server = axum_test::TestServer::new(router).unwrap(); let response = server.delete(url.as_str()).await; assert_eq!(response.status_code(), StatusCode::NOT_FOUND); diff --git a/pubky-homeserver/src/admin_server/routes/disable_users.rs b/pubky-homeserver/src/admin_server/routes/disable_users.rs index d74ee2c4..8de4c509 100644 --- a/pubky-homeserver/src/admin_server/routes/disable_users.rs +++ b/pubky-homeserver/src/admin_server/routes/disable_users.rs @@ -74,7 +74,7 @@ mod tests { use crate::{persistence::files::FileService, AppContext}; use axum::routing::post; use axum::Router; - use pkarr::Keypair; + use pubky_common::crypto::Keypair; #[tokio::test] #[pubky_test_utils::test] @@ -106,8 +106,9 @@ mod tests { // Disable the tenant let server = axum_test::TestServer::new(router).unwrap(); + let pubkey_path = pubkey.z32(); let response = server - .post(format!("/users/{}/disable", pubkey).as_str()) + .post(format!("/users/{}/disable", pubkey_path).as_str()) .await; assert_eq!(response.status_code(), StatusCode::OK); @@ -119,7 +120,7 @@ mod tests { // Enable the tenant again let response = server - .post(format!("/users/{}/enable", pubkey).as_str()) + .post(format!("/users/{}/enable", pubkey_path).as_str()) .await; assert_eq!(response.status_code(), StatusCode::OK); diff --git a/pubky-homeserver/src/app_context.rs b/pubky-homeserver/src/app_context.rs index 8eb00810..23f8f47f 100644 --- a/pubky-homeserver/src/app_context.rs +++ b/pubky-homeserver/src/app_context.rs @@ -16,7 +16,7 @@ use crate::{ }, ConfigToml, DataDir, }; -use pkarr::Keypair; +use pubky_common::crypto::Keypair; use std::{sync::Arc, time::Duration}; /// Errors that can occur when converting a `DataDir` to an `AppContext`. diff --git a/pubky-homeserver/src/client_server/app.rs b/pubky-homeserver/src/client_server/app.rs index 5f6ea95b..150c0f04 100644 --- a/pubky-homeserver/src/client_server/app.rs +++ b/pubky-homeserver/src/client_server/app.rs @@ -184,7 +184,7 @@ impl ClientServer { /// Get the URL of the pubky tls server with the Pubky DNS name. pub fn pubky_tls_dns_url_string(&self) -> String { - format!("https://{}", self.context.keypair.public_key()) + format!("https://{}", self.context.keypair.public_key().z32()) } /// Get the URL of the pubky tls server with the Pubky IP address. diff --git a/pubky-homeserver/src/client_server/err_if_user_is_invalid.rs b/pubky-homeserver/src/client_server/err_if_user_is_invalid.rs index 23feb61e..09c5a44c 100644 --- a/pubky-homeserver/src/client_server/err_if_user_is_invalid.rs +++ b/pubky-homeserver/src/client_server/err_if_user_is_invalid.rs @@ -5,7 +5,7 @@ use crate::{ }, shared::{HttpError, HttpResult}, }; -use pkarr::PublicKey; +use pubky_common::crypto::PublicKey; /// Returns the user if it exists and is not disabled, otherwise returns an error. /// - User doesn't exist: returns 404 diff --git a/pubky-homeserver/src/client_server/extractors.rs b/pubky-homeserver/src/client_server/extractors.rs index 0f328d5b..6aae3f22 100644 --- a/pubky-homeserver/src/client_server/extractors.rs +++ b/pubky-homeserver/src/client_server/extractors.rs @@ -7,7 +7,7 @@ use axum::{ RequestPartsExt, }; -use pkarr::PublicKey; +use pubky_common::crypto::PublicKey; use crate::shared::parse_bool; diff --git a/pubky-homeserver/src/client_server/layers/authz.rs b/pubky-homeserver/src/client_server/layers/authz.rs index 2b10a1a9..bdbda21c 100644 --- a/pubky-homeserver/src/client_server/layers/authz.rs +++ b/pubky-homeserver/src/client_server/layers/authz.rs @@ -8,7 +8,7 @@ use axum::{ http::{Request, StatusCode}, }; use futures_util::future::BoxFuture; -use pkarr::PublicKey; +use pubky_common::crypto::PublicKey; use std::{convert::Infallible, task::Poll}; use tower::{Layer, Service}; use tower_cookies::Cookies; @@ -198,7 +198,7 @@ pub fn session_secret_from_cookies( public_key: &PublicKey, ) -> Option { let value = cookies - .get(&public_key.to_string()) + .get(&public_key.z32()) .map(|c| c.value().to_string())?; SessionSecret::new(value).ok() } diff --git a/pubky-homeserver/src/client_server/layers/pubky_host.rs b/pubky-homeserver/src/client_server/layers/pubky_host.rs index 781ca957..a5d454e3 100644 --- a/pubky-homeserver/src/client_server/layers/pubky_host.rs +++ b/pubky-homeserver/src/client_server/layers/pubky_host.rs @@ -1,7 +1,7 @@ use crate::client_server::extractors::PubkyHost; use axum::{body::Body, http::Request}; use futures_util::future::BoxFuture; -use pkarr::PublicKey; +use pubky_common::crypto::{is_prefixed_pubky, PublicKey}; use std::{convert::Infallible, task::Poll}; use tower::{Layer, Service}; @@ -58,7 +58,10 @@ fn extract_pubky(req: &Request) -> Option { for header in ["host", "pubky-host"].iter() { if let Some(val) = req.headers().get(*header) { if let Ok(s) = val.to_str() { - if let Ok(key) = PublicKey::try_from(s) { + if is_prefixed_pubky(s) { + continue; + } + if let Ok(key) = PublicKey::try_from_z32(s) { pubky = Some(key); } } @@ -71,7 +74,10 @@ fn extract_pubky(req: &Request) -> Option { let mut parts = pair.splitn(2, '='); if let (Some(key), Some(val)) = (parts.next(), parts.next()) { if key == "pubky-host" { - return PublicKey::try_from(val).ok(); + if is_prefixed_pubky(val) { + return None; + } + return PublicKey::try_from_z32(val).ok(); } } None diff --git a/pubky-homeserver/src/client_server/layers/rate_limiter/layer.rs b/pubky-homeserver/src/client_server/layers/rate_limiter/layer.rs index 51d29915..335aa45e 100644 --- a/pubky-homeserver/src/client_server/layers/rate_limiter/layer.rs +++ b/pubky-homeserver/src/client_server/layers/rate_limiter/layer.rs @@ -358,7 +358,7 @@ mod tests { Router, }; use axum_server::Server; - use pkarr::{Keypair, PublicKey}; + use pubky_common::crypto::{Keypair, PublicKey}; use reqwest::{Client, Response}; use tokio::{task::JoinHandle, time::Instant}; @@ -544,7 +544,11 @@ mod tests { tokio::spawn(async move { let client = Client::new(); let response = client - .post(format!("http://{}/upload?pubky-host={user_pubkey}", socket)) + .post(format!( + "http://{}/upload?pubky-host={}", + socket, + user_pubkey.z32() + )) .send() .await .unwrap(); diff --git a/pubky-homeserver/src/client_server/layers/trace.rs b/pubky-homeserver/src/client_server/layers/trace.rs index c3f0a224..02c36e0c 100644 --- a/pubky-homeserver/src/client_server/layers/trace.rs +++ b/pubky-homeserver/src/client_server/layers/trace.rs @@ -24,7 +24,7 @@ pub fn with_trace_layer(router: Router) -> Router { TraceLayer::new_for_http() .make_span_with(move |request: &Request| { let uri = if let Some(pubky_host) = request.extensions().get::() { - format!("pubky://{pubky_host}{}", request.uri()) + format!("pubky://{}{}", pubky_host.public_key().z32(), request.uri()) } else { request.uri().to_string() }; diff --git a/pubky-homeserver/src/client_server/routes/auth.rs b/pubky-homeserver/src/client_server/routes/auth.rs index 06bd566a..716249b0 100644 --- a/pubky-homeserver/src/client_server/routes/auth.rs +++ b/pubky-homeserver/src/client_server/routes/auth.rs @@ -17,8 +17,8 @@ use axum::{ }; use axum_extra::extract::Host; use bytes::Bytes; -use pkarr::PublicKey; use pubky_common::capabilities::Capabilities; +use pubky_common::crypto::PublicKey; use pubky_common::session::SessionInfo; use std::collections::HashMap; use tower_cookies::{ @@ -137,7 +137,7 @@ async fn create_session_and_cookie( SessionRepository::create(user.id, capabilities, &mut state.sql_db.pool().into()).await?; // 3) Build and set cookie - let mut cookie = Cookie::new(user.public_key.to_string(), session_secret.to_string()); + let mut cookie = Cookie::new(user.public_key.z32(), session_secret.to_string()); configure_session_cookie(&mut cookie, host); // Set the cookie to expire in one year. let one_year = Duration::days(365); @@ -171,7 +171,7 @@ pub(crate) fn configure_session_cookie(cookie: &mut Cookie<'static>, host: &str) /// container names or localhost) are treated as non-secure development environments. fn is_secure(host: &str) -> bool { // A pkarr public key is always a secure context. - if PublicKey::try_from(host).is_ok() { + if PublicKey::try_from_z32(host).is_ok() { return true; } @@ -188,7 +188,7 @@ fn is_secure(host: &str) -> bool { #[cfg(test)] mod tests { - use pkarr::Keypair; + use pubky_common::crypto::Keypair; use super::*; @@ -202,7 +202,7 @@ mod tests { assert!(!is_secure("[2001:0db8:0000:0000:0000:ff00:0042:8329]")); assert!(!is_secure("localhost")); assert!(!is_secure("localhost:23423")); - assert!(is_secure(&Keypair::random().public_key().to_string())); + assert!(is_secure(&Keypair::random().public_key().z32())); assert!(is_secure("example.com")); } } diff --git a/pubky-homeserver/src/client_server/routes/events.rs b/pubky-homeserver/src/client_server/routes/events.rs index 5897e0f4..b01409cb 100644 --- a/pubky-homeserver/src/client_server/routes/events.rs +++ b/pubky-homeserver/src/client_server/routes/events.rs @@ -8,7 +8,7 @@ use axum::{ }, }; use futures_util::stream::Stream; -use pkarr::PublicKey; +use pubky_common::crypto::{is_prefixed_pubky, PublicKey}; use serde::Deserialize; use std::{collections::HashMap, convert::Infallible, time::Instant}; use url::form_urlencoded; @@ -147,7 +147,10 @@ impl TryFrom for EventStreamQueryParams { (value.as_str(), None) }; - let pubkey = PublicKey::try_from(pubkey_str) + if is_prefixed_pubky(pubkey_str) { + return Err(EventStreamError::InvalidPublicKey(pubkey_str.to_string())); + } + let pubkey = PublicKey::try_from_z32(pubkey_str) .map_err(|_| EventStreamError::InvalidPublicKey(pubkey_str.to_string()))?; user_cursors.push((pubkey, cursor_str.map(|s| s.to_string()))); @@ -205,8 +208,15 @@ impl TryFrom for EventStreamQueryParams { /// data: cursor: 42 /// data: content_hash: r0NJufX5oaagQE3qNtzJSZvLJcmtwRK3zJqTyuQfMmI= (only for PUT events, base64-encoded blake3 hash) /// ``` +fn formatted_event_path(entity: &EventEntity) -> String { + // TODO: switch this formatter to use the shared `PubkyResource` type from `pubky-sdk` + // once the homeserver crate depends on it directly, so we avoid ad-hoc string + // reconstruction here. + format!("pubky://{}{}", entity.user_pubkey.z32(), entity.path.path()) +} + fn event_to_sse_data(entity: &EventEntity) -> String { - let path = format!("pubky://{}", entity.path.as_str()); + let path = formatted_event_path(entity); let cursor_line = format!("cursor: {}", entity.cursor()); let mut lines = vec![path, cursor_line]; @@ -261,7 +271,7 @@ pub async fn feed( let mut result = events .iter() - .map(|event| format!("{} pubky://{}", event.event_type, event.path.as_str())) + .map(|event| format!("{} {}", event.event_type, formatted_event_path(event))) .collect::>(); let next_cursor = events.last().map(|event| event.id.to_string()); diff --git a/pubky-homeserver/src/client_server/routes/tenants/read.rs b/pubky-homeserver/src/client_server/routes/tenants/read.rs index 8d370037..d2748699 100644 --- a/pubky-homeserver/src/client_server/routes/tenants/read.rs +++ b/pubky-homeserver/src/client_server/routes/tenants/read.rs @@ -235,8 +235,11 @@ mod tests { use axum::http::{header, StatusCode}; use axum::Router; use axum_test::TestServer; - use pkarr::{Keypair, PublicKey}; - use pubky_common::{auth::AuthToken, capabilities::Capability}; + use pubky_common::{ + auth::AuthToken, + capabilities::Capability, + crypto::{Keypair, PublicKey}, + }; use crate::app_context::AppContext; use crate::client_server::ClientServer; @@ -249,7 +252,7 @@ mod tests { let body_bytes: axum::body::Bytes = auth_token.serialize().into(); let response = server .post("/signup") - .add_header("host", keypair.public_key().to_string()) + .add_header("host", keypair.public_key().to_z32()) .bytes(body_bytes) .expect_success() .await; @@ -289,7 +292,7 @@ mod tests { server .put("/pub/foo") - .add_header("host", public_key.to_string()) + .add_header("host", public_key.z32()) .add_header(header::COOKIE, cookie) .bytes(data.into()) .expect_success() @@ -297,13 +300,13 @@ mod tests { let response = server .get("/pub/foo") - .add_header("host", public_key.to_string()) + .add_header("host", public_key.z32()) .expect_success() .await; let response = server .get("/pub/foo") - .add_header("host", public_key.to_string()) + .add_header("host", public_key.z32()) .add_header( header::IF_MODIFIED_SINCE, response.headers().get(header::LAST_MODIFIED).unwrap(), @@ -322,7 +325,7 @@ mod tests { server .put("/pub/foo") - .add_header("host", public_key.to_string()) + .add_header("host", public_key.z32()) .add_header(header::COOKIE, cookie) .bytes(data.into()) .expect_success() @@ -330,13 +333,13 @@ mod tests { let response = server .get("/pub/foo") - .add_header("host", public_key.to_string()) + .add_header("host", public_key.z32()) .expect_success() .await; let response = server .get("/pub/foo") - .add_header("host", public_key.to_string()) + .add_header("host", public_key.z32()) .add_header( header::IF_NONE_MATCH, response.headers().get(header::ETAG).unwrap(), @@ -355,7 +358,7 @@ mod tests { server .put("/pub/foo") - .add_header("host", public_key.to_string()) + .add_header("host", public_key.z32()) .add_header(header::COOKIE, cookie) .bytes(data.into()) .expect_success() @@ -363,7 +366,7 @@ mod tests { let response = server .get("/pub/foo") - .add_header("host", public_key.to_string()) + .add_header("host", public_key.z32()) .await; response.assert_header(header::CONTENT_TYPE, "image/png"); @@ -378,7 +381,7 @@ mod tests { server .put("/pub/text.txt") - .add_header("host", public_key.to_string()) + .add_header("host", public_key.z32()) .add_header(header::COOKIE, cookie) .bytes(data.into()) .expect_success() @@ -386,7 +389,7 @@ mod tests { let response = server .get("/pub/text.txt") - .add_header("host", public_key.to_string()) + .add_header("host", public_key.z32()) .await; response.assert_header(header::CONTENT_TYPE, "text/plain"); @@ -398,7 +401,7 @@ mod tests { // Write v1 server .put("/pub/foo") - .add_header("host", public_key.to_string()) + .add_header("host", public_key.z32()) .add_header(header::COOKIE, cookie.clone()) .bytes(Vec::from("alice").into()) .expect_success() @@ -407,7 +410,7 @@ mod tests { // Baseline GET to capture ETag and Last-Modified let base = server .get("/pub/foo") - .add_header("host", public_key.to_string()) + .add_header("host", public_key.z32()) .expect_success() .await; let etag_v1 = base @@ -422,7 +425,7 @@ mod tests { // Overwrite with different content but same-second timestamp likely server .put("/pub/foo") - .add_header("host", public_key.to_string()) + .add_header("host", public_key.z32()) .add_header(header::COOKIE, cookie.clone()) .bytes(Vec::from("bob").into()) .expect_success() @@ -431,7 +434,7 @@ mod tests { // Conditional GET that sends both validators; must return 200 because ETag changed. let r = server .get("/pub/foo") - .add_header("host", public_key.to_string()) + .add_header("host", public_key.z32()) .add_header(header::IF_NONE_MATCH, etag_v1) .add_header(header::IF_MODIFIED_SINCE, lm_v1) .await; diff --git a/pubky-homeserver/src/client_server/routes/tenants/session.rs b/pubky-homeserver/src/client_server/routes/tenants/session.rs index f6cefcdc..b557bfe7 100644 --- a/pubky-homeserver/src/client_server/routes/tenants/session.rs +++ b/pubky-homeserver/src/client_server/routes/tenants/session.rs @@ -58,7 +58,7 @@ pub async fn signout( // Always instruct the client to drop the session cookie, even if the // database record was already gone. This keeps repeated signout calls // idempotent and lets browsers wipe stale cookies immediately. - let mut removal = Cookie::new(pubky.public_key().to_string(), String::new()); + let mut removal = Cookie::new(pubky.public_key().z32(), String::new()); removal.make_removal(); configure_session_cookie(&mut removal, &host); cookies.add(removal); diff --git a/pubky-homeserver/src/client_server/routes/tenants/write.rs b/pubky-homeserver/src/client_server/routes/tenants/write.rs index 389094cf..0e5d998b 100644 --- a/pubky-homeserver/src/client_server/routes/tenants/write.rs +++ b/pubky-homeserver/src/client_server/routes/tenants/write.rs @@ -110,7 +110,7 @@ pub async fn fail_if_size_hint_bigger_than_user_quota<'a>( #[cfg(test)] mod tests { - use pkarr::Keypair; + use pubky_common::crypto::Keypair; use crate::{persistence::sql::SqlDb, shared::webdav::WebDavPath}; diff --git a/pubky-homeserver/src/data_directory/data_dir.rs b/pubky-homeserver/src/data_directory/data_dir.rs index 12a785b8..75864ece 100644 --- a/pubky-homeserver/src/data_directory/data_dir.rs +++ b/pubky-homeserver/src/data_directory/data_dir.rs @@ -19,7 +19,7 @@ pub trait DataDir: std::fmt::Debug + DynClone + Send + Sync { /// Reads the secret file from the data directory. /// Creates a new secret file if it doesn't exist. - fn read_or_create_keypair(&self) -> anyhow::Result; + fn read_or_create_keypair(&self) -> anyhow::Result; } dyn_clone::clone_trait_object!(DataDir); diff --git a/pubky-homeserver/src/data_directory/mock_data_dir.rs b/pubky-homeserver/src/data_directory/mock_data_dir.rs index 7112f401..1603f2a1 100644 --- a/pubky-homeserver/src/data_directory/mock_data_dir.rs +++ b/pubky-homeserver/src/data_directory/mock_data_dir.rs @@ -13,7 +13,7 @@ pub struct MockDataDir { /// The configuration for the homeserver. pub config_toml: super::ConfigToml, /// The keypair for the homeserver. - pub keypair: pkarr::Keypair, + pub keypair: pubky_common::crypto::Keypair, } impl MockDataDir { @@ -22,9 +22,9 @@ impl MockDataDir { /// If keypair is not provided, a new one will be generated. pub fn new( config_toml: super::ConfigToml, - keypair: Option, + keypair: Option, ) -> anyhow::Result { - let keypair = keypair.unwrap_or_else(pkarr::Keypair::random); + let keypair = keypair.unwrap_or_else(pubky_common::crypto::Keypair::random); Ok(Self { temp_dir: std::sync::Arc::new(tempfile::TempDir::new()?), config_toml, @@ -36,7 +36,7 @@ impl MockDataDir { #[cfg(any(test, feature = "testing"))] pub fn test() -> Self { let config = super::ConfigToml::test(); - let keypair = pkarr::Keypair::from_secret_key(&[0; 32]); + let keypair = pubky_common::crypto::Keypair::from_secret_key(&[0; 32]); Self::new(config, Some(keypair)).expect("failed to create MockDataDir") } } @@ -60,7 +60,7 @@ impl DataDir for MockDataDir { Ok(self.config_toml.clone()) } - fn read_or_create_keypair(&self) -> anyhow::Result { + fn read_or_create_keypair(&self) -> anyhow::Result { Ok(self.keypair.clone()) } } diff --git a/pubky-homeserver/src/data_directory/persistent_data_dir.rs b/pubky-homeserver/src/data_directory/persistent_data_dir.rs index 3acbe62c..5b50eace 100644 --- a/pubky-homeserver/src/data_directory/persistent_data_dir.rs +++ b/pubky-homeserver/src/data_directory/persistent_data_dir.rs @@ -103,15 +103,15 @@ impl DataDir for PersistentDataDir { } /// Reads the secret file. Creates a new secret file if it doesn't exist. - fn read_or_create_keypair(&self) -> anyhow::Result { + fn read_or_create_keypair(&self) -> anyhow::Result { let secret_file_path = self.get_secret_file_path(); if !secret_file_path.exists() { // Create a new secret file - pkarr::Keypair::random().write_secret_key_file(&secret_file_path)?; + pubky_common::crypto::Keypair::random().write_secret_key_file(&secret_file_path)?; tracing::info!("Secret file created at {}", secret_file_path.display()); } // Read the secret file - let keypair = pkarr::Keypair::from_secret_key_file(&secret_file_path)?; + let keypair = pubky_common::crypto::Keypair::from_secret_key_file(&secret_file_path)?; Ok(keypair) } } @@ -230,7 +230,7 @@ mod tests { data_dir.ensure_data_dir_exists_and_is_writable().unwrap(); // Create a secret file - let keypair = pkarr::Keypair::random(); + let keypair = pubky_common::crypto::Keypair::random(); let secret_file_path = data_dir.get_secret_file_path(); let file_content = format!("\n {}\n \n", hex::encode(keypair.secret_key())); std::fs::write(secret_file_path.clone(), file_content).unwrap(); diff --git a/pubky-homeserver/src/data_directory/quota_config/limit_key.rs b/pubky-homeserver/src/data_directory/quota_config/limit_key.rs index 3e225039..b6a45291 100644 --- a/pubky-homeserver/src/data_directory/quota_config/limit_key.rs +++ b/pubky-homeserver/src/data_directory/quota_config/limit_key.rs @@ -2,7 +2,7 @@ use std::fmt; use std::net::IpAddr; use std::str::FromStr; -use pkarr::PublicKey; +use pubky_common::crypto::PublicKey; /// The key to limit the quota on. #[derive(Debug, Clone, PartialEq, Eq, Hash)] @@ -47,7 +47,7 @@ impl fmt::Display for LimitKey { f, "{}", match self { - LimitKey::User(user_pubkey) => user_pubkey.to_string(), + LimitKey::User(user_pubkey) => user_pubkey.z32(), LimitKey::Ip(ip_addr) => ip_addr.to_string(), } ) @@ -130,7 +130,7 @@ impl<'de> serde::Deserialize<'de> for LimitKeyType { mod tests { use std::net::Ipv4Addr; - use pkarr::Keypair; + use pubky_common::crypto::Keypair; use super::*; diff --git a/pubky-homeserver/src/data_directory/quota_config/path_limit.rs b/pubky-homeserver/src/data_directory/quota_config/path_limit.rs index b5103141..7b112560 100644 --- a/pubky-homeserver/src/data_directory/quota_config/path_limit.rs +++ b/pubky-homeserver/src/data_directory/quota_config/path_limit.rs @@ -98,7 +98,7 @@ mod tests { str::FromStr, }; - use pkarr::Keypair; + use pubky_common::crypto::Keypair; use super::*; diff --git a/pubky-homeserver/src/homeserver_app.rs b/pubky-homeserver/src/homeserver_app.rs index 66900a5c..1c25d69e 100644 --- a/pubky-homeserver/src/homeserver_app.rs +++ b/pubky-homeserver/src/homeserver_app.rs @@ -9,7 +9,7 @@ use crate::tracing::init_tracing_logs_with_config_if_set; use crate::MockDataDir; use crate::{app_context::AppContext, data_directory::PersistentDataDir}; use anyhow::Result; -use pkarr::PublicKey; +use pubky_common::crypto::PublicKey; use std::path::PathBuf; use std::time::Duration; @@ -137,7 +137,7 @@ impl HomeserverApp { /// Returns the `https://` url pub fn pubky_url(&self) -> url::Url { - url::Url::parse(&format!("https://{}", self.public_key())).expect("valid url") + url::Url::parse(&format!("https://{}", self.public_key().z32())).expect("valid url") } /// Returns the `https://` url diff --git a/pubky-homeserver/src/persistence/files/entry/entry_layer.rs b/pubky-homeserver/src/persistence/files/entry/entry_layer.rs index a8ad3cf3..ea1dce6c 100644 --- a/pubky-homeserver/src/persistence/files/entry/entry_layer.rs +++ b/pubky-homeserver/src/persistence/files/entry/entry_layer.rs @@ -243,7 +243,7 @@ mod tests { let layer = EntryLayer::new(db.clone()); let operator = operator.layer(layer); - let pubkey = pkarr::Keypair::random().public_key(); + let pubkey = pubky_common::crypto::Keypair::random().public_key(); UserRepository::create(&pubkey, &mut db.pool().into()) .await .unwrap(); diff --git a/pubky-homeserver/src/persistence/files/events/events_entity.rs b/pubky-homeserver/src/persistence/files/events/events_entity.rs index 01575e98..8ae1c021 100644 --- a/pubky-homeserver/src/persistence/files/events/events_entity.rs +++ b/pubky-homeserver/src/persistence/files/events/events_entity.rs @@ -1,7 +1,5 @@ -use std::str::FromStr; - -use pkarr::PublicKey; use pubky_common::crypto::Hash; +use pubky_common::crypto::PublicKey; use sea_query::Iden; use sqlx::{postgres::PgRow, FromRow, Row}; @@ -38,10 +36,10 @@ impl FromRow<'_, PgRow> for EventEntity { let user_id: i32 = row.try_get(EventIden::User.to_string().as_str())?; let user_public_key: String = row.try_get(UserIden::PublicKey.to_string().as_str())?; let user_pubkey = - PublicKey::from_str(&user_public_key).map_err(|e| sqlx::Error::Decode(e.into()))?; + PublicKey::try_from_z32(&user_public_key).map_err(|e| sqlx::Error::Decode(e.into()))?; let event_type_str: String = row.try_get(EventIden::Type.to_string().as_str())?; let user_public_key = - PublicKey::from_str(&user_public_key).map_err(|e| sqlx::Error::Decode(e.into()))?; + PublicKey::try_from_z32(&user_public_key).map_err(|e| sqlx::Error::Decode(e.into()))?; let path: String = row.try_get(EventIden::Path.to_string().as_str())?; let path = WebDavPath::new(&path).map_err(|e| sqlx::Error::Decode(e.into()))?; let created_at: sqlx::types::chrono::NaiveDateTime = diff --git a/pubky-homeserver/src/persistence/files/events/events_layer.rs b/pubky-homeserver/src/persistence/files/events/events_layer.rs index 67533854..1bb230fd 100644 --- a/pubky-homeserver/src/persistence/files/events/events_layer.rs +++ b/pubky-homeserver/src/persistence/files/events/events_layer.rs @@ -280,7 +280,7 @@ mod tests { let layer = EventsLayer::new(db.clone(), events_service); let operator = operator.layer(layer); - let pubkey = pkarr::Keypair::random().public_key(); + let pubkey = pubky_common::crypto::Keypair::random().public_key(); UserRepository::create(&pubkey, &mut db.pool().into()) .await .unwrap(); diff --git a/pubky-homeserver/src/persistence/files/events/events_repository.rs b/pubky-homeserver/src/persistence/files/events/events_repository.rs index 0745bef3..d65bca31 100644 --- a/pubky-homeserver/src/persistence/files/events/events_repository.rs +++ b/pubky-homeserver/src/persistence/files/events/events_repository.rs @@ -334,7 +334,7 @@ impl Display for EventType { #[cfg(test)] mod tests { - use pkarr::Keypair; + use pubky_common::crypto::Keypair; use crate::{ persistence::sql::{user::UserRepository, SqlDb}, diff --git a/pubky-homeserver/src/persistence/files/events/events_service.rs b/pubky-homeserver/src/persistence/files/events/events_service.rs index 8e743695..46b1339a 100644 --- a/pubky-homeserver/src/persistence/files/events/events_service.rs +++ b/pubky-homeserver/src/persistence/files/events/events_service.rs @@ -122,7 +122,7 @@ mod tests { use super::*; use crate::persistence::sql::{user::UserRepository, SqlDb}; use crate::shared::webdav::WebDavPath; - use pkarr::Keypair; + use pubky_common::crypto::Keypair; #[tokio::test] #[pubky_test_utils::test] diff --git a/pubky-homeserver/src/persistence/files/file/file_service.rs b/pubky-homeserver/src/persistence/files/file/file_service.rs index 2be2dedd..865eabc3 100644 --- a/pubky-homeserver/src/persistence/files/file/file_service.rs +++ b/pubky-homeserver/src/persistence/files/file/file_service.rs @@ -147,7 +147,7 @@ mod tests { let context = AppContext::test().await; let file_service = FileService::new_from_context(&context).unwrap(); let db = context.sql_db.clone(); - let pubkey = pkarr::Keypair::random().public_key(); + let pubkey = pubky_common::crypto::Keypair::random().public_key(); let user = UserRepository::create(&pubkey, &mut db.pool().into()) .await @@ -263,7 +263,7 @@ mod tests { let file_service = FileService::new_from_context(&context).unwrap(); let db = context.sql_db.clone(); - let pubkey = pkarr::Keypair::random().public_key(); + let pubkey = pubky_common::crypto::Keypair::random().public_key(); UserRepository::create(&pubkey, &mut db.pool().into()) .await .unwrap(); @@ -295,7 +295,7 @@ mod tests { let file_service = FileService::new_from_context(&context).unwrap(); let db = context.sql_db.clone(); - let pubkey = pkarr::Keypair::random().public_key(); + let pubkey = pubky_common::crypto::Keypair::random().public_key(); UserRepository::create(&pubkey, &mut db.pool().into()) .await .unwrap(); @@ -327,7 +327,7 @@ mod tests { let file_service = FileService::new_from_context(&context).unwrap(); let db = context.sql_db.clone(); - let pubkey = pkarr::Keypair::random().public_key(); + let pubkey = pubky_common::crypto::Keypair::random().public_key(); UserRepository::create(&pubkey, &mut db.pool().into()) .await .unwrap(); @@ -362,7 +362,7 @@ mod tests { let file_service = FileService::new_from_context(&context).unwrap(); let db = context.sql_db.clone(); - let pubkey = pkarr::Keypair::random().public_key(); + let pubkey = pubky_common::crypto::Keypair::random().public_key(); UserRepository::create(&pubkey, &mut db.pool().into()) .await .unwrap(); @@ -390,7 +390,7 @@ mod tests { let file_service = FileService::new_from_context(&context).unwrap(); let db = context.sql_db.clone(); - let pubkey = pkarr::Keypair::random().public_key(); + let pubkey = pubky_common::crypto::Keypair::random().public_key(); UserRepository::create(&pubkey, &mut db.pool().into()) .await .unwrap(); @@ -425,7 +425,7 @@ mod tests { let file_service = FileService::new_from_context(&context).unwrap(); let db = context.sql_db.clone(); - let pubkey = pkarr::Keypair::random().public_key(); + let pubkey = pubky_common::crypto::Keypair::random().public_key(); UserRepository::create(&pubkey, &mut db.pool().into()) .await .unwrap(); diff --git a/pubky-homeserver/src/persistence/files/opendal/opendal_service.rs b/pubky-homeserver/src/persistence/files/opendal/opendal_service.rs index 20c30a34..f92b8a07 100644 --- a/pubky-homeserver/src/persistence/files/opendal/opendal_service.rs +++ b/pubky-homeserver/src/persistence/files/opendal/opendal_service.rs @@ -285,7 +285,7 @@ mod tests { let service = OpendalService::new(&context).expect("Failed to create OpenDAL service for testing"); - let pubky = pkarr::Keypair::random().public_key(); + let pubky = pubky_common::crypto::Keypair::random().public_key(); UserRepository::create(&pubky, &mut context.sql_db.pool().into()) .await .unwrap(); @@ -302,7 +302,7 @@ mod tests { context.config_toml.general.user_storage_quota_mb = 1; let service = OpendalService::new(&context).expect("Failed to create OpenDAL service for testing"); - let pubky = pkarr::Keypair::random().public_key(); + let pubky = pubky_common::crypto::Keypair::random().public_key(); UserRepository::create(&pubky, &mut context.sql_db.pool().into()) .await .unwrap(); @@ -323,7 +323,7 @@ mod tests { for (_scheme, operator) in operators.operators() { let file_service = OpendalService::new_from_operator(operator); - let pubkey = pkarr::Keypair::random().public_key(); + let pubkey = pubky_common::crypto::Keypair::random().public_key(); let path = EntryPath::new(pubkey, WebDavPath::new("/test.txt").unwrap()); // Write a 10KB file filled with test data @@ -385,7 +385,7 @@ mod tests { for (_scheme, operator) in operators.operators() { let file_service = OpendalService::new_from_operator(operator); - let pubkey = pkarr::Keypair::random().public_key(); + let pubkey = pubky_common::crypto::Keypair::random().public_key(); let path = EntryPath::new(pubkey, WebDavPath::new("/test_stream.txt").unwrap()); // Create test data - multiple chunks to test streaming diff --git a/pubky-homeserver/src/persistence/files/user_quota_layer.rs b/pubky-homeserver/src/persistence/files/user_quota_layer.rs index 6d856ad5..b73ea898 100644 --- a/pubky-homeserver/src/persistence/files/user_quota_layer.rs +++ b/pubky-homeserver/src/persistence/files/user_quota_layer.rs @@ -1,6 +1,8 @@ use std::collections::HashMap; use std::sync::Arc; +use pubky_common::crypto::PublicKey; + use crate::persistence::files::utils::ensure_valid_path; use crate::persistence::sql::SqlDb; use crate::persistence::sql::{uexecutor, user::UserRepository}; @@ -63,17 +65,19 @@ impl LayeredAccess for UserQuotaAccessor { } async fn create_dir(&self, path: &str, args: OpCreateDir) -> Result { - ensure_valid_path(path)?; - self.inner.create_dir(path, args).await + let entry_path = ensure_valid_path(path)?; + self.inner.create_dir(entry_path.as_str(), args).await } async fn read(&self, path: &str, args: OpRead) -> Result<(RpRead, Self::Reader)> { - self.inner.read(path, args).await + let entry_path = ensure_valid_path(path)?; + self.inner.read(entry_path.as_str(), args).await } async fn write(&self, path: &str, args: OpWrite) -> Result<(RpWrite, Self::Writer)> { let entry_path = ensure_valid_path(path)?; - let (rp, writer) = self.inner.write(path, args).await?; + let canonical_path = entry_path.to_string(); + let (rp, writer) = self.inner.write(&canonical_path, args).await?; Ok(( rp, WriterWrapper { @@ -88,17 +92,20 @@ impl LayeredAccess for UserQuotaAccessor { } async fn copy(&self, from: &str, to: &str, args: OpCopy) -> Result { - let _ = ensure_valid_path(to)?; - self.inner.copy(from, to, args).await + let from = ensure_valid_path(from)?; + let to = ensure_valid_path(to)?; + self.inner.copy(from.as_str(), to.as_str(), args).await } async fn rename(&self, from: &str, to: &str, args: OpRename) -> Result { - let _ = ensure_valid_path(to)?; - self.inner.rename(from, to, args).await + let from = ensure_valid_path(from)?; + let to = ensure_valid_path(to)?; + self.inner.rename(from.as_str(), to.as_str(), args).await } async fn stat(&self, path: &str, args: OpStat) -> Result { - self.inner.stat(path, args).await + let entry_path = ensure_valid_path(path)?; + self.inner.stat(entry_path.as_str(), args).await } async fn delete(&self) -> Result<(RpDelete, Self::Deleter)> { @@ -119,7 +126,8 @@ impl LayeredAccess for UserQuotaAccessor { } async fn presign(&self, path: &str, args: OpPresign) -> Result { - self.inner.presign(path, args).await + let entry_path = ensure_valid_path(path)?; + self.inner.presign(entry_path.as_str(), args).await } } @@ -272,7 +280,7 @@ pub struct DeleterWrapper { impl DeleterWrapper { async fn update_user_quota(&self, deleted_paths: Vec) -> Result<()> { // Group deleted paths by user pubkey - let mut user_paths: HashMap> = HashMap::new(); + let mut user_paths: HashMap> = HashMap::new(); for path in deleted_paths { user_paths .entry(path.entry_path.pubkey().clone()) @@ -351,7 +359,7 @@ impl oio::Delete for DeleterWrapper { )); } }; - self.inner.delete(path, args)?; + self.inner.delete(helper.entry_path.as_str(), args)?; self.path_queue.push(helper); Ok(()) } @@ -365,10 +373,7 @@ mod tests { use super::*; - async fn get_user_data_usage( - db: &SqlDb, - user_pubkey: &pkarr::PublicKey, - ) -> anyhow::Result { + async fn get_user_data_usage(db: &SqlDb, user_pubkey: &PublicKey) -> anyhow::Result { let user = UserRepository::get(user_pubkey, &mut db.pool().into()) .await .map_err(|e| opendal::Error::new(opendal::ErrorKind::Unexpected, e.to_string()))?; @@ -387,12 +392,13 @@ mod tests { .write("1234567890/test.txt", vec![0; 10]) .await .expect_err("Should fail because the path doesn't start with a pubkey"); - let pubkey = pkarr::Keypair::random().public_key(); + let pubkey = pubky_common::crypto::Keypair::random().public_key(); + let pubkey_raw = pubkey.z32(); UserRepository::create(&pubkey, &mut db.pool().into()) .await .unwrap(); operator - .write(format!("{}/test.txt", pubkey).as_str(), vec![0; 10]) + .write(format!("{}/test.txt", pubkey_raw).as_str(), vec![0; 10]) .await .expect("Should succeed because the path starts with a pubkey"); operator @@ -409,14 +415,18 @@ mod tests { let layer = UserQuotaLayer::new(db.clone(), 1024 * 1024); let operator = get_memory_operator().layer(layer); - let user_pubkey1 = pkarr::Keypair::random().public_key(); + let user_pubkey1 = pubky_common::crypto::Keypair::random().public_key(); + let user_pubkey1_raw = user_pubkey1.z32(); UserRepository::create(&user_pubkey1, &mut db.pool().into()) .await .unwrap(); // Write a file and see if the user usage is updated operator - .write(format!("{}/test.txt1", user_pubkey1).as_str(), vec![0; 10]) + .write( + format!("{}/test.txt1", user_pubkey1_raw).as_str(), + vec![0; 10], + ) .await .unwrap(); let user_usage = get_user_data_usage(&db, &user_pubkey1).await.unwrap(); @@ -424,7 +434,10 @@ mod tests { // Write the same file again but with a different size operator - .write(format!("{}/test.txt1", user_pubkey1).as_str(), vec![0; 12]) + .write( + format!("{}/test.txt1", user_pubkey1_raw).as_str(), + vec![0; 12], + ) .await .unwrap(); let user_usage = get_user_data_usage(&db, &user_pubkey1).await.unwrap(); @@ -432,7 +445,10 @@ mod tests { // Write a second file and see if the user usage is updated operator - .write(format!("{}/test.txt2", user_pubkey1).as_str(), vec![0; 5]) + .write( + format!("{}/test.txt2", user_pubkey1_raw).as_str(), + vec![0; 5], + ) .await .unwrap(); let user_usage = get_user_data_usage(&db, &user_pubkey1).await.unwrap(); @@ -440,7 +456,7 @@ mod tests { // Delete the first file and see if the user usage is updated operator - .delete(format!("{}/test.txt1", user_pubkey1).as_str()) + .delete(format!("{}/test.txt1", user_pubkey1_raw).as_str()) .await .unwrap(); let user_usage = get_user_data_usage(&db, &user_pubkey1).await.unwrap(); @@ -448,7 +464,7 @@ mod tests { // Delete the second file and see if the user usage is updated operator - .delete(format!("{}/test.txt2", user_pubkey1).as_str()) + .delete(format!("{}/test.txt2", user_pubkey1_raw).as_str()) .await .unwrap(); let user_usage = get_user_data_usage(&db, &user_pubkey1).await.unwrap(); @@ -473,12 +489,13 @@ mod tests { .layer(entry_layer) .layer(events_layer); - let user_pubkey1 = pkarr::Keypair::random().public_key(); + let user_pubkey1 = pubky_common::crypto::Keypair::random().public_key(); + let user_pubkey1_raw = user_pubkey1.z32(); UserRepository::create(&user_pubkey1, &mut db.pool().into()) .await .unwrap(); - let file_name1 = format!("{}/test1.txt", user_pubkey1); + let file_name1 = format!("{}/test1.txt", user_pubkey1_raw); let entry_path1 = EntryPath::new(user_pubkey1.clone(), WebDavPath::new("/test1.txt").unwrap()); @@ -541,7 +558,7 @@ mod tests { "Event should be created after successful write" ); - let file_name2 = format!("{}/test2.txt", user_pubkey1); + let file_name2 = format!("{}/test2.txt", user_pubkey1_raw); // Write a second file and see if the user usage is updated operator .write(file_name2.as_str(), vec![0; 1]) diff --git a/pubky-homeserver/src/persistence/lmdb/migrations/m220420251247_add_user_disabled_used_bytes.rs b/pubky-homeserver/src/persistence/lmdb/migrations/m220420251247_add_user_disabled_used_bytes.rs index 2f2cd04c..1915577b 100644 --- a/pubky-homeserver/src/persistence/lmdb/migrations/m220420251247_add_user_disabled_used_bytes.rs +++ b/pubky-homeserver/src/persistence/lmdb/migrations/m220420251247_add_user_disabled_used_bytes.rs @@ -1,8 +1,8 @@ use super::super::tables::users; use crate::persistence::lmdb::tables::users::PublicKeyCodec; use heed::{BoxedError, BytesDecode, BytesEncode, Database, Env, RwTxn}; -use pkarr::PublicKey; use postcard::{from_bytes, to_allocvec}; +use pubky_common::crypto::PublicKey; use serde::{Deserialize, Serialize}; use std::borrow::Cow; @@ -103,7 +103,7 @@ fn read_old_users_table(env: &Env, wtxn: &mut RwTxn) -> anyhow::Result = vec![]; for entry in table.iter(wtxn)? { let (key, old_user) = entry?; - new_users.push((key, old_user)); + new_users.push((key.into(), old_user)); } Ok(new_users) @@ -152,7 +152,7 @@ pub fn run(env: &Env, wtxn: &mut RwTxn) -> anyhow::Result<()> { #[cfg(test)] mod tests { use heed::EnvOpenOptions; - use pkarr::Keypair; + use pubky_common::crypto::Keypair; use crate::persistence::lmdb::{db::DEFAULT_MAP_SIZE, migrations::m0}; diff --git a/pubky-homeserver/src/persistence/lmdb/sql_migrator/entries.rs b/pubky-homeserver/src/persistence/lmdb/sql_migrator/entries.rs index bd711d6f..481ee09b 100644 --- a/pubky-homeserver/src/persistence/lmdb/sql_migrator/entries.rs +++ b/pubky-homeserver/src/persistence/lmdb/sql_migrator/entries.rs @@ -74,7 +74,7 @@ pub async fn migrate_entries<'a>( #[cfg(test)] mod tests { - use pkarr::Keypair; + use pubky_common::crypto::Keypair; use pubky_common::{crypto::Hash, timestamp::Timestamp}; use crate::{ diff --git a/pubky-homeserver/src/persistence/lmdb/sql_migrator/events.rs b/pubky-homeserver/src/persistence/lmdb/sql_migrator/events.rs index 01f36cd5..7de0edee 100644 --- a/pubky-homeserver/src/persistence/lmdb/sql_migrator/events.rs +++ b/pubky-homeserver/src/persistence/lmdb/sql_migrator/events.rs @@ -92,7 +92,7 @@ pub async fn migrate_events<'a>( #[cfg(test)] mod tests { - use pkarr::Keypair; + use pubky_common::crypto::Keypair; use pubky_common::timestamp::Timestamp; use sqlx::types::chrono::DateTime; diff --git a/pubky-homeserver/src/persistence/lmdb/sql_migrator/sessions.rs b/pubky-homeserver/src/persistence/lmdb/sql_migrator/sessions.rs index 76c544f0..e413d748 100644 --- a/pubky-homeserver/src/persistence/lmdb/sql_migrator/sessions.rs +++ b/pubky-homeserver/src/persistence/lmdb/sql_migrator/sessions.rs @@ -71,8 +71,8 @@ pub async fn migrate_sessions<'a>( mod tests { use std::time::{SystemTime, UNIX_EPOCH}; - use pkarr::Keypair; use pubky_common::capabilities::{Capabilities, Capability}; + use pubky_common::crypto::Keypair; use crate::persistence::sql::{ session::{SessionRepository, SessionSecret}, diff --git a/pubky-homeserver/src/persistence/lmdb/sql_migrator/signup_codes.rs b/pubky-homeserver/src/persistence/lmdb/sql_migrator/signup_codes.rs index 1314d408..4076d7b8 100644 --- a/pubky-homeserver/src/persistence/lmdb/sql_migrator/signup_codes.rs +++ b/pubky-homeserver/src/persistence/lmdb/sql_migrator/signup_codes.rs @@ -18,7 +18,7 @@ pub async fn create<'a>( executor: &mut UnifiedExecutor<'a>, ) -> Result<(), sqlx::Error> { let used_by = match lmdb_token.used.as_ref() { - Some(p) => SimpleExpr::Value(p.to_string().into()), + Some(p) => SimpleExpr::Value(p.z32().into()), None => SimpleExpr::Value(Value::String(None)), }; let created_at = @@ -66,7 +66,7 @@ pub async fn migrate_signup_codes<'a>( mod tests { use std::time::{SystemTime, UNIX_EPOCH}; - use pkarr::Keypair; + use pubky_common::crypto::Keypair; use crate::persistence::sql::{ signup_code::{SignupCodeId, SignupCodeRepository}, diff --git a/pubky-homeserver/src/persistence/lmdb/sql_migrator/users.rs b/pubky-homeserver/src/persistence/lmdb/sql_migrator/users.rs index b0e36f55..90cba211 100644 --- a/pubky-homeserver/src/persistence/lmdb/sql_migrator/users.rs +++ b/pubky-homeserver/src/persistence/lmdb/sql_migrator/users.rs @@ -9,6 +9,7 @@ use crate::persistence::{ UnifiedExecutor, }, }; +use pubky_common::crypto::PublicKey; /// Convert nano seconds to a timestamp. pub fn nano_seconds_to_timestamp(nano_seconds: u64) -> Option> { @@ -56,6 +57,7 @@ pub async fn migrate_users<'a>( let mut count = 0; for record in lmdb.tables.users.iter(&lmdb_txn)? { let (public_key, lmdb_user) = record?; + let public_key: PublicKey = public_key.into(); let mut sql_user = UserRepository::create(&public_key, executor).await?; sql_user.created_at = nano_seconds_to_timestamp(lmdb_user.created_at) .expect("Failed to convert nano seconds to timestamp") @@ -73,7 +75,7 @@ pub async fn migrate_users<'a>( mod tests { use std::time::{SystemTime, UNIX_EPOCH}; - use pkarr::Keypair; + use pubky_common::crypto::Keypair; use crate::persistence::{lmdb::tables::users::User, sql::SqlDb}; @@ -88,14 +90,16 @@ mod tests { let mut wtxn = lmdb.env.write_txn().unwrap(); // User1 let user1_pubkey = Keypair::random().public_key(); - let mut lmdb_user1 = User::default(); - lmdb_user1.created_at = SystemTime::now() + let user1_created_at = SystemTime::now() .duration_since(UNIX_EPOCH) .unwrap() .as_secs() * 1_000_000; - lmdb_user1.used_bytes = 100; - lmdb_user1.disabled = true; + let lmdb_user1 = User { + created_at: user1_created_at, + used_bytes: 100, + disabled: true, + }; lmdb.tables .users .put(&mut wtxn, &user1_pubkey, &lmdb_user1) @@ -103,14 +107,16 @@ mod tests { // User2 let user2_pubkey = Keypair::random().public_key(); - let mut lmdb_user2 = User::default(); - lmdb_user2.created_at = SystemTime::now() + let user2_created_at = SystemTime::now() .duration_since(UNIX_EPOCH) .unwrap() .as_secs() * 1_000_000; - lmdb_user2.used_bytes = 200; - lmdb_user2.disabled = false; + let lmdb_user2 = User { + created_at: user2_created_at, + used_bytes: 200, + disabled: false, + }; lmdb.tables .users .put(&mut wtxn, &user2_pubkey, &lmdb_user2) diff --git a/pubky-homeserver/src/persistence/lmdb/tables/signup_tokens.rs b/pubky-homeserver/src/persistence/lmdb/tables/signup_tokens.rs index 4f883a7c..625640d3 100644 --- a/pubky-homeserver/src/persistence/lmdb/tables/signup_tokens.rs +++ b/pubky-homeserver/src/persistence/lmdb/tables/signup_tokens.rs @@ -2,8 +2,8 @@ use heed::{ types::{Bytes, Str}, Database, }; -use pkarr::PublicKey; use postcard::from_bytes; +use pubky_common::crypto::PublicKey; use serde::{Deserialize, Serialize}; diff --git a/pubky-homeserver/src/persistence/sql/entities/entry/entity.rs b/pubky-homeserver/src/persistence/sql/entities/entry/entity.rs index 8367dcf1..44915df0 100644 --- a/pubky-homeserver/src/persistence/sql/entities/entry/entity.rs +++ b/pubky-homeserver/src/persistence/sql/entities/entry/entity.rs @@ -1,4 +1,4 @@ -use pkarr::PublicKey; +use pubky_common::crypto::PublicKey; use sea_query::Iden; use sqlx::{postgres::PgRow, FromRow, Row}; diff --git a/pubky-homeserver/src/persistence/sql/entities/entry/repository.rs b/pubky-homeserver/src/persistence/sql/entities/entry/repository.rs index c4565996..1083b2c4 100644 --- a/pubky-homeserver/src/persistence/sql/entities/entry/repository.rs +++ b/pubky-homeserver/src/persistence/sql/entities/entry/repository.rs @@ -110,7 +110,7 @@ impl EntryRepository { Expr::col((ENTRY_TABLE, EntryIden::User)).eq(Expr::col((USER_TABLE, UserIden::Id))), ) .and_where(Expr::col((ENTRY_TABLE, EntryIden::Path)).eq(path.path().as_str())) - .and_where(Expr::col((USER_TABLE, UserIden::PublicKey)).eq(path.pubkey().to_string())) + .and_where(Expr::col((USER_TABLE, UserIden::PublicKey)).eq(path.pubkey().z32())) .to_owned(); let (query, values) = statement.build_sqlx(PostgresQueryBuilder); let con = executor.get_con().await?; @@ -179,7 +179,7 @@ impl EntryRepository { Expr::col((ENTRY_TABLE, EntryIden::User)).eq(Expr::col((USER_TABLE, UserIden::Id))), ) .and_where(Expr::col((ENTRY_TABLE, EntryIden::Path)).eq(path.path().as_str())) - .and_where(Expr::col((USER_TABLE, UserIden::PublicKey)).eq(path.pubkey().to_string())) + .and_where(Expr::col((USER_TABLE, UserIden::PublicKey)).eq(path.pubkey().z32())) .to_owned(); // Then delete the entry by the id @@ -213,7 +213,7 @@ impl EntryRepository { Expr::col((ENTRY_TABLE, EntryIden::User)).eq(Expr::col((USER_TABLE, UserIden::Id))), ) .and_where(Expr::col((ENTRY_TABLE, EntryIden::Path)).like(format!("{}%", full_path))) // Everything that starts with the path - .and_where(Expr::col((USER_TABLE, UserIden::PublicKey)).eq(path.pubkey().to_string())) + .and_where(Expr::col((USER_TABLE, UserIden::PublicKey)).eq(path.pubkey().z32())) .limit(1) .to_owned(); @@ -256,7 +256,7 @@ impl EntryRepository { Expr::col((ENTRY_TABLE, EntryIden::User)).eq(Expr::col((USER_TABLE, UserIden::Id))), ) .and_where(Expr::col((ENTRY_TABLE, EntryIden::Path)).like(format!("{}%", dir_path))) // Everything that starts with the path - .and_where(Expr::col((USER_TABLE, UserIden::PublicKey)).eq(path.pubkey().to_string())) + .and_where(Expr::col((USER_TABLE, UserIden::PublicKey)).eq(path.pubkey().z32())) .to_owned(); // Use a select in select to filter the previous regex regpath @@ -343,7 +343,7 @@ impl EntryRepository { Expr::col((ENTRY_TABLE, EntryIden::User)).eq(Expr::col((USER_TABLE, UserIden::Id))), ) .and_where(Expr::col((ENTRY_TABLE, EntryIden::Path)).like(format!("{}%", full_path))) // Everything that starts with the path - .and_where(Expr::col((USER_TABLE, UserIden::PublicKey)).eq(path.pubkey().to_string())) + .and_where(Expr::col((USER_TABLE, UserIden::PublicKey)).eq(path.pubkey().z32())) .to_owned(); if reverse { @@ -414,7 +414,7 @@ pub enum EntryIden { mod tests { use super::*; use crate::persistence::sql::{entities::user::UserRepository, SqlDb}; - use pkarr::Keypair; + use pubky_common::crypto::Keypair; use std::collections::HashSet; #[tokio::test] diff --git a/pubky-homeserver/src/persistence/sql/entities/session.rs b/pubky-homeserver/src/persistence/sql/entities/session.rs index 86eed8cf..daa05e4d 100644 --- a/pubky-homeserver/src/persistence/sql/entities/session.rs +++ b/pubky-homeserver/src/persistence/sql/entities/session.rs @@ -1,6 +1,6 @@ use std::{fmt::Display, str::FromStr}; -use pkarr::PublicKey; +use pubky_common::crypto::PublicKey; use pubky_common::{capabilities::Capabilities, crypto::random_bytes, session::SessionInfo}; use sea_query::{Expr, Iden, PostgresQueryBuilder, Query, SimpleExpr}; use sea_query_binder::SqlxBinder; @@ -197,8 +197,8 @@ impl FromRow<'_, PgRow> for SessionEntity { #[cfg(test)] mod tests { - use pkarr::Keypair; use pubky_common::capabilities::Capability; + use pubky_common::crypto::Keypair; use crate::persistence::sql::{entities::user::UserRepository, SqlDb}; diff --git a/pubky-homeserver/src/persistence/sql/entities/signup_code.rs b/pubky-homeserver/src/persistence/sql/entities/signup_code.rs index 1cfe9298..ee9bfae2 100644 --- a/pubky-homeserver/src/persistence/sql/entities/signup_code.rs +++ b/pubky-homeserver/src/persistence/sql/entities/signup_code.rs @@ -1,8 +1,8 @@ use std::{fmt::Display, str::FromStr}; use base32::{decode, encode, Alphabet}; -use pkarr::PublicKey; use pubky_common::crypto::random_bytes; +use pubky_common::crypto::PublicKey; use sea_query::{Expr, Iden, PostgresQueryBuilder, Query, SimpleExpr}; use sea_query_binder::SqlxBinder; use sqlx::{postgres::PgRow, FromRow, Row}; @@ -101,7 +101,7 @@ impl SignupCodeRepository { .table(SIGNUP_CODE_TABLE) .values(vec![( SignupCodeIden::UsedBy, - SimpleExpr::Value(used_by.to_string().into()), + SimpleExpr::Value(used_by.z32().into()), )]) .and_where(Expr::col(SignupCodeIden::Id).eq(id.to_string())) .returning_all() @@ -207,7 +207,9 @@ impl FromRow<'_, PgRow> for SignupCodeEntity { let used_by_raw: Option = row.try_get(SignupCodeIden::UsedBy.to_string().as_str())?; let used_by = used_by_raw - .map(|s| PublicKey::try_from(s.as_str()).map_err(|e| sqlx::Error::Decode(Box::new(e)))) + .map(|s| { + PublicKey::try_from_z32(s.as_str()).map_err(|e| sqlx::Error::Decode(Box::new(e))) + }) .transpose()?; Ok(SignupCodeEntity { id, @@ -220,7 +222,7 @@ impl FromRow<'_, PgRow> for SignupCodeEntity { #[cfg(test)] mod tests { use crate::persistence::sql::SqlDb; - use pkarr::Keypair; + use pubky_common::crypto::Keypair; use super::*; diff --git a/pubky-homeserver/src/persistence/sql/entities/user.rs b/pubky-homeserver/src/persistence/sql/entities/user.rs index 66b8862f..df0a5d83 100644 --- a/pubky-homeserver/src/persistence/sql/entities/user.rs +++ b/pubky-homeserver/src/persistence/sql/entities/user.rs @@ -1,4 +1,4 @@ -use pkarr::PublicKey; +use pubky_common::crypto::PublicKey; use sea_query::{Expr, Iden, PostgresQueryBuilder, Query, SimpleExpr}; use sea_query_binder::SqlxBinder; use sqlx::{postgres::PgRow, FromRow, Row}; @@ -19,7 +19,7 @@ impl UserRepository { let statement = Query::insert() .into_table(USER_TABLE) .columns([UserIden::PublicKey]) - .values(vec![SimpleExpr::Value(public_key.to_string().into())]) + .values(vec![SimpleExpr::Value(public_key.z32().into())]) .unwrap() .returning_all() .to_owned(); @@ -46,7 +46,7 @@ impl UserRepository { UserIden::Disabled, UserIden::UsedBytes, ]) - .and_where(Expr::col(UserIden::PublicKey).eq(public_key.to_string())) + .and_where(Expr::col(UserIden::PublicKey).eq(public_key.z32())) .to_owned(); let (query, values) = statement.build_sqlx(PostgresQueryBuilder); let con = executor.get_con().await?; @@ -62,7 +62,7 @@ impl UserRepository { let statement = Query::select() .from(USER_TABLE) .columns([UserIden::Id]) - .and_where(Expr::col(UserIden::PublicKey).eq(public_key.to_string())) + .and_where(Expr::col(UserIden::PublicKey).eq(public_key.z32())) .to_owned(); let (query, values) = statement.build_sqlx(PostgresQueryBuilder); let con = executor.get_con().await?; @@ -216,7 +216,7 @@ impl FromRow<'_, PgRow> for UserEntity { fn from_row(row: &PgRow) -> Result { let id: i32 = row.try_get(UserIden::Id.to_string().as_str())?; let raw_pubkey: String = row.try_get(UserIden::PublicKey.to_string().as_str())?; - let public_key = PublicKey::try_from(raw_pubkey.as_str()) + let public_key = PublicKey::try_from_z32(raw_pubkey.as_str()) .map_err(|e| sqlx::Error::Decode(Box::new(e)))?; let disabled: bool = row.try_get(UserIden::Disabled.to_string().as_str())?; let raw_used_bytes: i64 = row.try_get(UserIden::UsedBytes.to_string().as_str())?; @@ -235,7 +235,7 @@ impl FromRow<'_, PgRow> for UserEntity { #[cfg(test)] mod tests { - use pkarr::Keypair; + use pubky_common::crypto::Keypair; use crate::persistence::sql::SqlDb; diff --git a/pubky-homeserver/src/persistence/sql/migrations/m20250806_create_user.rs b/pubky-homeserver/src/persistence/sql/migrations/m20250806_create_user.rs index d37b461a..bc511358 100644 --- a/pubky-homeserver/src/persistence/sql/migrations/m20250806_create_user.rs +++ b/pubky-homeserver/src/persistence/sql/migrations/m20250806_create_user.rs @@ -75,12 +75,12 @@ enum User { #[cfg(test)] mod tests { - use pkarr::Keypair; + use pubky_common::crypto::Keypair; use sea_query::{Query, SimpleExpr}; use sea_query_binder::SqlxBinder; use crate::persistence::sql::{migrator::Migrator, SqlDb}; - use pkarr::PublicKey; + use pubky_common::crypto::PublicKey; use sea_query::PostgresQueryBuilder; use sqlx::{postgres::PgRow, FromRow, Row}; @@ -99,7 +99,7 @@ mod tests { fn from_row(row: &PgRow) -> Result { let id: i32 = row.try_get(User::Id.to_string().as_str())?; let raw_pubkey: String = row.try_get(User::PublicKey.to_string().as_str())?; - let public_key = PublicKey::try_from(raw_pubkey.as_str()) + let public_key = PublicKey::try_from_z32(raw_pubkey.as_str()) .map_err(|e| sqlx::Error::Decode(Box::new(e)))?; let disabled: bool = row.try_get(User::Disabled.to_string().as_str())?; let raw_used_bytes: i64 = row.try_get(User::UsedBytes.to_string().as_str())?; @@ -131,7 +131,7 @@ mod tests { let statement = Query::insert() .into_table(USER_TABLE) .columns([User::PublicKey]) - .values(vec![SimpleExpr::Value(pubkey.to_string().into())]) + .values(vec![SimpleExpr::Value(pubkey.z32().into())]) .unwrap() .to_owned(); let (query, values) = statement.build_sqlx(PostgresQueryBuilder); diff --git a/pubky-homeserver/src/persistence/sql/migrations/m20250812_create_signup_code.rs b/pubky-homeserver/src/persistence/sql/migrations/m20250812_create_signup_code.rs index cd6160fd..7666ed09 100644 --- a/pubky-homeserver/src/persistence/sql/migrations/m20250812_create_signup_code.rs +++ b/pubky-homeserver/src/persistence/sql/migrations/m20250812_create_signup_code.rs @@ -50,7 +50,7 @@ enum SignupCodeIden { #[cfg(test)] mod tests { - use pkarr::{Keypair, PublicKey}; + use pubky_common::crypto::{Keypair, PublicKey}; use sea_query::{Query, SimpleExpr}; use sea_query_binder::SqlxBinder; use sqlx::{postgres::PgRow, FromRow, Row}; @@ -77,7 +77,8 @@ mod tests { row.try_get(SignupCodeIden::UsedBy.to_string().as_str())?; let used_by = used_by_raw .map(|s| { - PublicKey::try_from(s.as_str()).map_err(|e| sqlx::Error::Decode(Box::new(e))) + PublicKey::try_from_z32(s.as_str()) + .map_err(|e| sqlx::Error::Decode(Box::new(e))) }) .transpose()?; Ok(SignupCodeEntity { @@ -139,7 +140,7 @@ mod tests { .table(SIGNUP_CODE_TABLE) .values(vec![( SignupCodeIden::UsedBy, - SimpleExpr::Value(pubkey.to_string().into()), + SimpleExpr::Value(pubkey.z32().into()), )]) .and_where(Expr::col(SignupCodeIden::Id).eq(code.id)) .to_owned(); diff --git a/pubky-homeserver/src/persistence/sql/migrations/m20250813_create_session.rs b/pubky-homeserver/src/persistence/sql/migrations/m20250813_create_session.rs index f1f67140..29a102dd 100644 --- a/pubky-homeserver/src/persistence/sql/migrations/m20250813_create_session.rs +++ b/pubky-homeserver/src/persistence/sql/migrations/m20250813_create_session.rs @@ -82,8 +82,8 @@ enum SessionIden { #[cfg(test)] mod tests { - use pkarr::Keypair; use pubky_common::capabilities::{Capabilities, Capability, CapsBuilder}; + use pubky_common::crypto::Keypair; use sea_query::{Query, SimpleExpr}; use sea_query_binder::SqlxBinder; use sqlx::{postgres::PgRow, FromRow, Row}; @@ -149,7 +149,7 @@ mod tests { let statement = Query::insert() .into_table(USERS_TABLE) .columns([UserIden::PublicKey]) - .values(vec![SimpleExpr::Value(pubkey.to_string().into())]) + .values(vec![SimpleExpr::Value(pubkey.z32().into())]) .unwrap() .to_owned(); let (query, values) = statement.build_sqlx(PostgresQueryBuilder); diff --git a/pubky-homeserver/src/persistence/sql/migrations/m20250814_create_event.rs b/pubky-homeserver/src/persistence/sql/migrations/m20250814_create_event.rs index 19fe1b37..ba13fc40 100644 --- a/pubky-homeserver/src/persistence/sql/migrations/m20250814_create_event.rs +++ b/pubky-homeserver/src/persistence/sql/migrations/m20250814_create_event.rs @@ -66,7 +66,7 @@ enum EventIden { #[cfg(test)] mod tests { - use pkarr::Keypair; + use pubky_common::crypto::Keypair; use sea_query::{Query, SimpleExpr}; use sea_query_binder::SqlxBinder; @@ -126,7 +126,7 @@ mod tests { let statement = Query::insert() .into_table(USERS_TABLE) .columns([UserIden::PublicKey]) - .values(vec![SimpleExpr::Value(pubkey.to_string().into())]) + .values(vec![SimpleExpr::Value(pubkey.z32().into())]) .unwrap() .to_owned(); let (query, values) = statement.build_sqlx(PostgresQueryBuilder); diff --git a/pubky-homeserver/src/persistence/sql/migrations/m20250815_create_entry.rs b/pubky-homeserver/src/persistence/sql/migrations/m20250815_create_entry.rs index 6a2c8515..7a4f1cb7 100644 --- a/pubky-homeserver/src/persistence/sql/migrations/m20250815_create_entry.rs +++ b/pubky-homeserver/src/persistence/sql/migrations/m20250815_create_entry.rs @@ -98,7 +98,7 @@ enum EntryIden { #[cfg(test)] mod tests { - use pkarr::Keypair; + use pubky_common::crypto::Keypair; use sea_query::{Query, SimpleExpr}; use sea_query_binder::SqlxBinder; @@ -167,7 +167,7 @@ mod tests { let statement = Query::insert() .into_table(USERS_TABLE) .columns([UserIden::PublicKey]) - .values(vec![SimpleExpr::Value(pubkey.to_string().into())]) + .values(vec![SimpleExpr::Value(pubkey.z32().into())]) .unwrap() .to_owned(); let (query, values) = statement.build_sqlx(PostgresQueryBuilder); @@ -247,7 +247,7 @@ mod tests { let statement = Query::insert() .into_table(USERS_TABLE) .columns([UserIden::PublicKey]) - .values(vec![SimpleExpr::Value(pubkey.to_string().into())]) + .values(vec![SimpleExpr::Value(pubkey.z32().into())]) .unwrap() .to_owned(); let (query, values) = statement.build_sqlx(PostgresQueryBuilder); diff --git a/pubky-homeserver/src/persistence/sql/migrations/m20251014_events_table_index_and_content_hash.rs b/pubky-homeserver/src/persistence/sql/migrations/m20251014_events_table_index_and_content_hash.rs index 3bfc564c..1f303f1e 100644 --- a/pubky-homeserver/src/persistence/sql/migrations/m20251014_events_table_index_and_content_hash.rs +++ b/pubky-homeserver/src/persistence/sql/migrations/m20251014_events_table_index_and_content_hash.rs @@ -87,8 +87,8 @@ impl MigrationTrait for M20251014EventsTableIndexAndContentHashMigration { #[cfg(test)] mod tests { - use pkarr::Keypair; use pubky_common::crypto::Hash; + use pubky_common::crypto::Keypair; use sea_query::{Iden, Query, SimpleExpr}; use sea_query_binder::SqlxBinder; @@ -158,7 +158,7 @@ mod tests { let statement = Query::insert() .into_table(USERS_TABLE) .columns([UserIden::PublicKey]) - .values(vec![SimpleExpr::Value(pubkey.to_string().into())]) + .values(vec![SimpleExpr::Value(pubkey.z32().into())]) .unwrap() .to_owned(); let (query, values) = statement.build_sqlx(PostgresQueryBuilder); @@ -303,7 +303,7 @@ mod tests { let statement = Query::insert() .into_table(USERS_TABLE) .columns([UserIden::PublicKey]) - .values(vec![SimpleExpr::Value(pubkey.to_string().into())]) + .values(vec![SimpleExpr::Value(pubkey.z32().into())]) .unwrap() .to_owned(); let (query, values) = statement.build_sqlx(PostgresQueryBuilder); diff --git a/pubky-homeserver/src/republishers/key_republisher.rs b/pubky-homeserver/src/republishers/key_republisher.rs index ee0818bb..de142aaa 100644 --- a/pubky-homeserver/src/republishers/key_republisher.rs +++ b/pubky-homeserver/src/republishers/key_republisher.rs @@ -183,7 +183,7 @@ mod tests { // Make sure the pkarr packet of the hs is resolvable. let _packet = pkarr_client.resolve(&hs_pubky).await.unwrap(); // Make sure the pkarr client can resolve the endpoint of the hs. - let qname = format!("{}", hs_pubky); + let qname = hs_pubky.z32(); let endpoint = pkarr_client .resolve_https_endpoint(qname.as_str()) .await @@ -198,7 +198,7 @@ mod tests { #[pubky_test_utils::test] async fn test_endpoints() { let mut context = AppContext::test().await; - context.keypair = pkarr::Keypair::random(); + context.keypair = pubky_common::crypto::Keypair::random(); let _republisher = HomeserverKeyRepublisher::start(&context, 8080, 8080) .await .unwrap(); diff --git a/pubky-homeserver/src/republishers/user_keys_republisher.rs b/pubky-homeserver/src/republishers/user_keys_republisher.rs index dc5a3b13..b3fa3474 100644 --- a/pubky-homeserver/src/republishers/user_keys_republisher.rs +++ b/pubky-homeserver/src/republishers/user_keys_republisher.rs @@ -1,9 +1,9 @@ use std::{collections::HashMap, time::Duration}; -use pkarr::PublicKey; use pkarr_republisher::{ MultiRepublishResult, MultiRepublisher, RepublisherSettings, ResilientClientBuilderError, }; +use pubky_common::crypto::PublicKey; use tokio::{ task::JoinHandle, time::{interval, Instant}, @@ -100,8 +100,10 @@ impl UserKeysRepublisher { settings.republish_condition(|_| true); let republisher = MultiRepublisher::new_with_settings(settings, Some(pkarr_builder)); // TODO: Only publish if user points to this home server. + let pkarr_keys: Vec = + keys.into_iter().map(pkarr::PublicKey::from).collect(); let results = republisher - .run(keys, 12) + .run(pkarr_keys, 12) .await .map_err(UserKeysRepublisherError::Pkarr)?; Ok(results) @@ -165,7 +167,7 @@ mod tests { use crate::persistence::sql::user::UserRepository; use crate::persistence::sql::SqlDb; use crate::republishers::user_keys_republisher::UserKeysRepublisher; - use pkarr::Keypair; + use pubky_common::crypto::Keypair; async fn init_db_with_users(count: usize) -> SqlDb { let db = SqlDb::test().await; diff --git a/pubky-homeserver/src/shared/pubkey_path_validator.rs b/pubky-homeserver/src/shared/pubkey_path_validator.rs index 92cf5b85..01e63194 100644 --- a/pubky-homeserver/src/shared/pubkey_path_validator.rs +++ b/pubky-homeserver/src/shared/pubkey_path_validator.rs @@ -1,4 +1,4 @@ -use pkarr::PublicKey; +use pubky_common::crypto::{is_prefixed_pubky, PublicKey}; /// Custom validator for the zbase32 pubkey in the route path. /// Usage: @@ -27,7 +27,12 @@ impl<'de> serde::Deserialize<'de> for Z32Pubkey { D: serde::Deserializer<'de>, { let s: String = serde::Deserialize::deserialize(deserializer)?; - let pubkey = PublicKey::try_from(s.as_str()).map_err(serde::de::Error::custom)?; + if is_prefixed_pubky(&s) { + return Err(serde::de::Error::custom( + "unexpected `pubky` prefix; expected raw z32", + )); + } + let pubkey = PublicKey::try_from_z32(s.as_str()).map_err(serde::de::Error::custom)?; Ok(Z32Pubkey(pubkey)) } } diff --git a/pubky-homeserver/src/shared/webdav/entry_path.rs b/pubky-homeserver/src/shared/webdav/entry_path.rs index 655ef87a..f6fca3ef 100644 --- a/pubky-homeserver/src/shared/webdav/entry_path.rs +++ b/pubky-homeserver/src/shared/webdav/entry_path.rs @@ -1,4 +1,4 @@ -use pkarr::PublicKey; +use pubky_common::crypto::{is_prefixed_pubky, PublicKey}; use std::str::FromStr; use super::WebDavPath; @@ -30,7 +30,7 @@ pub struct EntryPath { impl EntryPath { pub fn new(pubkey: PublicKey, path: WebDavPath) -> Self { - let key = format!("{}{}", pubkey, path); + let key = format!("{}{}", pubkey.z32(), path); Self { pubkey, path, key } } @@ -70,7 +70,12 @@ impl FromStr for EntryPath { Some((pubkey, path)) => (pubkey, path), None => return Err(EntryPathError::Invalid("Missing '/'".to_string())), }; - let pubkey = PublicKey::from_str(pubkey).map_err(EntryPathError::InvalidPubkey)?; + if is_prefixed_pubky(pubkey) { + return Err(EntryPathError::Invalid( + "unexpected `pubky` prefix; expected raw z32".to_string(), + )); + } + let pubkey = PublicKey::try_from_z32(pubkey).map_err(EntryPathError::InvalidPubkey)?; let webdav_path = WebDavPath::new(path).map_err(EntryPathError::InvalidWebdavPath)?; Ok(Self::new(pubkey, webdav_path)) } @@ -110,7 +115,7 @@ mod tests { let pubkey = PublicKey::from_str("8pinxxgqs41n4aididenw5apqp1urfmzdztr8jt4abrkdn435ewo").unwrap(); let path = WebDavPath::new("/pub/folder/file.txt").unwrap(); - let key = format!("{pubkey}{path}"); + let key = format!("{}{path}", pubkey.z32()); let entry_path = EntryPath::new(pubkey, path); assert_eq!(entry_path.as_ref(), key); } diff --git a/pubky-sdk/README.md b/pubky-sdk/README.md index 5d0c6b70..93ef47d9 100644 --- a/pubky-sdk/README.md +++ b/pubky-sdk/README.md @@ -37,10 +37,14 @@ let body = session.storage().get("/pub/my-cool-app/hello.txt").await?.text().awa assert_eq!(&body, "hello"); // 4) Public read of another user’s file -let txt = pubky.public_storage() - .get(format!("pubky{}/pub/my-cool-app/hello.txt", session.info().public_key())) - .await? - .text().await?; +let txt = pubky + .public_storage() + .get(format!( + "{}/pub/my-cool-app/hello.txt", + session.info().public_key() + )) + .await? + .text().await?; assert_eq!(txt, "hello"); // 5) Keyless app flow (QR/deeplink) @@ -105,13 +109,13 @@ let pubky = Pubky::new()?; let public = pubky.public_storage(); let file = public - .get(format!("pubky{user_id}/pub/example.com/file.bin")) + .get(format!("{user_id}/pub/example.com/file.bin")) .await? .bytes() .await?; let entries = public - .list(format!("pubky{user_id}/pub/example.com/"))? + .list(format!("{user_id}/pub/example.com/"))? .limit(10) .send() .await?; @@ -254,7 +258,7 @@ let homeserver = testnet.homeserver_app(); let pubky = testnet.sdk()?; let signer = pubky.signer(Keypair::random()); -let session = signer.signup(&homeserver.public_key(), None).await?; +let session = signer.signup(&homeserver.public_key().into(), None).await?; session.storage().put("/pub/my-cool-app/hello.txt", "hi").await?; let s = session.storage().get("/pub/my-cool-app/hello.txt").await?.text().await?; diff --git a/pubky-sdk/bindings/js/pkg/README.md b/pubky-sdk/bindings/js/pkg/README.md index a22aa74c..0a837e01 100644 --- a/pubky-sdk/bindings/js/pkg/README.md +++ b/pubky-sdk/bindings/js/pkg/README.md @@ -38,8 +38,8 @@ const path = "/pub/example.com/hello.json"; await session.storage.putJson(path, { hello: "world" }); // 4) Read it publicly (no auth needed) -const userPk = session.info.publicKey.z32(); -const addr = `pubky${userPk}/pub/example.com/hello.json`; +const userPk = session.info.publicKey.toString(); +const addr = `${userPk}/pub/example.com/hello.json`; const json = await pubky.publicStorage.getJson(addr); // -> { hello: "world" } // 5) Authenticate on a 3rd-party app @@ -91,12 +91,13 @@ const client = pubky.client; ### Client (HTTP bridge) ```js -import { Client, resolvePubky } from "@synonymdev/pubky"; +import { Client, PublicKey, resolvePubky } from "@synonymdev/pubky"; const client = new Client(); // or: pubky.client.fetch(); instead of constructing a client manually // Convert the identifier into a transport URL before fetching. -const url = resolvePubky("pubky/pub/example.com/file.txt"); +const userId = PublicKey.from("pubky").toString(); +const url = resolvePubky(`${userId}/pub/example.com/file.txt`); const res = await client.fetch(url); ``` @@ -155,7 +156,7 @@ await session.signout(); // invalidates server session **Session details** ```js -const userPk = session.info.publicKey.z32(); // -> PublicKey as z32 string +const userPk = session.info.publicKey.toString(); // -> pubky identifier const caps = session.info.capabilities; // -> string[] permissions and paths const storage = session.storage; // -> This User's storage API (absolute paths) @@ -256,20 +257,20 @@ const pub = pubky.publicStorage; // Reads const response = await pub.get( - `pubky${userPk.z32()}/pub/example.com/data.json` + `${userPk}/pub/example.com/data.json` ); // -> Response (stream it) -await pub.getJson(`pubky${userPk.z32()}/pub/example.com/data.json`); -await pub.getText(`pubky${userPk.z32()}/pub/example.com/readme.txt`); -await pub.getBytes(`pubky${userPk.z32()}/pub/example.com/icon.png`); // Uint8Array +await pub.getJson(`${userPk}/pub/example.com/data.json`); +await pub.getText(`${userPk}/pub/example.com/readme.txt`); +await pub.getBytes(`${userPk}/pub/example.com/icon.png`); // Uint8Array // Metadata -await pub.exists(`pubky${userPk.z32()}/pub/example.com/foo`); // boolean -await pub.stats(`pubky${userPk.z32()}/pub/example.com/foo`); // { content_length, content_type, etag, last_modified } | null +await pub.exists(`${userPk}/pub/example.com/foo`); // boolean +await pub.stats(`${userPk}/pub/example.com/foo`); // { content_length, content_type, etag, last_modified } | null // List directory (addressed path "/pub/.../") must include trailing `/`. // list(addr, cursor=null|suffix|fullUrl, reverse=false, limit?, shallow=false) await pub.list( - `pubky${userPk.z32()}/pub/example.com/`, + `${userPk}/pub/example.com/`, null, false, 100, @@ -327,7 +328,7 @@ import { Pubky, PublicKey, Keypair } from "@synonymdev/pubky"; const pubky = new Pubky(); // Read-only resolver -const homeserver = await pubky.getHomeserverOf(PublicKey.from("")); // string | undefined +const homeserver = await pubky.getHomeserverOf(PublicKey.from("pubky")); // string | undefined // With keys (signer-bound) const signer = pubky.signer(Keypair.random()); diff --git a/pubky-sdk/bindings/js/src/actors/storage/public.rs b/pubky-sdk/bindings/js/src/actors/storage/public.rs index fcf65741..67172fe9 100644 --- a/pubky-sdk/bindings/js/src/actors/storage/public.rs +++ b/pubky-sdk/bindings/js/src/actors/storage/public.rs @@ -11,7 +11,7 @@ use crate::js_error::JsResult; const TS_ADDRESS: &'static str = r#"export type Address = `pubky${string}/pub/${string}` | `pubky://${string}/pub/${string}`;"#; -/// Read-only public storage using addressed paths (`"/pub/...")`. +/// Read-only public storage using addressed paths (`"pubky/pub/..."`). #[wasm_bindgen] pub struct PublicStorage(pub(crate) pubky::PublicStorage); diff --git a/pubky-sdk/bindings/js/src/client/http.rs b/pubky-sdk/bindings/js/src/client/http.rs index 4c8754ba..78cb86d6 100644 --- a/pubky-sdk/bindings/js/src/client/http.rs +++ b/pubky-sdk/bindings/js/src/client/http.rs @@ -159,7 +159,8 @@ fn js_fetch(req: &web_sys::Request) -> Promise { #[cfg(test)] mod tests { use super::*; - use pkarr::{CacheKey, Keypair, SignedPacket}; + use pkarr::{CacheKey, SignedPacket}; + use pubky::Keypair; use wasm_bindgen_test::*; wasm_bindgen_test_configure!(run_in_browser); @@ -170,7 +171,7 @@ mod tests { let client = Client::testnet(None).unwrap(); let keypair = Keypair::random(); seed_pkarr_testnet_endpoint(&client, &keypair, "localhost", 15411); - let pk = keypair.public_key().to_string(); + let pk = keypair.public_key().to_z32(); let mut url = Url::parse(&format!("https://_pubky.{}/pub/file.txt", pk)).unwrap(); let err = client.0.prepare_request(&mut url).await.unwrap_err(); @@ -202,7 +203,8 @@ mod tests { .cache() .expect("pkarr client should expose a cache for tests"); - let cache_key: CacheKey = keypair.public_key().into(); + let pkarr_public_key = pkarr::PublicKey::from(keypair.public_key()); + let cache_key: CacheKey = pkarr_public_key.into(); let signed_packet = SignedPacket::builder() .sign(keypair) .expect("sign empty packet"); diff --git a/pubky-sdk/bindings/js/src/pubky.rs b/pubky-sdk/bindings/js/src/pubky.rs index 86c61d2a..bebbd389 100644 --- a/pubky-sdk/bindings/js/src/pubky.rs +++ b/pubky-sdk/bindings/js/src/pubky.rs @@ -118,12 +118,12 @@ impl Pubky { /// Public, unauthenticated storage API. /// /// Use for **read-only** public access via addressed paths: - /// `"/pub/…"`. + /// `"pubky/pub/…"`. /// /// @returns {PublicStorage} /// /// @example - /// const text = await pubky.publicStorage.getText(`${userPk.z32()}/pub/example.com/hello.txt`); + /// const text = await pubky.publicStorage.getText(`${userPk.toString()}/pub/example.com/hello.txt`); #[wasm_bindgen(js_name = "publicStorage", getter)] pub fn public_storage(&self) -> PublicStorage { PublicStorage(self.0.public_storage()) @@ -134,7 +134,7 @@ impl Pubky { /// Uses an internal read-only Pkdns actor. /// /// @param {PublicKey} user - /// @returns {Promise} Homeserver public key (z32) or `undefined` if not found. + /// @returns {Promise} Homeserver public key or `undefined` if not found. #[wasm_bindgen(js_name = "getHomeserverOf")] pub async fn get_homeserver_of(&self, user_public_key: &PublicKey) -> Option { self.0 @@ -149,7 +149,7 @@ impl Pubky { /// Use this for low-level `fetch()` calls or testing with raw URLs. /// /// @example - /// const r = await pubky.client.fetch(`pubky://${user}/pub/app/file.txt`, { credentials: "include" }); + /// const r = await pubky.client.fetch(`pubky://${userPk.z32()}/pub/app/file.txt`, { credentials: "include" }); #[wasm_bindgen(getter)] pub fn client(&self) -> Client { Client(self.0.client().clone()) diff --git a/pubky-sdk/bindings/js/src/wrappers/auth_token.rs b/pubky-sdk/bindings/js/src/wrappers/auth_token.rs index 2daed1a6..4d832185 100644 --- a/pubky-sdk/bindings/js/src/wrappers/auth_token.rs +++ b/pubky-sdk/bindings/js/src/wrappers/auth_token.rs @@ -12,7 +12,7 @@ // const token = await flow.awaitToken(); // <- AuthToken // // // Who just authenticated? -// console.log(token.publicKey().z32()); +// console.log(token.publicKey().toString()); // // // Optional: send to your backend and verify there // const bytes = token.toBytes(); // Uint8Array @@ -42,7 +42,7 @@ use crate::wrappers::keys::PublicKey; /// const token = await flow.awaitToken(); /// /// // Identify the user -/// console.log(token.publicKey().z32()); +/// console.log(token.publicKey().toString()); /// /// // Optionally forward to a server for verification: /// await fetch("/api/verify", { method: "POST", body: token.toBytes() }); @@ -73,7 +73,7 @@ impl AuthToken { /// export async function POST(req) { /// const bytes = new Uint8Array(await req.arrayBuffer()); /// const token = AuthToken.verify(bytes); // throws on failure - /// return new Response(token.publicKey().z32(), { status: 200 }); + /// return new Response(token.publicKey().toString(), { status: 200 }); /// } /// ``` #[wasm_bindgen(js_name = "verify")] @@ -102,10 +102,11 @@ impl AuthToken { /// Returns the **public key** that authenticated with this token. /// - /// Use `.z32()` on the returned `PublicKey` to get the string form. + /// Use `.toString()` on the returned `PublicKey` to get the `pubky` identifier. + /// Call `.z32()` when you specifically need the raw z-base32 value (e.g. hostnames). /// /// @example - /// const who = sessionInfo.publicKey.z32(); + /// const who = token.publicKey.toString(); #[wasm_bindgen(js_name = "publicKey", getter)] pub fn public_key(&self) -> PublicKey { // `pubky::PublicKey` implements `Clone` diff --git a/pubky-sdk/bindings/js/src/wrappers/keys.rs b/pubky-sdk/bindings/js/src/wrappers/keys.rs index 1e067dbe..ae2643bf 100644 --- a/pubky-sdk/bindings/js/src/wrappers/keys.rs +++ b/pubky-sdk/bindings/js/src/wrappers/keys.rs @@ -1,17 +1,19 @@ use wasm_bindgen::prelude::*; -use crate::js_error::JsResult; +use crate::js_error::{JsResult, PubkyError, PubkyErrorName}; use js_sys::Uint8Array; +use pubky::{Keypair as NativeKeypair, PublicKey as NativePublicKey}; +use pubky_common::crypto::is_prefixed_pubky; #[wasm_bindgen] -pub struct Keypair(pkarr::Keypair); +pub struct Keypair(NativeKeypair); #[wasm_bindgen] impl Keypair { #[wasm_bindgen] /// Generate a random [Keypair] pub fn random() -> Self { - Self(pkarr::Keypair::random()) + Self(NativeKeypair::random()) } /// Generate a [Keypair] from a secret key. @@ -21,7 +23,7 @@ impl Keypair { let secret: [u8; 32] = secret_key .try_into() .map_err(|_| format!("Expected secret_key to be 32 bytes, got {}", secret_len))?; - Ok(Self(pkarr::Keypair::from_secret_key(&secret))) + Ok(Self(NativeKeypair::from_secret_key(&secret))) } /// Returns the secret key of this keypair. @@ -32,10 +34,11 @@ impl Keypair { /// Returns the [PublicKey] of this keypair. /// - /// Use `.z32()` on the returned `PublicKey` to get the string form. + /// Use `.toString()` on the returned `PublicKey` to get the string form + /// or `.z32()` to get the z32 string form without prefix. /// /// @example - /// const who = keypair.publicKey.z32(); + /// const who = keypair.publicKey.toString(); #[wasm_bindgen(js_name = "publicKey", getter)] pub fn public_key(&self) -> PublicKey { PublicKey(self.0.public_key()) @@ -44,7 +47,7 @@ impl Keypair { /// Create a recovery file for this keypair (encrypted with the given passphrase). #[wasm_bindgen(js_name = "createRecoveryFile")] pub fn create_recovery_file(&self, passphrase: &str) -> Uint8Array { - pubky_common::recovery_file::create_recovery_file(self.as_inner(), passphrase) + pubky_common::recovery_file::create_recovery_file(&self.0, passphrase) .as_slice() .into() } @@ -59,51 +62,71 @@ impl Keypair { } impl Keypair { - pub fn as_inner(&self) -> &pkarr::Keypair { + pub fn as_inner(&self) -> &NativeKeypair { &self.0 } } -impl From for Keypair { - fn from(keypair: pkarr::Keypair) -> Self { +impl From for Keypair { + fn from(keypair: NativeKeypair) -> Self { Self(keypair) } } #[wasm_bindgen] #[derive(Clone)] -pub struct PublicKey(pub(crate) pkarr::PublicKey); +pub struct PublicKey(pub(crate) NativePublicKey); #[wasm_bindgen] impl PublicKey { /// Convert the PublicKey to Uint8Array #[wasm_bindgen(js_name = "toUint8Array")] pub fn to_uint8array(&self) -> Uint8Array { - Uint8Array::from(self.0.as_bytes().as_ref()) + Uint8Array::from(self.0.as_inner().as_bytes().as_ref()) } #[wasm_bindgen] /// Returns the z-base32 encoding of this public key pub fn z32(&self) -> String { + self.0.z32() + } + + #[wasm_bindgen(js_name = "toString")] + /// Returns the identifier form with the `pubky` prefix. + pub fn to_string_js(&self) -> String { self.0.to_string() } #[wasm_bindgen(js_name = "from")] /// @throws pub fn try_from(value: String) -> JsResult { - let native_pk = pkarr::PublicKey::try_from(value)?; + if value.starts_with("pubky://") { + return Err(PubkyError::new( + PubkyErrorName::InvalidInput, + "public key must be raw z32 or pubky; pubky:// is not supported", + )); + } + let value = if is_prefixed_pubky(&value) { + value + .strip_prefix("pubky") + .unwrap_or(value.as_str()) + .to_string() + } else { + value + }; + let native_pk = NativePublicKey::try_from(value)?; Ok(PublicKey(native_pk)) } } impl PublicKey { - pub fn as_inner(&self) -> &pkarr::PublicKey { + pub fn as_inner(&self) -> &NativePublicKey { &self.0 } } -impl From for PublicKey { - fn from(value: pkarr::PublicKey) -> Self { +impl From for PublicKey { + fn from(value: NativePublicKey) -> Self { PublicKey(value) } } diff --git a/pubky-sdk/bindings/js/src/wrappers/session_info.rs b/pubky-sdk/bindings/js/src/wrappers/session_info.rs index 4ae9a942..aceda5bb 100644 --- a/pubky-sdk/bindings/js/src/wrappers/session_info.rs +++ b/pubky-sdk/bindings/js/src/wrappers/session_info.rs @@ -12,12 +12,13 @@ pub struct SessionInfo(pub(crate) session::SessionInfo); impl SessionInfo { /// The user’s public key for this session. /// - /// Use `.z32()` on the returned `PublicKey` to get the string form. + /// Use `.toString()` on the returned `PublicKey` to get the `pubky` identifier. + /// Call `.z32()` when you specifically need the raw z-base32 value (e.g. hostnames). /// /// @returns {PublicKey} /// /// @example - /// const who = sessionInfo.publicKey.z32(); + /// const who = sessionInfo.publicKey.toString(); #[wasm_bindgen(js_name = "publicKey", getter)] pub fn public_key(&self) -> PublicKey { self.0.public_key().clone().into() diff --git a/pubky-sdk/src/actors/auth/auth_flow.rs b/pubky-sdk/src/actors/auth/auth_flow.rs index 8ddc144f..4b2475fe 100644 --- a/pubky-sdk/src/actors/auth/auth_flow.rs +++ b/pubky-sdk/src/actors/auth/auth_flow.rs @@ -60,13 +60,12 @@ //! URL and decrypts the payload locally. The relay **cannot decrypt anything**, it //! simply forwards bytes. -use pkarr::PublicKey; use url::Url; use pubky_common::crypto::random_bytes; use crate::{ - AuthToken, Capabilities, PubkyHttpClient, PubkySession, + AuthToken, Capabilities, PubkyHttpClient, PubkySession, PublicKey, actors::{ DEFAULT_HTTP_RELAY, auth::{ diff --git a/pubky-sdk/src/actors/auth/auth_subscription.rs b/pubky-sdk/src/actors/auth/auth_subscription.rs index 5239f021..167da9be 100644 --- a/pubky-sdk/src/actors/auth/auth_subscription.rs +++ b/pubky-sdk/src/actors/auth/auth_subscription.rs @@ -213,10 +213,10 @@ impl AuthSubscriptionBuilder { #[cfg(test)] mod tests { - use pkarr::Keypair; use pubky_common::capabilities::Capabilities; use super::*; + use crate::Keypair; #[tokio::test] async fn subscribe_to_auth_token() { @@ -248,7 +248,7 @@ mod tests { }); let (producer_result, poll_result) = tokio::join!(producer_handle, poll_handle); - assert!(producer_result.is_ok()); - assert!(poll_result.is_ok()); + producer_result.unwrap(); + poll_result.unwrap(); } } diff --git a/pubky-sdk/src/actors/auth/deep_links/seed_export.rs b/pubky-sdk/src/actors/auth/deep_links/seed_export.rs index e8ff2a74..7cb3d98f 100644 --- a/pubky-sdk/src/actors/auth/deep_links/seed_export.rs +++ b/pubky-sdk/src/actors/auth/deep_links/seed_export.rs @@ -82,21 +82,20 @@ impl From for Url { #[cfg(test)] mod tests { - use pkarr::Keypair; - use super::*; + use crate::Keypair; #[test] fn test_signin_deep_link_parse() { let keypair = Keypair::random(); let secret = keypair.secret_key(); - let deep_link = SeedExportDeepLink::new(secret.clone()); + let deep_link = SeedExportDeepLink::new(secret); let deep_link_str = deep_link.to_string(); assert_eq!( deep_link_str, format!( "pubkyauth://secret_export?secret={}", - URL_SAFE_NO_PAD.encode(&secret) + URL_SAFE_NO_PAD.encode(secret) ) ); let deep_link_parsed = SeedExportDeepLink::from_str(&deep_link_str).unwrap(); diff --git a/pubky-sdk/src/actors/auth/deep_links/signin.rs b/pubky-sdk/src/actors/auth/deep_links/signin.rs index 1bfb099a..e7f2fd69 100644 --- a/pubky-sdk/src/actors/auth/deep_links/signin.rs +++ b/pubky-sdk/src/actors/auth/deep_links/signin.rs @@ -139,7 +139,7 @@ mod tests { .finish(); let relay = Url::parse("https://httprelay.pubky.app/link/").unwrap(); let secret = [123; 32]; - let deep_link = SigninDeepLink::new(capabilities.clone(), relay.clone(), secret.clone()); + let deep_link = SigninDeepLink::new(capabilities.clone(), relay.clone(), secret); let deep_link_str = deep_link.to_string(); assert_eq!( deep_link_str, @@ -147,7 +147,7 @@ mod tests { "pubkyauth://signin?caps={}&relay={}&secret={}", capabilities, relay, - URL_SAFE_NO_PAD.encode(&secret) + URL_SAFE_NO_PAD.encode(secret) ) ); let deep_link_parsed = SigninDeepLink::from_str(&deep_link_str).unwrap(); diff --git a/pubky-sdk/src/actors/auth/deep_links/signup.rs b/pubky-sdk/src/actors/auth/deep_links/signup.rs index 308b7525..e17c8be0 100644 --- a/pubky-sdk/src/actors/auth/deep_links/signup.rs +++ b/pubky-sdk/src/actors/auth/deep_links/signup.rs @@ -1,9 +1,9 @@ use base64::{Engine, engine::general_purpose::URL_SAFE_NO_PAD}; -use pkarr::PublicKey; use pubky_common::capabilities::Capabilities; use std::{fmt::Display, str::FromStr}; use url::Url; +use crate::PublicKey; use crate::actors::auth::deep_links::{DEEP_LINK_SCHEMES, error::DeepLinkParseError}; /// A deep link for signing up to a Pubky homeserver. @@ -81,7 +81,7 @@ impl Display for SignupDeepLink { self.capabilities, self.relay, URL_SAFE_NO_PAD.encode(self.secret), - self.homeserver + self.homeserver.z32() ); write!(f, "{url}")?; if let Some(signup_token) = self.signup_token.as_ref() { @@ -146,7 +146,7 @@ impl FromStr for SignupDeepLink { .ok_or(DeepLinkParseError::MissingQueryParameter("hs"))? .1 .to_string(); - let homeserver = PublicKey::try_from(raw_homeserver.as_str()) + let homeserver = PublicKey::try_from_z32(raw_homeserver.as_str()) .map_err(|e| DeepLinkParseError::InvalidQueryParameter("hs", Box::new(e)))?; let signup_token = url @@ -187,7 +187,7 @@ mod tests { let deep_link = SignupDeepLink::new( capabilities.clone(), relay.clone(), - secret.clone(), + secret, homeserver.clone(), None, ); @@ -198,8 +198,8 @@ mod tests { "pubkyauth://signup?caps={}&relay={}&secret={}&hs={}", capabilities, relay, - URL_SAFE_NO_PAD.encode(&secret), - homeserver + URL_SAFE_NO_PAD.encode(secret), + homeserver.z32() ) ); let deep_link_parsed = SignupDeepLink::from_str(&deep_link_str).unwrap(); @@ -220,7 +220,7 @@ mod tests { let deep_link = SignupDeepLink::new( capabilities.clone(), relay.clone(), - secret.clone(), + secret, homeserver.clone(), Some(signup_token.to_string()), ); @@ -231,8 +231,8 @@ mod tests { "pubkyauth://signup?caps={}&relay={}&secret={}&hs={}&st={}", capabilities, relay, - URL_SAFE_NO_PAD.encode(&secret), - homeserver, + URL_SAFE_NO_PAD.encode(secret), + homeserver.z32(), signup_token ) ); diff --git a/pubky-sdk/src/actors/auth/http_relay_link_channel.rs b/pubky-sdk/src/actors/auth/http_relay_link_channel.rs index ed8a937d..aa13fcc2 100644 --- a/pubky-sdk/src/actors/auth/http_relay_link_channel.rs +++ b/pubky-sdk/src/actors/auth/http_relay_link_channel.rs @@ -308,17 +308,16 @@ mod tests { url::ParseError::RelativeUrlWithCannotBeABaseBase ) ), - "Expected MissingChannelId error, got {:?}", - e + "Expected MissingChannelId error, got {e:?}" ); } - }; + } } fn random_channel_url() -> String { let channel_bytes = random_bytes::<32>(); - let channel_id = URL_SAFE_NO_PAD.encode(&channel_bytes); - format!("{}/link/{}", DEFAULT_HTTP_RELAY, channel_id) + let channel_id = URL_SAFE_NO_PAD.encode(channel_bytes); + format!("{DEFAULT_HTTP_RELAY}/link/{channel_id}") } #[tokio::test] @@ -342,8 +341,8 @@ mod tests { }); let (poll_result, produce_result) = tokio::join!(poll_handle, produce_handle); - assert!(poll_result.is_ok()); - assert!(produce_result.is_ok()); + poll_result.unwrap(); + produce_result.unwrap(); } /// Test that a poll can time out and then resume successfully. @@ -364,7 +363,7 @@ mod tests { Err(e) => { assert!(matches!(e, PollError::Timeout)); } - }; + } // Try again and should succeed let response = channel.poll_once(&client, None).await.unwrap(); @@ -384,8 +383,8 @@ mod tests { }); let (poll_result, produce_result) = tokio::join!(poll_handle, produce_handle); - assert!(poll_result.is_ok()); - assert!(produce_result.is_ok()); + poll_result.unwrap(); + produce_result.unwrap(); } #[tokio::test] @@ -408,7 +407,7 @@ mod tests { }); let (produce_result, poll_result) = tokio::join!(produce_handle, poll_handle); - assert!(produce_result.is_ok()); - assert!(poll_result.is_ok()); + produce_result.unwrap(); + poll_result.unwrap(); } } diff --git a/pubky-sdk/src/actors/pkdns.rs b/pubky-sdk/src/actors/pkdns.rs index 6ea5a43b..720a01cc 100644 --- a/pubky-sdk/src/actors/pkdns.rs +++ b/pubky-sdk/src/actors/pkdns.rs @@ -8,12 +8,12 @@ use std::time::Duration; use pkarr::{ - Keypair, PublicKey, SignedPacket, Timestamp, + SignedPacket, Timestamp, dns::rdata::{RData, SVCB}, }; use crate::{ - PubkyHttpClient, PubkySigner, cross_log, + Keypair, PubkyHttpClient, PubkySigner, PublicKey, cross_log, errors::{AuthError, Error, PkarrError, Result}, }; @@ -151,7 +151,7 @@ impl Pkdns { ); let packet = self.client.pkarr().resolve(user_public_key).await?; let s = extract_host_from_packet(&packet)?; - let result = PublicKey::try_from(s).ok(); + let result = PublicKey::try_from_z32(&s).ok(); cross_log!( debug, "Homeserver resolution for {} yielded {:?}", @@ -415,7 +415,7 @@ fn determine_host( ) -> Option { if let Some(host) = override_host { cross_log!(info, "Using override host {} for `_pubky` publish", host); - return Some(host.to_string()); + return Some(host.z32()); } cross_log!(debug, "Deriving publish host from existing `_pubky` record"); dht_packet.and_then(extract_host_from_packet) @@ -440,7 +440,7 @@ mod tests { #[test] fn republish_preserves_non_pubky_records() { let keypair = Keypair::random(); - let original_host = Keypair::random().public_key().to_string(); + let original_host = Keypair::random().public_key().z32(); let mut dnslink_txt = TXT::new(); dnslink_txt @@ -467,7 +467,7 @@ mod tests { .sign(&keypair) .expect("signed existing packet"); - let new_host = Keypair::random().public_key().to_string(); + let new_host = Keypair::random().public_key().z32(); let republished = Pkdns::build_homeserver_packet(&keypair, &new_host, Some(&existing_packet)) @@ -481,13 +481,13 @@ mod tests { let original_dnslink = existing_packet .all_resource_records() .find(|rr| rr.name.to_string().starts_with("_dnslink")) - .map(|rr| rr.to_owned()) + .map(ToOwned::to_owned) .expect("original _dnslink record"); let republished_dnslink = republished .all_resource_records() .find(|rr| rr.name.to_string().starts_with("_dnslink")) - .map(|rr| rr.to_owned()) + .map(ToOwned::to_owned) .expect("republished _dnslink record"); assert_eq!(republished_dnslink.ttl, original_dnslink.ttl); diff --git a/pubky-sdk/src/actors/session/core.rs b/pubky-sdk/src/actors/session/core.rs index 3b1c7789..a47b1009 100644 --- a/pubky-sdk/src/actors/session/core.rs +++ b/pubky-sdk/src/actors/session/core.rs @@ -1,12 +1,10 @@ use reqwest::{Method, StatusCode}; -#[cfg(target_arch = "wasm32")] use base64::{Engine as _, engine::general_purpose::STANDARD}; use pubky_common::session::SessionInfo; use crate::actors::storage::resource::resolve_pubky; use crate::errors::AuthError; -#[cfg(target_arch = "wasm32")] use crate::errors::RequestError; use crate::{ AuthToken, Error, PubkyHttpClient, Result, SessionStorage, cross_log, util::check_http_status, @@ -49,7 +47,7 @@ impl PubkySession { /// This POSTs the resolved homeserver session endpoint with the token, validates the response /// and constructs a new session-bound [`PubkySession`] pub(crate) async fn new(token: &AuthToken, client: PubkyHttpClient) -> Result { - let url = format!("pubky://{}/session", token.public_key()); + let url = format!("pubky://{}/session", token.public_key().z32()); cross_log!( info, "Establishing new session exchange for {}", @@ -104,7 +102,7 @@ impl PubkySession { let info = SessionInfo::deserialize(&bytes)?; // 3) Find the cookie named exactly as the user's pubky. - let cookie_name = info.public_key().to_string(); + let cookie_name = info.public_key().z32(); let cookie = raw_set_cookies .iter() .filter_map(|raw| cookie::Cookie::parse(raw.clone()).ok()) @@ -209,25 +207,20 @@ impl PubkySession { Ok(()) // success => `self` is consumed } - /// Export session metadata for rehydrating in browsers after a tab refresh. + /// Export session metadata for rehydrating after a tab refresh or process restart. /// - /// This is available on WASM targets only. The returned string contains **no secrets**; - /// it is a base64 encoding of the public `SessionInfo`. The browser remains responsible - /// for persisting the HTTP-only session cookie; `export()` merely captures the metadata - /// needed to reconstruct a `PubkySession` handle. - #[cfg(target_arch = "wasm32")] + /// The returned string contains **no secrets**; it is a base64 encoding of the + /// public `SessionInfo`. The caller remains responsible for persisting the + /// HTTP-only session cookie; `export()` merely captures the metadata needed to + /// reconstruct a `PubkySession` handle. #[must_use] pub fn export(&self) -> String { - cross_log!( - info, - "Exporting WASM session for {}", - self.info.public_key() - ); + cross_log!(info, "Exporting session for {}", self.info.public_key()); STANDARD.encode(self.info.serialize()) } - /// Restore a WASM session from an `export()` string. No secrets are read or written; - /// the browser's HTTP-only cookie jar must still contain the session cookie. + /// Restore a session from an `export()` string. No secrets are read or written; + /// the HTTP-only cookie jar must still contain the session cookie. /// /// # Errors /// - Returns [`crate::errors::RequestError::Validation`] if the export string is malformed. @@ -255,14 +248,28 @@ impl PubkySession { .await? .ok_or(AuthError::RequestExpired)?; session.info = info; - cross_log!( - info, - "Rehydrated WASM session for {}", - session.info.public_key() - ); + cross_log!(info, "Rehydrated session for {}", session.info.public_key()); Ok(session) } + /// Restore a session from an `export()` string (unsupported on native targets). + /// + /// Use [`Self::import_secret`] on native to restore a session using the secret token instead. + /// + /// # Errors + /// - Returns [`crate::errors::RequestError::Validation`] because exports are only supported on WASM. + #[cfg(not(target_arch = "wasm32"))] + #[allow( + clippy::unused_async, + reason = "keep async signature aligned with WASM build" + )] + pub async fn import(_export: &str, _client: Option) -> Result { + Err(RequestError::Validation { + message: "session import is only supported on WASM targets".into(), + } + .into()) + } + /// Create a **session-mode** Storage bound to this user session. /// /// - Relative paths (e.g. `"pub/my-cool-app/file"`) are resolved to **this** user. diff --git a/pubky-sdk/src/actors/session/persist.rs b/pubky-sdk/src/actors/session/persist.rs index c1579bfc..4933a1d4 100644 --- a/pubky-sdk/src/actors/session/persist.rs +++ b/pubky-sdk/src/actors/session/persist.rs @@ -1,6 +1,6 @@ use std::path::Path; -use pkarr::PublicKey; +use crate::PublicKey; use pubky_common::session::SessionInfo; use super::core::PubkySession; @@ -18,8 +18,9 @@ impl PubkySession { /// /// Treat the returned String as a **bearer secret**. Do not log it; store it /// securely. + #[must_use] pub fn export_secret(&self) -> String { - let public_key = self.info().public_key().to_string(); + let public_key = self.info().public_key().z32(); let cookie = self.cookie.clone(); cross_log!(info, "Exporting session secret for {}", public_key); format!("{public_key}:{cookie}") @@ -49,9 +50,10 @@ impl PubkySession { message: "invalid secret: expected `:`".into(), })?; - let public_key = PublicKey::try_from(pk_str).map_err(|_err| RequestError::Validation { - message: "invalid public key".into(), - })?; + let public_key = + PublicKey::try_from_z32(pk_str).map_err(|_err| RequestError::Validation { + message: "invalid public key".into(), + })?; cross_log!(info, "Importing session secret for {}", public_key); // 3) Build minimal session; placeholder SessionInfo will be replaced after validation. diff --git a/pubky-sdk/src/actors/signer/session.rs b/pubky-sdk/src/actors/signer/session.rs index 4a01e3e0..8441f183 100644 --- a/pubky-sdk/src/actors/signer/session.rs +++ b/pubky-sdk/src/actors/signer/session.rs @@ -120,7 +120,7 @@ impl PubkySigner { } fn build_signup_url(homeserver: &PublicKey, signup_token: Option<&str>) -> Result { - let mut url = Url::parse(&format!("https://{homeserver}"))?; + let mut url = Url::parse(&format!("https://{}", homeserver.z32()))?; url.set_path("/signup"); if let Some(token) = signup_token { url.query_pairs_mut().append_pair("signup_token", token); diff --git a/pubky-sdk/src/actors/storage/core.rs b/pubky-sdk/src/actors/storage/core.rs index 4f915501..5478bef6 100644 --- a/pubky-sdk/src/actors/storage/core.rs +++ b/pubky-sdk/src/actors/storage/core.rs @@ -1,4 +1,4 @@ -use pkarr::PublicKey; +use crate::PublicKey; use reqwest::{Method, RequestBuilder}; use super::resource::{IntoPubkyResource, IntoResourcePath, PubkyResource, ResourcePath}; @@ -67,7 +67,7 @@ impl SessionStorage { #[cfg(not(target_arch = "wasm32"))] pub(crate) fn with_session_cookie(&self, rb: RequestBuilder) -> RequestBuilder { - let cookie_name = self.user.to_string(); + let cookie_name = self.user.z32(); rb.header( reqwest::header::COOKIE, format!("{cookie_name}={}", self.cookie), diff --git a/pubky-sdk/src/actors/storage/resource.rs b/pubky-sdk/src/actors/storage/resource.rs index ec187857..9d8f2b3c 100644 --- a/pubky-sdk/src/actors/storage/resource.rs +++ b/pubky-sdk/src/actors/storage/resource.rs @@ -20,7 +20,8 @@ use std::{fmt, str::FromStr}; -use pkarr::PublicKey; +use crate::PublicKey; +use pubky_common::crypto::is_prefixed_pubky; use url::Url; use crate::{Error, errors::RequestError}; @@ -165,16 +166,16 @@ impl fmt::Display for ResourcePath { /// /// ### Examples /// ``` -/// # use pkarr::Keypair; +/// # use pubky::Keypair; /// # use pubky::{PubkyResource, ResourcePath}; /// // Build from parts /// let pk = Keypair::random().public_key(); /// let r = PubkyResource::new(pk.clone(), "/pub/site/index.html")?; -/// assert_eq!(r.to_string(), format!("pubky{pk}/pub/site/index.html")); +/// assert_eq!(r.to_string(), format!("{pk}/pub/site/index.html")); /// /// // Parse from string /// // `pubky://` form -/// let parsed2: PubkyResource = format!("pubky://{pk}/pub/site/index.html").parse()?; +/// let parsed2: PubkyResource = format!("pubky://{}/pub/site/index.html", pk.z32()).parse()?; /// /// # Ok::<(), pubky::Error>(()) /// ``` @@ -207,7 +208,7 @@ impl PubkyResource { #[must_use] pub fn to_pubky_url(&self) -> String { let rel = self.path.as_str().trim_start_matches('/'); - format!("pubky://{}/{}", self.owner, rel) + format!("pubky://{}/{}", self.owner.z32(), rel) } /// Render as `https://_pubky./` for transport. @@ -220,7 +221,7 @@ impl PubkyResource { /// - Returns [`Error::Request`] if the constructed transport URL is invalid. pub fn to_transport_url(&self) -> Result { let rel = self.path.as_str().trim_start_matches('/'); - let https = format!("https://_pubky.{}/{}", self.owner, rel); + let https = format!("https://_pubky.{}/{}", self.owner.z32(), rel); Ok(Url::parse(&https)?) } @@ -239,7 +240,12 @@ impl PubkyResource { let owner = host .strip_prefix("_pubky.") .ok_or_else(|| invalid("transport URL host must start with '_pubky.'"))?; - let public_key = PublicKey::try_from(owner) + if is_prefixed_pubky(owner) { + return Err(invalid( + "transport URL host must use raw z32 without `pubky` prefix", + )); + } + let public_key = PublicKey::try_from_z32(owner) .map_err(|_err| invalid("transport URL host does not contain a valid public key"))?; let path = if url.path().is_empty() { @@ -253,7 +259,7 @@ impl PubkyResource { /// Render as the identifier form `pubky/`. pub(crate) fn to_identifier(&self) -> String { let rel = self.path.as_str().trim_start_matches('/'); - format!("pubky{}/{}", self.owner, rel) + format!("{}/{}", self.owner, rel) } } @@ -266,7 +272,12 @@ impl FromStr for PubkyResource { let (user_str, path) = rest .split_once('/') .ok_or_else(|| invalid("missing `/`"))?; - let user = PublicKey::try_from(user_str) + if is_prefixed_pubky(user_str) { + return Err(invalid( + "unexpected `pubky` prefix in user id; use raw z32 after `pubky://`", + )); + } + let user = PublicKey::try_from_z32(user_str) .map_err(|_err| invalid(format!("invalid user public key: {user_str}")))?; return Self::new(user, path); } @@ -274,7 +285,12 @@ impl FromStr for PubkyResource { // 2) pubky/ if let Some(rest) = s.strip_prefix("pubky") { if let Some((user_id, path)) = rest.split_once('/') { - let user = PublicKey::try_from(user_id).map_err(|_err| { + if is_prefixed_pubky(user_id) { + return Err(invalid( + "unexpected `pubky` prefix in user id; use raw z32 after `pubky`", + )); + } + let user = PublicKey::try_from_z32(user_id).map_err(|_err| { invalid("expected `pubky/` or `pubky:///`") })?; return Self::new(user, path); @@ -382,7 +398,7 @@ impl IntoResourcePath for &String { /// /// ### Examples /// ``` -/// # use pkarr::Keypair; +/// # use pubky::Keypair; /// # use pubky::{IntoPubkyResource, PubkyResource}; /// let user = Keypair::random().public_key(); /// @@ -390,10 +406,10 @@ impl IntoResourcePath for &String { /// let r1 = (user.clone(), "/pub/site/index.html").into_pubky_resource()?; /// /// // Parse `/` -/// let r2: PubkyResource = format!("pubky{}/pub/site/index.html", user).parse()?; +/// let r2: PubkyResource = format!("{}/pub/site/index.html", user).parse()?; /// /// // Parse `pubky://` -/// let r3: PubkyResource = format!("pubky://{}/pub/site/index.html", user).parse()?; +/// let r3: PubkyResource = format!("pubky://{}/pub/site/index.html", user.z32()).parse()?; /// # Ok::<(), pubky::Error>(()) /// ``` pub trait IntoPubkyResource { @@ -445,7 +461,7 @@ impl> IntoPubkyResource for (&PublicKey, P) { #[cfg(test)] mod tests { use super::*; - use pkarr::Keypair; + use crate::Keypair; #[test] fn file_path_normalization_and_rejections() { @@ -475,8 +491,9 @@ mod tests { fn parse_addressed_user_both_forms() { let kp = Keypair::random(); let user = kp.public_key(); - let s1 = format!("pubky://{}/pub/my-cool-app/file", user); - let s3 = format!("pubky{}/pub/my-cool-app/file", user); + let user_raw = user.z32(); + let s1 = format!("pubky://{user_raw}/pub/my-cool-app/file"); + let s3 = format!("pubky{user_raw}/pub/my-cool-app/file"); let p1 = PubkyResource::from_str(&s1).unwrap(); let p3 = PubkyResource::from_str(&s3).unwrap(); @@ -511,7 +528,7 @@ mod tests { let r = PubkyResource::new(user.clone(), p_abs.as_str()).unwrap(); assert_eq!( r.to_pubky_url(), - format!("pubky://{}/pub/my-cool-app/file", user) + format!("pubky://{}/pub/my-cool-app/file", user.z32()) ); } @@ -527,7 +544,7 @@ mod tests { )); // Double-slash inside path - let s_bad = format!("pubky{}/pub//app", user); + let s_bad = format!("{user}/pub//app"); assert!(matches!( PubkyResource::from_str(&s_bad), Err(Error::Request(RequestError::Validation { .. })) @@ -548,8 +565,8 @@ mod tests { #[test] fn rejects_dot_segments_but_allows_trailing_slash() { - assert!(ResourcePath::parse("/a/./b").is_err()); - assert!(ResourcePath::parse("/a/../b").is_err()); + ResourcePath::parse("/a/./b").unwrap_err(); + ResourcePath::parse("/a/../b").unwrap_err(); assert_eq!( ResourcePath::parse("/pub/my-cool-app/").unwrap().as_str(), "/pub/my-cool-app/" @@ -560,14 +577,14 @@ mod tests { fn resolve_identifiers() { let kp = Keypair::random(); let user = kp.public_key(); - let base = format!("pubky://{}/pub/site/index.html", user); + let base = format!("pubky://{}/pub/site/index.html", user.z32()); let resolved = resolve_pubky(&base).unwrap(); assert_eq!( resolved.as_str(), - format!("https://_pubky.{}/pub/site/index.html", user) + format!("https://_pubky.{}/pub/site/index.html", user.z32()) ); - let prefixed = format!("pubky{}/pub/site/index.html", user); + let prefixed = format!("pubky{}/pub/site/index.html", user.z32()); let resolved2 = resolve_pubky(&prefixed).unwrap(); assert_eq!(resolved, resolved2); @@ -577,7 +594,8 @@ mod tests { let parsed = PubkyResource::from_transport_url(&resolved).unwrap(); assert_eq!(parsed, resource); - let http_url = Url::parse(&format!("http://_pubky.{}/pub/site/index.html", user)).unwrap(); + let http_url = + Url::parse(&format!("http://_pubky.{}/pub/site/index.html", user.z32())).unwrap(); let parsed_http = PubkyResource::from_transport_url(&http_url).unwrap(); assert_eq!(parsed_http, resource); } diff --git a/pubky-sdk/src/client/http_targets/native.rs b/pubky-sdk/src/client/http_targets/native.rs index 4d8ba981..d030a5b0 100644 --- a/pubky-sdk/src/client/http_targets/native.rs +++ b/pubky-sdk/src/client/http_targets/native.rs @@ -1,4 +1,6 @@ +use crate::errors::RequestError; use crate::{PubkyHttpClient, PublicKey, Result, cross_log}; +use pubky_common::crypto::is_prefixed_pubky; use reqwest::{IntoUrl, Method, RequestBuilder}; use url::Url; @@ -11,10 +13,13 @@ enum HostKind { fn classify_host(host: &str) -> HostKind { if let Some(pk_host) = host.strip_prefix("_pubky.") { - if PublicKey::try_from(pk_host).is_ok() { + if is_prefixed_pubky(pk_host) { + return HostKind::Icann; + } + if PublicKey::try_from_z32(pk_host).is_ok() { return HostKind::ResolvedPubky; } - } else if PublicKey::try_from(host).is_err() { + } else if is_prefixed_pubky(host) || PublicKey::try_from_z32(host).is_err() { return HostKind::Icann; } HostKind::Pubky @@ -40,17 +45,63 @@ impl PubkyHttpClient { clippy::unused_async, reason = "native implementation stays async to share the same signature as the WASM backend" )] - pub(crate) async fn cross_request(&self, method: Method, url: Url) -> Result { + pub(crate) async fn cross_request( + &self, + method: Method, + mut url: Url, + ) -> Result { + let _ = self.prepare_request(&mut url).await?; Ok(self.request(method, &url)) } + /// Prepare a request for callers that need the WASM-style preflight. + /// + /// Native builds do not rewrite URLs; we only detect pubky hosts and return the + /// `pubky-host` value when applicable. + /// + /// # Errors + /// - Returns [`crate::errors::RequestError::Validation`] if the host uses a `pubky` prefix. + #[allow( + clippy::unused_async, + reason = "keep async signature aligned with WASM build" + )] + pub async fn prepare_request(&self, url: &mut Url) -> Result> { + let host = url.host_str().unwrap_or(""); + + if let Some(stripped) = host.strip_prefix("_pubky.") { + if is_prefixed_pubky(stripped) { + return Err(RequestError::Validation { + message: "pubky prefix is not allowed in transport hosts; use raw z32" + .to_string(), + } + .into()); + } + if PublicKey::try_from_z32(stripped).is_ok() { + return Ok(Some(stripped.to_string())); + } + } else { + if is_prefixed_pubky(host) { + return Err(RequestError::Validation { + message: "pubky prefix is not allowed in transport hosts; use raw z32" + .to_string(), + } + .into()); + } + if PublicKey::try_from_z32(host).is_ok() { + return Ok(Some(host.to_string())); + } + } + + Ok(None) + } + /// Start building a `Request` with the `Method` and `Url` (native-only) /// /// Returns a `RequestBuilder`, which will allow setting headers and /// the request body before sending. /// /// Differs from [`reqwest::Client::request`], in that it can make requests to: - /// 1. HTTPS URLs with a [`pkarr::PublicKey`] as top-level domain, by resolving + /// 1. HTTPS URLs with a [`crate::PublicKey`] as top-level domain, by resolving /// corresponding endpoints, and verifying TLS certificates accordingly. /// (example: `https://o4dksfbqk85ogzdb5osziw6befigbuxmuxkuxq8434q89uj56uyy`) /// 2. `_pubky.` URLs like `https://_pubky.o4dksfbqk85ogzdb5osziw6befigbuxmuxkuxq8434q89uj56uyy` diff --git a/pubky-sdk/src/client/http_targets/wasm.rs b/pubky-sdk/src/client/http_targets/wasm.rs index 41645db1..a6a457ee 100644 --- a/pubky-sdk/src/client/http_targets/wasm.rs +++ b/pubky-sdk/src/client/http_targets/wasm.rs @@ -1,10 +1,11 @@ //! HTTP methods that support `https://` with Pkarr domains, including `_pubky.` URLs -use crate::errors::{PkarrError, Result}; +use crate::PublicKey; +use crate::errors::{PkarrError, RequestError, Result}; use crate::{PubkyHttpClient, cross_log}; use futures_lite::StreamExt; -use pkarr::PublicKey; use pkarr::extra::endpoints::Endpoint; +use pubky_common::crypto::is_prefixed_pubky; use reqwest::{IntoUrl, Method, RequestBuilder}; use url::Url; @@ -45,13 +46,29 @@ impl PubkyHttpClient { let mut pubky_host = None; if let Some(stripped) = host.strip_prefix("_pubky.") { - if PublicKey::try_from(stripped).is_ok() { + if is_prefixed_pubky(stripped) { + return Err(RequestError::Validation { + message: "pubky prefix is not allowed in transport hosts; use raw z32" + .to_string(), + } + .into()); + } + if PublicKey::try_from_z32(stripped).is_ok() { self.transform_url(url).await?; pubky_host = Some(stripped.to_string()); } - } else if PublicKey::try_from(host.clone()).is_ok() { - self.transform_url(url).await?; - pubky_host = Some(host); + } else { + if is_prefixed_pubky(&host) { + return Err(RequestError::Validation { + message: "pubky prefix is not allowed in transport hosts; use raw z32" + .to_string(), + } + .into()); + } + if PublicKey::try_from_z32(&host).is_ok() { + self.transform_url(url).await?; + pubky_host = Some(host); + } } Ok(pubky_host) @@ -158,8 +175,8 @@ impl PubkyHttpClient { #[cfg(all(test, target_arch = "wasm32"))] mod tests { use super::*; + use crate::Keypair; use futures_lite::stream; - use pkarr::Keypair; use wasm_bindgen_test::*; wasm_bindgen_test_configure!(run_in_browser); @@ -167,7 +184,7 @@ mod tests { #[wasm_bindgen_test(async)] async fn transform_url_errors_when_no_domain_is_found() { let client = PubkyHttpClient::new().unwrap(); - let pk = Keypair::random().public_key().to_string(); + let pk = Keypair::random().public_key().z32(); let mut url = Url::parse(&format!("https://_pubky.{pk}/pub/app/file.txt")).unwrap(); let original = url.clone(); diff --git a/pubky-sdk/src/lib.rs b/pubky-sdk/src/lib.rs index ede1f293..2b7b060b 100644 --- a/pubky-sdk/src/lib.rs +++ b/pubky-sdk/src/lib.rs @@ -63,11 +63,10 @@ pub use pkarr::DEFAULT_RELAYS; // Re-exports #[doc(inline)] -pub use pkarr::{Keypair, PublicKey}; -#[doc(inline)] pub use pubky_common::{ auth::AuthToken, capabilities::{Capabilities, Capability}, + crypto::{Keypair, PublicKey}, recovery_file, }; pub use reqwest::{Method, StatusCode}; diff --git a/pubky-sdk/src/pubky.rs b/pubky-sdk/src/pubky.rs index de47227d..b8857805 100644 --- a/pubky-sdk/src/pubky.rs +++ b/pubky-sdk/src/pubky.rs @@ -45,12 +45,12 @@ //! # async fn run(user: pubky::PublicKey) -> pubky::Result<()> { //! let pubky = Pubky::new()?; //! let public = pubky.public_storage(); -//! let addr = format!("pubky{}/pub/site/index.html", user); +//! let addr = format!("{}/pub/site/index.html", user); //! let html = public.get(addr).await?.text().await?; //! # Ok(()) } //! ``` -use pkarr::PublicKey; +use crate::PublicKey; use crate::{ Capabilities, Pkdns, PubkyAuthFlow, PubkyHttpClient, PubkySigner, PublicStorage, Result, diff --git a/pubky-testnet/src/ephemeral_testnet.rs b/pubky-testnet/src/ephemeral_testnet.rs index a21a5d31..bbae0be0 100644 --- a/pubky-testnet/src/ephemeral_testnet.rs +++ b/pubky-testnet/src/ephemeral_testnet.rs @@ -130,7 +130,7 @@ mod test { #[tokio::test] async fn test_homeserver_with_random_keypair() { let mut network = EphemeralTestnet::start_minimal().await.unwrap(); - assert!(network.testnet.homeservers.len() == 0); + assert!(network.testnet.homeservers.is_empty()); let _ = network.create_random_homeserver().await.unwrap(); let _ = network.create_random_homeserver().await.unwrap(); diff --git a/pubky-testnet/src/static_testnet.rs b/pubky-testnet/src/static_testnet.rs index 3a7dc41b..c615e1a6 100644 --- a/pubky-testnet/src/static_testnet.rs +++ b/pubky-testnet/src/static_testnet.rs @@ -190,7 +190,7 @@ impl StaticTestnet { } else { ConfigToml::test() }; - let keypair = pkarr::Keypair::from_secret_key(&[0; 32]); + let keypair = pubky_common::crypto::Keypair::from_secret_key(&[0; 32]); config.pkdns.dht_bootstrap_nodes = Some( self.bootstrap_nodes() .iter() diff --git a/pubky-testnet/src/testnet.rs b/pubky-testnet/src/testnet.rs index 846e817d..68ec86c1 100644 --- a/pubky-testnet/src/testnet.rs +++ b/pubky-testnet/src/testnet.rs @@ -292,7 +292,7 @@ mod test { let _packet = pkarr_client.resolve(&hs_pubky).await.unwrap(); // Make sure the pkarr can resolve the hs_pubky. - let pubkey = format!("{}", hs_pubky); + let pubkey = hs_pubky.z32(); let _endpoint = pkarr_client .resolve_https_endpoint(pubkey.as_str()) .await diff --git a/test_utils/test_macro/src/lib.rs b/test_utils/test_macro/src/lib.rs index 834d2064..4de540e3 100644 --- a/test_utils/test_macro/src/lib.rs +++ b/test_utils/test_macro/src/lib.rs @@ -7,7 +7,7 @@ use syn::{parse_macro_input, ItemFn}; /// database(s) are dropped after the test completes/panics. /// /// Usage: -/// ```no_run +/// ```ignore /// #[tokio::test] /// #[pubky_testnet::test] /// async fn test_function() { @@ -130,6 +130,6 @@ mod tests { fn macro_compiles() { // This test just ensures the macro compiles correctly // The actual functionality is tested in integration tests - assert!(true); + let _ = (); } }