mod db; mod editor; use db::{DB, Item}; use clap::{Parser, Subcommand}; use rpassword; use once_cell::sync::Lazy; use std::fs; use std::io::{self, Write}; use std::path::Path; use std::process; static STORAGE_FOLDER: &str = "storage"; static STORAGE_PATH: Lazy = Lazy::new(|| { format!("{}/db.mps", STORAGE_FOLDER) }); #[derive(Parser)] #[command(name = "mps", version = "0.0.1", about = "MyPasswordStorage: Tool for storing your passwords locally with git synchronization")] struct Cli { #[command(subcommand)] command: Option, } #[derive(Subcommand)] enum Commands { /// Initialization of storage and config, use this in first time of usage of mps Init, /// Adds new item with unique id to the db Add { #[arg(value_name="item_id")] id: String }, /// Lists all ids stored in db List // TODO: show // TODO: edit } enum PROMPT { YES, NO } fn get_prompt(question: &str) -> io::Result { print!("{} [Y/n] ", question); io::stdout().flush()?; let mut input = String::new(); io::stdin().read_line(&mut input)?; let input = input.trim().to_lowercase(); match input.as_str() { "y" => Ok(PROMPT::YES), "yes" => Ok(PROMPT::YES), "n" => Ok(PROMPT::NO), "no" => Ok(PROMPT::NO), _ => Ok(PROMPT::YES), } } // TODO: change password functionality fn login() -> io::Result { println!("Enter passphrase: "); // TODO: check in db // TODO: return error if db is not inited let password = rpassword::read_password()?; if password != "pass" { return Err(io::Error::new(io::ErrorKind::InvalidData, "Wrong password")); } Ok(String::from(password)) } fn is_inited() -> bool { let path = Path::new(STORAGE_FOLDER); return path.exists(); } fn init() -> io::Result<()> { if is_inited() { return Err(io::Error::new(io::ErrorKind::AlreadyExists, "Reinitialization attempted")); } print!("Enter passphrase for db: "); io::stdout().flush()?; let password = rpassword::read_password()?; print!("Reenter passphrase: "); io::stdout().flush()?; let password2 = rpassword::read_password()?; if password != password2 { return Err(io::Error::new(io::ErrorKind::InvalidInput, "Passwords should be equal")); } fs::create_dir(STORAGE_FOLDER)?; println!("Storage folder created"); //let mut db = DB::init(&*STORAGE_PATH, pass)?; // TODO: enter password // TODO: dont create storate if master password hasn't entered fs::File::create(&*STORAGE_PATH)?; println!("Storage db created."); println!("Initialization complete."); println!(""); println!("Now it's required to add folder `{}` under git manually.", STORAGE_FOLDER); println!("Don't worry it's going to be encrypted."); Ok(()) } fn add(id: &String) -> io::Result<()> { let mut db = DB::new(&*STORAGE_PATH, String::from(""))?; if db.contains(id) { // TODO: ask to edit existing in outer function which invoked this one return Err(io::Error::new( io::ErrorKind::InvalidInput, format!("Dublicate item id: {}. Item id's must be unique.", id) )) } let content = editor::open_to_edit()?; db.items.insert(Item::from(id.clone(), content)); db.dump()?; Ok(()) } fn list() -> io:: Result<()> { let db = DB::new(&STORAGE_PATH, String::from(""))?; let mut vec: Vec<_> = db.items.iter().collect(); vec.sort(); for item in &vec { println!("{}", item.id); } Ok(()) } fn run_command() -> io::Result<()> { let cli = Cli::parse(); match &cli.command { Some(Commands::Init) => { init()?; } Some(Commands::Add{ id }) => { add(id)?; } Some(Commands::List) => { list()?; } None => { if !is_inited() { match get_prompt("Do you want to init your storage?")? { PROMPT::YES => init()?, PROMPT::NO => { println!("mps can work only when storage inited."); println!("Hint: you can restore your storage if you have it already:"); println!(" git clone {}", STORAGE_FOLDER); println!("to init manually your storage and config") }, } } else { login()?; } } } Ok(()) } fn main() { match run_command() { Ok(()) => return, Err(e) => { println!("{}", e); process::exit(2); // TODO: better codes for different errors } } }