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
@@ -232,12 +232,18 @@ pub async fn is_service_active() -> bool {
|
||||
/// Tor (fallback). The call sites migrating off direct-Tor dialing build
|
||||
/// one of these and call [`send_json`] / [`send_get`]; the helper handles
|
||||
/// dial, timeout, fallback, and cross-transport auth headers.
|
||||
///
|
||||
/// The optional `service` field ties the request to a user-configurable
|
||||
/// transport preference (see `crate::settings::transport`). Leaving it
|
||||
/// unset picks Auto (FIPS preferred, Tor fallback) — the same default as
|
||||
/// before the Settings UI landed.
|
||||
pub struct PeerRequest<'a> {
|
||||
pub fips_npub: Option<&'a str>,
|
||||
pub onion_host: &'a str,
|
||||
pub path: &'a str,
|
||||
pub headers: Vec<(&'a str, String)>,
|
||||
pub timeout: std::time::Duration,
|
||||
pub service: Option<crate::settings::transport::PeerService>,
|
||||
}
|
||||
|
||||
impl<'a> PeerRequest<'a> {
|
||||
@@ -252,9 +258,18 @@ impl<'a> PeerRequest<'a> {
|
||||
path,
|
||||
headers: Vec::new(),
|
||||
timeout: std::time::Duration::from_secs(30),
|
||||
service: None,
|
||||
}
|
||||
}
|
||||
|
||||
/// Tie this request to a user-configurable service preference. If
|
||||
/// the user has set that service to `Fips` or `Tor`, the builder
|
||||
/// respects it.
|
||||
pub fn service(mut self, s: crate::settings::transport::PeerService) -> Self {
|
||||
self.service = Some(s);
|
||||
self
|
||||
}
|
||||
|
||||
pub fn header(mut self, name: &'a str, value: impl Into<String>) -> Self {
|
||||
self.headers.push((name, value.into()));
|
||||
self
|
||||
@@ -265,14 +280,32 @@ impl<'a> PeerRequest<'a> {
|
||||
self
|
||||
}
|
||||
|
||||
/// Resolved preference: user setting if `service` was set, else Auto.
|
||||
async fn preference(&self) -> crate::settings::transport::TransportPref {
|
||||
match self.service {
|
||||
Some(s) => crate::settings::transport::get(s).await,
|
||||
None => crate::settings::transport::TransportPref::Auto,
|
||||
}
|
||||
}
|
||||
|
||||
/// POST a JSON body. Returns the `reqwest::Response` — caller decides
|
||||
/// how to interpret the status code.
|
||||
pub async fn send_json<B: serde::Serialize>(
|
||||
&self,
|
||||
body: &B,
|
||||
) -> Result<(reqwest::Response, crate::transport::TransportKind)> {
|
||||
if let Some(resp) = self.try_fips_post_json(body).await? {
|
||||
return Ok((resp, crate::transport::TransportKind::Fips));
|
||||
use crate::settings::transport::TransportPref;
|
||||
let pref = self.preference().await;
|
||||
// FIPS-only or Auto: try FIPS first.
|
||||
if matches!(pref, TransportPref::Auto | TransportPref::Fips) {
|
||||
if let Some(resp) = self.try_fips_post_json(body).await? {
|
||||
return Ok((resp, crate::transport::TransportKind::Fips));
|
||||
}
|
||||
if pref == TransportPref::Fips {
|
||||
anyhow::bail!(
|
||||
"User set transport preference to FIPS only, but peer is unreachable over FIPS"
|
||||
);
|
||||
}
|
||||
}
|
||||
let resp = self.send_tor_post_json(body).await?;
|
||||
Ok((resp, crate::transport::TransportKind::Tor))
|
||||
@@ -282,8 +315,17 @@ impl<'a> PeerRequest<'a> {
|
||||
pub async fn send_get(
|
||||
&self,
|
||||
) -> Result<(reqwest::Response, crate::transport::TransportKind)> {
|
||||
if let Some(resp) = self.try_fips_get().await? {
|
||||
return Ok((resp, crate::transport::TransportKind::Fips));
|
||||
use crate::settings::transport::TransportPref;
|
||||
let pref = self.preference().await;
|
||||
if matches!(pref, TransportPref::Auto | TransportPref::Fips) {
|
||||
if let Some(resp) = self.try_fips_get().await? {
|
||||
return Ok((resp, crate::transport::TransportKind::Fips));
|
||||
}
|
||||
if pref == TransportPref::Fips {
|
||||
anyhow::bail!(
|
||||
"User set transport preference to FIPS only, but peer is unreachable over FIPS"
|
||||
);
|
||||
}
|
||||
}
|
||||
let resp = self.send_tor_get().await?;
|
||||
Ok((resp, crate::transport::TransportKind::Tor))
|
||||
|
||||
Reference in New Issue
Block a user