aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorJesse Luehrs <doy@tozt.net>2020-04-10 00:12:40 -0400
committerJesse Luehrs <doy@tozt.net>2020-04-10 00:12:40 -0400
commitb3a04c4a143c34ba92008cf018eed159f87a0c6e (patch)
treed94bdb932079318a094cbfa5ba3e9a7e83a6a468
parentc255e08021bf722558988b5a08c8e1427488c618 (diff)
downloadrbw-b3a04c4a143c34ba92008cf018eed159f87a0c6e.tar.gz
rbw-b3a04c4a143c34ba92008cf018eed159f87a0c6e.zip
move some basic stuff into config
-rw-r--r--src/actions.rs6
-rw-r--r--src/api.rs30
-rw-r--r--src/bin/agent.rs13
-rw-r--r--src/bin/rbw.rs63
-rw-r--r--src/config.rs76
-rw-r--r--src/error.rs15
-rw-r--r--src/lib.rs1
7 files changed, 172 insertions, 32 deletions
diff --git a/src/actions.rs b/src/actions.rs
index 3658139..14f140e 100644
--- a/src/actions.rs
+++ b/src/actions.rs
@@ -9,8 +9,9 @@ pub async fn login(
crate::cipherstring::CipherString,
crate::locked::Keys,
)> {
+ let config = crate::config::Config::load_async().await?;
let client =
- crate::api::Client::new_self_hosted("https://bitwarden.tozt.net");
+ crate::api::Client::new(&config.base_url(), &config.identity_url());
let iterations = client.prelogin(email).await?;
let identity =
@@ -48,7 +49,8 @@ pub async fn unlock(
pub async fn sync(
access_token: &str,
) -> Result<(String, Vec<crate::api::Cipher>)> {
+ let config = crate::config::Config::load_async().await?;
let client =
- crate::api::Client::new_self_hosted("https://bitwarden.tozt.net");
+ crate::api::Client::new(&config.base_url(), &config.identity_url());
client.sync(access_token).await
}
diff --git a/src/api.rs b/src/api.rs
index fc59d25..e70c45b 100644
--- a/src/api.rs
+++ b/src/api.rs
@@ -72,31 +72,15 @@ pub struct Login {
#[derive(Debug)]
pub struct Client {
- api_url_base: String,
- identity_url_base: String,
-}
-
-impl Default for Client {
- fn default() -> Self {
- Self {
- api_url_base: "https://api.bitwarden.com".to_string(),
- identity_url_base: "https://identity.bitwarden.com".to_string(),
- }
- }
+ base_url: String,
+ identity_url: String,
}
impl Client {
- #[allow(dead_code)]
- #[must_use]
- pub fn new() -> Self {
- Self::default()
- }
-
- #[must_use]
- pub fn new_self_hosted(base_url: &str) -> Self {
+ pub fn new(base_url: &str, identity_url: &str) -> Self {
Self {
- api_url_base: format!("{}/api", base_url),
- identity_url_base: format!("{}/identity", base_url),
+ base_url: base_url.to_string(),
+ identity_url: identity_url.to_string(),
}
}
@@ -167,10 +151,10 @@ impl Client {
}
fn api_url(&self, path: &str) -> String {
- format!("{}{}", self.api_url_base, path)
+ format!("{}{}", self.base_url, path)
}
fn identity_url(&self, path: &str) -> String {
- format!("{}{}", self.identity_url_base, path)
+ format!("{}{}", self.identity_url, path)
}
}
diff --git a/src/bin/agent.rs b/src/bin/agent.rs
index 9ee5f32..0c65da6 100644
--- a/src/bin/agent.rs
+++ b/src/bin/agent.rs
@@ -38,11 +38,11 @@ async fn login(
tty: Option<&str>,
) {
let mut state = state.write().await;
- let email = "bitwarden@tozt.net"; // XXX read from config
+ let email = config_email().await;
let password =
rbw::pinentry::getpin("prompt", "desc", tty).await.unwrap();
let (access_token, iterations, protected_key, keys) =
- rbw::actions::login(email, &password).await.unwrap();
+ rbw::actions::login(&email, &password).await.unwrap();
state.access_token = Some(access_token);
state.iterations = Some(iterations);
state.protected_key = Some(protected_key);
@@ -67,11 +67,11 @@ async fn unlock(
tty: Option<&str>,
) {
let mut state = state.write().await;
- let email = "bitwarden@tozt.net"; // XXX read from config
+ let email = config_email().await;
let password =
rbw::pinentry::getpin("prompt", "desc", tty).await.unwrap();
let keys = rbw::actions::unlock(
- email,
+ &email,
&password,
state.iterations.unwrap(),
state.protected_key.as_ref().unwrap(),
@@ -141,6 +141,11 @@ async fn handle_sock(
}
}
+async fn config_email() -> String {
+ let config = rbw::config::Config::load_async().await.unwrap();
+ config.email.unwrap()
+}
+
struct Agent {
timeout: tokio::time::Delay,
state: std::sync::Arc<tokio::sync::RwLock<State>>,
diff --git a/src/bin/rbw.rs b/src/bin/rbw.rs
index 0cb9fe7..3aae676 100644
--- a/src/bin/rbw.rs
+++ b/src/bin/rbw.rs
@@ -39,7 +39,27 @@ fn recv(sock: &mut std::os::unix::net::UnixStream) -> rbw::agent::Response {
serde_json::from_str(&line).unwrap()
}
+fn config_show() {
+ let config = rbw::config::Config::load().unwrap();
+ serde_json::to_writer_pretty(std::io::stdout(), &config).unwrap();
+ println!();
+}
+
+fn config_set(key: &str, value: &str) {
+ let mut config = rbw::config::Config::load()
+ .unwrap_or_else(|_| rbw::config::Config::new());
+ match key {
+ "email" => config.email = Some(value.to_string()),
+ "base_url" => config.base_url = Some(value.to_string()),
+ "identity_url" => config.identity_url = Some(value.to_string()),
+ _ => unimplemented!(),
+ }
+ config.save().unwrap();
+}
+
fn login() {
+ ensure_agent();
+
let mut sock = connect();
send(
&mut sock,
@@ -59,6 +79,8 @@ fn login() {
}
fn unlock() {
+ ensure_agent();
+
let mut sock = connect();
send(
&mut sock,
@@ -78,6 +100,8 @@ fn unlock() {
}
fn sync() {
+ ensure_agent();
+
let mut sock = connect();
send(
&mut sock,
@@ -97,26 +121,38 @@ fn sync() {
}
fn list() {
+ ensure_agent();
+
todo!()
}
fn get() {
+ ensure_agent();
+
todo!()
}
fn add() {
+ ensure_agent();
+
todo!()
}
fn generate() {
+ ensure_agent();
+
todo!()
}
fn edit() {
+ ensure_agent();
+
todo!()
}
fn remove() {
+ ensure_agent();
+
todo!()
}
@@ -144,6 +180,15 @@ fn main() {
.about("unofficial bitwarden cli")
.author(clap::crate_authors!())
.version(clap::crate_version!())
+ .subcommand(
+ clap::SubCommand::with_name("config")
+ .subcommand(clap::SubCommand::with_name("show"))
+ .subcommand(
+ clap::SubCommand::with_name("set")
+ .arg(clap::Arg::with_name("key"))
+ .arg(clap::Arg::with_name("value")),
+ ),
+ )
.subcommand(clap::SubCommand::with_name("login"))
.subcommand(clap::SubCommand::with_name("unlock"))
.subcommand(clap::SubCommand::with_name("sync"))
@@ -158,9 +203,18 @@ fn main() {
.subcommand(clap::SubCommand::with_name("stop-agent"))
.get_matches();
- ensure_agent();
-
match matches.subcommand() {
+ ("config", Some(smatches)) => match smatches.subcommand() {
+ ("show", Some(_)) => config_show(),
+ ("set", Some(ssmatches)) => config_set(
+ ssmatches.value_of("key").unwrap(),
+ ssmatches.value_of("value").unwrap(),
+ ),
+ _ => {
+ eprintln!("{}", smatches.usage());
+ std::process::exit(1);
+ }
+ },
("login", Some(_)) => login(),
("unlock", Some(_)) => unlock(),
("sync", Some(_)) => sync(),
@@ -173,6 +227,9 @@ fn main() {
("lock", Some(_)) => lock(),
("purge", Some(_)) => purge(),
("stop-agent", Some(_)) => stop_agent(),
- _ => unimplemented!(),
+ _ => {
+ eprintln!("{}", matches.usage());
+ std::process::exit(1);
+ }
}
}
diff --git a/src/config.rs b/src/config.rs
new file mode 100644
index 0000000..d02bc11
--- /dev/null
+++ b/src/config.rs
@@ -0,0 +1,76 @@
+use crate::prelude::*;
+
+use std::io::{Read as _, Write as _};
+use tokio::io::AsyncReadExt as _;
+
+#[derive(serde::Serialize, serde::Deserialize, Default, Debug)]
+pub struct Config {
+ pub email: Option<String>,
+ pub base_url: Option<String>,
+ pub identity_url: Option<String>,
+}
+
+impl Config {
+ pub fn new() -> Self {
+ Self::default()
+ }
+
+ pub fn load() -> Result<Self> {
+ let mut fh = std::fs::File::open(Self::filename())
+ .context(crate::error::LoadConfig)?;
+ let mut json = String::new();
+ fh.read_to_string(&mut json)
+ .context(crate::error::LoadConfig)?;
+ let slf: Self = serde_json::from_str(&json)
+ .context(crate::error::LoadConfigJson)?;
+ Ok(slf)
+ }
+
+ pub async fn load_async() -> Result<Self> {
+ let mut fh = tokio::fs::File::open(Self::filename())
+ .await
+ .context(crate::error::LoadConfigAsync)?;
+ let mut json = String::new();
+ fh.read_to_string(&mut json)
+ .await
+ .context(crate::error::LoadConfigAsync)?;
+ let slf: Self = serde_json::from_str(&json)
+ .context(crate::error::LoadConfigJson)?;
+ Ok(slf)
+ }
+
+ pub fn save(&self) -> Result<()> {
+ let filename = Self::filename();
+ std::fs::create_dir_all(filename.parent().unwrap())
+ .context(crate::error::SaveConfig)?;
+ let mut fh = std::fs::File::create(filename)
+ .context(crate::error::SaveConfig)?;
+ fh.write_all(
+ serde_json::to_string(self)
+ .context(crate::error::SaveConfigJson)?
+ .as_bytes(),
+ )
+ .context(crate::error::SaveConfig)?;
+ Ok(())
+ }
+
+ pub fn base_url(&self) -> String {
+ self.base_url.clone().map_or_else(
+ || "https://api.bitwarden.com".to_string(),
+ |url| format!("{}/api", url),
+ )
+ }
+
+ pub fn identity_url(&self) -> String {
+ self.identity_url.clone().unwrap_or_else(|| {
+ self.base_url.clone().map_or_else(
+ || "https://identity.bitwarden.com".to_string(),
+ |url| format!("{}/identity", url),
+ )
+ })
+ }
+
+ fn filename() -> std::path::PathBuf {
+ crate::dirs::config_dir().join("config.json")
+ }
+}
diff --git a/src/error.rs b/src/error.rs
index 8642e5e..4554b14 100644
--- a/src/error.rs
+++ b/src/error.rs
@@ -39,6 +39,15 @@ pub enum Error {
#[snafu(display("invalid mac key"))]
InvalidMacKey,
+ #[snafu(display("failed to load config: {}", source))]
+ LoadConfig { source: std::io::Error },
+
+ #[snafu(display("failed to load config: {}", source))]
+ LoadConfigAsync { source: tokio::io::Error },
+
+ #[snafu(display("failed to load config: {}", source))]
+ LoadConfigJson { source: serde_json::Error },
+
#[snafu(display("error reading pinentry output: {}", source))]
PinentryReadOutput { source: tokio::io::Error },
@@ -48,6 +57,12 @@ pub enum Error {
#[snafu(display("error making api request: {}", source))]
Reqwest { source: reqwest::Error },
+ #[snafu(display("failed to save config: {}", source))]
+ SaveConfig { source: std::io::Error },
+
+ #[snafu(display("failed to save config: {}", source))]
+ SaveConfigJson { source: serde_json::Error },
+
#[snafu(display("error spawning pinentry: {}", source))]
Spawn { source: tokio::io::Error },
diff --git a/src/lib.rs b/src/lib.rs
index 340de00..62a06c5 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -9,6 +9,7 @@ pub mod actions;
pub mod agent;
pub mod api;
pub mod cipherstring;
+pub mod config;
pub mod dirs;
mod error;
pub mod identity;