Compare commits

...

13 Commits

  1. 37
      README.md
  2. 150
      src/main.rs
  3. 8
      src/paths.rs
  4. 50
      src/storage.rs

37
README.md

@ -2,7 +2,7 @@
A small tool for storing passwords locally with git sync A small tool for storing passwords locally with git sync
## MPS ## Installation
1. Clone MPS repo: 1. Clone MPS repo:
``` ```
git clone git@code.corpglory.net:corpglory/mps.git git clone git@code.corpglory.net:corpglory/mps.git
@ -14,18 +14,37 @@ cd mps
cargo build --release cargo build --release
``` ```
3. Add your mps build to PATH in your ~/.zshenv 3. Run init
```
mps init
``` ```
export PATH=$PATH:"<mps_git_repo_path>/target/release"
4. Add your storage under git
```bash
cd storage
git init
git checkout -b main
git add db.mps
git commit -m "init mps"
git remote add origin <your_git_storage_url>
git push -u origin main
``` ```
## Intilization ### Intilization with exisitng storage db repo
1. Create empty repository for storing your passwords on gitlab or somewhere you prefer 1. Create empty repository for storing your passwords on gitlab or somewhere you prefer
2. Create dir `mps`: `mkdir ~/.mps` 2. Clone to your home folder `<mps_path>/storage`: `git clone <your_git_storage_url> storage`
3. Clone to your home folder `~/.mps/storage`: `git clone <your_storage_url> storage`
3. Export varable `$MPS_HOME`: `export MPS_HOME="/home/<your_username>/.mps"` in your `~/.zshenv` #### Exporting `MPS_HOME`
4. Run `mps init` You can export variable `$MPS_HOME` to init storage for example in your home directory:
`export MPS_HOME="/home/<your_username>/.mps"` in your `~/.zshenv`
now `mps` will try to get storage from `$MPS_HOME/storage`
#### Add your mps build to PATH in your ~/.zshenv
```
export PATH=$PATH:"<mps_path>/target/release"
```

150
src/main.rs

