wraith/src-tauri/src/commands/vault.rs
Vantz Stockwell 2848d79915 feat: Phase 1 complete — Tauri v2 foundation
Rust backend: SQLite (WAL mode, 8 tables), vault encryption
(Argon2id + AES-256-GCM), settings/connections/credentials
services, 19 Tauri command wrappers. 46/46 tests passing.

Vue 3 frontend: unlock/create vault flow, Pinia app store,
Tailwind CSS v4 dark theme with Wraith branding.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-17 15:09:41 -04:00

94 lines
3.2 KiB
Rust

use tauri::State;
use crate::vault::{self, VaultService};
use crate::credentials::CredentialService;
use crate::AppState;
/// Returns `true` if no vault has ever been created on this machine.
///
/// The frontend shows the first-run setup screen when this returns `true`.
#[tauri::command]
pub fn is_first_run(state: State<'_, AppState>) -> bool {
state.is_first_run()
}
/// Create a new vault protected by `password`.
///
/// Derives a 256-bit key from the password, encrypts a known check value,
/// persists the salt and check value to the settings table, and activates
/// the vault for the current session.
///
/// Returns `Err` if the vault has already been set up or if any storage
/// operation fails.
#[tauri::command]
pub fn create_vault(password: String, state: State<'_, AppState>) -> Result<(), String> {
if !state.is_first_run() {
return Err("Vault already exists — use unlock instead of create".into());
}
let salt = vault::generate_salt();
let key = vault::derive_key(&password, &salt);
let vs = VaultService::new(key);
// Persist the salt so we can re-derive the key on future unlocks.
state.settings.set("vault_salt", &hex::encode(salt))?;
// Persist a known-plaintext check so unlock can verify the password.
let check = vs.encrypt("wraith-vault-check")?;
state.settings.set("vault_check", &check)?;
// Activate the vault and credentials service for this session.
let cred_svc = CredentialService::new(state.db.clone(), VaultService::new(key));
*state.credentials.lock().unwrap() = Some(cred_svc);
*state.vault.lock().unwrap() = Some(vs);
Ok(())
}
/// Unlock an existing vault using the master password.
///
/// Re-derives the key from the stored salt, decrypts the check value to
/// verify the password is correct, then activates the vault for the session.
///
/// Returns `Err("Incorrect master password")` if the password is wrong.
#[tauri::command]
pub fn unlock(password: String, state: State<'_, AppState>) -> Result<(), String> {
let salt_hex = state
.settings
.get("vault_salt")
.ok_or_else(|| "Vault has not been set up — call create_vault first".to_string())?;
let salt = hex::decode(&salt_hex)
.map_err(|e| format!("Stored vault salt is corrupt: {e}"))?;
let key = vault::derive_key(&password, &salt);
let vs = VaultService::new(key);
// Verify the password by decrypting the check value.
let check_blob = state
.settings
.get("vault_check")
.ok_or_else(|| "Vault check value is missing — vault may be corrupt".to_string())?;
let check_plain = vs
.decrypt(&check_blob)
.map_err(|_| "Incorrect master password".to_string())?;
if check_plain != "wraith-vault-check" {
return Err("Incorrect master password".into());
}
// Activate the vault and credentials service for this session.
let cred_svc = CredentialService::new(state.db.clone(), VaultService::new(key));
*state.credentials.lock().unwrap() = Some(cred_svc);
*state.vault.lock().unwrap() = Some(vs);
Ok(())
}
/// Returns `true` if the vault is currently unlocked for this session.
#[tauri::command]
pub fn is_unlocked(state: State<'_, AppState>) -> bool {
state.is_unlocked()
}