Save SSH passwords in Windows Credential Manager and auto-type at prompt

This commit is contained in:
megaproxy 2026-05-25 20:08:31 +01:00
parent 872fb0e80e
commit 1c243b3f3f
11 changed files with 538 additions and 38 deletions

46
src-tauri/src/creds.rs Normal file
View file

@ -0,0 +1,46 @@
//! 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> {
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<Option<String>> {
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(_)))
}