aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorJesse Luehrs <doy@tozt.net>2020-04-18 04:24:26 -0400
committerJesse Luehrs <doy@tozt.net>2020-04-18 04:30:04 -0400
commit3765d4f2edc5933f61fd46ab130e366774556cf5 (patch)
tree930ce485993d46dd1e13f00fae00930521c0ef39
parentb3c69bf88d973af04433d450a659ef1581d813e2 (diff)
downloadrbw-3765d4f2edc5933f61fd46ab130e366774556cf5.tar.gz
rbw-3765d4f2edc5933f61fd46ab130e366774556cf5.zip
implement remove
-rw-r--r--src/actions.rs24
-rw-r--r--src/api.rs19
-rw-r--r--src/bin/rbw/commands.rs32
-rw-r--r--src/bin/rbw/main.rs15
-rw-r--r--src/db.rs1
5 files changed, 86 insertions, 5 deletions
diff --git a/src/actions.rs b/src/actions.rs
index ad99b5f..60cc61d 100644
--- a/src/actions.rs
+++ b/src/actions.rs
@@ -102,6 +102,30 @@ fn add_once(
Ok(())
}
+pub fn remove(
+ access_token: &str,
+ refresh_token: &str,
+ id: &str,
+) -> Result<Option<String>> {
+ match remove_once(access_token, id) {
+ Ok(()) => Ok(None),
+ Err(crate::error::Error::RequestUnauthorized) => {
+ let access_token = exchange_refresh_token(refresh_token)?;
+ remove_once(&access_token, id)?;
+ Ok(Some(access_token))
+ }
+ Err(e) => Err(e),
+ }
+}
+
+fn remove_once(access_token: &str, id: &str) -> Result<()> {
+ let config = crate::config::Config::load()?;
+ let client =
+ crate::api::Client::new(&config.base_url(), &config.identity_url());
+ client.remove(access_token, id)?;
+ Ok(())
+}
+
fn exchange_refresh_token(refresh_token: &str) -> Result<String> {
let config = crate::config::Config::load()?;
let client =
diff --git a/src/api.rs b/src/api.rs
index 9588b4c..f40ff6d 100644
--- a/src/api.rs
+++ b/src/api.rs
@@ -159,6 +159,7 @@ struct SyncResCipher {
impl SyncResCipher {
fn to_entry(&self) -> crate::db::Entry {
crate::db::Entry {
+ id: self.id.clone(),
name: self.name.clone(),
username: self.login.username.clone(),
password: self.login.password.clone(),
@@ -310,6 +311,24 @@ impl Client {
}
}
+ pub fn remove(&self, access_token: &str, id: &str) -> Result<()> {
+ let client = reqwest::blocking::Client::new();
+ let res = client
+ .delete(&self.api_url(&format!("/ciphers/{}", id)))
+ .header("Authorization", format!("Bearer {}", access_token))
+ .send()
+ .context(crate::error::Reqwest)?;
+ match res.status() {
+ reqwest::StatusCode::OK => Ok(()),
+ reqwest::StatusCode::UNAUTHORIZED => {
+ Err(Error::RequestUnauthorized)
+ }
+ _ => Err(Error::RequestFailed {
+ status: res.status().as_u16(),
+ }),
+ }
+ }
+
pub fn exchange_refresh_token(
&self,
refresh_token: &str,
diff --git a/src/bin/rbw/commands.rs b/src/bin/rbw/commands.rs
index 30c5d1e..f3977d8 100644
--- a/src/bin/rbw/commands.rs
+++ b/src/bin/rbw/commands.rs
@@ -2,6 +2,7 @@ use anyhow::Context as _;
#[derive(Debug, Clone)]
struct DecryptedCipher {
+ id: String,
name: String,
username: Option<String>,
password: Option<String>,
@@ -211,10 +212,36 @@ pub fn edit() -> anyhow::Result<()> {
todo!()
}
-pub fn remove() -> anyhow::Result<()> {
+pub fn remove(name: &str, username: Option<&str>) -> anyhow::Result<()> {
unlock()?;
- todo!()
+ let email = config_email()?;
+ let mut db = rbw::db::Db::load(&email)
+ .context("failed to load password database")?;
+ let access_token = db.access_token.as_ref().unwrap();
+ let refresh_token = db.refresh_token.as_ref().unwrap();
+
+ let desc = format!(
+ "{}{}",
+ username
+ .map(|s| format!("{}@", s))
+ .unwrap_or_else(|| "".to_string()),
+ name
+ );
+
+ let entry = find_entry(&db, name, username)
+ .with_context(|| format!("couldn't find entry for '{}'", desc))?;
+
+ if let Some(access_token) =
+ rbw::actions::remove(&access_token, &refresh_token, &entry.id)?
+ {
+ db.access_token = Some(access_token);
+ db.save(&email).context("failed to save database")?;
+ }
+
+ crate::actions::sync()?;
+
+ Ok(())
}
pub fn lock() -> anyhow::Result<()> {
@@ -308,6 +335,7 @@ fn find_entry(
fn decrypt_cipher(entry: rbw::db::Entry) -> anyhow::Result<DecryptedCipher> {
Ok(DecryptedCipher {
+ id: entry.id.clone(),
name: crate::actions::decrypt(&entry.name)?,
username: entry
.username
diff --git a/src/bin/rbw/main.rs b/src/bin/rbw/main.rs
index 136322a..45f1985 100644
--- a/src/bin/rbw/main.rs
+++ b/src/bin/rbw/main.rs
@@ -30,7 +30,7 @@ fn main() {
.subcommand(
clap::SubCommand::with_name("add")
.arg(clap::Arg::with_name("name").required(true))
- .arg(clap::Arg::with_name("user").required(true)),
+ .arg(clap::Arg::with_name("user")),
)
.subcommand(
clap::SubCommand::with_name("generate")
@@ -54,7 +54,11 @@ fn main() {
])),
)
.subcommand(clap::SubCommand::with_name("edit"))
- .subcommand(clap::SubCommand::with_name("remove"))
+ .subcommand(
+ clap::SubCommand::with_name("remove")
+ .arg(clap::Arg::with_name("name").required(true))
+ .arg(clap::Arg::with_name("user")),
+ )
.subcommand(clap::SubCommand::with_name("lock"))
.subcommand(clap::SubCommand::with_name("purge"))
.subcommand(clap::SubCommand::with_name("stop-agent"))
@@ -119,7 +123,12 @@ fn main() {
}
}
("edit", Some(_)) => commands::edit().context("edit"),
- ("remove", Some(_)) => commands::remove().context("remove"),
+ // this unwrap is safe because name is marked .required(true)
+ ("remove", Some(smatches)) => commands::remove(
+ smatches.value_of("name").unwrap(),
+ smatches.value_of("user"),
+ )
+ .context("remove"),
("lock", Some(_)) => commands::lock().context("lock"),
("purge", Some(_)) => commands::purge().context("purge"),
("stop-agent", Some(_)) => {
diff --git a/src/db.rs b/src/db.rs
index 6e73852..4ac6e9c 100644
--- a/src/db.rs
+++ b/src/db.rs
@@ -5,6 +5,7 @@ use tokio::io::{AsyncReadExt as _, AsyncWriteExt as _};
#[derive(serde::Serialize, serde::Deserialize, Debug, Clone)]
pub struct Entry {
+ pub id: String,
pub name: String,
pub username: Option<String>,
pub password: Option<String>,