aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--Cargo.lock1
-rw-r--r--Cargo.toml1
-rw-r--r--src/actions.rs14
-rw-r--r--src/api.rs40
-rw-r--r--src/bin/rbw/commands.rs31
-rw-r--r--src/db.rs9
-rw-r--r--src/lib.rs1
7 files changed, 95 insertions, 2 deletions
diff --git a/Cargo.lock b/Cargo.lock
index b9545da..e0ce3e2 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -1147,6 +1147,7 @@ dependencies = [
"env_logger",
"hkdf",
"hmac",
+ "humantime",
"log",
"nix",
"pbkdf2",
diff --git a/Cargo.toml b/Cargo.toml
index aeb27b9..4e3ac4d 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -17,6 +17,7 @@ directories = "*"
env_logger = "*"
hkdf = "*"
hmac = "*"
+humantime = "*"
log = "*"
nix = "*"
pbkdf2 = "*"
diff --git a/src/actions.rs b/src/actions.rs
index 4dbe4ca..0a5bee4 100644
--- a/src/actions.rs
+++ b/src/actions.rs
@@ -105,9 +105,10 @@ pub fn edit(
username: Option<&str>,
password: Option<&str>,
notes: Option<&str>,
+ history: &[crate::db::HistoryEntry],
) -> Result<(Option<String>, ())> {
with_exchange_refresh_token(access_token, refresh_token, |access_token| {
- edit_once(access_token, id, name, username, password, notes)
+ edit_once(access_token, id, name, username, password, notes, history)
})
}
@@ -118,11 +119,20 @@ fn edit_once(
username: Option<&str>,
password: Option<&str>,
notes: Option<&str>,
+ history: &[crate::db::HistoryEntry],
) -> Result<()> {
let config = crate::config::Config::load()?;
let client =
crate::api::Client::new(&config.base_url(), &config.identity_url());
- client.edit(access_token, id, name, username, password, notes)?;
+ client.edit(
+ access_token,
+ id,
+ name,
+ username,
+ password,
+ notes,
+ history,
+ )?;
Ok(())
}
diff --git a/src/api.rs b/src/api.rs
index d5511c5..efceda8 100644
--- a/src/api.rs
+++ b/src/api.rs
@@ -106,6 +106,8 @@ struct CiphersPutReq {
name: String,
notes: Option<String>,
login: CiphersPutReqLogin,
+ #[serde(rename = "passwordHistory")]
+ password_history: Vec<CiphersPutReqHistory>,
}
#[derive(serde::Serialize, Debug)]
@@ -114,6 +116,14 @@ struct CiphersPutReqLogin {
password: Option<String>,
}
+#[derive(serde::Serialize, Debug)]
+struct CiphersPutReqHistory {
+ #[serde(rename = "LastUsedDate")]
+ last_used_date: String,
+ #[serde(rename = "Password")]
+ password: String,
+}
+
#[derive(serde::Deserialize, Debug)]
struct CiphersRes {
#[serde(rename = "FolderId")]
@@ -181,16 +191,30 @@ struct SyncResCipher {
login: SyncResLogin,
#[serde(rename = "Notes")]
notes: Option<String>,
+ #[serde(rename = "PasswordHistory")]
+ password_history: Option<Vec<SyncResPasswordHistory>>,
}
impl SyncResCipher {
fn to_entry(&self) -> crate::db::Entry {
+ let history = if let Some(history) = &self.password_history {
+ history
+ .iter()
+ .map(|entry| crate::db::HistoryEntry {
+ last_used_date: entry.last_used_date.clone(),
+ password: entry.password.clone(),
+ })
+ .collect()
+ } else {
+ vec![]
+ };
crate::db::Entry {
id: self.id.clone(),
name: self.name.clone(),
username: self.login.username.clone(),
password: self.login.password.clone(),
notes: self.notes.clone(),
+ history,
}
}
}
@@ -203,6 +227,14 @@ struct SyncResLogin {
password: Option<String>,
}
+#[derive(serde::Serialize, serde::Deserialize, Debug, Clone)]
+struct SyncResPasswordHistory {
+ #[serde(rename = "LastUsedDate")]
+ last_used_date: String,
+ #[serde(rename = "Password")]
+ password: String,
+}
+
#[derive(Debug)]
pub struct Client {
base_url: String,
@@ -359,6 +391,7 @@ impl Client {
username: Option<&str>,
password: Option<&str>,
notes: Option<&str>,
+ history: &[crate::db::HistoryEntry],
) -> Result<()> {
let req = CiphersPutReq {
ty: 1,
@@ -368,6 +401,13 @@ impl Client {
username: username.map(std::string::ToString::to_string),
password: password.map(std::string::ToString::to_string),
},
+ password_history: history
+ .iter()
+ .map(|entry| CiphersPutReqHistory {
+ last_used_date: entry.last_used_date.clone(),
+ password: entry.password.clone(),
+ })
+ .collect(),
};
let client = reqwest::blocking::Client::new();
let res = client
diff --git a/src/bin/rbw/commands.rs b/src/bin/rbw/commands.rs
index 1b6f9ee..7152a59 100644
--- a/src/bin/rbw/commands.rs
+++ b/src/bin/rbw/commands.rs
@@ -8,6 +8,14 @@ struct DecryptedCipher {
username: Option<String>,
password: Option<String>,
notes: Option<String>,
+ history: Vec<DecryptedHistoryEntry>,
+}
+
+#[derive(Debug, Clone)]
+#[cfg_attr(test, derive(Eq, PartialEq))]
+struct DecryptedHistoryEntry {
+ last_used_date: String,
+ password: String,
}
const HELP: &str = r#"
@@ -231,6 +239,15 @@ pub fn edit(name: &str, username: Option<&str>) -> anyhow::Result<()> {
let notes = notes
.map(|notes| crate::actions::encrypt(&notes))
.transpose()?;
+ let mut history = entry.history.clone();
+ let new_history_entry = rbw::db::HistoryEntry {
+ last_used_date: format!(
+ "{}",
+ humantime::format_rfc3339(std::time::SystemTime::now())
+ ),
+ password: entry.password.unwrap_or_else(String::new),
+ };
+ history.insert(0, new_history_entry);
if let (Some(access_token), ()) = rbw::actions::edit(
&access_token,
@@ -240,6 +257,7 @@ pub fn edit(name: &str, username: Option<&str>) -> anyhow::Result<()> {
entry.username.as_deref(),
password.as_deref(),
notes.as_deref(),
+ &history,
)? {
db.access_token = Some(access_token);
db.save(&email).context("failed to save database")?;
@@ -453,12 +471,23 @@ fn decrypt_cipher(entry: &rbw::db::Entry) -> anyhow::Result<DecryptedCipher> {
None
}
};
+ let history = entry
+ .history
+ .iter()
+ .map(|entry| {
+ Ok(DecryptedHistoryEntry {
+ last_used_date: entry.last_used_date.clone(),
+ password: crate::actions::decrypt(&entry.password)?,
+ })
+ })
+ .collect::<anyhow::Result<_>>()?;
Ok(DecryptedCipher {
id: entry.id.clone(),
name: crate::actions::decrypt(&entry.name)?,
username,
password,
notes,
+ history,
})
}
@@ -581,6 +610,7 @@ mod test {
.map(|_| "this is the encrypted username".to_string()),
password: None,
notes: None,
+ history: vec![],
},
DecryptedCipher {
id: "irrelevant".to_string(),
@@ -588,6 +618,7 @@ mod test {
username: username.map(std::string::ToString::to_string),
password: None,
notes: None,
+ history: vec![],
},
)
}
diff --git a/src/db.rs b/src/db.rs
index 54f8179..9a02598 100644
--- a/src/db.rs
+++ b/src/db.rs
@@ -12,6 +12,15 @@ pub struct Entry {
pub username: Option<String>,
pub password: Option<String>,
pub notes: Option<String>,
+ pub history: Vec<HistoryEntry>,
+}
+
+#[derive(
+ serde::Serialize, serde::Deserialize, Debug, Clone, Eq, PartialEq,
+)]
+pub struct HistoryEntry {
+ pub last_used_date: String,
+ pub password: String,
}
#[derive(serde::Serialize, serde::Deserialize, Default, Debug)]
diff --git a/src/lib.rs b/src/lib.rs
index 12c8c6e..ef69e49 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -5,6 +5,7 @@
#![allow(clippy::must_use_candidate)]
#![allow(clippy::similar_names)]
#![allow(clippy::single_match)]
+#![allow(clippy::too_many_arguments)]
pub mod actions;
pub mod api;