diff --git a/src/bw.rs b/src/bw.rs new file mode 100644 index 0000000..7c55f64 --- /dev/null +++ b/src/bw.rs @@ -0,0 +1,115 @@ +use crate::sh; +use serde::Deserialize; +use std::io::{Error, ErrorKind}; + +const EXECUTABLE: &str = "bw"; + +#[derive(Deserialize, Clone)] +pub struct Item { + pub id: String, + pub name: String, + #[serde(rename = "passwordHistory")] + pub password_history: Vec, + #[serde(rename = "revisionDate")] + pub revision_date: String, + #[serde(rename = "creationDate")] + pub creation_date: String, + pub object: String, + #[serde(rename = "type")] + pub type_: u16, + pub notes: Option, + pub favorite: bool, + pub fields: Vec, + pub login: Login, +} + +#[derive(Deserialize, Clone)] +pub struct PasswordHistoryItem { + #[serde(rename = "lastUsedDate")] + pub last_used_date: String, + pub password: String, +} + +#[derive(Deserialize, Clone)] +pub struct ItemField { + pub name: String, + pub value: String, + #[serde(rename = "type")] + pub type_: u16, +} + +#[derive(Deserialize, Clone)] +pub struct Login { + pub uris: Vec, + pub username: String, + pub password: String, + pub totp: Option, + #[serde(rename = "passwordRevisionDate")] + pub password_revision_date: Option, + #[serde(rename = "fido2Credentials")] + pub fido2_credentials: Vec, +} + +#[derive(Deserialize, Clone)] +pub struct LoginUri { + pub uri: String, +} + +#[derive(Deserialize, Clone)] +pub struct Fido2Credential { + #[serde(rename = "credentialId")] + pub credential_id: String, + #[serde(rename = "keyType")] + pub key_type: String, + #[serde(rename = "keyAlgorithm")] + pub key_algorithm: String, + #[serde(rename = "keyCurve")] + pub key_curve: String, + #[serde(rename = "keyValue")] + pub key_value: String, + #[serde(rename = "rpId")] + pub rp_id: String, + #[serde(rename = "userHandle")] + pub user_handle: String, + #[serde(rename = "userName")] + pub user_name: String, + pub counter: String, + #[serde(rename = "rpName")] + pub rp_name: String, + #[serde(rename = "userDisplayName")] + pub user_display_name: String, + pub discoverable: String, + #[serde(rename = "creationDate")] + pub creation_date: String, +} + +pub fn check_bw() -> Result { + let which = sh::sh(format!("which {}", EXECUTABLE)); + if which.is_empty() { + return Err(Error::new( + ErrorKind::Other, + format!("{} is not installed", EXECUTABLE), + )); + } + + let session = sh::sh(format!("{} unlock", EXECUTABLE)); + if session.is_empty() { + return Err(Error::new( + ErrorKind::Other, + format!("{} couldn't unlock", EXECUTABLE), + )); + } + + Ok(session) +} + +pub fn get_items(session: String) -> Result, Error> { + let items_raw = sh::sh(format!("BW_SESSION={} {} list --raw", session, EXECUTABLE)); + 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}"), + )), + } +} diff --git a/src/main.rs b/src/main.rs index 61566f2..8159081 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,11 +1,11 @@ +mod bw; mod pass; -mod rbw; mod sh; +use clap::Parser; use std::env; use std::io; use std::io::{Error, ErrorKind, Write}; -use clap::Parser; const ENV_VAR_DEFAULT_VAULT: &str = "PASS_VAULT"; @@ -22,12 +22,12 @@ fn ask_consent(question: String, line1: String, line2: String) -> Result Result<(), Error> { pass::check_pass()?; - rbw::check_rbw()?; + let bw_session = bw::check_bw()?; let vaults = pass::get_vaults()?.vaults; if vaults.len() <= 0 { @@ -62,21 +62,20 @@ fn sync() -> Result<(), Error> { let pass_items = pass::get_items(&vault)?; let mut pass_logins = pass::get_logins(pass_items); - let rbw_items = rbw::get_items()?; - let mut rbw_logins = rbw::get_logins(rbw_items)?; + let mut bw_items = bw::get_items(bw_session)?; println!(); println!( - "Got {} rbw logins and {} pass logins...", - rbw_logins.len(), + "Got {} bw logins and {} pass logins...", + bw_items.len(), pass_logins.len() ); - for rbw_login in rbw_logins.clone().iter() { + for bw_item in bw_items.clone().iter() { let mut pass_login: Option<&pass::LoginItem> = None; 'inner: for pass_login_iter in pass_logins.iter() { - if rbw_login.name == pass_login_iter.title { + if bw_item.name == pass_login_iter.title { pass_login = Some(pass_login_iter); break 'inner; } @@ -87,39 +86,39 @@ fn sync() -> Result<(), Error> { } let pass_login = pass_login.unwrap(); - let rbw_password = rbw_login.password.clone(); + let bw_password = bw_item.login.password.clone(); let pass_password = pass_login.password.clone(); - let rbw_user = rbw_login.user.clone(); + let bw_user = bw_item.login.username.clone(); let pass_user = match pass_login.username.is_empty() { true => pass_login.email.clone(), false => pass_login.username.clone(), }; let pass_user_is_actually_email = pass_user == pass_login.email; - if rbw_user != pass_user || rbw_password != pass_password { + if bw_user != pass_user || bw_password != pass_password { let consent = ask_consent( - format!("Attempting to update {}:", rbw_login.name), - format!("{} -> {}", pass_user, rbw_user.clone()), - format!("{} -> {}", pass_password, rbw_password.clone()), + format!("Attempting to update {}:", bw_item.name), + format!("{} -> {}", pass_user, bw_user.clone()), + format!("{} -> {}", pass_password, bw_password.clone()), )?; if consent { let updated_pass_login = pass::LoginItem { id: pass_login.id.clone(), title: pass_login.title.clone(), - username: rbw_user.clone(), - email: rbw_user.clone(), - password: rbw_password, + username: bw_user.clone(), + email: bw_user.clone(), + password: bw_password, }; pass::update(&vault, updated_pass_login, pass_user_is_actually_email); } } - let rbw_index = rbw_logins + let bw_index = bw_items .iter() - .position(|x| x.id == rbw_login.id) + .position(|x| x.id == bw_item.id) .unwrap(); - rbw_logins.remove(rbw_index); + bw_items.remove(bw_index); let pass_index = pass_logins .iter() @@ -128,22 +127,21 @@ fn sync() -> Result<(), Error> { pass_logins.remove(pass_index); } - // TODO: need to delete these println!(); println!( - "Remaining {} rbw logins and {} pass logins...", - rbw_logins.len(), + "Remaining {} bw logins and {} pass logins...", + bw_items.len(), pass_logins.len() ); - for rbw_login in rbw_logins { + for bw_item in bw_items { let consent = ask_consent( - format!("Attempting to create {}:", rbw_login.name), - format!("{}", rbw_login.user), - format!("{}", rbw_login.password), + format!("Attempting to create {}:", bw_item.name), + format!("{}", bw_item.login.username), + format!("{}", bw_item.login.password), )?; if consent { - pass::create(&vault, rbw_login.name, rbw_login.user, rbw_login.password); + pass::create(&vault, bw_item.name, bw_item.login.username, bw_item.login.password); } } @@ -165,11 +163,11 @@ fn sync() -> Result<(), Error> { } } - return Ok(()); + Ok(()) } fn main() -> Result<(), Error> { Cli::parse(); sync()?; - return Ok(()); + Ok(()) } diff --git a/src/pass.rs b/src/pass.rs index 731b705..b88a59c 100644 --- a/src/pass.rs +++ b/src/pass.rs @@ -75,18 +75,18 @@ pub fn check_pass() -> Result<(), Error> { )); } - return Ok(()); + Ok(()) } pub fn get_vaults() -> Result { let vaults_raw = sh::sh(format!("{} vault list --output json", EXECUTABLE)); - return match serde_json::from_str(vaults_raw.as_str()) { + 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 { @@ -94,13 +94,13 @@ pub fn get_items(vault: &String) -> Result { "{} item list '{}' --output json --show-secrets", EXECUTABLE, vault )); - return match serde_json::from_str(items_raw.as_str()) { + 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 { diff --git a/src/rbw.rs b/src/rbw.rs deleted file mode 100644 index 74003d9..0000000 --- a/src/rbw.rs +++ /dev/null @@ -1,83 +0,0 @@ -use crate::sh; -use serde::Deserialize; -use std::io::{Error, ErrorKind}; - -const EXECUTABLE: &str = "rbw"; - -#[derive(Deserialize, Debug)] -pub struct Item { - pub id: String, - pub name: String, - pub user: Option, - #[serde(rename = "type")] - pub type_: String, -} - -#[derive(Deserialize, Debug, Clone)] -pub struct LoginItem { - pub id: String, - pub name: String, - pub user: String, - pub password: String, -} - -pub fn check_rbw() -> 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 unlocked = sh::sh(format!("{} unlocked", EXECUTABLE)); - if !unlocked.is_empty() { - return Err(Error::new( - ErrorKind::Other, - format!("{} database not unlocked", EXECUTABLE), - )); - } - - return Ok(()); -} - -pub fn get_items() -> Result, Error> { - let items_raw = sh::sh(format!("{} list --raw", EXECUTABLE)); - return 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: Vec) -> Result, Error> { - let items = items - .iter() - .filter(|item| item.type_.to_lowercase() == "login") - .collect::>(); - let mut login_items = Vec::::new(); - - for item in items { - let password = sh::sh(format!("{} get '{}'", EXECUTABLE, item.id)); - if let None = item.user { - println!("WARNING: user not found for {}", item.name); - } - - login_items.push(LoginItem { - id: item.id.clone(), - name: item.name.clone(), - user: match item.user.clone() { - Some(val) => val, - None => "".to_string(), - }, - password: match password.strip_suffix("\n") { - Some(s) => s.to_string(), - None => password, - }, - }); - } - - return Ok(login_items); -} diff --git a/src/sh.rs b/src/sh.rs index 42db266..861bdd5 100644 --- a/src/sh.rs +++ b/src/sh.rs @@ -9,5 +9,5 @@ pub fn sh(command: impl Into) -> String { .output() .expect("Failed to execute command"); - return String::from_utf8(output.stdout).expect("Invalid UTF-8 sequence"); + String::from_utf8(output.stdout).expect("Invalid UTF-8 sequence") }