fix: add publish and resolve

Adds publish & resolve methods.
Updates example app.
Add npmignore.
Bumps version to 0.3.0.
This commit is contained in:
coreyphillips
2024-09-21 16:35:14 -04:00
parent acde22bd5f
commit 476235399f
27 changed files with 721 additions and 140 deletions

View File

@@ -1,48 +1,138 @@
mod types;
mod keypair;
mod auth;
mod utils;
pub use types::*;
pub use keypair::*;
pub use auth::*;
pub use utils::*;
uniffi::setup_scaffolding!();
use std::collections::HashMap;
use base64::Engine;
use base64::engine::general_purpose;
use pubky::PubkyClient;
use hex;
use serde::Serialize;
use url::Url;
use tokio;
use pkarr::{PkarrClient, SignedPacket, Keypair, dns, PublicKey};
use pkarr::dns::rdata::RData;
use pkarr::dns::ResourceRecord;
use serde_json::json;
use utils::*;
async fn authorize(url: String, secret_key: String) -> Vec<String> {
let bytes = match hex::decode(&secret_key) {
Ok(bytes) => bytes,
Err(_) => return create_response_vector(true, "Failed to decode secret key".to_string())
#[uniffi::export]
fn resolve(public_key: String) -> Vec<String> {
let public_key = match public_key.as_str().try_into() {
Ok(key) => key,
Err(e) => return create_response_vector(true, format!("Invalid zbase32 encoded key: {}", e)),
};
let client = match PkarrClient::builder().build() {
Ok(client) => client,
Err(e) => return create_response_vector(true, format!("Failed to build PkarrClient: {}", e)),
};
let secret_key_bytes: [u8; 32] = match bytes.try_into() {
Ok(secret_key) => secret_key,
Err(_) => {
return create_response_vector(true, "Failed to convert secret key to 32-byte array".to_string());
match client.resolve(&public_key) {
Ok(Some(signed_packet)) => {
// Collect references to ResourceRecords from the signed packet's answers
let all_records: Vec<&ResourceRecord> = signed_packet.packet().answers.iter().collect();
// Convert each ResourceRecord to a JSON value, handling errors appropriately
let json_records: Vec<serde_json::Value> = all_records
.iter()
.filter_map(|record| {
match resource_record_to_json(record) {
Ok(json_value) => Some(json_value),
Err(e) => {
eprintln!("Error converting record to JSON: {}", e);
None
}
}
})
.collect();
let bytes = signed_packet.as_bytes();
let public_key = &bytes[..32];
let signature = &bytes[32..96];
let timestamp = u64::from_be_bytes(match bytes[96..104].try_into() {
Ok(tsbytes) => tsbytes,
Err(_) => return create_response_vector(true, "Failed to convert timestamp bytes".to_string())
});
let dns_packet = &bytes[104..];
let json_obj = json!({
"public_key": general_purpose::STANDARD.encode(public_key),
"signature": general_purpose::STANDARD.encode(signature),
"timestamp": timestamp,
"dns_packet": general_purpose::STANDARD.encode(dns_packet),
"records": json_records
});
let json_str = serde_json::to_string(&json_obj)
.expect("Failed to convert JSON object to string");
create_response_vector(false, json_str)
},
Ok(None) => {
create_response_vector(true, "No signed packet found".to_string())
}
Err(e) => {
create_response_vector(true, format!("Failed to resolve: {}", e))
}
}
}
#[uniffi::export]
fn publish(record_name: String, record_content: String, secret_key: String) -> Vec<String> {
let client = match PkarrClient::builder().build() {
Ok(client) => client,
Err(e) => return create_response_vector(true, format!("Failed to build PkarrClient: {}", e)),
};
let keypair = match get_keypair_from_secret_key(&secret_key) {
Ok(keypair) => keypair,
Err(error) => return create_response_vector(true, error),
};
let mut packet = dns::Packet::new_reply(0);
let dns_name = match dns::Name::new(&record_name) {
Ok(name) => name,
Err(e) => return create_response_vector(true, format!("Failed to create DNS name: {}", e)),
};
let record_content_str: &str = record_content.as_str();
let txt_record = match record_content_str.try_into() {
Ok(value) => RData::TXT(value),
Err(e) => {
return create_response_vector(true, format!("Failed to convert string to TXT record: {}", e))
}
};
let client = PubkyClient::testnet();
let keypair = pkarr::Keypair::from_secret_key(&secret_key_bytes);
packet.answers.push(dns::ResourceRecord::new(
dns_name,
dns::CLASS::IN,
30,
txt_record,
));
// const HOMESERVER: &'static str = "8pinxxgqs41n4aididenw5apqp1urfmzdztr8jt4abrkdn435ewo";
// const URL: &'static str = "http://localhost:6287?relay=http://demo.httprelay.io/link";
// match client.signin(&keypair).await {
// Ok(_) => {}, // Signin successful, continue to send_auth_token
// Err(_) => {
// match client.signup(&keypair, &PublicKey::try_from(HOMESERVER).unwrap()).await {
// Ok(_) => {}, // Signup successful, continue to send_auth_token
// Err(error) => return create_response_vector(true, format!("Failed to signup: {}", error)),
// }
// }
// }
let parsed_url = match Url::parse(&url) {
Ok(url) => url,
Err(_) => return create_response_vector(true, "Failed to parse URL".to_string()),
};
match client.send_auth_token(&keypair, parsed_url).await {
Ok(_) => create_response_vector(false, "send_auth_token success".to_string()),
Err(error) => create_response_vector(true, format!("send_auth_token failure: {}", error)),
match SignedPacket::from_packet(&keypair, &packet) {
Ok(signed_packet) => {
match client.publish(&signed_packet) {
Ok(()) => {
create_response_vector(false, keypair.public_key().to_string())
}
Err(e) => {
create_response_vector(true, format!("Failed to publish: {}", e))
}
}
}
Err(e) => {
create_response_vector(true, format!("Failed to create signed packet: {}", e))
}
}
}
@@ -64,79 +154,3 @@ fn parse_auth_url(url: String) -> Vec<String> {
Err(error) => create_response_vector(true, error),
}
}
#[derive(Debug, Serialize)]
struct Capability {
path: String,
permission: String,
}
#[derive(Debug, Serialize)]
struct PubkyAuthDetails {
relay: String,
capabilities: Vec<Capability>,
secret: String,
}
fn pubky_auth_details_to_json(details: &PubkyAuthDetails) -> Result<String, String> {
serde_json::to_string(details).map_err(|_| "Error serializing to JSON".to_string())
}
fn parse_pubky_auth_url(url_str: &str) -> Result<PubkyAuthDetails, String> {
let url = Url::parse(url_str).map_err(|_| "Invalid URL".to_string())?;
if url.scheme() != "pubkyauth" {
return Err("Invalid scheme, expected 'pubkyauth'".to_string());
}
// Collect query pairs into a HashMap for efficient access
let query_params: HashMap<_, _> = url.query_pairs().into_owned().collect();
let relay = query_params
.get("relay")
.cloned()
.ok_or_else(|| "Missing relay".to_string())?;
let capabilities_str = query_params
.get("capabilities")
.or_else(|| query_params.get("caps"))
.cloned()
.ok_or_else(|| "Missing capabilities".to_string())?;
let secret = query_params
.get("secret")
.cloned()
.ok_or_else(|| "Missing secret".to_string())?;
// Parse capabilities
let capabilities = capabilities_str
.split(',')
.map(|capability| {
let mut parts = capability.splitn(2, ':');
let path = parts
.next()
.ok_or_else(|| format!("Invalid capability format in '{}'", capability))?;
let permission = parts
.next()
.ok_or_else(|| format!("Invalid capability format in '{}'", capability))?;
Ok(Capability {
path: path.to_string(),
permission: permission.to_string(),
})
})
.collect::<Result<Vec<_>, String>>()?;
Ok(PubkyAuthDetails {
relay,
capabilities,
secret,
})
}
fn create_response_vector(error: bool, data: String) -> Vec<String> {
if error {
vec!["error".to_string(), data]
} else {
vec!["success".to_string(), data]
}
}