refactor: update dependencies and remove unused code
- Added new dependencies: `adler2`, `crc32fast`, `flate2`, `miniz_oxide`, and `libredox`. - Updated existing dependencies: `tokio-rustls` to version 0.26.4 and `filetime` to version 0.2.27. - Removed the `backup.rs` file as it is no longer needed. - Introduced tests for configuration and credential management. - Enhanced the `identity` module to generate W3C compliant DID documents. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 4.6
parent
fd2a837bea
commit
f07ce10b1a
@@ -2,7 +2,7 @@
|
||||
|
||||
use super::RpcHandler;
|
||||
use crate::identity_manager::{IdentityManager, IdentityPurpose};
|
||||
use anyhow::Result;
|
||||
use anyhow::{Context, Result};
|
||||
|
||||
impl RpcHandler {
|
||||
/// List all identities with their default status.
|
||||
@@ -180,6 +180,101 @@ impl RpcHandler {
|
||||
Ok(serde_json::json!({ "valid": valid }))
|
||||
}
|
||||
|
||||
/// Resolve a DID to its W3C DID Document.
|
||||
/// If no DID is provided, returns the node's own DID Document.
|
||||
pub(super) async fn handle_identity_resolve_did(
|
||||
&self,
|
||||
params: Option<serde_json::Value>,
|
||||
) -> Result<serde_json::Value> {
|
||||
let params = params.unwrap_or_default();
|
||||
|
||||
// If a DID is provided, resolve it; otherwise use the node's DID
|
||||
let pubkey_hex = if let Some(did) = params.get("did").and_then(|v| v.as_str()) {
|
||||
// Extract pubkey from did:key format
|
||||
let pubkey_bytes = crate::identity::pubkey_bytes_from_did_key(did)?;
|
||||
hex::encode(pubkey_bytes)
|
||||
} else {
|
||||
// Use node's own pubkey
|
||||
let (data, _) = self.state_manager.get_snapshot().await;
|
||||
data.server_info.pubkey.clone()
|
||||
};
|
||||
|
||||
let document = crate::identity::did_document_from_pubkey_hex(&pubkey_hex)?;
|
||||
Ok(document)
|
||||
}
|
||||
|
||||
/// Verify a DID Document: validate structure, check key material matches DID.
|
||||
pub(super) async fn handle_identity_verify_did_document(
|
||||
&self,
|
||||
params: Option<serde_json::Value>,
|
||||
) -> Result<serde_json::Value> {
|
||||
let params = params.ok_or_else(|| anyhow::anyhow!("Missing params"))?;
|
||||
let document = params
|
||||
.get("document")
|
||||
.ok_or_else(|| anyhow::anyhow!("Missing required parameter: document"))?;
|
||||
|
||||
// Validate required fields
|
||||
let did = document["id"]
|
||||
.as_str()
|
||||
.ok_or_else(|| anyhow::anyhow!("DID Document missing 'id' field"))?;
|
||||
|
||||
let context = document["@context"]
|
||||
.as_array()
|
||||
.ok_or_else(|| anyhow::anyhow!("DID Document missing '@context' array"))?;
|
||||
|
||||
let has_did_context = context.iter().any(|c| c.as_str() == Some("https://www.w3.org/ns/did/v1"));
|
||||
if !has_did_context {
|
||||
return Ok(serde_json::json!({
|
||||
"valid": false,
|
||||
"errors": ["Missing required @context: https://www.w3.org/ns/did/v1"]
|
||||
}));
|
||||
}
|
||||
|
||||
let verification_methods = document["verificationMethod"]
|
||||
.as_array()
|
||||
.ok_or_else(|| anyhow::anyhow!("DID Document missing 'verificationMethod' array"))?;
|
||||
|
||||
if verification_methods.is_empty() {
|
||||
return Ok(serde_json::json!({
|
||||
"valid": false,
|
||||
"errors": ["verificationMethod array is empty"]
|
||||
}));
|
||||
}
|
||||
|
||||
// Verify the DID matches the key material (for did:key method)
|
||||
let mut errors: Vec<String> = Vec::new();
|
||||
|
||||
if did.starts_with("did:key:") {
|
||||
match crate::identity::pubkey_bytes_from_did_key(did) {
|
||||
Ok(pubkey_bytes) => {
|
||||
// Check that at least one verification method has matching key
|
||||
let pubkey_multibase = format!("z{}", bs58::encode(&pubkey_bytes).into_string());
|
||||
let has_matching_key = verification_methods.iter().any(|vm| {
|
||||
vm["publicKeyMultibase"].as_str() == Some(&pubkey_multibase)
|
||||
});
|
||||
if !has_matching_key {
|
||||
errors.push("No verificationMethod matches the DID's public key".to_string());
|
||||
}
|
||||
}
|
||||
Err(e) => {
|
||||
errors.push(format!("Failed to extract pubkey from DID: {}", e));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Check authentication is present
|
||||
if document["authentication"].as_array().map_or(true, |a| a.is_empty()) {
|
||||
errors.push("Missing or empty 'authentication' field".to_string());
|
||||
}
|
||||
|
||||
Ok(serde_json::json!({
|
||||
"valid": errors.is_empty(),
|
||||
"did": did,
|
||||
"errors": errors,
|
||||
"verification_methods": verification_methods.len(),
|
||||
}))
|
||||
}
|
||||
|
||||
/// Create a Nostr keypair linked to an identity.
|
||||
pub(super) async fn handle_identity_create_nostr_key(
|
||||
&self,
|
||||
@@ -221,4 +316,81 @@ impl RpcHandler {
|
||||
"signature": signature,
|
||||
}))
|
||||
}
|
||||
|
||||
/// Resolve a remote peer's DID Document over Tor.
|
||||
/// Queries the peer's /rpc/ endpoint for identity.resolve-did.
|
||||
pub(super) async fn handle_identity_resolve_remote_did(
|
||||
&self,
|
||||
params: Option<serde_json::Value>,
|
||||
) -> Result<serde_json::Value> {
|
||||
let params = params.ok_or_else(|| anyhow::anyhow!("Missing params"))?;
|
||||
let onion = params
|
||||
.get("onion")
|
||||
.and_then(|v| v.as_str())
|
||||
.ok_or_else(|| anyhow::anyhow!("Missing required parameter: onion"))?;
|
||||
|
||||
// Build URL for peer's RPC endpoint over Tor
|
||||
let host = if onion.ends_with(".onion") {
|
||||
onion.to_string()
|
||||
} else {
|
||||
format!("{}.onion", onion)
|
||||
};
|
||||
let url = format!("http://{}/rpc/", host);
|
||||
|
||||
// Use SOCKS5 proxy to reach .onion address
|
||||
let proxy = reqwest::Proxy::all("socks5h://127.0.0.1:9050")
|
||||
.context("Failed to create Tor proxy")?;
|
||||
let client = reqwest::Client::builder()
|
||||
.proxy(proxy)
|
||||
.timeout(std::time::Duration::from_secs(30))
|
||||
.build()
|
||||
.context("Failed to build HTTP client")?;
|
||||
|
||||
let rpc_body = serde_json::json!({
|
||||
"jsonrpc": "2.0",
|
||||
"id": 1,
|
||||
"method": "identity.resolve-did",
|
||||
"params": {}
|
||||
});
|
||||
|
||||
let resp = client
|
||||
.post(&url)
|
||||
.json(&rpc_body)
|
||||
.send()
|
||||
.await
|
||||
.context("Failed to connect to peer over Tor")?;
|
||||
|
||||
let body: serde_json::Value = resp
|
||||
.json()
|
||||
.await
|
||||
.context("Failed to parse peer response")?;
|
||||
|
||||
// Extract the DID Document from the RPC response
|
||||
let document = body
|
||||
.get("result")
|
||||
.ok_or_else(|| anyhow::anyhow!("Peer returned error or missing result"))?;
|
||||
|
||||
// Cache the resolved DID locally
|
||||
let did = document["id"]
|
||||
.as_str()
|
||||
.unwrap_or("unknown");
|
||||
let cache_dir = self.config.data_dir.join("did-cache");
|
||||
tokio::fs::create_dir_all(&cache_dir).await.ok();
|
||||
let cache_file = cache_dir.join(format!("{}.json", onion.replace('.', "_")));
|
||||
let cache_entry = serde_json::json!({
|
||||
"document": document,
|
||||
"resolved_at": chrono::Utc::now().to_rfc3339(),
|
||||
"onion": onion,
|
||||
});
|
||||
tokio::fs::write(&cache_file, serde_json::to_string_pretty(&cache_entry).unwrap_or_default())
|
||||
.await
|
||||
.ok();
|
||||
|
||||
Ok(serde_json::json!({
|
||||
"document": document,
|
||||
"did": did,
|
||||
"resolved_from": onion,
|
||||
"cached": true,
|
||||
}))
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user