remove decryption from api-client (#1063)

This commit is contained in:
Conrad Ludgate 2023-06-21 08:45:23 +01:00 committed by GitHub
parent 9558fec211
commit a75e516986
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
5 changed files with 39 additions and 84 deletions

View file

@ -1,5 +1,4 @@
use std::collections::HashMap; use std::collections::HashMap;
use std::collections::HashSet;
use std::env; use std::env;
use chrono::Utc; use chrono::Utc;
@ -14,22 +13,13 @@ use atuin_common::api::{
LoginRequest, LoginResponse, RegisterResponse, StatusResponse, SyncHistoryResponse, LoginRequest, LoginResponse, RegisterResponse, StatusResponse, SyncHistoryResponse,
}; };
use semver::Version; use semver::Version;
use xsalsa20poly1305::Key;
use crate::{ use crate::{history::History, sync::hash_str};
encryption::{decode_key, decrypt},
history::History,
sync::hash_str,
};
static APP_USER_AGENT: &str = concat!("atuin/", env!("CARGO_PKG_VERSION"),); 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> { pub struct Client<'a> {
sync_addr: &'a str, sync_addr: &'a str,
key: Key,
client: reqwest::Client, client: reqwest::Client,
} }
@ -111,13 +101,12 @@ pub async fn latest_version() -> Result<Version> {
} }
impl<'a> Client<'a> { impl<'a> Client<'a> {
pub fn new(sync_addr: &'a str, session_token: &'a str, key: String) -> Result<Self> { pub fn new(sync_addr: &'a str, session_token: &'a str) -> Result<Self> {
let mut headers = HeaderMap::new(); let mut headers = HeaderMap::new();
headers.insert(AUTHORIZATION, format!("Token {session_token}").parse()?); headers.insert(AUTHORIZATION, format!("Token {session_token}").parse()?);
Ok(Client { Ok(Client {
sync_addr, sync_addr,
key: decode_key(key)?,
client: reqwest::Client::builder() client: reqwest::Client::builder()
.user_agent(APP_USER_AGENT) .user_agent(APP_USER_AGENT)
.default_headers(headers) .default_headers(headers)
@ -160,8 +149,7 @@ impl<'a> Client<'a> {
sync_ts: chrono::DateTime<Utc>, sync_ts: chrono::DateTime<Utc>,
history_ts: chrono::DateTime<Utc>, history_ts: chrono::DateTime<Utc>,
host: Option<String>, host: Option<String>,
deleted: HashSet<String>, ) -> Result<SyncHistoryResponse> {
) -> Result<Vec<History>> {
let host = host.unwrap_or_else(|| { let host = host.unwrap_or_else(|| {
hash_str(&format!( hash_str(&format!(
"{}:{}", "{}:{}",
@ -181,21 +169,6 @@ impl<'a> Client<'a> {
let resp = self.client.get(url).send().await?; let resp = self.client.get(url).send().await?;
let history = resp.json::<SyncHistoryResponse>().await?; let history = resp.json::<SyncHistoryResponse>().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) Ok(history)
} }

View file

@ -56,23 +56,6 @@ pub fn load_key(settings: &Settings) -> Result<Key> {
Ok(key) Ok(key)
} }
pub fn load_encoded_key(settings: &Settings) -> Result<String> {
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<String> { pub fn encode_key(key: &Key) -> Result<String> {
let mut buf = vec![]; let mut buf = vec![];
rmp::encode::write_bin(&mut buf, key.as_slice()) rmp::encode::write_bin(&mut buf, key.as_slice())

View file

@ -6,11 +6,12 @@ use chrono::prelude::*;
use eyre::Result; use eyre::Result;
use atuin_common::api::AddHistoryRequest; use atuin_common::api::AddHistoryRequest;
use xsalsa20poly1305::Key;
use crate::{ use crate::{
api_client, api_client,
database::Database, database::Database,
encryption::{encrypt, load_encoded_key, load_key}, encryption::{decrypt, encrypt, load_key},
settings::Settings, 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. // Check if remote has things we don't, and if so, download them.
// Returns (num downloaded, total local) // Returns (num downloaded, total local)
async fn sync_download( async fn sync_download(
key: &Key,
force: bool, force: bool,
client: &api_client::Client<'_>, client: &api_client::Client<'_>,
db: &mut (impl Database + Send), db: &mut (impl Database + Send),
@ -43,7 +45,8 @@ async fn sync_download(
let remote_count = remote_status.count; let remote_count = remote_status.count;
// useful to ensure we don't even save something that hasn't yet been synced + deleted // 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 initial_local = db.history_count().await?;
let mut local_count = initial_local; let mut local_count = initial_local;
@ -60,23 +63,34 @@ async fn sync_download(
while remote_count > local_count { while remote_count > local_count {
let page = client let page = client
.get_history( .get_history(last_sync, last_timestamp, host.clone())
last_sync,
last_timestamp,
host.clone(),
remote_deleted.clone(),
)
.await?; .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?; 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; break;
} }
let page_last = page let page_last = history
.last() .last()
.expect("could not get last element of page") .expect("could not get last element of page")
.timestamp; .timestamp;
@ -110,7 +124,7 @@ async fn sync_download(
// Check if we have things remote doesn't, and if so, upload them // Check if we have things remote doesn't, and if so, upload them
async fn sync_upload( async fn sync_upload(
settings: &Settings, key: &Key,
_force: bool, _force: bool,
client: &api_client::Client<'_>, client: &api_client::Client<'_>,
db: &mut (impl Database + Send), db: &mut (impl Database + Send),
@ -127,10 +141,7 @@ async fn sync_upload(
debug!("remote has {}, we have {}", remote_count, local_count); debug!("remote has {}, we have {}", remote_count, local_count);
let key = load_key(settings)?; // encryption key
// first just try the most recent set // first just try the most recent set
let mut cursor = Utc::now(); let mut cursor = Utc::now();
while local_count > remote_count { while local_count > remote_count {
@ -142,7 +153,7 @@ async fn sync_upload(
} }
for i in last { for i in last {
let data = encrypt(&i, &key)?; let data = encrypt(&i, key)?;
let data = serde_json::to_string(&data)?; let data = serde_json::to_string(&data)?;
let add_hist = AddHistoryRequest { 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<()> { pub async fn sync(settings: &Settings, force: bool, db: &mut (impl Database + Send)) -> Result<()> {
let client = api_client::Client::new( let client = api_client::Client::new(&settings.sync_address, &settings.session_token)?;
&settings.sync_address,
&settings.session_token,
load_encoded_key(settings)?,
)?;
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); debug!("sync downloaded {}", download.0);

View file

@ -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 eyre::{bail, Result};
use std::path::PathBuf; use std::path::PathBuf;
@ -9,11 +9,7 @@ pub async fn run(settings: &Settings) -> Result<()> {
bail!("You are not logged in"); bail!("You are not logged in");
} }
let client = api_client::Client::new( let client = api_client::Client::new(&settings.sync_address, &settings.session_token)?;
&settings.sync_address,
&settings.session_token,
load_encoded_key(settings)?,
)?;
client.delete().await?; client.delete().await?;

View file

@ -1,15 +1,9 @@
use atuin_client::{ use atuin_client::{api_client, database::Database, settings::Settings};
api_client, database::Database, encryption::load_encoded_key, settings::Settings,
};
use colored::Colorize; use colored::Colorize;
use eyre::Result; use eyre::Result;
pub async fn run(settings: &Settings, db: &impl Database) -> Result<()> { pub async fn run(settings: &Settings, db: &impl Database) -> Result<()> {
let client = api_client::Client::new( let client = api_client::Client::new(&settings.sync_address, &settings.session_token)?;
&settings.sync_address,
&settings.session_token,
load_encoded_key(settings)?,
)?;
let status = client.status().await?; let status = client.status().await?;
let last_sync = Settings::last_sync()?; let last_sync = Settings::last_sync()?;