Compare commits

...

13 Commits

  1. 37
      README.md
  2. 154
      src/main.rs
  3. 8
      src/paths.rs
  4. 52
      src/storage.rs

37
README.md

@ -2,7 +2,7 @@
A small tool for storing passwords locally with git sync
## MPS
## Installation
1. Clone MPS repo:
```
git clone git@code.corpglory.net:corpglory/mps.git
@ -14,18 +14,37 @@ cd mps
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
2. Create dir `mps`: `mkdir ~/.mps`
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`
4. Run `mps init`
2. Clone to your home folder `<mps_path>/storage`: `git clone <your_git_storage_url> storage`
#### Exporting `MPS_HOME`
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"
```

154
src/main.rs

@ -7,15 +7,18 @@ mod git;
use storage::{Storage, Item};
use clap::{Parser, Subcommand};
use clap::{Parser, Subcommand, ArgGroup};
use rpassword;
use std::io::{self, Write};
use std::process;
const VERSION: &str = "0.0.1";
#[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 {
#[command(subcommand)]
command: Option<Commands>,
@ -31,10 +34,7 @@ enum Commands {
List,
/// Show content of an item
Show {
#[arg(value_name="item_id")]
id: String
},
Show(ItemIdArgs),
/// Adds new item with unique id to the storage
Add {
@ -43,18 +43,31 @@ enum Commands {
},
/// Edit item content
Edit {
#[arg(value_name="item_id")]
id: String
},
Edit(ItemIdArgs),
Delete {
#[arg(value_name="item_id")]
id: String
}
/// Delete item
Delete(ItemIdArgs),
/// Set new passphrase
Password
}
#[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 {
YES,
NO
@ -86,11 +99,10 @@ impl MPS {
}
fn print_init_hint() {
println!("1. Create empty repository for storing your passwords on gitlab or somewhere you prefer");
println!("2. Create dir `mps`: `mkdir ~/mps`");
println!("3. Clone to your home folder `~/.mps/storage`: `git clone <your_storage_url> storage`");
println!("4. xport varable `$MPS_HOME`: `export MPS_HOME=\"/home/<your_username>/.mps\"`");
println!("5. Run `mps init`");
println!("1. Run `mps init`");
println!("2. Init `storage` under git:");
println!(" cd storage");
println!(" git init");
}
fn prompt_new_password() -> io::Result<String> {
@ -112,10 +124,7 @@ impl MPS {
Ok(ps)
}
fn init() -> io::Result<()> {
if Storage::is_inited()? {
return Err(io::Error::new(io::ErrorKind::AlreadyExists, "Reinitialization attempted"));
}
fn read_new_password() -> io::Result<String> {
let passphrase;
loop {
match MPS::prompt_new_password() {
@ -129,8 +138,15 @@ impl MPS {
}
}
}
Storage::init(passphrase)?;
Ok(())
Ok(passphrase)
}
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<()> {
@ -147,33 +163,35 @@ impl MPS {
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<()> {
self.login()?;
let ids = self.storage.as_ref().unwrap().ids();
// let mut counter = 1;
let mut counter = 1;
if ids.len() == 0 {
println!("No items");
} else {
for id in ids {
println!("{}", id);
//counter += 1;
println!("[{}] {}", counter, id);
counter += 1;
}
}
Ok(())
}
/// Show item. Id must exists
fn show(&mut self, id: &String) -> io::Result<()> {
self.login()?;
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);
editor::open_to_show(&item.content)?;
Ok(())
editor::open_to_show(&item.content)
}
fn add(&mut self, id: &String) -> io::Result<()> {
@ -192,10 +210,10 @@ impl MPS {
// set empty string because there is no content yet
let content = editor::open_to_edit(&String::from(""))?;
st.add(Item::from(id.clone(), content));
st.dump()?;
Ok(())
st.dump()
}
/// Edit item, id need not to exist
fn edit(&mut self, id: &String) -> io::Result<()> {
self.login()?;
let st = self.storage.as_mut().unwrap();
@ -213,26 +231,49 @@ impl MPS {
let new_content = editor::open_to_edit(&item.content)?;
item.content = new_content;
st.update(item);
st.dump()?;
Ok(())
st.dump()
}
// Delete item by id, is must exist
fn delete(&mut self, id: &String) -> io::Result<()> {
self.login()?;
let st = self.storage.as_mut().unwrap();
if !st.contains(id) {
return Err(io::Error::new(
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(
io::ErrorKind::InvalidInput,
format!("No such item {}", id)
));
}
}
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,
"No such item"
format!("Bag arguments")
));
}
st.remove(id);
st.dump()?;
Ok(())
Ok(item_id.clone())
}
}
fn run_command() -> io::Result<()> {
let cli = Cli::parse();
match &cli.command {
@ -243,21 +284,28 @@ fn run_command() -> io::Result<()> {
let mut mps = MPS::new();
mps.list()?;
}
Some(Commands::Show { id }) => {
Some(Commands::Show(args)) => {
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 }) => {
let mut mps = MPS::new();
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();
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();
mps.delete(id)?
mps.password()?;
}
None => {
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> {
let mps_home = env::var(ENV_MPS_HOME).map_err(|err| {
io::Error::new(io::ErrorKind::NotFound, format!("{} error: {}", ENV_MPS_HOME, err))
})?;
let result = format!("{}/{}", mps_home, PATH_STORAGE);
let result = match env::var(ENV_MPS_HOME) {
Ok(mps_home) => format!("{}/{}", mps_home, PATH_STORAGE),
Err(_) => PATH_STORAGE.to_string()
};
Ok(result)
}

52
src/storage.rs

@ -131,9 +131,11 @@ impl Storage {
}
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);
st.dump()?;
st.dump_db()?;
println!("Storage db created");
Ok(())
}
@ -159,7 +161,7 @@ impl Storage {
}
pub fn is_inited() -> io::Result<bool> {
Storage::check_installed()?;
//Storage::check_installed()?;
let db = paths::get_db_path()?;
let db_path = Path::new(&db);
Ok(db_path.exists())
@ -178,11 +180,31 @@ impl Storage {
let item = Item::from_empty(id.clone());
self.items.contains(&item)
}
// TODO: return Result<Item>
pub fn get(&self, id: &String) -> &Item {
let item = Item::from_empty(id.clone());
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) {
self.items.insert(item);
@ -194,17 +216,17 @@ impl Storage {
}
pub fn remove(&mut self, id: &String) {
for it in self.items.iter() {
println!("x: {}", it.id);
}
let item = Item::from_empty(id.clone());
self.items.remove(&item);
for it in self.items.iter() {
println!("y: {}", it.id);
}
}
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()
.write(true)
.truncate(true) // Clear the file content before writing
@ -213,13 +235,19 @@ impl Storage {
writeln!(file, "{}", self.encoder.get_encoded_test_passphrase()?)?;
for item in self.items.iter() {
println!("--- {}", item.id);
writeln!(file, "{}", self.encoder.encrypt(&item.id)?)?;
let content = self.encoder.encrypt(&item.content)?;
writeln!(file, "{}", content)?;
}
git::Git::sync()?;
Ok(())
}
pub fn new_from_passphrase(&self, passphrase: &String) -> Storage {
return Storage {
encoder: Encoder::from(passphrase),
items: self.items.clone()
}
}
}

Loading…
Cancel
Save