Skip to content

Commit

Permalink
sync the db every hour, like other bitwarden clients
Browse files Browse the repository at this point in the history
  • Loading branch information
doy committed Mar 26, 2023
1 parent b659cc5 commit 5eab3c4
Show file tree
Hide file tree
Showing 6 changed files with 55 additions and 9 deletions.
3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
3 changes: 3 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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`.
Expand Down
12 changes: 4 additions & 8 deletions src/bin/rbw-agent/actions.rs
Original file line number Diff line number Diff line change
Expand Up @@ -130,7 +130,6 @@ pub async fn login(
protected_key,
)) => {
login_success(
sock,
state,
access_token,
refresh_token,
Expand Down Expand Up @@ -166,11 +165,10 @@ pub async fn login(
tty,
&email,
password.clone(),
provider
provider,
)
.await?;
login_success(
sock,
state,
access_token,
refresh_token,
Expand Down Expand Up @@ -307,7 +305,6 @@ async fn two_factor(
}

async fn login_success(
sock: &mut crate::sock::Sock,
state: std::sync::Arc<tokio::sync::RwLock<crate::agent::State>>,
access_token: String,
refresh_token: String,
Expand All @@ -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
Expand Down Expand Up @@ -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?;

Expand Down Expand Up @@ -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?;
}

Expand Down
32 changes: 31 additions & 1 deletion src/bin/rbw-agent/agent.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@ pub struct State {
Option<std::collections::HashMap<String, rbw::locked::Keys>>,
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 {
Expand All @@ -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<tokio::sync::RwLock<State>>,
}

Expand All @@ -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,
})),
})
}
Expand All @@ -60,6 +76,7 @@ impl Agent {
enum Event {
Request(std::io::Result<tokio::net::UnixStream>),
Timeout(()),
Sync(()),
}
let mut stream = futures_util::stream::select_all([
tokio_stream::wrappers::UnixListenerStream::new(listener)
Expand All @@ -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 {
Expand All @@ -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(())
Expand Down Expand Up @@ -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 {
Expand Down
6 changes: 6 additions & 0 deletions src/bin/rbw/commands.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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)),
}
Expand Down
8 changes: 8 additions & 0 deletions src/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@ pub struct Config {
pub identity_url: Option<String>,
#[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<std::path::PathBuf>,
Expand All @@ -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,
Expand All @@ -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()
Expand Down

0 comments on commit 5eab3c4

Please sign in to comment.