diff --git a/atuin-client/src/api_client.rs b/atuin-client/src/api_client.rs index 2510d19..350c419 100644 --- a/atuin-client/src/api_client.rs +++ b/atuin-client/src/api_client.rs @@ -1,5 +1,4 @@ use std::collections::HashMap; -use std::collections::HashSet; use std::env; use chrono::Utc; @@ -14,22 +13,13 @@ use atuin_common::api::{ LoginRequest, LoginResponse, RegisterResponse, StatusResponse, SyncHistoryResponse, }; use semver::Version; -use xsalsa20poly1305::Key; -use crate::{ - encryption::{decode_key, decrypt}, - history::History, - sync::hash_str, -}; +use crate::{history::History, sync::hash_str}; static APP_USER_AGENT: &str = concat!("atuin/", env!("CARGO_PKG_VERSION"),); -// TODO: remove all references to the encryption key from this -// It should be handled *elsewhere* - pub struct Client<'a> { sync_addr: &'a str, - key: Key, client: reqwest::Client, } @@ -111,13 +101,12 @@ pub async fn latest_version() -> Result { } impl<'a> Client<'a> { - pub fn new(sync_addr: &'a str, session_token: &'a str, key: String) -> Result { + pub fn new(sync_addr: &'a str, session_token: &'a str) -> Result { let mut headers = HeaderMap::new(); headers.insert(AUTHORIZATION, format!("Token {session_token}").parse()?); Ok(Client { sync_addr, - key: decode_key(key)?, client: reqwest::Client::builder() .user_agent(APP_USER_AGENT) .default_headers(headers) @@ -160,8 +149,7 @@ impl<'a> Client<'a> { sync_ts: chrono::DateTime, history_ts: chrono::DateTime, host: Option, - deleted: HashSet, - ) -> Result> { + ) -> Result { let host = host.unwrap_or_else(|| { hash_str(&format!( "{}:{}", @@ -181,21 +169,6 @@ impl<'a> Client<'a> { let resp = self.client.get(url).send().await?; let history = resp.json::().await?; - let history = history - .history - .iter() - // TODO: handle deletion earlier in this chain - .map(|h| serde_json::from_str(h).expect("invalid base64")) - .map(|h| decrypt(h, &self.key).expect("failed to decrypt history! check your key")) - .map(|mut h| { - if deleted.contains(&h.id) { - h.deleted_at = Some(chrono::Utc::now()); - h.command = String::from(""); - } - - h - }) - .collect(); Ok(history) } diff --git a/atuin-client/src/encryption.rs b/atuin-client/src/encryption.rs index a7aec0e..8612515 100644 --- a/atuin-client/src/encryption.rs +++ b/atuin-client/src/encryption.rs @@ -56,23 +56,6 @@ pub fn load_key(settings: &Settings) -> Result { Ok(key) } -pub fn load_encoded_key(settings: &Settings) -> Result { - let path = settings.key_path.as_str(); - - if PathBuf::from(path).exists() { - let key = fs::read_to_string(path)?; - Ok(key) - } else { - let key = XSalsa20Poly1305::generate_key(&mut OsRng); - let encoded = encode_key(&key)?; - - let mut file = fs::File::create(path)?; - file.write_all(encoded.as_bytes())?; - - Ok(encoded) - } -} - pub fn encode_key(key: &Key) -> Result { let mut buf = vec![]; rmp::encode::write_bin(&mut buf, key.as_slice()) diff --git a/atuin-client/src/sync.rs b/atuin-client/src/sync.rs index abe6ce7..f62dca3 100644 --- a/atuin-client/src/sync.rs +++ b/atuin-client/src/sync.rs @@ -6,11 +6,12 @@ use chrono::prelude::*; use eyre::Result; use atuin_common::api::AddHistoryRequest; +use xsalsa20poly1305::Key; use crate::{ api_client, database::Database, - encryption::{encrypt, load_encoded_key, load_key}, + encryption::{decrypt, encrypt, load_key}, settings::Settings, }; @@ -33,6 +34,7 @@ pub fn hash_str(string: &str) -> String { // Check if remote has things we don't, and if so, download them. // Returns (num downloaded, total local) async fn sync_download( + key: &Key, force: bool, client: &api_client::Client<'_>, db: &mut (impl Database + Send), @@ -43,7 +45,8 @@ async fn sync_download( let remote_count = remote_status.count; // useful to ensure we don't even save something that hasn't yet been synced + deleted - let remote_deleted = HashSet::from_iter(remote_status.deleted.clone()); + let remote_deleted = + HashSet::<&str>::from_iter(remote_status.deleted.iter().map(String::as_str)); let initial_local = db.history_count().await?; let mut local_count = initial_local; @@ -60,23 +63,34 @@ async fn sync_download( while remote_count > local_count { let page = client - .get_history( - last_sync, - last_timestamp, - host.clone(), - remote_deleted.clone(), - ) + .get_history(last_sync, last_timestamp, host.clone()) .await?; - db.save_bulk(&page).await?; + let history: Vec<_> = page + .history + .iter() + // TODO: handle deletion earlier in this chain + .map(|h| serde_json::from_str(h).expect("invalid base64")) + .map(|h| decrypt(h, key).expect("failed to decrypt history! check your key")) + .map(|mut h| { + if remote_deleted.contains(h.id.as_str()) { + h.deleted_at = Some(chrono::Utc::now()); + h.command = String::from(""); + } + + h + }) + .collect(); + + db.save_bulk(&history).await?; local_count = db.history_count().await?; - if page.len() < remote_status.page_size.try_into().unwrap() { + if history.len() < remote_status.page_size.try_into().unwrap() { break; } - let page_last = page + let page_last = history .last() .expect("could not get last element of page") .timestamp; @@ -110,7 +124,7 @@ async fn sync_download( // Check if we have things remote doesn't, and if so, upload them async fn sync_upload( - settings: &Settings, + key: &Key, _force: bool, client: &api_client::Client<'_>, db: &mut (impl Database + Send), @@ -127,10 +141,7 @@ async fn sync_upload( debug!("remote has {}, we have {}", remote_count, local_count); - let key = load_key(settings)?; // encryption key - // first just try the most recent set - let mut cursor = Utc::now(); while local_count > remote_count { @@ -142,7 +153,7 @@ async fn sync_upload( } for i in last { - let data = encrypt(&i, &key)?; + let data = encrypt(&i, key)?; let data = serde_json::to_string(&data)?; let add_hist = AddHistoryRequest { @@ -178,15 +189,13 @@ async fn sync_upload( } pub async fn sync(settings: &Settings, force: bool, db: &mut (impl Database + Send)) -> Result<()> { - let client = api_client::Client::new( - &settings.sync_address, - &settings.session_token, - load_encoded_key(settings)?, - )?; + let client = api_client::Client::new(&settings.sync_address, &settings.session_token)?; - sync_upload(settings, force, &client, db).await?; + let key = load_key(settings)?; // encryption key - let download = sync_download(force, &client, db).await?; + sync_upload(&key, force, &client, db).await?; + + let download = sync_download(&key, force, &client, db).await?; debug!("sync downloaded {}", download.0); diff --git a/atuin/src/command/client/account/delete.rs b/atuin/src/command/client/account/delete.rs index 63e5b74..e09f58a 100644 --- a/atuin/src/command/client/account/delete.rs +++ b/atuin/src/command/client/account/delete.rs @@ -1,4 +1,4 @@ -use atuin_client::{api_client, encryption::load_encoded_key, settings::Settings}; +use atuin_client::{api_client, settings::Settings}; use eyre::{bail, Result}; use std::path::PathBuf; @@ -9,11 +9,7 @@ pub async fn run(settings: &Settings) -> Result<()> { bail!("You are not logged in"); } - let client = api_client::Client::new( - &settings.sync_address, - &settings.session_token, - load_encoded_key(settings)?, - )?; + let client = api_client::Client::new(&settings.sync_address, &settings.session_token)?; client.delete().await?; diff --git a/atuin/src/command/client/sync/status.rs b/atuin/src/command/client/sync/status.rs index b3e73e8..e0d45ca 100644 --- a/atuin/src/command/client/sync/status.rs +++ b/atuin/src/command/client/sync/status.rs @@ -1,15 +1,9 @@ -use atuin_client::{ - api_client, database::Database, encryption::load_encoded_key, settings::Settings, -}; +use atuin_client::{api_client, database::Database, settings::Settings}; use colored::Colorize; use eyre::Result; pub async fn run(settings: &Settings, db: &impl Database) -> Result<()> { - let client = api_client::Client::new( - &settings.sync_address, - &settings.session_token, - load_encoded_key(settings)?, - )?; + let client = api_client::Client::new(&settings.sync_address, &settings.session_token)?; let status = client.status().await?; let last_sync = Settings::last_sync()?;