diff --git a/Cargo.lock b/Cargo.lock index 6ca8e68..7c41519 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -63,9 +63,9 @@ dependencies = [ [[package]] name = "anyhow" -version = "1.0.100" +version = "1.0.101" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a23eb6b1614318a8071c9b2521f36b424b2c83db5eb3a0fead4a6c0809af6e61" +checksum = "5f0e0fee31ef5ed1ba1316088939cea399010ed7731dba877ed44aeb407a75ea" [[package]] name = "argon2" @@ -121,9 +121,9 @@ dependencies = [ [[package]] name = "base64ct" -version = "1.8.1" +version = "1.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0e050f626429857a27ddccb31e0aca21356bfa709c04041aefddac081a8f068a" +checksum = "2af50177e190e07a26ab74f8b1efbfe2ef87da2116221318cb1c2e82baf7de06" [[package]] name = "bitflags" @@ -167,9 +167,9 @@ checksum = "5dd9dc738b7a8311c7ade152424974d8115f2cdad61e8dab8dac9f2362298510" [[package]] name = "cc" -version = "1.2.51" +version = "1.2.55" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7a0aeaff4ff1a90589618835a598e545176939b97874f7abc7851caa0618f203" +checksum = "47b26a0954ae34af09b50f0de26458fa95369a0d478d8236d3f93082b219bd29" dependencies = [ "find-msvc-tools", "shlex", @@ -189,9 +189,9 @@ checksum = "613afe47fcd5fac7ccf1db93babcb082c5994d996f20b8b159f2ad1658eb5724" [[package]] name = "chrono" -version = "0.4.42" +version = "0.4.43" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "145052bdd345b87320e369255277e3fb5152762ad123a901ef5c262dd38fe8d2" +checksum = "fac4744fb15ae8337dc853fee7fb3f4e48c0fbaa23d0afe49c447b4fab126118" dependencies = [ "iana-time-zone", "js-sys", @@ -282,7 +282,7 @@ dependencies = [ "proc-macro2", "quote", "strsim", - "syn 2.0.111", + "syn 2.0.114", ] [[package]] @@ -293,7 +293,7 @@ checksum = "d38308df82d1080de0afee5d069fa14b0326a88c14f15c5ccda35b4a6c414c81" dependencies = [ "darling_core", "quote", - "syn 2.0.111", + "syn 2.0.114", ] [[package]] @@ -331,7 +331,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb" dependencies = [ "libc", - "windows-sys 0.59.0", + "windows-sys 0.61.2", ] [[package]] @@ -375,9 +375,9 @@ dependencies = [ [[package]] name = "find-msvc-tools" -version = "0.1.6" +version = "0.1.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "645cbb3a84e60b7531617d5ae4e57f7e27308f6445f5abf653209ea76dec8dff" +checksum = "5baebc0774151f905a1a2cc41989300b1e6fbb29aff0ceffa1064fdd3088d582" [[package]] name = "fnv" @@ -403,9 +403,9 @@ dependencies = [ [[package]] name = "getrandom" -version = "0.2.16" +version = "0.2.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "335ff9f135e4384c8150d6f27c6daed433577f86b4750418338c01a1a2528592" +checksum = "ff2abc00be7fca6ebc474524697ae276ad847ad0a6b3faa4bcb027e9a4614ad0" dependencies = [ "cfg-if", "libc", @@ -430,18 +430,18 @@ checksum = "e629b9b98ef3dd8afe6ca2bd0f89306cec16d43d907889945bc5d6687f2f13c7" [[package]] name = "home" -version = "0.5.12" +version = "0.5.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cc627f471c528ff0c4a49e1d5e60450c8f6461dd6d10ba9dcd3a61d3dff7728d" +checksum = "589533453244b0995c858700322199b2becb13b627df2851f64a2775d024abcf" dependencies = [ - "windows-sys 0.61.2", + "windows-sys 0.59.0", ] [[package]] name = "iana-time-zone" -version = "0.1.64" +version = "0.1.65" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "33e57f83510bb73707521ebaffa789ec8caf86f9657cad665b092b581d40e9fb" +checksum = "e31bc9ad994ba00e440a8aa5c9ef0ec67d5cb5e5cb0cc7f8b744a35b389cc470" dependencies = [ "android_system_properties", "core-foundation-sys", @@ -478,9 +478,9 @@ dependencies = [ [[package]] name = "js-sys" -version = "0.3.83" +version = "0.3.85" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "464a3709c7f55f1f721e5389aa6ea4e3bc6aba669353300af094b29ffbdde1d8" +checksum = "8c942ebf8e95485ca0d52d97da7c5a2c387d0e7f0ba4c35e93bfcaee045955b3" dependencies = [ "once_cell", "wasm-bindgen", @@ -488,9 +488,9 @@ dependencies = [ [[package]] name = "libc" -version = "0.2.178" +version = "0.2.180" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "37c93d8daa9d8a012fd8ab92f088405fb202ea0b6ab73ee2482ae66af4f42091" +checksum = "bcc35a38544a891a5f7c865aca548a982ccb3b8650a5b06d0fd33a10283c56fc" [[package]] name = "linux-raw-sys" @@ -506,9 +506,9 @@ checksum = "5e5032e24019045c762d3c0f28f5b6b8bbf38563a65908389bf7978758920897" [[package]] name = "memchr" -version = "2.7.6" +version = "2.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f52b00d39961fc5b2736ea853c9cc86238e165017a493d1d5c8eac6bdc4cc273" +checksum = "f8ca58f447f06ed17d5fc4043ce1b10dd205e060fb3ce5b979b8ed8e59ff3f79" [[package]] name = "miniz_oxide" @@ -601,18 +601,18 @@ dependencies = [ [[package]] name = "proc-macro2" -version = "1.0.104" +version = "1.0.106" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9695f8df41bb4f3d222c95a67532365f569318332d03d5f3f67f37b20e6ebdf0" +checksum = "8fd00f0bb2e90d81d1044c2b32617f68fcb9fa3bb7640c23e9c748e53fb30934" dependencies = [ "unicode-ident", ] [[package]] name = "quote" -version = "1.0.42" +version = "1.0.44" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a338cc41d27e6cc6dce6cefc13a0729dfbb81c262b1f519331575dd80ef3067f" +checksum = "21b2ebcf727b7760c461f091f9f0f539b77b8e87f2fd88131e7f1b433b3cece4" dependencies = [ "proc-macro2", ] @@ -714,9 +714,9 @@ dependencies = [ [[package]] name = "rustc-demangle" -version = "0.1.26" +version = "0.1.27" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "56f7d92ca342cea22a06f2121d944b4fd82af56988c270852495420f961d4ace" +checksum = "b50b8869d9fc858ce7266cce0194bd74df58b9d0e3f6df3a9fc8eb470d95c09d" [[package]] name = "rustix" @@ -728,7 +728,7 @@ dependencies = [ "errno", "libc", "linux-raw-sys", - "windows-sys 0.59.0", + "windows-sys 0.61.2", ] [[package]] @@ -792,7 +792,7 @@ checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79" dependencies = [ "proc-macro2", "quote", - "syn 2.0.111", + "syn 2.0.114", ] [[package]] @@ -843,9 +843,9 @@ dependencies = [ [[package]] name = "syn" -version = "2.0.111" +version = "2.0.114" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "390cc9a294ab71bdb1aa2e99d13be9c753cd2d7bd6560c77118597410c4d2e87" +checksum = "d4d107df263a3013ef9b1879b0df87d706ff80f65a86ea879bd9c31f9b307c2a" dependencies = [ "proc-macro2", "quote", @@ -866,22 +866,22 @@ dependencies = [ [[package]] name = "thiserror" -version = "2.0.17" +version = "2.0.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f63587ca0f12b72a0600bcba1d40081f830876000bb46dd2337a3051618f4fc8" +checksum = "4288b5bcbc7920c07a1149a35cf9590a2aa808e0bc1eafaade0b80947865fbc4" dependencies = [ "thiserror-impl", ] [[package]] name = "thiserror-impl" -version = "2.0.17" +version = "2.0.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3ff15c8ecd7de3849db632e14d18d2571fa09dfc5ed93479bc4485c7a517c913" +checksum = "ebc4ee7f67670e9b64d05fa4253e753e016c6c95ff35b89b7941d6b856dec1d5" dependencies = [ "proc-macro2", "quote", - "syn 2.0.111", + "syn 2.0.114", ] [[package]] @@ -892,9 +892,9 @@ checksum = "562d481066bde0658276a35467c4af00bdc6ee726305698a55b86e61d7ad82bb" [[package]] name = "unicode-ident" -version = "1.0.22" +version = "1.0.23" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9312f7c4f6ff9069b165498234ce8be658059c6728633667c526e27dc2cf1df5" +checksum = "537dd038a89878be9b64dd4bd1b260315c1bb94f4d784956b81e27a088d9a09e" [[package]] name = "unicode-segmentation" @@ -962,9 +962,9 @@ checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b" [[package]] name = "wasm-bindgen" -version = "0.2.106" +version = "0.2.108" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0d759f433fa64a2d763d1340820e46e111a7a5ab75f993d1852d70b03dbb80fd" +checksum = "64024a30ec1e37399cf85a7ffefebdb72205ca1c972291c51512360d90bd8566" dependencies = [ "cfg-if", "once_cell", @@ -975,9 +975,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro" -version = "0.2.106" +version = "0.2.108" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "48cb0d2638f8baedbc542ed444afc0644a29166f1595371af4fecf8ce1e7eeb3" +checksum = "008b239d9c740232e71bd39e8ef6429d27097518b6b30bdf9086833bd5b6d608" dependencies = [ "quote", "wasm-bindgen-macro-support", @@ -985,22 +985,22 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro-support" -version = "0.2.106" +version = "0.2.108" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cefb59d5cd5f92d9dcf80e4683949f15ca4b511f4ac0a6e14d4e1ac60c6ecd40" +checksum = "5256bae2d58f54820e6490f9839c49780dff84c65aeab9e772f15d5f0e913a55" dependencies = [ "bumpalo", "proc-macro2", "quote", - "syn 2.0.111", + "syn 2.0.114", "wasm-bindgen-shared", ] [[package]] name = "wasm-bindgen-shared" -version = "0.2.106" +version = "0.2.108" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cbc538057e648b67f72a982e708d485b2efa771e1ac05fec311f9f63e5800db4" +checksum = "1f01b580c9ac74c8d8f0c0e4afb04eeef2acf145458e52c03845ee9cd23e3d12" dependencies = [ "unicode-ident", ] @@ -1048,7 +1048,7 @@ dependencies = [ "darling", "proc-macro2", "quote", - "syn 2.0.111", + "syn 2.0.114", ] [[package]] @@ -1072,7 +1072,7 @@ checksum = "053e2e040ab57b9dc951b72c264860db7eb3b0200ba345b4e4c3b14f67855ddf" dependencies = [ "proc-macro2", "quote", - "syn 2.0.111", + "syn 2.0.114", ] [[package]] @@ -1083,7 +1083,7 @@ checksum = "3f316c4a2570ba26bbec722032c4099d8c8bc095efccdc15688708623367e358" dependencies = [ "proc-macro2", "quote", - "syn 2.0.111", + "syn 2.0.114", ] [[package]] @@ -1292,5 +1292,5 @@ checksum = "85a5b4158499876c763cb03bc4e49185d3cccbabb15b33c627f7884f43db852e" dependencies = [ "proc-macro2", "quote", - "syn 2.0.111", + "syn 2.0.114", ] diff --git a/src/adapters/cli/commands.rs b/src/adapters/cli/commands.rs new file mode 100644 index 0000000..2580f96 --- /dev/null +++ b/src/adapters/cli/commands.rs @@ -0,0 +1,13 @@ +pub enum Command { + Lock, + List, + Help, + Exit, + Commit, + Clear, + Get(String), + Unlock(String), + Create(String), + Remove(String), + Add { service: String, username: String }, +} diff --git a/src/adapters/cli.rs b/src/adapters/cli/mod.rs similarity index 74% rename from src/adapters/cli.rs rename to src/adapters/cli/mod.rs index 612c8c7..9a4834c 100644 --- a/src/adapters/cli.rs +++ b/src/adapters/cli/mod.rs @@ -1,13 +1,8 @@ +mod commands; +mod ui; + use anyhow::Result; -use rustyline::{ - Editor, Helper, - completion::{Completer, Pair}, - error::ReadlineError, - highlight::Highlighter, - hint::Hinter, - history::DefaultHistory, - validate::Validator, -}; +use rustyline::{Editor, error::ReadlineError, history::DefaultHistory}; use std::io::{self, Write}; use zeroize::Zeroize; @@ -16,92 +11,22 @@ use crate::{ domain::ports::{CryptoPort, StoragePort}, }; -/* ======================= - ANSI COLORS -======================= */ -const BLUE: &str = "\x1b[34m"; -const GREEN: &str = "\x1b[32m"; -const RED: &str = "\x1b[31m"; -const YELLOW: &str = "\x1b[33m"; -const CYAN: &str = "\x1b[36m"; -const RESET: &str = "\x1b[0m"; - -/* ======================= - AUTOCOMPLETE -======================= */ -struct VaultHelper { - commands: Vec<&'static str>, -} - -impl Helper for VaultHelper {} -impl Hinter for VaultHelper { - type Hint = String; -} -impl Highlighter for VaultHelper {} -impl Validator for VaultHelper {} - -impl Completer for VaultHelper { - type Candidate = Pair; - - fn complete( - &self, - line: &str, - pos: usize, - _: &rustyline::Context<'_>, - ) -> rustyline::Result<(usize, Vec)> { - let start = 0; - let input = &line[..pos]; - - let matches = self - .commands - .iter() - .filter(|cmd| cmd.starts_with(input)) - .map(|cmd| Pair { - display: cmd.to_string(), - replacement: cmd.to_string(), - }) - .collect(); - - Ok((start, matches)) - } -} - -/* ======================= - COMMAND ENUM -======================= */ -enum Command { - Lock, - List, - Help, - Exit, - Commit, - Clear, - Get(String), - Unlock(String), - Create(String), - Remove(String), - Add { service: String, username: String }, -} +use commands::Command; +use ui::{BLUE, CYAN, GREEN, RED, RESET, VaultHelper, YELLOW}; -/* ======================= - CLI STRUCT -======================= */ +/// Vault CLI struct, containing the engine and readline editor pub struct VaultCli { engine: VaultEngine, rl: Editor, } -/* ======================= - IMPLEMENTATION -======================= */ +/// Main CLI implementation, handling user input, commands, and interactions with the engine impl VaultCli { pub fn new(engine: VaultEngine) -> Result { - let helper = VaultHelper { - commands: vec![ - "create", "unlock", "lock", "add", "get", "rm", "commit", "ls", "list", "help", - "exit", "clear", - ], - }; + let helper = VaultHelper::new(vec![ + "create", "unlock", "lock", "add", "get", "rm", "commit", "ls", "list", "help", "exit", + "clear", + ]); let mut rl = Editor::::new()?; rl.set_helper(Some(helper)); @@ -109,6 +34,7 @@ impl VaultCli { Ok(Self { engine, rl }) } + /// Main loop that runs the CLI, handling user input and commands pub fn run(&mut self) -> Result<()> { println!("--- Vault CLI ---\n"); @@ -156,9 +82,7 @@ impl VaultCli { Ok(()) } - /* ======================= - COMMAND PARSING - ======================= */ + /// Parses user input into Command enum fn parse_command(&self, input: &str) -> Option { let mut p = input.split_whitespace(); let cmd = p.next()?; @@ -182,9 +106,7 @@ impl VaultCli { }) } - /* ======================= - COMMAND HANDLER - ======================= */ + /// Handles parsed command and interacts with engine fn handle_command(&mut self, cmd: Command) -> Result<()> { match cmd { Command::Unlock(v) => { @@ -244,8 +166,12 @@ impl VaultCli { } Command::Lock => { - self.engine.lock()?; - println!("Vault locked.\n"); + if self.confirm_lock()? { + self.engine.lock()?; + println!("Vault locked.\n"); + } else { + println!("Aborted.\n"); + } } Command::Clear => { @@ -263,9 +189,7 @@ impl VaultCli { Ok(()) } - /* ======================= - EXIT CONFIRMATION - ======================= */ + /// Prints confirmation message when exiting with unsaved changes fn confirm_exit(&mut self) -> Result { if self.engine.is_dirty() { println!("You have uncommitted changes."); @@ -292,14 +216,42 @@ impl VaultCli { } } - /* ======================= - PROMPT - ======================= */ + /// Prints confirmation message when locking with unsaved changes + fn confirm_lock(&mut self) -> Result { + if self.engine.is_dirty() { + println!("You have uncommitted changes."); + println!("1) Commit and lock"); + println!("2) Lock without committing"); + println!("3) Cancel\n"); + + print!("Choose an option [1-3]: "); + io::stdout().flush()?; + + let mut input = String::new(); + io::stdin().read_line(&mut input)?; + + match input.trim() { + "1" => { + self.engine.commit()?; + Ok(true) + } + "2" => Ok(true), + _ => Ok(false), + } + } else { + Ok(true) + } + } + + /// Generates dynamic prompt based on vault state + /// - Locked: vault[locked]> + /// - Unlocked: vault[vault_name|entry_count]> + /// - Unlocked and dirty: vault[vault_name*|entry_count]> fn prompt(&self) -> String { if self.engine.is_locked() { format!("{BLUE}vault{RESET}[{RED}locked{RESET}]> ") } else { - let name = self.engine.current_vault().unwrap_or("unknown"); + let name = self.engine.current_vault().unwrap_or("unknown".into()); let dirty = self.engine.is_dirty(); let count = self.engine.get_entries().unwrap_or(Vec::new()).len(); @@ -313,15 +265,14 @@ impl VaultCli { } } - /* ======================= - UTIL - ======================= */ + /// Prompts user for password without echoing fn request_password(&self, label: &str) -> String { print!("{}", label); io::stdout().flush().unwrap(); rpassword::read_password().unwrap() } + /// Prints confirmation message fn confirm(&self, msg: &str) -> bool { print!("{} (y/N): ", msg); io::stdout().flush().unwrap(); @@ -330,6 +281,7 @@ impl VaultCli { matches!(input.trim(), "y" | "Y") } + /// Prints help message fn print_help() { println!( r#" diff --git a/src/adapters/cli/ui.rs b/src/adapters/cli/ui.rs new file mode 100644 index 0000000..1e7a3ca --- /dev/null +++ b/src/adapters/cli/ui.rs @@ -0,0 +1,70 @@ +use rustyline::{ + Helper, + completion::{Completer, Pair}, + highlight::Highlighter, + hint::Hinter, + validate::Validator, +}; + +/* ======================= + ANSI COLORS +======================= */ +pub const BLUE: &str = "\x1b[34m"; +pub const GREEN: &str = "\x1b[32m"; +pub const RED: &str = "\x1b[31m"; +pub const YELLOW: &str = "\x1b[33m"; +pub const CYAN: &str = "\x1b[36m"; +pub const RESET: &str = "\x1b[0m"; + +/// Helper struct for command autocompletion and hints +pub struct VaultHelper { + commands: Vec<&'static str>, +} + +/// Implementing traits for VaultHelper to enable autocompletion, validation, and hints in the CLI +impl Helper for VaultHelper {} + +/// Empty implementations for Validator and Highlighter, as we don't have specific validation or highlighting logic +impl Validator for VaultHelper {} + +/// Empty implementation for Highlighter, as we don't have specific highlighting logic +impl Highlighter for VaultHelper {} + +/// Implementing Hinter to provide hints for commands, currently unused but can be extended in the future +impl Hinter for VaultHelper { + type Hint = String; +} + +/// Implements command autocompletion based on available commands +impl Completer for VaultHelper { + type Candidate = Pair; + + fn complete( + &self, + line: &str, + pos: usize, + _: &rustyline::Context<'_>, + ) -> rustyline::Result<(usize, Vec)> { + let start = 0; + let input = &line[..pos]; + + let matches = self + .commands + .iter() + .filter(|cmd| cmd.starts_with(input)) + .map(|cmd| Pair { + display: cmd.to_string(), + replacement: cmd.to_string(), + }) + .collect(); + + Ok((start, matches)) + } +} + +/// Constructor for VaultHelper to initialize it with a list of commands +impl VaultHelper { + pub fn new(commands: Vec<&'static str>) -> Self { + Self { commands } + } +} diff --git a/src/adapters/file_storage.rs b/src/adapters/file_storage.rs index a3c3949..52be3e8 100644 --- a/src/adapters/file_storage.rs +++ b/src/adapters/file_storage.rs @@ -43,6 +43,10 @@ impl StoragePort for FileStorage { self.path = self.base_path.join(path); } + fn get_path(&self) -> Option { + self.path.to_str().map(|s| s.to_string()) + } + fn save(&self, data: &[u8]) -> Result<(), StorageError> { // Checks dir if let Some(parent) = self.path.parent() { diff --git a/src/application/engine.rs b/src/application/engine.rs index c2e40cf..e2f4cb2 100644 --- a/src/application/engine.rs +++ b/src/application/engine.rs @@ -3,16 +3,21 @@ use std::collections::BTreeMap; use zeroize::Zeroize; use crate::domain::{ - errors::VaultError, models::{Entry, VaultState}, ports::{CryptoPort, StoragePort} + errors::VaultError, + models::{Entry, VaultState}, + ports::{CryptoPort, StoragePort}, }; +/// VaultEngine is the core of the application, responsible for managing vault state, entries and interactions with storage and crypto ports pub struct VaultEngine { storage: S, crypto: C, vault_state: Option, entries: BTreeMap, + dirty: bool, } +/// VaultEngine is the core of the application, responsible for managing vault state, entries and interactions with storage and crypto ports impl VaultEngine { pub fn new(storage: S, crypto: C) -> Self { Self { @@ -20,21 +25,26 @@ impl VaultEngine { crypto: crypto, vault_state: None, entries: BTreeMap::new(), + dirty: false, } } + /// Checks if vault is locked by checking if vault state is None pub fn is_locked(&self) -> bool { self.vault_state.is_none() } - pub fn current_vault(&self) -> Option<&str>{ - Some("teste") + /// Gets current vault name if exists + pub fn current_vault(&self) -> Option { + self.storage.get_path() } - pub fn is_dirty(&self) -> bool{ - true + /// Checks if vault has unsaved changes + pub fn is_dirty(&self) -> bool { + self.dirty } + /// Creates new vault with given name and password, initializing vault state and deriving key from password and salt pub fn create_vault(&mut self, name: &str, password: &str) -> Result<(), VaultError> { if !self.is_locked() { return Err(VaultError::Unlocked); @@ -49,6 +59,7 @@ impl VaultEngine { Ok(()) } + /// Commits vault state and entries to storage, encrypting them with crypto port pub fn commit(&mut self) -> Result<(), VaultError> { let vault_state = self.vault_state.as_mut().ok_or(VaultError::Locked)?; @@ -67,9 +78,11 @@ impl VaultEngine { self.storage.save(&vault_buffer)?; + self.dirty = false; Ok(()) } + /// Unlocks vault by name, loading entries into memory and deriving key from password pub fn unlock(&mut self, vault: &str, password: &str) -> Result<(), VaultError> { self.storage.set_path(vault.into()); @@ -93,14 +106,11 @@ impl VaultEngine { // Derive key self.crypto.init(password, &v_state.salt)?; - // Decrypt entries - // let v_state = self.vault_state.as_ref().ok_or(VaultError::Locked)?; - let stream = self .crypto .decrypt(&v_state.cipher, &v_state.nonce) .map_err(|_| { - self.vault_state = None; //Invalid password, keeps vault locked + self.vault_state = None; //Invalid password, keeps vault locked VaultError::InvalidPassword }); @@ -111,6 +121,7 @@ impl VaultEngine { Ok(()) } + /// Locks vault, clearing all entries from memory and zeroizing them pub fn lock(&mut self) -> Result<(), VaultError> { if self.is_locked() { return Err(VaultError::Locked); @@ -126,6 +137,7 @@ impl VaultEngine { Ok(()) } + /// Adds new entry to vault, indexed by service name pub fn add(&mut self, service: &str, username: &str, password: &str) -> Result<(), VaultError> { if self.is_locked() { return Err(VaultError::Locked); @@ -138,28 +150,31 @@ impl VaultEngine { } self.entries.insert(service.into(), entry); + self.dirty = true; Ok(()) } + /// Deletes entry by service name pub fn delete(&mut self, service: &str) -> Result { if self.is_locked() { return Err(VaultError::Locked); } + self.dirty = true; self.entries .remove(service) .ok_or(VaultError::EntryNotFound) } + /// Gets entry by service name pub fn get(&self, service: &str) -> Result<&Entry, VaultError> { if self.is_locked() { return Err(VaultError::Locked); } - self.entries - .get(service) - .ok_or(VaultError::EntryNotFound) + self.entries.get(service).ok_or(VaultError::EntryNotFound) } + /// Lists entries in vault pub fn get_entries(&self) -> Result, VaultError> { if self.is_locked() { return Err(VaultError::Locked); @@ -167,6 +182,7 @@ impl VaultEngine { Ok(self.entries.keys().cloned().collect()) } + /// Lists vaults in storage dir pub fn get_vaults(&self) -> Result, VaultError> { let vaults = self.storage.list_vaults()?; Ok(vaults) diff --git a/src/domain/ports.rs b/src/domain/ports.rs index c3fedec..cf7b864 100644 --- a/src/domain/ports.rs +++ b/src/domain/ports.rs @@ -10,6 +10,7 @@ pub trait CryptoPort { pub trait StoragePort { fn exists(&self) -> bool; fn set_path(&mut self, path: String); + fn get_path(&self) -> Option; fn load(&self) -> Result, StorageError>; fn save(&self, data: &[u8]) -> Result<(), StorageError>; fn list_vaults(&self) -> Result, StorageError>;