//! Saved SSH-host credentials. Backed by Windows Credential Manager via //! `keyring-core` + `windows-native-keyring-store` — passwords are DPAPI- //! encrypted at rest and scoped to the user account. Never written to //! disk in plaintext, never logged, never sent to the frontend. use anyhow::{Context, Result}; use keyring_core::{Entry, Error as KeyringError}; const SERVICE: &str = "tiletopia"; fn target_for(host_id: &str) -> String { format!("ssh-host:{host_id}") } fn entry(host_id: &str) -> Result { Entry::new(SERVICE, &target_for(host_id)) .with_context(|| format!("create keyring entry for {host_id}")) } pub fn set(host_id: &str, password: &str) -> Result<()> { entry(host_id)? .set_password(password) .with_context(|| format!("write credential for {host_id}")) } pub fn get(host_id: &str) -> Result> { match entry(host_id)?.get_password() { Ok(p) => Ok(Some(p)), Err(KeyringError::NoEntry) => Ok(None), Err(e) => Err(anyhow::Error::from(e) .context(format!("read credential for {host_id}"))), } } pub fn delete(host_id: &str) -> Result<()> { match entry(host_id)?.delete_credential() { Ok(()) => Ok(()), Err(KeyringError::NoEntry) => Ok(()), Err(e) => Err(anyhow::Error::from(e) .context(format!("delete credential for {host_id}"))), } } pub fn has(host_id: &str) -> bool { matches!(get(host_id), Ok(Some(_))) }