use tauri::State; use zeroize::Zeroize; 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 async fn create_vault(mut password: String, state: State<'_, AppState>) -> Result<(), String> { let result = async { 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.clone()); // 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().await = Some(cred_svc); *state.vault.lock().await = Some(vs); Ok(()) }.await; password.zeroize(); result } /// 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 async fn unlock(mut password: String, state: State<'_, AppState>) -> Result<(), String> { let result = async { 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.clone()); // 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().await = Some(cred_svc); *state.vault.lock().await = Some(vs); Ok(()) }.await; password.zeroize(); result } /// Returns `true` if the vault is currently unlocked for this session. #[tauri::command] pub async fn is_unlocked(state: State<'_, AppState>) -> Result { Ok(state.is_unlocked().await) }