Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We鈥檒l occasionally send you account related emails.

Already on GitHub? Sign in to your account

remove decryption from api-client #1063

Merged
merged 1 commit into from
Jun 21, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
33 changes: 3 additions & 30 deletions atuin-client/src/api_client.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
use std::collections::HashMap;
use std::collections::HashSet;
use std::env;

use chrono::Utc;
Expand All @@ -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
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Finally 鈽猴笍

// It should be handled *elsewhere*

pub struct Client<'a> {
sync_addr: &'a str,
key: Key,
client: reqwest::Client,
}

Expand Down Expand Up @@ -111,13 +101,12 @@ pub async fn latest_version() -> Result<Version> {
}

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();
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)
Expand Down Expand Up @@ -160,8 +149,7 @@ impl<'a> Client<'a> {
sync_ts: chrono::DateTime<Utc>,
history_ts: chrono::DateTime<Utc>,
host: Option<String>,
deleted: HashSet<String>,
) -> Result<Vec<History>> {
) -> Result<SyncHistoryResponse> {
let host = host.unwrap_or_else(|| {
hash_str(&format!(
"{}:{}",
Expand All @@ -181,21 +169,6 @@ impl<'a> Client<'a> {
let resp = self.client.get(url).send().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)
}
Expand Down
17 changes: 0 additions & 17 deletions atuin-client/src/encryption.rs
Original file line number Diff line number Diff line change
Expand Up @@ -56,23 +56,6 @@ pub fn load_key(settings: &Settings) -> Result<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> {
let mut buf = vec![];
rmp::encode::write_bin(&mut buf, key.as_slice())
Expand Down
55 changes: 32 additions & 23 deletions atuin-client/src/sync.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
};

Expand All @@ -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),
Expand All @@ -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;
Expand All @@ -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;
Expand Down Expand Up @@ -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),
Expand All @@ -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 {
Expand All @@ -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 {
Expand Down Expand Up @@ -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)?;

let key = load_key(settings)?; // encryption key

sync_upload(settings, force, &client, db).await?;
sync_upload(&key, force, &client, db).await?;

let download = sync_download(force, &client, db).await?;
let download = sync_download(&key, force, &client, db).await?;

debug!("sync downloaded {}", download.0);

Expand Down
8 changes: 2 additions & 6 deletions atuin/src/command/client/account/delete.rs
Original file line number Diff line number Diff line change
@@ -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;

Expand All @@ -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?;

Expand Down
10 changes: 2 additions & 8 deletions atuin/src/command/client/sync/status.rs
Original file line number Diff line number Diff line change
@@ -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()?;
Expand Down