mirror of
https://github.com/aljazceru/react-native-pubky.git
synced 2025-12-19 07:34:29 +01:00
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:
92
rust/src/auth.rs
Normal file
92
rust/src/auth.rs
Normal file
@@ -0,0 +1,92 @@
|
||||
use crate::keypair::get_keypair_from_secret_key;
|
||||
use crate::{PubkyAuthDetails, Capability};
|
||||
use crate::utils::create_response_vector;
|
||||
use std::collections::HashMap;
|
||||
use pubky::PubkyClient;
|
||||
use serde_json;
|
||||
use url::Url;
|
||||
|
||||
pub async fn authorize(url: String, secret_key: String) -> Vec<String> {
|
||||
let client = PubkyClient::testnet();
|
||||
let keypair = match get_keypair_from_secret_key(&secret_key) {
|
||||
Ok(keypair) => keypair,
|
||||
Err(error) => return create_response_vector(true, error),
|
||||
};
|
||||
|
||||
// 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)),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn pubky_auth_details_to_json(details: &PubkyAuthDetails) -> Result<String, String> {
|
||||
serde_json::to_string(details).map_err(|_| "Error serializing to JSON".to_string())
|
||||
}
|
||||
|
||||
pub 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,
|
||||
})
|
||||
}
|
||||
17
rust/src/keypair.rs
Normal file
17
rust/src/keypair.rs
Normal file
@@ -0,0 +1,17 @@
|
||||
use pkarr::Keypair;
|
||||
|
||||
pub fn get_keypair_from_secret_key(secret_key: &str) -> Result<Keypair, String> {
|
||||
let bytes = match hex::decode(&secret_key) {
|
||||
Ok(bytes) => bytes,
|
||||
Err(_) => return Err("Failed to decode secret key".to_string())
|
||||
};
|
||||
|
||||
let secret_key_bytes: [u8; 32] = match bytes.try_into() {
|
||||
Ok(secret_key) => secret_key,
|
||||
Err(_) => {
|
||||
return Err("Failed to convert secret key to 32-byte array".to_string());
|
||||
}
|
||||
};
|
||||
|
||||
Ok(Keypair::from_secret_key(&secret_key_bytes))
|
||||
}
|
||||
226
rust/src/lib.rs
226
rust/src/lib.rs
@@ -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]
|
||||
}
|
||||
}
|
||||
|
||||
14
rust/src/types.rs
Normal file
14
rust/src/types.rs
Normal file
@@ -0,0 +1,14 @@
|
||||
use serde::Serialize;
|
||||
|
||||
#[derive(Debug, Serialize)]
|
||||
pub struct Capability {
|
||||
pub path: String,
|
||||
pub permission: String,
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize)]
|
||||
pub struct PubkyAuthDetails {
|
||||
pub relay: String,
|
||||
pub capabilities: Vec<Capability>,
|
||||
pub secret: String,
|
||||
}
|
||||
203
rust/src/utils.rs
Normal file
203
rust/src/utils.rs
Normal file
@@ -0,0 +1,203 @@
|
||||
use std::error::Error;
|
||||
use std::net::{Ipv4Addr, Ipv6Addr};
|
||||
use serde_json::json;
|
||||
use base64::{engine::general_purpose, Engine};
|
||||
use pkarr::dns::rdata::RData;
|
||||
use pkarr::dns::ResourceRecord;
|
||||
|
||||
pub fn create_response_vector(error: bool, data: String) -> Vec<String> {
|
||||
if error {
|
||||
vec!["error".to_string(), data]
|
||||
} else {
|
||||
vec!["success".to_string(), data]
|
||||
}
|
||||
}
|
||||
|
||||
pub fn extract_rdata_for_json(record: &ResourceRecord) -> serde_json::Value {
|
||||
match &record.rdata {
|
||||
RData::TXT(txt) => {
|
||||
let attributes = txt.attributes();
|
||||
let strings: Vec<String> = attributes.into_iter()
|
||||
.map(|(key, value)| {
|
||||
match value {
|
||||
Some(v) => format!("{}={}", key, v),
|
||||
None => key,
|
||||
}
|
||||
})
|
||||
.collect();
|
||||
json!({
|
||||
"type": "TXT",
|
||||
"strings": strings
|
||||
})
|
||||
},
|
||||
RData::A(a) => {
|
||||
let ipv4 = Ipv4Addr::from(a.address);
|
||||
json!({
|
||||
"type": "A",
|
||||
"address": ipv4.to_string()
|
||||
})
|
||||
},
|
||||
RData::AAAA(aaaa) => {
|
||||
let ipv6 = Ipv6Addr::from(aaaa.address);
|
||||
json!({
|
||||
"type": "AAAA",
|
||||
"address": ipv6.to_string()
|
||||
})
|
||||
},
|
||||
RData::AFSDB(afsdb) => {
|
||||
json!({
|
||||
"type": "AFSDB",
|
||||
"subtype": afsdb.subtype,
|
||||
"hostname": afsdb.hostname.to_string()
|
||||
})
|
||||
},
|
||||
RData::CAA(caa) => {
|
||||
json!({
|
||||
"type": "CAA",
|
||||
"flag": caa.flag,
|
||||
"tag": caa.tag.to_string(),
|
||||
"value": caa.value.to_string()
|
||||
})
|
||||
},
|
||||
RData::HINFO(hinfo) => {
|
||||
json!({
|
||||
"type": "HINFO",
|
||||
"cpu": hinfo.cpu.to_string(),
|
||||
"os": hinfo.os.to_string()
|
||||
})
|
||||
},
|
||||
RData::ISDN(isdn) => {
|
||||
json!({
|
||||
"type": "ISDN",
|
||||
"address": isdn.address.to_string(),
|
||||
"sa": isdn.sa.to_string()
|
||||
})
|
||||
},
|
||||
RData::LOC(loc) => {
|
||||
json!({
|
||||
"type": "LOC",
|
||||
"version": loc.version,
|
||||
"size": loc.size,
|
||||
"horizontal_precision": loc.horizontal_precision,
|
||||
"vertical_precision": loc.vertical_precision,
|
||||
"latitude": loc.latitude,
|
||||
"longitude": loc.longitude,
|
||||
"altitude": loc.altitude
|
||||
})
|
||||
},
|
||||
RData::MINFO(minfo) => {
|
||||
json!({
|
||||
"type": "MINFO",
|
||||
"rmailbox": minfo.rmailbox.to_string(),
|
||||
"emailbox": minfo.emailbox.to_string()
|
||||
})
|
||||
},
|
||||
RData::MX(mx) => {
|
||||
json!({
|
||||
"type": "MX",
|
||||
"preference": mx.preference,
|
||||
"exchange": mx.exchange.to_string()
|
||||
})
|
||||
},
|
||||
RData::NAPTR(naptr) => {
|
||||
json!({
|
||||
"type": "NAPTR",
|
||||
"order": naptr.order,
|
||||
"preference": naptr.preference,
|
||||
"flags": naptr.flags.to_string(),
|
||||
"services": naptr.services.to_string(),
|
||||
"regexp": naptr.regexp.to_string(),
|
||||
"replacement": naptr.replacement.to_string()
|
||||
})
|
||||
},
|
||||
RData::NULL(_, null_record) => {
|
||||
json!({
|
||||
"type": "NULL",
|
||||
"data": base64::encode(null_record.get_data())
|
||||
})
|
||||
},
|
||||
RData::OPT(opt) => {
|
||||
json!({
|
||||
"type": "OPT",
|
||||
"udp_packet_size": opt.udp_packet_size,
|
||||
"version": opt.version,
|
||||
"opt_codes": opt.opt_codes.iter().map(|code| {
|
||||
json!({
|
||||
"code": code.code,
|
||||
"data": base64::encode(&code.data)
|
||||
})
|
||||
}).collect::<Vec<_>>()
|
||||
})
|
||||
},
|
||||
RData::RouteThrough(rt) => {
|
||||
json!({
|
||||
"type": "RT",
|
||||
"preference": rt.preference,
|
||||
"intermediate_host": rt.intermediate_host.to_string()
|
||||
})
|
||||
},
|
||||
RData::RP(rp) => {
|
||||
json!({
|
||||
"type": "RP",
|
||||
"mbox": rp.mbox.to_string(),
|
||||
"txt": rp.txt.to_string()
|
||||
})
|
||||
},
|
||||
RData::SOA(soa) => {
|
||||
json!({
|
||||
"type": "SOA",
|
||||
"mname": soa.mname.to_string(),
|
||||
"rname": soa.rname.to_string(),
|
||||
"serial": soa.serial,
|
||||
"refresh": soa.refresh,
|
||||
"retry": soa.retry,
|
||||
"expire": soa.expire,
|
||||
"minimum": soa.minimum
|
||||
})
|
||||
},
|
||||
RData::SRV(srv) => {
|
||||
json!({
|
||||
"type": "SRV",
|
||||
"priority": srv.priority,
|
||||
"weight": srv.weight,
|
||||
"port": srv.port,
|
||||
"target": srv.target.to_string()
|
||||
})
|
||||
},
|
||||
RData::SVCB(svcb) => {
|
||||
let mut params = serde_json::Map::new();
|
||||
for (key, value) in svcb.iter_params() {
|
||||
params.insert(key.to_string(), json!(base64::encode(value)));
|
||||
}
|
||||
json!({
|
||||
"type": "SVCB",
|
||||
"priority": svcb.priority,
|
||||
"target": svcb.target.to_string(),
|
||||
"params": params
|
||||
})
|
||||
},
|
||||
RData::WKS(wks) => {
|
||||
json!({
|
||||
"type": "WKS",
|
||||
"address": Ipv4Addr::from(wks.address).to_string(),
|
||||
"protocol": wks.protocol,
|
||||
"bit_map": base64::encode(&wks.bit_map)
|
||||
})
|
||||
},
|
||||
|
||||
_ => json!({
|
||||
"type": format!("{:?}", record.rdata.type_code()),
|
||||
"data": "Unhandled record type"
|
||||
}),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn resource_record_to_json(record: &ResourceRecord) -> Result<serde_json::Value, Box<dyn Error>> {
|
||||
Ok(json!({
|
||||
"name": record.name.to_string(),
|
||||
"class": format!("{:?}", record.class),
|
||||
"ttl": record.ttl,
|
||||
"rdata": extract_rdata_for_json(record),
|
||||
"cache_flush": record.cache_flush
|
||||
}))
|
||||
}
|
||||
Reference in New Issue
Block a user