@ -7,15 +7,18 @@ mod git;
use storage::{Storage, Item}; use storage::{Storage, Item};
use clap::{Parser, Subcommand}; use clap::{Parser, Subcommand, ArgGroup};
use rpassword; use rpassword;
use std::io::{self, Write}; use std::io::{self, Write};
use std::process; use std::process;
const VERSION: &str = "0.0.1";
#[derive(Parser)] #[derive(Parser)]
#[command(name = "mps", version = "0.0.1", about = "MyPasswordStorage: Tool for storing your passwords locally with git synchronization")] #[command(name = "mps", version = VERSION, about = "MyPasswordStorage: Tool for storing your passwords locally with git synchronization")]
struct Cli { struct Cli {
#[command(subcommand)] #[command(subcommand)]
command: Option<Commands>, command: Option<Commands>,
@ -31,10 +34,7 @@ enum Commands {
List, List,
/// Show content of an item /// Show content of an item
Show { Show(ItemIdArgs),
#[arg(value_name="item_id")]
id: String
},
/// Adds new item with unique id to the storage /// Adds new item with unique id to the storage
Add { Add {
@ -43,16 +43,29 @@ enum Commands {
}, },
/// Edit item content /// Edit item content
Edit { Edit(ItemIdArgs),
#[arg(value_name="item_id")]
id: String /// Delete item
}, Delete(ItemIdArgs),
/// Set new passphrase
Password
Delete {
#[arg(value_name="item_id")]
id: String
} }
#[derive(Parser)]
#[command(group(
ArgGroup::new("item")
.required(true)
.args(&["id", "number"])
))]
struct ItemIdArgs {
/// Item id
#[arg(required=false)]
id: Option<String>,
#[arg(short='n', long, value_name="number")]
number: Option<u32>
} }
enum PROMPT { enum PROMPT {
@ -86,11 +99,10 @@ impl MPS {
} }
fn print_init_hint() { fn print_init_hint() {
println!("1. Create empty repository for storing your passwords on gitlab or somewhere you prefer"); println!("1. Run `mps init`");
println!("2. Create dir `mps`: `mkdir ~/mps`"); println!("2. Init `storage` under git:");
println!("3. Clone to your home folder `~/.mps/storage`: `git clone <your_storage_url> storage`"); println!(" cd storage");
println!("4. xport varable `$MPS_HOME`: `export MPS_HOME=\"/home/<your_username>/.mps\"`"); println!(" git init");
println!("5. Run `mps init`");
} }
fn prompt_new_password() -> io::Result<String> { fn prompt_new_password() -> io::Result<String> {
@ -112,10 +124,7 @@ impl MPS {
Ok(ps) Ok(ps)
} }
fn init() -> io::Result<()> { fn read_new_password() -> io::Result<String> {
if Storage::is_inited()? {
return Err(io::Error::new(io::ErrorKind::AlreadyExists, "Reinitialization attempted"));
}
let passphrase; let passphrase;
loop { loop {
match MPS::prompt_new_password() { match MPS::prompt_new_password() {
@ -129,8 +138,15 @@ impl MPS {
} }
} }
} }
Storage::init(passphrase)?; Ok(passphrase)
Ok(()) }
fn init() -> io::Result<()> {
if Storage::is_inited()? {
return Err(io::Error::new(io::ErrorKind::AlreadyExists, "Reinitialization attempted"));
}
let passphrase = MPS::read_new_password()?;
Storage::init(passphrase)
} }
pub fn login(&mut self) -> io::Result<()> { pub fn login(&mut self) -> io::Result<()> {
@ -147,33 +163,35 @@ impl MPS {
Ok(()) Ok(())
} }
pub fn password(&mut self) -> io::Result<()> {
self.login()?;
let st = self.storage.as_ref().unwrap();
let passphrase = MPS::read_new_password()?;
let new_st = st.new_from_passphrase(&passphrase);
new_st.dump()
}
fn list(&mut self) -> io:: Result<()> { fn list(&mut self) -> io:: Result<()> {
self.login()?; self.login()?;
let ids = self.storage.as_ref().unwrap().ids(); let ids = self.storage.as_ref().unwrap().ids();
// let mut counter = 1; let mut counter = 1;
if ids.len() == 0 { if ids.len() == 0 {
println!("No items"); println!("No items");
} else { } else {
for id in ids { for id in ids {
println!("{}", id); println!("[{}] {}", counter, id);
//counter += 1; counter += 1;
} }
} }
Ok(()) Ok(())
} }
/// Show item. Id must exists
fn show(&mut self, id: &String) -> io::Result<()> { fn show(&mut self, id: &String) -> io::Result<()> {
self.login()?; self.login()?;
let st = self.storage.as_ref().unwrap(); let st = self.storage.as_ref().unwrap();
if !st.contains(id) {
return Err(io::Error::new(
io::ErrorKind::InvalidInput,
format!("Can`t find id: {}", id)
));
}
let item = st.get(id); let item = st.get(id);
editor::open_to_show(&item.content)?; editor::open_to_show(&item.content)
Ok(())
} }
fn add(&mut self, id: &String) -> io::Result<()> { fn add(&mut self, id: &String) -> io::Result<()> {
@ -192,10 +210,10 @@ impl MPS {
// set empty string because there is no content yet // set empty string because there is no content yet
let content = editor::open_to_edit(&String::from(""))?; let content = editor::open_to_edit(&String::from(""))?;
st.add(Item::from(id.clone(), content)); st.add(Item::from(id.clone(), content));
st.dump()?; st.dump()
Ok(())
} }
/// Edit item, id need not to exist
fn edit(&mut self, id: &String) -> io::Result<()> { fn edit(&mut self, id: &String) -> io::Result<()> {
self.login()?; self.login()?;
let st = self.storage.as_mut().unwrap(); let st = self.storage.as_mut().unwrap();
@ -213,26 +231,49 @@ impl MPS {
let new_content = editor::open_to_edit(&item.content)?; let new_content = editor::open_to_edit(&item.content)?;
item.content = new_content; item.content = new_content;
st.update(item); st.update(item);
st.dump()?; st.dump()
Ok(())
} }
// Delete item by id, is must exist
fn delete(&mut self, id: &String) -> io::Result<()> { fn delete(&mut self, id: &String) -> io::Result<()> {
self.login()?; self.login()?;
let st = self.storage.as_mut().unwrap(); let st = self.storage.as_mut().unwrap();
if !st.contains(id) { st.remove(id);
st.dump()
}
/// Resolve id by ItemArgs.
/// # Arguments
/// * `args` - arguments to parse
/// * `check` - check that id existing
fn item_id_by_item_id_args(&mut self, args: &ItemIdArgs, check: bool) -> io::Result<String> {
self.login()?;
let st = self.storage.as_mut().unwrap();
let mut item_id: String = "NOT_INITIALIZED".to_string();
if let Some(id) = &args.id {
item_id = id.clone();
if check && !st.contains(&id) {
return Err(io::Error::new( return Err(io::Error::new(
io::ErrorKind::InvalidInput, io::ErrorKind::InvalidInput,
"No such item" format!("No such item {}", id)
)); ));
} }
st.remove(id);
st.dump()?;
Ok(())
} }
if let Some(number) = &args.number {
item_id = st.get_id_by_number(*number)?;
// we can guarantee that id exists because we take it by id
}
if args.id.is_none() && args.number.is_none() {
return Err(io::Error::new(
io::ErrorKind::InvalidInput,
format!("Bag arguments")
));
}
Ok(item_id.clone())
}
} }
fn run_command() -> io::Result<()> { fn run_command() -> io::Result<()> {
let cli = Cli::parse(); let cli = Cli::parse();
match &cli.command { match &cli.command {
@ -243,21 +284,28 @@ fn run_command() -> io::Result<()> {
let mut mps = MPS::new(); let mut mps = MPS::new();
mps.list()?; mps.list()?;
} }
Some(Commands::Show { id }) => { Some(Commands::Show(args)) => {
let mut mps = MPS::new(); let mut mps = MPS::new();
mps.show(id)?; let id = mps.item_id_by_item_id_args(args, true)?;
mps.show(&id)?;
} }
Some(Commands::Add { id }) => { Some(Commands::Add { id }) => {
let mut mps = MPS::new(); let mut mps = MPS::new();
mps.add(id)?; mps.add(id)?;
} }
Some(Commands::Edit { id }) => { Some(Commands::Edit(args)) => {
let mut mps = MPS::new();
let id = mps.item_id_by_item_id_args(args, false)?;
mps.edit(&id)?;
}
Some(Commands::Delete(args)) => {
let mut mps = MPS::new(); let mut mps = MPS::new();
mps.edit(id)? let id = mps.item_id_by_item_id_args(args, true)?;
mps.delete(&id)?;
} }
Some(Commands::Delete { id }) => { Some(Commands::Password) => {
let mut mps = MPS::new(); let mut mps = MPS::new();
mps.delete(id)? mps.password()?;
} }
None => { None => {
match Storage::check_installed() { match Storage::check_installed() {

8
src/paths.rs

@ -9,10 +9,10 @@ pub static PATH_DB: &str = "db.mps";
pub fn get_storage_path() -> io::Result<String> { pub fn get_storage_path() -> io::Result<String> {
let mps_home = env::var(ENV_MPS_HOME).map_err(|err| { let result = match env::var(ENV_MPS_HOME) {
io::Error::new(io::ErrorKind::NotFound, format!("{} error: {}", ENV_MPS_HOME, err)) Ok(mps_home) => format!("{}/{}", mps_home, PATH_STORAGE),
})?; Err(_) => PATH_STORAGE.to_string()
let result = format!("{}/{}", mps_home, PATH_STORAGE); };
Ok(result) Ok(result)
} }

50
src/storage.rs

@ -131,9 +131,11 @@ impl Storage {
} }
pub fn init(passphrase: String) -> io::Result<()> { pub fn init(passphrase: String) -> io::Result<()> {
Storage::check_installed()?; //Storage::check_installed()?;
let sp = paths::get_storage_path()?;
fs::create_dir(sp)?;
let st = Storage::new(passphrase); let st = Storage::new(passphrase);
st.dump()?; st.dump_db()?;
println!("Storage db created"); println!("Storage db created");
Ok(()) Ok(())
} }
@ -159,7 +161,7 @@ impl Storage {
} }
pub fn is_inited() -> io::Result<bool> { pub fn is_inited() -> io::Result<bool> {
Storage::check_installed()?; //Storage::check_installed()?;
let db = paths::get_db_path()?; let db = paths::get_db_path()?;
let db_path = Path::new(&db); let db_path = Path::new(&db);
Ok(db_path.exists()) Ok(db_path.exists())
@ -179,11 +181,31 @@ impl Storage {
self.items.contains(&item) self.items.contains(&item)
} }
// TODO: return Result<Item>
pub fn get(&self, id: &String) -> &Item { pub fn get(&self, id: &String) -> &Item {
let item = Item::from_empty(id.clone()); let item = Item::from_empty(id.clone());
self.items.get(&item).unwrap() self.items.get(&item).unwrap()
} }
/// Counting starts from 1, according to UI
pub fn get_id_by_number(&self, number: u32) -> io::Result<String> {
let number = number as usize;
if number == 0 {
return Err(io::Error::new(
io::ErrorKind::InvalidInput,
"Items numbering starts from 1"
));
}
let ids = self.ids();
if number > ids.len() {
return Err(io::Error::new(
io::ErrorKind::InvalidInput,
format!("There are only {} items, but asked with number {}", ids.len(), number)
));
}
Ok(ids[number - 1].clone())
}
pub fn add(&mut self, item: Item) { pub fn add(&mut self, item: Item) {
self.items.insert(item); self.items.insert(item);
} }
@ -194,17 +216,17 @@ impl Storage {
} }
pub fn remove(&mut self, id: &String) { pub fn remove(&mut self, id: &String) {
for it in self.items.iter() {
println!("x: {}", it.id);
}
let item = Item::from_empty(id.clone()); let item = Item::from_empty(id.clone());
self.items.remove(&item); self.items.remove(&item);
for it in self.items.iter() {
println!("y: {}", it.id);
}
} }
pub fn dump(&self) -> io::Result<()> { pub fn dump(&self) -> io::Result<()> {
self.dump_db()?;
git::Git::sync()?;
Ok(())
}
fn dump_db(&self) -> io::Result<()> {
let mut file = fs::OpenOptions::new() let mut file = fs::OpenOptions::new()
.write(true) .write(true)
.truncate(true) // Clear the file content before writing .truncate(true) // Clear the file content before writing
@ -213,13 +235,19 @@ impl Storage {
writeln!(file, "{}", self.encoder.get_encoded_test_passphrase()?)?; writeln!(file, "{}", self.encoder.get_encoded_test_passphrase()?)?;
for item in self.items.iter() { for item in self.items.iter() {
println!("--- {}", item.id);
writeln!(file, "{}", self.encoder.encrypt(&item.id)?)?; writeln!(file, "{}", self.encoder.encrypt(&item.id)?)?;
let content = self.encoder.encrypt(&item.content)?; let content = self.encoder.encrypt(&item.content)?;
writeln!(file, "{}", content)?; writeln!(file, "{}", content)?;
} }
git::Git::sync()?;
Ok(()) Ok(())
} }
pub fn new_from_passphrase(&self, passphrase: &String) -> Storage {
return Storage {
encoder: Encoder::from(passphrase),
items: self.items.clone()
}
}
} }

Loading…
Cancel
Save