remove decryption from api-client (#1063)
This commit is contained in:
parent
9558fec211
commit
a75e516986
5 changed files with 39 additions and 84 deletions
|
@ -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)
|
||||||
}
|
}
|
||||||
|
|
|
@ -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())
|
||||||
|
|
|
@ -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);
|
||||||
|
|
||||||
|
|
|
@ -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?;
|
||||||
|
|
||||||
|
|
|
@ -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()?;
|
||||||
|
|
Loading…
Reference in a new issue