Some checks failed
Build & Sign Wraith / Build Windows + Sign (push) Failing after 6s
Added decrypt_password and decrypt_ssh_key Tauri commands. Connect flow now resolves credentialId → decrypted credentials from the vault. Falls back to window.prompt on auth failure. Fixed case-sensitive error string matching. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
99 lines
3.3 KiB
Rust
99 lines
3.3 KiB
Rust
use tauri::State;
|
|
|
|
use crate::credentials::Credential;
|
|
use crate::AppState;
|
|
|
|
/// Guard helper: lock the credentials mutex and return a ref to the inner
|
|
/// `CredentialService`, or a "Vault is locked" error if the vault has not
|
|
/// been unlocked for this session.
|
|
///
|
|
/// This is a macro rather than a function because returning a `MutexGuard`
|
|
/// from a helper function would require lifetime annotations that complicate
|
|
/// the tauri command signatures unnecessarily.
|
|
macro_rules! require_unlocked {
|
|
($state:expr) => {{
|
|
let guard = $state
|
|
.credentials
|
|
.lock()
|
|
.map_err(|_| "Credentials mutex was poisoned".to_string())?;
|
|
if guard.is_none() {
|
|
return Err("Vault is locked — call unlock before accessing credentials".into());
|
|
}
|
|
// SAFETY: we just checked `is_none` above, so `unwrap` cannot panic.
|
|
guard
|
|
}};
|
|
}
|
|
|
|
/// Return all credentials ordered by name.
|
|
///
|
|
/// Secret values (passwords, private keys) are never included — only metadata.
|
|
#[tauri::command]
|
|
pub fn list_credentials(state: State<'_, AppState>) -> Result<Vec<Credential>, String> {
|
|
let guard = require_unlocked!(state);
|
|
guard.as_ref().unwrap().list()
|
|
}
|
|
|
|
/// Store a new username/password credential.
|
|
///
|
|
/// The `password` value is encrypted with AES-256-GCM before being persisted.
|
|
/// Returns the created credential record (without the plaintext password).
|
|
/// `domain` is `None` for non-domain credentials; `Some("")` is treated as NULL.
|
|
#[tauri::command]
|
|
pub fn create_password(
|
|
name: String,
|
|
username: String,
|
|
password: String,
|
|
domain: Option<String>,
|
|
state: State<'_, AppState>,
|
|
) -> Result<Credential, String> {
|
|
let guard = require_unlocked!(state);
|
|
guard
|
|
.as_ref()
|
|
.unwrap()
|
|
.create_password(name, username, password, domain)
|
|
}
|
|
|
|
/// Store a new SSH private key credential.
|
|
///
|
|
/// Both `private_key_pem` and `passphrase` are encrypted before storage.
|
|
/// Pass `None` for `passphrase` when the key has no passphrase.
|
|
/// Returns the created credential record without any secret material.
|
|
#[tauri::command]
|
|
pub fn create_ssh_key(
|
|
name: String,
|
|
username: String,
|
|
private_key_pem: String,
|
|
passphrase: Option<String>,
|
|
state: State<'_, AppState>,
|
|
) -> Result<Credential, String> {
|
|
let guard = require_unlocked!(state);
|
|
guard
|
|
.as_ref()
|
|
.unwrap()
|
|
.create_ssh_key(name, username, private_key_pem, passphrase)
|
|
}
|
|
|
|
/// Delete a credential by id.
|
|
///
|
|
/// For SSH key credentials, the associated `ssh_keys` row is also deleted.
|
|
/// Returns `Err` if the vault is locked or the id does not exist.
|
|
#[tauri::command]
|
|
pub fn delete_credential(id: i64, state: State<'_, AppState>) -> Result<(), String> {
|
|
let guard = require_unlocked!(state);
|
|
guard.as_ref().unwrap().delete(id)
|
|
}
|
|
|
|
/// Decrypt and return the password for a credential.
|
|
#[tauri::command]
|
|
pub fn decrypt_password(credential_id: i64, state: State<'_, AppState>) -> Result<String, String> {
|
|
let guard = require_unlocked!(state);
|
|
guard.as_ref().unwrap().decrypt_password(credential_id)
|
|
}
|
|
|
|
/// Decrypt and return the SSH private key and passphrase.
|
|
#[tauri::command]
|
|
pub fn decrypt_ssh_key(ssh_key_id: i64, state: State<'_, AppState>) -> Result<(String, String), String> {
|
|
let guard = require_unlocked!(state);
|
|
guard.as_ref().unwrap().decrypt_ssh_key(ssh_key_id)
|
|
}
|