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.
This commit is contained in:
@@ -249,8 +249,6 @@ pub fn write_private(path: &std::path::Path, data: &[u8]) -> Result<()> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub const KEYCHAIN_SERVICE: &str = "NexusTerminal";
|
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,
|
/// 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).
|
/// which panics if the caller is already inside tokio (as SSH authentication is).
|
||||||
@@ -259,20 +257,18 @@ fn off_runtime<T: Send + 'static>(f: impl FnOnce() -> T + Send + 'static) -> T {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn entry(service: &str, key: &str) -> keyring::Result<keyring::Entry> {
|
fn entry(service: &str, key: &str) -> keyring::Result<keyring::Entry> {
|
||||||
// go-keyring's Windows target is "service:user"; keyring's default differs.
|
keyring::Entry::new(service, key)
|
||||||
if cfg!(windows) { keyring::Entry::new_with_target(&format!("{service}:{key}"), service, key) } else { keyring::Entry::new(service, key) }
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// The OS keychain. Every call can fail (no secret service, locked keyring); callers fall back
|
/// The OS keychain. Every call can fail (no secret service, locked keyring); callers fall back
|
||||||
/// to the vault.
|
/// to the vault.
|
||||||
pub struct Keychain {
|
pub struct Keychain {
|
||||||
service: String,
|
service: String,
|
||||||
legacy: Option<String>,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Keychain {
|
impl Keychain {
|
||||||
pub fn new(service: &str, legacy: Option<&str>) -> Keychain {
|
pub fn new(service: &str) -> Keychain {
|
||||||
Keychain { service: service.into(), legacy: legacy.map(Into::into) }
|
Keychain { service: service.into() }
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Probes with a read of an entry that doesn't exist: "no entry" proves the keychain answers,
|
/// 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)))
|
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<String> {
|
pub fn get(&self, key: &str) -> Option<String> {
|
||||||
let (service, key2) = (self.service.clone(), key.to_string());
|
let (service, key) = (self.service.clone(), key.to_string());
|
||||||
if let Ok(v) = off_runtime(move || entry(&service, &key2).and_then(|e| e.get_password())) {
|
off_runtime(move || entry(&service, &key).and_then(|e| e.get_password())).ok()
|
||||||
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)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn set(&self, key: &str, value: &str) -> Result<()> {
|
pub fn set(&self, key: &str, value: &str) -> Result<()> {
|
||||||
@@ -439,7 +427,7 @@ mod tests {
|
|||||||
fn deleting_a_profile_clears_every_vault_kind() {
|
fn deleting_a_profile_clears_every_vault_kind() {
|
||||||
let dir = std::env::temp_dir().join(format!("nexus-delprof-{}", std::process::id()));
|
let dir = std::env::temp_dir().join(format!("nexus-delprof-{}", std::process::id()));
|
||||||
let _ = std::fs::remove_dir_all(&dir);
|
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();
|
s.vault.initialise("pw").unwrap();
|
||||||
for kind in SecretKind::ALL {
|
for kind in SecretKind::ALL {
|
||||||
s.vault.set(&kind.key("p1"), "v").unwrap();
|
s.vault.set(&kind.key("p1"), "v").unwrap();
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ use crate::clients::sftp::{self, FileEntry};
|
|||||||
use crate::config::AppConfig;
|
use crate::config::AppConfig;
|
||||||
use crate::models::*;
|
use crate::models::*;
|
||||||
use crate::secrets::SecretKind;
|
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::sessions::Manager;
|
||||||
use crate::storage::Store;
|
use crate::storage::Store;
|
||||||
use crate::{Result, exportimport, keyfiles, sshconfig, updater, validation};
|
use crate::{Result, exportimport, keyfiles, sshconfig, updater, validation};
|
||||||
@@ -39,7 +39,7 @@ pub struct SshKeyInfo {
|
|||||||
impl Service {
|
impl Service {
|
||||||
pub fn open(config: AppConfig) -> Result<Service> {
|
pub fn open(config: AppConfig) -> Result<Service> {
|
||||||
let store = Arc::new(Store::open(&config.db_path)?);
|
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());
|
let sessions = Manager::new(store.clone(), secrets.clone(), config.recordings_dir.clone());
|
||||||
Ok(Service { config, store, secrets, sessions })
|
Ok(Service { config, store, secrets, sessions })
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1452,7 +1452,7 @@ mod tests {
|
|||||||
let _ = std::fs::remove_dir_all(&dir);
|
let _ = std::fs::remove_dir_all(&dir);
|
||||||
std::fs::create_dir_all(&dir).unwrap();
|
std::fs::create_dir_all(&dir).unwrap();
|
||||||
let store = Arc::new(Store::open(&dir.join("nexus.db")).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)
|
(Manager::new(store, secrets, dir.join("recordings")), dir)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -16,7 +16,7 @@ fn manager(name: &str) -> (Arc<Manager>, mpsc::Receiver<Event>, std::path::PathB
|
|||||||
let _ = std::fs::remove_dir_all(&dir);
|
let _ = std::fs::remove_dir_all(&dir);
|
||||||
std::fs::create_dir_all(&dir).unwrap();
|
std::fs::create_dir_all(&dir).unwrap();
|
||||||
let store = Arc::new(Store::open(&dir.join("nexus.db")).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 m = Manager::new(store, secrets, dir.join("recordings"));
|
||||||
let (tx, rx) = mpsc::channel();
|
let (tx, rx) = mpsc::channel();
|
||||||
let tx = Mutex::new(tx);
|
let tx = Mutex::new(tx);
|
||||||
|
|||||||
Reference in New Issue
Block a user