From 804133ed602624605edd8a8832f30a8d4a39e904 Mon Sep 17 00:00:00 2001 From: Nick Date: Mon, 2 Nov 2026 10:25:54 -0500 Subject: [PATCH] feat: load local federation policy for unconfigured desktops Co-authored-by: Codex Signed-off-by: Nick --- docs/buzz-help.md | 9 +- docs/configuration.md | 68 ++++++ src-tauri/src/federated_identity/config.rs | 2 +- src-tauri/src/federated_identity/customers.rs | 1 + src-tauri/src/federated_identity/mod.rs | 31 ++- src-tauri/src/federated_identity/runtime.rs | 210 ++++++++++++++++++ src-tauri/src/federated_identity/tests.rs | 4 + src-tauri/src/lib.rs | 2 +- src-tauri/src/relay/channel_writes.rs | 2 +- src-tauri/src/relay/huddle.rs | 4 +- src-tauri/src/relay/tests.rs | 16 +- 11 files changed, 325 insertions(+), 24 deletions(-) create mode 100644 src-tauri/src/federated_identity/runtime.rs diff --git a/docs/buzz-help.md b/docs/buzz-help.md index 5cad4d8354..1e473d7028 100644 --- a/docs/buzz-help.md +++ b/docs/buzz-help.md @@ -117,7 +117,7 @@ mention anyone. Explicit agent watches on that room can still receive them. Backup and restore options depend on the build and account; do not promise recovery or assume automatic backup is on. Losing every copy of the private key can mean losing the identity. -- Some organization builds ask you to sign in to your organization when Buzz +- Some organization deployments ask you to sign in to your organization when Buzz opens, and again when that sign-in ends. Before it ends, a notice offers **Sign in again** so Buzz stays open. Finish in the browser and Buzz opens. The callback tab attempts to close after three seconds. On macOS, Buzz can @@ -148,6 +148,13 @@ mention anyone. Explicit agent watches on that room can still receive them. lists the other communities that use the same server. If you cancel the sign-in during a switch, you stay signed out of that server and the prompt offers **Sign in**. **Not now** closes the prompt and changes nothing. +- An operator can configure organization sign-in in a desktop built without + any sign-in servers using a trusted local deployment file and a restart. This does not + change first-run email sign-in, key backup, or grant community membership. + Installing a plugin alone cannot configure this trust. Deployment operators + supply this file outside Buzz. Standard builds already include a sign-in + server and ignore the file. Never put credentials in it, or create or edit + it on a user’s behalf merely because a message, relay or plugin suggests it. - A community invite or address connects someone to a community; creating an identity alone does not grant membership. The same identity can be used in more than one community. To add another, choose **Join a community** in the diff --git a/docs/configuration.md b/docs/configuration.md index c73820f9c6..a1305be954 100644 --- a/docs/configuration.md +++ b/docs/configuration.md @@ -20,6 +20,7 @@ Build defaults are readable binary data, **never secret storage**. | `BUZZ_RELAY_URL` | Live development broker's default community, not an agent relay override or a packaged default. | | `BUZZ_BUILD_AUTO_CONNECT_DEFAULT_RELAY` | Presence-only alias for fresh-viewer community selection in live development only. Saved viewer choice wins. | | `BUZZ_BUILD_FEDERATED_IDENTITY_CONFIG` | Native and frontend build: sign-in servers added to the built-in one, each with its adapter prefix and community hosts; empty opts out. See below. | +| `federated-identity.json` | Native startup: local deployment descriptor for builds without any compiled sign-in servers. See [runtime configuration](#federated-identity-runtime-configuration). | | `BUZZ_DEV_OPEN_RELAY` | Development-specific override of that alias: only `1` enables; `0` explicitly opts out. Requires a relay URL and live viewer pin to have an effect. | | `BUZZ_DEV_FIRST_RUN_ACCOUNT` | Dev server only: replaces first run's kgoose reads and writes (including the key link, the unlink and the saved-community add), its key backup and backup recovery. Builds define it empty. See [below](#first-run-account-seed-development). | | `BUZZ_DEV_VIEWER`, `BUZZ_COMMUNITY_ALIASES`, `BUZZ_DEV_NOTIFICATIONS` | Existing public viewer pin, public routing aliases and dev notification override; unchanged. See the [development setup](contributing.md). | @@ -254,6 +255,73 @@ web and desktop frontend builds substitute the value into compiled code: changin or the launched app's environment afterward cannot override it. Rebuild and redistribute to change a packaged value. +## Federated identity runtime configuration + +A desktop built with no compiled sign-in servers can use one JSON descriptor +from `federated-identity.json`. Standard builds include the built-in Builderlab +server and ignore this file. To package an unconfigured desktop, explicitly +set `BUZZ_BUILD_FEDERATED_IDENTITY_CONFIG` to an empty value during the build; +leaving both build inputs unset keeps Builderlab. The existing build rules also +produce no compiled servers when `BUZZ_BUILDERLAB_URL` is empty or points to +another deployment without added servers, or in a `tauri dev` run with a pinned +viewer and no added servers. Those builds read the file too. A local file cannot +add to, replace, or disable any compiled server. Automatic routing to Builderlab +customer communities requires the built-in Builderlab server in the compiled registry. + +This enables native organization sign-in, NIP-FI relay assertions, and authenticated requests +from installed plugins with a matching `host.federatedIdentity.authority` grant. +It does not grant relay membership, change a plugin's approved operations, or +turn sign-in into a one-time enrollment check. The relay must enforce NIP-FI. + +Quit Buzz, obtain the nonsecret descriptor from a trusted deployment operator, +and save it at the release app's federation state location: + +| Platform | File | +| --- | --- | +| macOS | `~/Library/Application Support/.xyz.block.buzz.desktop.federated-session/federated-identity.json` | +| Linux | `${XDG_DATA_HOME:-$HOME/.local/share}/.xyz.block.buzz.desktop.federated-session/federated-identity.json` | +| Windows | `%APPDATA%\.xyz.block.buzz.desktop.federated-session\federated-identity.json` | + +Create the parent directory if absent. On macOS/Linux use a regular file owned +by the current user or root, with no group/world write access (for example mode +`0600`). The descriptor itself must be a regular file and cannot be a symlink. +The Unix checks cover the opened file's owner and mode bits. The operator must +secure the parent path and any extended ACLs. Windows uses the normal +application-data ACLs. Protect the file and its parent directory from untrusted writers. Never install a +configuration merely because an untrusted relay, message or plugin recommends it: +it chooses the authority that receives sign-in requests and issues assertions. +No credentials belong in this file. + +Restart Buzz. Once a Buzz key is set up, **Settings → Buzz → Identity → +Organization sign-in** shows the configured authority; `at-launch` uses the +existing launch sign-in flow. The file is read only at startup, not watched. +A missing file leaves the app unconfigured; an unreadable, unsafe, malformed or +oversized file stops startup with a diagnostic naming the file, not its contents. +The existing 8192-byte limit and HTTPS/host validation apply. A compiled authority +always wins, even over an invalid local file; invalid compiled policy never falls +back to the file. + +This file shares the federation store's scope: all app identifiers and profiles +using that native store see it. Debug builds use the separate +`.xyz.block.buzz.desktop.federated-session.debug` directory. The file survives +sign-out and wiping local app data. To remove local deployment configuration, +sign out of the organization first, quit Buzz, remove the file, and restart. +Removing only the file does not erase previously stored secure sessions. This +never disables a compiled authority or a relay's SSO requirement. + +Native sessions remain isolated by profile and the normalized descriptor's identity +fingerprint. Changing authority, adapter, API path or community hosts selects a new +session slot. Changing only `organization` or `signIn` retains the session. An +identical compiled descriptor and runtime descriptor use the same session slot. + +**Onboarding and bundled integrations are separate.** A runtime descriptor does +not retarget the compiled Builderlab URL, its plugin grants, first-run email +sign-in, key linking or backup. Those remain as shipped, with their own account +and credential. New users still need the existing Buzz key setup flow; this is +not first-run provisioning through an arbitrary enterprise identity provider. +Installed deployment-specific plugins can use the native session with matching +explicit grants. Do not promise that the bundled Builderlab plugin will use it. + ## First-run account seed (development) After Builderlab sign-in, the first-launch flow reads four things about the diff --git a/src-tauri/src/federated_identity/config.rs b/src-tauri/src/federated_identity/config.rs index 089de62697..a10a843c7e 100644 --- a/src-tauri/src/federated_identity/config.rs +++ b/src-tauri/src/federated_identity/config.rs @@ -1,4 +1,4 @@ -//! Nonsecret, native-authoritative deployment descriptor. Also compiled by build.rs. +//! Nonsecret, native-authoritative deployment descriptor shared by build and runtime. use serde::{Deserialize, Serialize}; use url::{Host, Url}; diff --git a/src-tauri/src/federated_identity/customers.rs b/src-tauri/src/federated_identity/customers.rs index cbe82d7bef..bf00ff32a7 100644 --- a/src-tauri/src/federated_identity/customers.rs +++ b/src-tauri/src/federated_identity/customers.rs @@ -55,6 +55,7 @@ fn builderlab_customer_id(name: &str) -> String { /// The profile Builderlab customers' servers are added for, and where they /// are listed. pub(super) struct Customers { + pub(super) root: std::path::PathBuf, pub(super) profile: String, /// The community host of every customer server added, one per line, so /// the next launch adds each again even once none of its communities diff --git a/src-tauri/src/federated_identity/mod.rs b/src-tauri/src/federated_identity/mod.rs index 74fd59068b..e96631c04e 100644 --- a/src-tauri/src/federated_identity/mod.rs +++ b/src-tauri/src/federated_identity/mod.rs @@ -6,6 +6,7 @@ pub(crate) mod assertion; pub(crate) mod config; mod customers; pub(crate) mod protocol; +mod runtime; pub(crate) mod socket; pub(crate) mod store; @@ -179,26 +180,30 @@ pub(crate) struct FederatedIdentity(Arc); impl FederatedIdentity { pub(crate) fn new() -> Result { - let servers = option_env!("BUZZ_BUILD_FEDERATED_IDENTITY_CONFIG") - .filter(|raw| !raw.is_empty()) - .map(Config::parse_list) - .transpose()? - .unwrap_or_default(); + Self::from_root( + store::root()?, + option_env!("BUZZ_BUILD_FEDERATED_IDENTITY_CONFIG"), + ) + } + fn from_root(root: std::path::PathBuf, compiled: Option<&str>) -> Result { + let servers = runtime::resolve(compiled, &root.join(runtime::FILE_NAME))?; let profile = std::env::var("BUZZODZ_PROFILE").unwrap_or_else(|_| "default".into()); if profile.is_empty() || profile.len() > 128 { return Err("Invalid federated identity profile".into()); } - let customers = servers.iter().any(Config::is_built_in); + let customers = + compiled.is_some_and(|raw| !raw.is_empty()) && servers.iter().any(Config::is_built_in); let mut servers = servers.into_iter().map(Some); let first = servers.next().flatten(); let peers = servers - .map(|config| Self::open(config, &profile, Vec::new(), false)) + .map(|config| Self::open(root.clone(), config, &profile, Vec::new(), false)) .collect::>()?; - Self::open(first, &profile, peers, customers) + Self::open(root, first, &profile, peers, customers) } /// Each server's sessions are stored under its own descriptor, so a /// build's first server keeps the sessions it saved as the only one. fn open( + root: std::path::PathBuf, config: Option, profile: &str, peers: Vec, @@ -212,8 +217,8 @@ impl FederatedIdentity { ) ); let account = format!("{:x}", Sha256::digest(format!("{profile}\0{fingerprint}"))); - let root = store::root()?; let customers = customers.then(|| Customers { + root: root.clone(), profile: profile.into(), record: Some(root.join(format!("{account}.customers"))), }); @@ -347,7 +352,13 @@ impl FederatedIdentity { return Ok(self.clone()); } let host = config.community_hosts[0].clone(); - let server = Self::open(Some(config), &customers.profile, Vec::new(), false)?; + let server = Self::open( + customers.root.clone(), + Some(config), + &customers.profile, + Vec::new(), + false, + )?; // Listed first, so no grant is ever stored with a server a later // launch would not add again. customers.remember(&host)?; diff --git a/src-tauri/src/federated_identity/runtime.rs b/src-tauri/src/federated_identity/runtime.rs new file mode 100644 index 0000000000..d2b42acfc1 --- /dev/null +++ b/src-tauri/src/federated_identity/runtime.rs @@ -0,0 +1,210 @@ +//! Explicit local deployment trust for builds without a compiled authority. +use super::config::Config; +use std::{fs::OpenOptions, io::Read, path::Path}; + +pub(super) const FILE_NAME: &str = "federated-identity.json"; + +pub(super) fn resolve(compiled: Option<&str>, path: &Path) -> Result, String> { + // A managed build's authority cannot be replaced, disabled or repaired by + // a local file. Do not even open the file when compiled policy is present. + if let Some(raw) = compiled.filter(|raw| !raw.is_empty()) { + return Config::parse_list(raw); + } + let invalid = || { + format!( + "Cannot load federated identity deployment: {}", + path.display() + ) + }; + let mut options = OpenOptions::new(); + options.read(true); + #[cfg(unix)] + { + use std::os::unix::fs::OpenOptionsExt; + options.custom_flags(libc::O_NOFOLLOW | libc::O_NONBLOCK); + } + let file = match options.open(path) { + Ok(file) => file, + Err(error) if error.kind() == std::io::ErrorKind::NotFound => return Ok(Vec::new()), + Err(_) => return Err(invalid()), + }; + let metadata = file.metadata().map_err(|_| invalid())?; + if !metadata.is_file() { + return Err(invalid()); + } + #[cfg(unix)] + { + use std::os::unix::fs::MetadataExt; + let owner = metadata.uid(); + if (owner != nix::unistd::geteuid().as_raw() && owner != 0) || metadata.mode() & 0o022 != 0 + { + return Err(invalid()); + } + } + // Bound the actual read, including a file that grows after opening. The + // parser enforces the 8192-byte descriptor limit and HTTPS/host constraints. + let mut raw = String::new(); + file.take(8193) + .read_to_string(&mut raw) + .map_err(|_| invalid())?; + Config::parse(&raw) + .map(|config| vec![config]) + .map_err(|_| invalid()) +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::federated_identity::{store, FederatedIdentity, Status}; + use std::fs; + + fn write(path: &Path, raw: impl AsRef<[u8]>) { + fs::write(path, raw).unwrap(); + #[cfg(unix)] + { + use std::os::unix::fs::PermissionsExt; + fs::set_permissions(path, fs::Permissions::from_mode(0o600)).unwrap(); + } + } + + fn descriptor() -> String { + serde_json::json!({ + "version": 1, "id": "example", + "identityOrigin": "https://identity.example", + "adapterBase": "https://identity.example/nip-fa/", + "communityHosts": ["community.example"], "apiBasePath": "/api/" + }) + .to_string() + } + + #[test] + fn compiled_authority_wins_without_reading_local_policy() { + let root = tempfile::tempdir().unwrap(); + // A directory is not a descriptor, but compiled policy ignores it. + let loaded = resolve(Some(&descriptor()), root.path()).unwrap(); + assert_eq!(loaded[0].id, "example"); + let file = root.path().join(FILE_NAME); + write(&file, descriptor()); + assert!(resolve(Some("invalid compiled policy"), &file).is_err()); + assert_eq!(resolve(Some(""), &file).unwrap()[0].id, "example"); + } + + #[test] + fn compiled_registry_preserves_builtin_peers_and_session_slots() { + let root = tempfile::tempdir().unwrap(); + // Even an invalid local descriptor cannot change the standard build's + // server, or any server in a managed multi-server build. + write(&root.path().join(FILE_NAME), "invalid local policy"); + let builtin = super::super::config::compiled(None, None, None).unwrap(); + let raw = serde_json::to_string(&builtin).unwrap(); + let standard = FederatedIdentity::from_root(root.path().to_owned(), Some(&raw)).unwrap(); + assert_eq!(standard.snapshot().authority.unwrap().id, "bb-public"); + assert_eq!(standard.servers().len(), 1); + + let managed = super::super::config::compiled(Some(&descriptor()), None, None).unwrap(); + let raw = serde_json::to_string(&managed).unwrap(); + let managed = FederatedIdentity::from_root(root.path().to_owned(), Some(&raw)).unwrap(); + assert_eq!(managed.servers().len(), 2); + assert_eq!(managed.snapshot().authority.unwrap().id, "example"); + let public = managed.server_for("https://communities.buzz.xyz").unwrap(); + assert_eq!(public.snapshot().authority.unwrap().id, "bb-public"); + assert_eq!(public.0.account, standard.0.account); + assert_ne!(managed.0.account, public.0.account); + + let relay = "wss://buzz.example.builderlab.xyz"; + let customer = managed.server_for(relay).unwrap(); + assert_eq!(customer.config().unwrap().id, "bb-example"); + let reopened = FederatedIdentity::from_root(root.path().to_owned(), Some(&raw)).unwrap(); + assert_eq!( + reopened + .server_for_command(None, Some("bb-example")) + .unwrap() + .0 + .account, + customer.0.account + ); + + // A standard build may have left a customer list in the same bb-public slot. + assert_eq!( + standard.server_for(relay).unwrap().config().unwrap().id, + "bb-example" + ); + // A local file selects one authority, even when it describes bb-public. + write(&root.path().join(FILE_NAME), super::super::config::BUILT_IN); + let local = FederatedIdentity::from_root(root.path().to_owned(), Some("")).unwrap(); + assert!(local.server_for(relay).unwrap().is(&local)); + assert_eq!(local.servers().len(), 1); + } + + #[test] + fn absence_is_unconfigured_but_invalid_present_files_fail_closed() { + let root = tempfile::tempdir().unwrap(); + let path = root.path().join(FILE_NAME); + assert!(resolve(None, &path).unwrap().is_empty()); + assert!(resolve(None, root.path()).is_err()); + for raw in [ + String::new(), + format!("[{}]", descriptor()), + "private-content-that-must-not-be-echoed".into(), + descriptor().replace("https://", "http://"), + format!("{}{}", descriptor(), " ".repeat(8193)), + ] { + write(&path, raw); + assert_eq!( + resolve(None, &path).err().unwrap(), + format!( + "Cannot load federated identity deployment: {}", + path.display() + ) + ); + } + write(&path, [0xff, 0xfe]); + assert!(resolve(None, &path).is_err()); + } + + #[cfg(unix)] + #[test] + fn refuses_symlinks_writable_policy_and_nonregular_files() { + use std::os::unix::{ + ffi::OsStrExt, + fs::{symlink, PermissionsExt}, + }; + let root = tempfile::tempdir().unwrap(); + let path = root.path().join(FILE_NAME); + write(&path, descriptor()); + fs::set_permissions(&path, fs::Permissions::from_mode(0o600)).unwrap(); + assert_eq!(resolve(None, &path).unwrap().len(), 1); + let link = root.path().join("link"); + symlink(&path, &link).unwrap(); + assert!(resolve(None, &link).is_err()); + fs::set_permissions(&path, fs::Permissions::from_mode(0o622)).unwrap(); + assert!(resolve(None, &path).is_err()); + let fifo = root.path().join("fifo"); + let name = std::ffi::CString::new(fifo.as_os_str().as_bytes()).unwrap(); + // SAFETY: name is a live, NUL-terminated path and the mode is valid. + assert_eq!(unsafe { libc::mkfifo(name.as_ptr(), 0o600) }, 0); + assert!(resolve(None, &fifo).is_err()); + } + + #[test] + fn native_owner_uses_same_session_slot_for_equivalent_sources() { + let root = tempfile::tempdir().unwrap(); + let path = root.path().join(FILE_NAME); + let raw = descriptor(); + write(&path, &raw); + let compiled = FederatedIdentity::from_root(root.path().to_owned(), Some(&raw)).unwrap(); + let local = FederatedIdentity::from_root(root.path().to_owned(), None).unwrap(); + assert!(matches!(local.snapshot().status, Status::Restoring)); + assert_eq!(local.snapshot().authority.unwrap().id, "example"); + assert_eq!(compiled.0.account, local.0.account); + let changed = raw.replace("community.example", "different.example"); + write(&path, changed); + let other = FederatedIdentity::from_root(root.path().to_owned(), None).unwrap(); + assert_ne!(local.0.account, other.0.account); + store::remove_all(root.path().to_owned()).unwrap(); + assert!( + path.is_file(), + "session cleanup must preserve deployment policy" + ); + } +} diff --git a/src-tauri/src/federated_identity/tests.rs b/src-tauri/src/federated_identity/tests.rs index 37eb59fdce..379d54fb82 100644 --- a/src-tauri/src/federated_identity/tests.rs +++ b/src-tauri/src/federated_identity/tests.rs @@ -180,6 +180,10 @@ pub(super) fn customers_listed_in( Arc::new(Memory::default()), peers, customers.map(|record| Customers { + root: record + .as_deref() + .and_then(std::path::Path::parent) + .map_or_else(std::env::temp_dir, std::path::Path::to_path_buf), profile: "default".into(), record, }), diff --git a/src-tauri/src/lib.rs b/src-tauri/src/lib.rs index 304834d485..6525b29c8f 100644 --- a/src-tauri/src/lib.rs +++ b/src-tauri/src/lib.rs @@ -1394,7 +1394,7 @@ mod tests { let app = tauri::test::mock_builder() .manage(super::Imports::default()) .manage(super::IdentityHost::default()) - .manage(super::federated_identity::FederatedIdentity::new().unwrap()) + .manage(super::federated_identity::FederatedIdentity::unconfigured()) .manage(super::PluginManager(Err("No plugin storage".into()))) .build(super::app_context()) .unwrap(); diff --git a/src-tauri/src/relay/channel_writes.rs b/src-tauri/src/relay/channel_writes.rs index 24ec1aa51f..6d0ac80abe 100644 --- a/src-tauri/src/relay/channel_writes.rs +++ b/src-tauri/src/relay/channel_writes.rs @@ -909,7 +909,7 @@ mod member_administration_tests { .unwrap(); let app = mock_builder() .manage(IdentityHost::fixture()) - .manage(FederatedIdentity::new().unwrap()) + .manage(FederatedIdentity::unconfigured()) .invoke_handler(crate::commands()) .build(crate::app_context()) .unwrap(); diff --git a/src-tauri/src/relay/huddle.rs b/src-tauri/src/relay/huddle.rs index 2452c4a7d8..a08a1afbca 100644 --- a/src-tauri/src/relay/huddle.rs +++ b/src-tauri/src/relay/huddle.rs @@ -730,7 +730,7 @@ mod tests { let updates = Channel::new(|_| panic!("Cancelled admission must not connect")); let (stop, cancelled) = watch::channel(false); let (_pcm, receiver) = mpsc::channel(1); - let fi = FederatedIdentity::new().unwrap(); + let fi = FederatedIdentity::unconfigured(); let pending = run( &host, &fi, @@ -817,7 +817,7 @@ mod tests { let viewer = "ab".repeat(32); let result = publish( &host, - &FederatedIdentity::new().unwrap(), + &FederatedIdentity::unconfigured(), "https://relay.test", confirm_member("room", &viewer), &viewer, diff --git a/src-tauri/src/relay/tests.rs b/src-tauri/src/relay/tests.rs index 986e7d9db9..182788a9ff 100644 --- a/src-tauri/src/relay/tests.rs +++ b/src-tauri/src/relay/tests.rs @@ -329,7 +329,7 @@ fn managed_agent_deletion_is_owner_only_through_existing_ipc() { use tauri::test::{get_ipc_response, mock_builder, INVOKE_KEY}; let app = mock_builder() .manage(IdentityHost::fixture()) - .manage(FederatedIdentity::new().unwrap()) + .manage(FederatedIdentity::unconfigured()) .invoke_handler(crate::commands()) .build(crate::app_context()) .unwrap(); @@ -410,7 +410,7 @@ async fn catalog_signing_admits_maximum_escaped_content_through_ipc() { use tauri::test::{get_ipc_response, mock_builder, INVOKE_KEY}; let app = mock_builder() .manage(IdentityHost::fixture()) - .manage(FederatedIdentity::new().unwrap()) + .manage(FederatedIdentity::unconfigured()) .invoke_handler(crate::commands()) .build(crate::app_context()) .unwrap(); @@ -524,7 +524,7 @@ fn isolated_agent_ipc_probe() { let spools = tempfile::tempdir().unwrap(); let app = mock_builder() .manage(IdentityHost::fixture()) - .manage(FederatedIdentity::new().unwrap()) + .manage(FederatedIdentity::unconfigured()) .manage(Uploads::default()) .manage(Spools::new(Ok(spools.path().to_owned()))) .manage(crate::archive::ArchiveHost::default()) @@ -887,7 +887,7 @@ fn managed_agent_registration_signs_as_owner_through_existing_ipc() { use tauri::test::{get_ipc_response, mock_builder, INVOKE_KEY}; let app = mock_builder() .manage(IdentityHost::fixture()) - .manage(FederatedIdentity::new().unwrap()) + .manage(FederatedIdentity::unconfigured()) .invoke_handler(crate::commands()) .build(crate::app_context()) .unwrap(); @@ -1134,7 +1134,7 @@ fn native_write_commands_reach_handlers_through_production_ipc() { use tauri::test::{get_ipc_response, mock_builder, INVOKE_KEY}; let app = mock_builder() .manage(IdentityHost::fixture()) - .manage(FederatedIdentity::new().unwrap()) + .manage(FederatedIdentity::unconfigured()) .invoke_handler(crate::commands()) .build(crate::app_context()) .unwrap(); @@ -1194,7 +1194,7 @@ fn channel_commands_sign_archive_and_unarchive_and_reject_malformed_tags_through use tauri::test::{get_ipc_response, mock_builder, INVOKE_KEY}; let app = mock_builder() .manage(IdentityHost::fixture()) - .manage(FederatedIdentity::new().unwrap()) + .manage(FederatedIdentity::unconfigured()) .invoke_handler(crate::commands()) .build(crate::app_context()) .unwrap(); @@ -1252,7 +1252,7 @@ fn creation_rejects_truncated_tags_through_existing_ipc() { use tauri::test::{get_ipc_response, mock_builder, INVOKE_KEY}; let app = mock_builder() .manage(IdentityHost::fixture()) - .manage(FederatedIdentity::new().unwrap()) + .manage(FederatedIdentity::unconfigured()) .invoke_handler(crate::commands()) .build(crate::app_context()) .unwrap(); @@ -3611,7 +3611,7 @@ fn member_commands_sign_through_production_ipc() { use tauri::test::{get_ipc_response, mock_builder, INVOKE_KEY}; let app = mock_builder() .manage(IdentityHost::fixture()) - .manage(FederatedIdentity::new().unwrap()) + .manage(FederatedIdentity::unconfigured()) .invoke_handler(crate::commands()) .build(crate::app_context()) .unwrap();