From 88c5cca02139ff609c0691da977558bd680a0651 Mon Sep 17 00:00:00 2001 From: Andy Dixon Date: Mon, 12 Oct 2026 10:06:38 +0100 Subject: [PATCH] refactor: drop the AlchementaryLink keychain fallback and Go's Windows target format Keychain reads no longer fall back to the AlchementaryLink service and copy entries forward, and Windows Credential Manager entries use keyring's own target naming instead of go-keyring's 'service:user'. Credentials saved by Alchementary Link, or by earlier builds on Windows, are no longer found and need re-entering. --- rust/core/src/secrets.rs | 24 ++++++------------------ rust/core/src/service.rs | 4 ++-- rust/core/src/sessions.rs | 2 +- rust/core/tests/sessions.rs | 2 +- 4 files changed, 10 insertions(+), 22 deletions(-) diff --git a/rust/core/src/secrets.rs b/rust/core/src/secrets.rs index 83b2fba..7222690 100644 --- a/rust/core/src/secrets.rs +++ b/rust/core/src/secrets.rs @@ -249,8 +249,6 @@ pub fn write_private(path: &std::path::Path, data: &[u8]) -> Result<()> { } pub const KEYCHAIN_SERVICE: &str = "NexusTerminal"; -/// The service name Alchementary Link wrote under; read as a fallback (ยง7 of the port plan). -pub const LEGACY_KEYCHAIN_SERVICE: &str = "AlchementaryLink"; /// Runs a keychain call on a plain thread: the D-Bus backend may start its own async runtime, /// which panics if the caller is already inside tokio (as SSH authentication is). @@ -259,20 +257,18 @@ fn off_runtime(f: impl FnOnce() -> T + Send + 'static) -> T { } fn entry(service: &str, key: &str) -> keyring::Result { - // go-keyring's Windows target is "service:user"; keyring's default differs. - if cfg!(windows) { keyring::Entry::new_with_target(&format!("{service}:{key}"), service, key) } else { keyring::Entry::new(service, key) } + keyring::Entry::new(service, key) } /// The OS keychain. Every call can fail (no secret service, locked keyring); callers fall back /// to the vault. pub struct Keychain { service: String, - legacy: Option, } impl Keychain { - pub fn new(service: &str, legacy: Option<&str>) -> Keychain { - Keychain { service: service.into(), legacy: legacy.map(Into::into) } + pub fn new(service: &str) -> Keychain { + Keychain { service: service.into() } } /// Probes with a read of an entry that doesn't exist: "no entry" proves the keychain answers, @@ -283,17 +279,9 @@ impl Keychain { off_runtime(move || matches!(entry(&service, "__nexus_keychain_probe__").and_then(|e| e.get_password()), Ok(_) | Err(keyring::Error::NoEntry))) } - /// Reads a secret; a miss under the current service falls back to the legacy one and copies - /// the value forward. Legacy items are never deleted, so the old app keeps working. pub fn get(&self, key: &str) -> Option { - let (service, key2) = (self.service.clone(), key.to_string()); - if let Ok(v) = off_runtime(move || entry(&service, &key2).and_then(|e| e.get_password())) { - return Some(v); - } - let (legacy, key2) = (self.legacy.clone()?, key.to_string()); - let v = off_runtime(move || entry(&legacy, &key2).and_then(|e| e.get_password())).ok()?; - let _ = self.set(key, &v); - Some(v) + let (service, key) = (self.service.clone(), key.to_string()); + off_runtime(move || entry(&service, &key).and_then(|e| e.get_password())).ok() } pub fn set(&self, key: &str, value: &str) -> Result<()> { @@ -439,7 +427,7 @@ mod tests { fn deleting_a_profile_clears_every_vault_kind() { let dir = std::env::temp_dir().join(format!("nexus-delprof-{}", std::process::id())); let _ = std::fs::remove_dir_all(&dir); - let s = Secrets { vault: Vault::new(dir.clone()), keychain: Keychain::new("NexusTerminalTest", None), keychain_ok: false }; + let s = Secrets { vault: Vault::new(dir.clone()), keychain: Keychain::new("NexusTerminalTest"), keychain_ok: false }; s.vault.initialise("pw").unwrap(); for kind in SecretKind::ALL { s.vault.set(&kind.key("p1"), "v").unwrap(); diff --git a/rust/core/src/service.rs b/rust/core/src/service.rs index 469eada..9df2ca1 100644 --- a/rust/core/src/service.rs +++ b/rust/core/src/service.rs @@ -6,7 +6,7 @@ use crate::clients::sftp::{self, FileEntry}; use crate::config::AppConfig; use crate::models::*; use crate::secrets::SecretKind; -use crate::secrets::{KEYCHAIN_SERVICE, Keychain, LEGACY_KEYCHAIN_SERVICE, Secrets, Vault}; +use crate::secrets::{KEYCHAIN_SERVICE, Keychain, Secrets, Vault}; use crate::sessions::Manager; use crate::storage::Store; use crate::{Result, exportimport, keyfiles, sshconfig, updater, validation}; @@ -39,7 +39,7 @@ pub struct SshKeyInfo { impl Service { pub fn open(config: AppConfig) -> Result { let store = Arc::new(Store::open(&config.db_path)?); - let secrets = Arc::new(Secrets::new(Vault::new(config.vault_dir.clone()), Keychain::new(KEYCHAIN_SERVICE, Some(LEGACY_KEYCHAIN_SERVICE)))); + let secrets = Arc::new(Secrets::new(Vault::new(config.vault_dir.clone()), Keychain::new(KEYCHAIN_SERVICE))); let sessions = Manager::new(store.clone(), secrets.clone(), config.recordings_dir.clone()); Ok(Service { config, store, secrets, sessions }) } diff --git a/rust/core/src/sessions.rs b/rust/core/src/sessions.rs index 7bba87b..0fba362 100644 --- a/rust/core/src/sessions.rs +++ b/rust/core/src/sessions.rs @@ -1452,7 +1452,7 @@ mod tests { let _ = std::fs::remove_dir_all(&dir); std::fs::create_dir_all(&dir).unwrap(); let store = Arc::new(Store::open(&dir.join("nexus.db")).unwrap()); - let secrets = Arc::new(Secrets { vault: Vault::new(dir.join("vault")), keychain: Keychain::new("NexusTerminalTest", None), keychain_ok: false }); + let secrets = Arc::new(Secrets { vault: Vault::new(dir.join("vault")), keychain: Keychain::new("NexusTerminalTest"), keychain_ok: false }); (Manager::new(store, secrets, dir.join("recordings")), dir) } diff --git a/rust/core/tests/sessions.rs b/rust/core/tests/sessions.rs index abc0e5f..f27a922 100644 --- a/rust/core/tests/sessions.rs +++ b/rust/core/tests/sessions.rs @@ -16,7 +16,7 @@ fn manager(name: &str) -> (Arc, mpsc::Receiver, std::path::PathB let _ = std::fs::remove_dir_all(&dir); std::fs::create_dir_all(&dir).unwrap(); let store = Arc::new(Store::open(&dir.join("nexus.db")).unwrap()); - let secrets = Arc::new(Secrets { vault: Vault::new(dir.join("vault")), keychain: Keychain::new("NexusTerminalTest", None), keychain_ok: false }); + let secrets = Arc::new(Secrets { vault: Vault::new(dir.join("vault")), keychain: Keychain::new("NexusTerminalTest"), keychain_ok: false }); let m = Manager::new(store, secrets, dir.join("recordings")); let (tx, rx) = mpsc::channel(); let tx = Mutex::new(tx);