feat(settings): per-service FIPS/Tor transport preference
Adds a user-configurable toggle for how each peer-to-peer service reaches federated peers. Three options per service: - Auto (default) — FIPS preferred, Tor fallback (current behavior). - FIPS only — fail rather than fall through to Tor. - Tor only — explicit opt-in to onion anonymity for that service. Services covered (matching the UI rows): - Federation — state sync, invites, peer notifications - Peers — address/DID rotation broadcasts - Peer Files — content catalog download/browse/preview - Messaging — archipelago channel + mesh bridge - Mesh File Sharing — content_ref blob fetches Implementation: - settings::transport — persisted struct + process-wide OnceLock handle (so deep call sites don't need data_dir threaded through signatures). On-disk file: <data_dir>/settings/transport_preferences.json; missing or corrupt → defaults (Auto everywhere). - settings::transport::init() called from main.rs after config load. - fips::dial::PeerRequest gains a .service(kind) builder; send_* checks the preference before choosing a transport. FIPS-only fails loudly when FIPS is unavailable (so users who pick it know when something falls back). - Every FIPS-first migration site tags its PeerRequest with the matching PeerService so the toggle actually applies. - transport.preferences + transport.set-preference RPCs added; wired into the dispatcher. - neode-ui/src/views/settings/TransportPrefsCard.vue — standalone card with a 5-row Auto/FIPS/Tor tri-state. Not wired into Settings.vue — the user places components themselves (see feedback_ui_entry_points). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 4.7
parent
dbd19006f2
commit
8b88c45262
@@ -237,6 +237,7 @@ impl RpcHandler {
|
||||
let path = format!("/content/{}", content_id);
|
||||
let (response, _transport) =
|
||||
crate::fips::dial::PeerRequest::new(fips_npub.as_deref(), onion, &path)
|
||||
.service(crate::settings::transport::PeerService::PeerFiles)
|
||||
.header("X-Federation-DID", local_did)
|
||||
.timeout(std::time::Duration::from_secs(120))
|
||||
.send_get()
|
||||
@@ -293,6 +294,7 @@ impl RpcHandler {
|
||||
|
||||
let (response, _transport) =
|
||||
crate::fips::dial::PeerRequest::new(fips_npub.as_deref(), onion, "/content")
|
||||
.service(crate::settings::transport::PeerService::PeerFiles)
|
||||
.timeout(std::time::Duration::from_secs(30))
|
||||
.send_get()
|
||||
.await
|
||||
@@ -352,6 +354,7 @@ impl RpcHandler {
|
||||
let path = format!("/content/{}", content_id);
|
||||
let (response, _transport) =
|
||||
crate::fips::dial::PeerRequest::new(fips_npub.as_deref(), onion, &path)
|
||||
.service(crate::settings::transport::PeerService::PeerFiles)
|
||||
.header("X-Federation-DID", local_did)
|
||||
.header("X-Payment-Token", token_str)
|
||||
.timeout(std::time::Duration::from_secs(120))
|
||||
@@ -412,6 +415,7 @@ impl RpcHandler {
|
||||
|
||||
let (response, _transport) =
|
||||
crate::fips::dial::PeerRequest::new(fips_npub.as_deref(), onion, &path)
|
||||
.service(crate::settings::transport::PeerService::PeerFiles)
|
||||
.timeout(std::time::Duration::from_secs(30))
|
||||
.send_get()
|
||||
.await
|
||||
|
||||
@@ -363,6 +363,8 @@ impl RpcHandler {
|
||||
"transport.peers" => self.handle_transport_peers().await,
|
||||
"transport.send" => self.handle_transport_send(params).await,
|
||||
"transport.set-mode" => self.handle_transport_set_mode(params).await,
|
||||
"transport.preferences" => self.handle_transport_preferences().await,
|
||||
"transport.set-preference" => self.handle_transport_set_preference(params).await,
|
||||
|
||||
// Server settings
|
||||
"server.set-name" => self.handle_server_set_name(params).await,
|
||||
|
||||
@@ -656,6 +656,7 @@ impl RpcHandler {
|
||||
&node.onion,
|
||||
"/rpc/v1",
|
||||
)
|
||||
.service(crate::settings::transport::PeerService::Peers)
|
||||
.timeout(std::time::Duration::from_secs(30));
|
||||
|
||||
match req.send_json(&body).await {
|
||||
|
||||
@@ -742,6 +742,7 @@ impl RpcHandler {
|
||||
|
||||
let (resp, transport) =
|
||||
crate::fips::dial::PeerRequest::new(fips_npub.as_deref(), &onion_bare, &path)
|
||||
.service(crate::settings::transport::PeerService::MeshFileSharing)
|
||||
.timeout(std::time::Duration::from_secs(120))
|
||||
.send_get()
|
||||
.await
|
||||
|
||||
@@ -442,6 +442,7 @@ pub(super) async fn notify_federation_peers_address_change(
|
||||
&peer.onion,
|
||||
"/rpc/v1",
|
||||
)
|
||||
.service(crate::settings::transport::PeerService::Peers)
|
||||
.timeout(std::time::Duration::from_secs(30));
|
||||
match req.send_json(&payload).await {
|
||||
Ok((_, transport)) => {
|
||||
|
||||
@@ -108,6 +108,46 @@ impl RpcHandler {
|
||||
}))
|
||||
}
|
||||
|
||||
/// transport.preferences — Return the user's per-service transport
|
||||
/// preferences. The UI renders these as five FIPS/Auto/Tor rows.
|
||||
pub(super) async fn handle_transport_preferences(&self) -> Result<serde_json::Value> {
|
||||
let prefs = crate::settings::transport::snapshot().await;
|
||||
Ok(serde_json::to_value(prefs)?)
|
||||
}
|
||||
|
||||
/// transport.set-preference — Change a single service preference.
|
||||
/// Persists to disk and hot-swaps the in-memory handle so future
|
||||
/// calls see the new value without restart.
|
||||
pub(super) async fn handle_transport_set_preference(
|
||||
&self,
|
||||
params: Option<serde_json::Value>,
|
||||
) -> Result<serde_json::Value> {
|
||||
use crate::settings::transport::{set, PeerService, TransportPref};
|
||||
let params = params
|
||||
.as_ref()
|
||||
.ok_or_else(|| anyhow::anyhow!("Missing params"))?;
|
||||
let service: PeerService = serde_json::from_value(
|
||||
params
|
||||
.get("service")
|
||||
.cloned()
|
||||
.ok_or_else(|| anyhow::anyhow!("Missing 'service' param"))?,
|
||||
)
|
||||
.map_err(|e| anyhow::anyhow!("Invalid service: {}", e))?;
|
||||
let pref: TransportPref = serde_json::from_value(
|
||||
params
|
||||
.get("pref")
|
||||
.cloned()
|
||||
.ok_or_else(|| anyhow::anyhow!("Missing 'pref' param"))?,
|
||||
)
|
||||
.map_err(|e| anyhow::anyhow!("Invalid pref: {}", e))?;
|
||||
|
||||
set(&self.config.data_dir, service, pref).await?;
|
||||
info!(service = ?service, pref = ?pref, "Transport preference updated");
|
||||
|
||||
let current = crate::settings::transport::snapshot().await;
|
||||
Ok(serde_json::to_value(current)?)
|
||||
}
|
||||
|
||||
/// transport.set-mode — Toggle mesh-only (off-grid) mode.
|
||||
pub(super) async fn handle_transport_set_mode(
|
||||
&self,
|
||||
|
||||
Reference in New Issue
Block a user