189 lines
4.9 KiB
Rust
189 lines
4.9 KiB
Rust
use crate::sh;
|
|
use regex::Regex;
|
|
use serde::Deserialize;
|
|
use std::io::{Error, ErrorKind};
|
|
|
|
const EXECUTABLE: &str = "pass-cli";
|
|
const EMAIL_REGEX: &str = r"^[\w\-\.]+@([\w-]+\.)+[\w-]{2,}$";
|
|
|
|
#[derive(Deserialize, Debug)]
|
|
pub struct Vaults {
|
|
pub vaults: Vec<Vault>,
|
|
}
|
|
|
|
#[derive(Deserialize, Debug)]
|
|
pub struct Vault {
|
|
pub name: String,
|
|
}
|
|
|
|
#[derive(Deserialize, Debug)]
|
|
pub struct Items {
|
|
pub items: Vec<Item>,
|
|
}
|
|
|
|
#[derive(Deserialize, Debug)]
|
|
pub struct Item {
|
|
pub id: String,
|
|
pub content: ItemContent,
|
|
pub state: String,
|
|
}
|
|
|
|
#[derive(Deserialize, Debug, Clone)]
|
|
pub struct ItemContent {
|
|
pub title: String,
|
|
pub note: String,
|
|
pub content: ItemContentContent,
|
|
}
|
|
|
|
#[derive(Deserialize, Debug, Clone)]
|
|
pub struct ItemContentContent {
|
|
#[serde(rename = "Login")]
|
|
pub login: Option<ItemLogin>,
|
|
}
|
|
|
|
// pass-cli provided structure, this is what it's called inside the JSON data
|
|
#[derive(Deserialize, Debug, Clone)]
|
|
pub struct ItemLogin {
|
|
pub email: String,
|
|
pub username: String,
|
|
pub password: String,
|
|
pub urls: Vec<String>,
|
|
pub totp_uri: Option<String>,
|
|
}
|
|
|
|
// our own custom structure, a counterpart to rbw::LoginItem
|
|
#[derive(Debug, Clone)]
|
|
pub struct LoginItem {
|
|
pub id: String,
|
|
pub title: String,
|
|
pub username: String,
|
|
pub email: String,
|
|
pub password: String,
|
|
pub urls: Option<Vec<String>>,
|
|
pub notes: String,
|
|
pub totp: Option<String>,
|
|
}
|
|
|
|
pub fn check_pass() -> Result<(), Error> {
|
|
let which = sh::sh(format!("which {}", EXECUTABLE));
|
|
if which.is_empty() {
|
|
return Err(Error::new(
|
|
ErrorKind::Other,
|
|
format!("{} is not installed", EXECUTABLE),
|
|
));
|
|
}
|
|
|
|
let test = sh::sh(format!("{} test", EXECUTABLE));
|
|
if test != "Connection successful\n" {
|
|
return Err(Error::new(
|
|
ErrorKind::Other,
|
|
format!("{} test failed", EXECUTABLE),
|
|
));
|
|
}
|
|
|
|
Ok(())
|
|
}
|
|
|
|
pub fn get_vaults() -> Result<Vaults, Error> {
|
|
let vaults_raw = sh::sh(format!("{} vault list --output json", EXECUTABLE));
|
|
match serde_json::from_str(vaults_raw.as_str()) {
|
|
Ok(val) => Ok(val),
|
|
Err(e) => Err(Error::new(
|
|
ErrorKind::Other,
|
|
format!("Couldn't get vaults: {e}"),
|
|
)),
|
|
}
|
|
}
|
|
|
|
pub fn get_items(vault: &String) -> Result<Items, Error> {
|
|
let items_raw = sh::sh(format!(
|
|
"{} item list '{}' --output json --show-secrets",
|
|
EXECUTABLE, vault
|
|
));
|
|
match serde_json::from_str(items_raw.as_str()) {
|
|
Ok(val) => Ok(val),
|
|
Err(e) => Err(Error::new(
|
|
ErrorKind::Other,
|
|
format!("Couldn't get items: {e}"),
|
|
)),
|
|
}
|
|
}
|
|
|
|
pub fn get_logins(items: Items) -> Vec<LoginItem> {
|
|
items
|
|
.items
|
|
.iter()
|
|
.filter(|item| {
|
|
if item.state.to_lowercase() != "active" {
|
|
false
|
|
} else if let None = item.content.content.login {
|
|
false
|
|
} else {
|
|
true
|
|
}
|
|
})
|
|
.map(|item| {
|
|
let login = item.content.content.login.clone().unwrap();
|
|
LoginItem {
|
|
id: item.id.clone(),
|
|
title: item.content.title.clone(),
|
|
username: login.username,
|
|
email: login.email,
|
|
password: login.password,
|
|
notes: item.content.note.clone(),
|
|
urls: Some(login.urls),
|
|
totp: login.totp_uri,
|
|
}
|
|
})
|
|
.collect()
|
|
}
|
|
|
|
pub fn update(vault: &String, item: LoginItem, user_is_actually_email: bool) {
|
|
let user_field_update = if user_is_actually_email {
|
|
format!("email={}", item.email)
|
|
} else {
|
|
format!("usename={}", item.username)
|
|
};
|
|
|
|
let maybe_urls = if let Some(urls) = item.urls {
|
|
let mut it = "".to_string();
|
|
for url in urls {
|
|
it = format!("{} --field 'urls={}'", it, url);
|
|
}
|
|
it
|
|
} else {
|
|
"".to_string()
|
|
};
|
|
|
|
let cmd = format!(
|
|
"{} item update --vault-name '{}' --item-id '{}' --field 'password={}' --field '{}' --field 'note={}' {}",
|
|
EXECUTABLE, vault, item.id, item.password, user_field_update, item.notes, maybe_urls
|
|
);
|
|
let output = sh::sh(cmd.clone());
|
|
println!("> {}", output);
|
|
}
|
|
|
|
pub fn create(vault: &String, title: String, user: String, password: String) {
|
|
let re = Regex::new(EMAIL_REGEX).expect("Couldn't parse regex");
|
|
let is_email = re.is_match(&user);
|
|
let user_arg = if is_email {
|
|
format!("--email '{}'", user)
|
|
} else {
|
|
format!("--username '{}'", user)
|
|
};
|
|
|
|
let output = sh::sh(format!(
|
|
"{} item create login --vault-name '{}' --title '{}' {} --password '{}'",
|
|
EXECUTABLE, vault, title, user_arg, password
|
|
));
|
|
println!("> {}", output);
|
|
}
|
|
|
|
pub fn trash(vault: &String, id: String) {
|
|
let output = sh::sh(format!(
|
|
"{} item trash --vault-name '{}' --item-id '{}'",
|
|
EXECUTABLE, vault, id
|
|
));
|
|
println!("> {}", output);
|
|
}
|