From 5eab3c4b33f2b0b594993a095eae86f88828827d Mon Sep 17 00:00:00 2001 From: Jesse Luehrs Date: Sat, 25 Mar 2023 18:44:21 -0400 Subject: sync the db every hour, like other bitwarden clients --- CHANGELOG.md | 3 +++ README.md | 3 +++ src/bin/rbw-agent/actions.rs | 12 ++++-------- src/bin/rbw-agent/agent.rs | 32 +++++++++++++++++++++++++++++++- src/bin/rbw/commands.rs | 6 ++++++ src/config.rs | 8 ++++++++ 6 files changed, 55 insertions(+), 9 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index f14a4cd..b1cecb0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,9 @@ * `rbw get` now supports a `--raw` option to display the entire contents of the entry in JSON format (#97, classabbyamp) +* `rbw` now automatically syncs the database from the server at a specified + interval while it is running. This defaults to once an hour, but is + configurable via the `sync_interval` option ## [1.5.0] - 2023-02-18 diff --git a/README.md b/README.md index 5efe1f8..7ea61ee 100644 --- a/README.md +++ b/README.md @@ -59,6 +59,9 @@ configuration options: * `lock_timeout`: The number of seconds to keep the master keys in memory for before requiring the password to be entered again. Defaults to `3600` (one hour). +* `sync_interval`: `rbw` will automatically sync the database from the server + at an interval of this many seconds, while the agent is running. Setting + this value to `0` disables this behavior. Defaults to `3600` (one hour). * `pinentry`: The [pinentry](https://www.gnupg.org/related_software/pinentry/index.html) executable to use. Defaults to `pinentry`. diff --git a/src/bin/rbw-agent/actions.rs b/src/bin/rbw-agent/actions.rs index e857a1e..7b5dc58 100644 --- a/src/bin/rbw-agent/actions.rs +++ b/src/bin/rbw-agent/actions.rs @@ -130,7 +130,6 @@ pub async fn login( protected_key, )) => { login_success( - sock, state, access_token, refresh_token, @@ -166,11 +165,10 @@ pub async fn login( tty, &email, password.clone(), - provider + provider, ) .await?; login_success( - sock, state, access_token, refresh_token, @@ -307,7 +305,6 @@ async fn two_factor( } async fn login_success( - sock: &mut crate::sock::Sock, state: std::sync::Arc>, access_token: String, refresh_token: String, @@ -329,7 +326,7 @@ async fn login_success( db.protected_key = Some(protected_key.to_string()); save_db(&db).await?; - sync(sock, false).await?; + sync(None).await?; let db = load_db().await?; let Some(protected_private_key) = db.protected_private_key @@ -497,8 +494,7 @@ pub async fn check_lock( } pub async fn sync( - sock: &mut crate::sock::Sock, - ack: bool, + sock: Option<&mut crate::sock::Sock>, ) -> anyhow::Result<()> { let mut db = load_db().await?; @@ -527,7 +523,7 @@ pub async fn sync( db.entries = entries; save_db(&db).await?; - if ack { + if let Some(sock) = sock { respond_ack(sock).await?; } diff --git a/src/bin/rbw-agent/agent.rs b/src/bin/rbw-agent/agent.rs index 8fa6768..7dcab16 100644 --- a/src/bin/rbw-agent/agent.rs +++ b/src/bin/rbw-agent/agent.rs @@ -7,6 +7,8 @@ pub struct State { Option>, pub timeout: crate::timeout::Timeout, pub timeout_duration: std::time::Duration, + pub sync_timeout: crate::timeout::Timeout, + pub sync_timeout_duration: std::time::Duration, } impl State { @@ -29,10 +31,15 @@ impl State { self.org_keys = None; self.timeout.clear(); } + + pub fn set_sync_timeout(&mut self) { + self.sync_timeout.set(self.sync_timeout_duration); + } } pub struct Agent { timer_r: tokio::sync::mpsc::UnboundedReceiver<()>, + sync_timer_r: tokio::sync::mpsc::UnboundedReceiver<()>, state: std::sync::Arc>, } @@ -41,14 +48,23 @@ impl Agent { let config = rbw::config::Config::load()?; let timeout_duration = std::time::Duration::from_secs(config.lock_timeout); + let sync_timeout_duration = + std::time::Duration::from_secs(config.sync_interval); let (timeout, timer_r) = crate::timeout::Timeout::new(); + let (sync_timeout, sync_timer_r) = crate::timeout::Timeout::new(); + if sync_timeout_duration > std::time::Duration::ZERO { + sync_timeout.set(sync_timeout_duration); + } Ok(Self { timer_r, + sync_timer_r, state: std::sync::Arc::new(tokio::sync::RwLock::new(State { priv_key: None, org_keys: None, timeout, timeout_duration, + sync_timeout, + sync_timeout_duration, })), }) } @@ -60,6 +76,7 @@ impl Agent { enum Event { Request(std::io::Result), Timeout(()), + Sync(()), } let mut stream = futures_util::stream::select_all([ tokio_stream::wrappers::UnixListenerStream::new(listener) @@ -70,6 +87,11 @@ impl Agent { ) .map(Event::Timeout) .boxed(), + tokio_stream::wrappers::UnboundedReceiverStream::new( + self.sync_timer_r, + ) + .map(Event::Sync) + .boxed(), ]); while let Some(event) = stream.next().await { match event { @@ -94,6 +116,14 @@ impl Agent { Event::Timeout(()) => { self.state.write().await.clear(); } + Event::Sync(()) => { + // this could fail if we aren't logged in, but we don't + // care about that + tokio::spawn(async move { + let _ = crate::actions::sync(None).await; + }); + self.state.write().await.set_sync_timeout(); + } } } Ok(()) @@ -141,7 +171,7 @@ async fn handle_request( false } rbw::protocol::Action::Sync => { - crate::actions::sync(sock, true).await?; + crate::actions::sync(Some(sock)).await?; false } rbw::protocol::Action::Decrypt { diff --git a/src/bin/rbw/commands.rs b/src/bin/rbw/commands.rs index 668cb89..5d51f70 100644 --- a/src/bin/rbw/commands.rs +++ b/src/bin/rbw/commands.rs @@ -639,6 +639,12 @@ pub fn config_set(key: &str, value: &str) -> anyhow::Result<()> { config.lock_timeout = timeout; } } + "sync_interval" => { + let interval = value + .parse() + .context("failed to parse value for sync_interval")?; + config.sync_interval = interval; + } "pinentry" => config.pinentry = value.to_string(), _ => return Err(anyhow::anyhow!("invalid config key: {}", key)), } diff --git a/src/config.rs b/src/config.rs index 91aa449..d70a21b 100644 --- a/src/config.rs +++ b/src/config.rs @@ -10,6 +10,8 @@ pub struct Config { pub identity_url: Option, #[serde(default = "default_lock_timeout")] pub lock_timeout: u64, + #[serde(default = "default_sync_interval")] + pub sync_interval: u64, #[serde(default = "default_pinentry")] pub pinentry: String, pub client_cert_path: Option, @@ -25,6 +27,7 @@ impl Default for Config { base_url: None, identity_url: None, lock_timeout: default_lock_timeout(), + sync_interval: default_sync_interval(), pinentry: default_pinentry(), client_cert_path: None, device_id: None, @@ -37,6 +40,11 @@ pub fn default_lock_timeout() -> u64 { 3600 } +#[must_use] +pub fn default_sync_interval() -> u64 { + 3600 +} + #[must_use] pub fn default_pinentry() -> String { "pinentry".to_string() -- cgit v1.2.3-54-g00ecf