fix: zero-amount invoices, identity.verify DID extraction, tor service permissions
- Allow zero-amount Lightning invoices (BOLT11 "any amount") by changing validation from amount_sats < 1 to amount_sats < 0 - identity.verify now extracts pubkey directly from did:key format instead of requiring the DID to belong to a local identity - tor.create-service writes config to data_dir/tor-config/ instead of /var/lib/archipelago/tor/ (owned by debian-tor, not archipelago user) - Add E2E test script (scripts/run-e2e-tests.sh) covering 47 RPC endpoints - Add testing plan with results (loop/testing.md) Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -387,8 +387,8 @@ impl RpcHandler {
|
||||
.and_then(|v| v.as_str())
|
||||
.unwrap_or("");
|
||||
|
||||
if amount_sats < 1 {
|
||||
return Err(anyhow::anyhow!("Amount must be at least 1 sat"));
|
||||
if amount_sats < 0 {
|
||||
return Err(anyhow::anyhow!("Amount must be non-negative"));
|
||||
}
|
||||
|
||||
info!(amount_sats = amount_sats, "Creating Lightning invoice");
|
||||
|
||||
@@ -36,7 +36,8 @@ impl RpcHandler {
|
||||
pub(super) async fn handle_tor_list_services(
|
||||
&self,
|
||||
) -> Result<serde_json::Value> {
|
||||
let services = list_services().await?;
|
||||
let config_dir = self.config.data_dir.join("tor-config");
|
||||
let services = list_services(&config_dir).await?;
|
||||
Ok(serde_json::json!({ "services": services }))
|
||||
}
|
||||
|
||||
@@ -60,7 +61,8 @@ impl RpcHandler {
|
||||
return Err(anyhow::anyhow!("Invalid service name (alphanumeric, hyphens, underscores only)"));
|
||||
}
|
||||
|
||||
let mut config = load_services_config().await;
|
||||
let config_dir = self.config.data_dir.join("tor-config");
|
||||
let mut config = load_services_config(&config_dir).await;
|
||||
if config.services.iter().any(|s| s.name == name) {
|
||||
return Err(anyhow::anyhow!("Service '{}' already exists", name));
|
||||
}
|
||||
@@ -70,7 +72,7 @@ impl RpcHandler {
|
||||
local_port,
|
||||
enabled: true,
|
||||
});
|
||||
save_services_config(&config).await?;
|
||||
save_services_config(&config_dir, &config).await?;
|
||||
|
||||
debug!("Tor service created: {} -> port {}", name, local_port);
|
||||
Ok(serde_json::json!({ "created": true, "name": name }))
|
||||
@@ -87,13 +89,14 @@ impl RpcHandler {
|
||||
.and_then(|v| v.as_str())
|
||||
.ok_or_else(|| anyhow::anyhow!("Missing name"))?;
|
||||
|
||||
let mut config = load_services_config().await;
|
||||
let config_dir = self.config.data_dir.join("tor-config");
|
||||
let mut config = load_services_config(&config_dir).await;
|
||||
let before = config.services.len();
|
||||
config.services.retain(|s| s.name != name);
|
||||
if config.services.len() == before {
|
||||
return Err(anyhow::anyhow!("Service '{}' not found", name));
|
||||
}
|
||||
save_services_config(&config).await?;
|
||||
save_services_config(&config_dir, &config).await?;
|
||||
|
||||
debug!("Tor service deleted: {}", name);
|
||||
Ok(serde_json::json!({ "deleted": true, "name": name }))
|
||||
@@ -116,9 +119,9 @@ impl RpcHandler {
|
||||
}
|
||||
|
||||
/// List all hidden services by scanning the filesystem and merging with config.
|
||||
async fn list_services() -> Result<Vec<TorService>> {
|
||||
async fn list_services(config_dir: &std::path::Path) -> Result<Vec<TorService>> {
|
||||
let base = tor_data_dir();
|
||||
let config = load_services_config().await;
|
||||
let config = load_services_config(config_dir).await;
|
||||
let mut services = Vec::new();
|
||||
let mut seen = std::collections::HashSet::new();
|
||||
|
||||
@@ -186,18 +189,17 @@ fn tor_data_dir() -> String {
|
||||
std::env::var("TOR_DATA_DIR").unwrap_or_else(|_| TOR_DATA_DIR.to_string())
|
||||
}
|
||||
|
||||
async fn load_services_config() -> ServicesConfig {
|
||||
let path = std::path::Path::new(&tor_data_dir()).join(SERVICES_CONFIG);
|
||||
async fn load_services_config(config_dir: &std::path::Path) -> ServicesConfig {
|
||||
let path = config_dir.join(SERVICES_CONFIG);
|
||||
match tokio::fs::read_to_string(&path).await {
|
||||
Ok(content) => serde_json::from_str(&content).unwrap_or_default(),
|
||||
Err(_) => ServicesConfig::default(),
|
||||
}
|
||||
}
|
||||
|
||||
async fn save_services_config(config: &ServicesConfig) -> Result<()> {
|
||||
let dir = tor_data_dir();
|
||||
tokio::fs::create_dir_all(&dir).await.context("Failed to create tor data dir")?;
|
||||
let path = std::path::Path::new(&dir).join(SERVICES_CONFIG);
|
||||
async fn save_services_config(config_dir: &std::path::Path, config: &ServicesConfig) -> Result<()> {
|
||||
tokio::fs::create_dir_all(config_dir).await.context("Failed to create tor config dir")?;
|
||||
let path = config_dir.join(SERVICES_CONFIG);
|
||||
let content = serde_json::to_string_pretty(config).context("Failed to serialize services config")?;
|
||||
tokio::fs::write(&path, content).await.context("Failed to write services config")?;
|
||||
Ok(())
|
||||
|
||||
Reference in New Issue
Block a user