diff options
author | Ellie Huxtable <e@elm.sh> | 2021-04-20 21:53:07 +0100 |
---|---|---|
committer | GitHub <noreply@github.com> | 2021-04-20 20:53:07 +0000 |
commit | a21737e2b7f8d1e426726bdd7536033f299d476a (patch) | |
tree | e940afdff9c145d25d9a2895fd44a77d70719a2e /src | |
parent | 34888827f8a06de835cbe5833a06914f28cce514 (diff) |
Use cargo workspaces (#37)
* Switch to Cargo workspaces
Breaking things into "client", "server" and "common" makes managing the
codebase much easier!
client - anything running on a user's machine for adding history
server - handles storing/syncing history and running a HTTP server
common - request/response API definitions, common utils, etc
* Update dockerfile
Diffstat (limited to 'src')
-rw-r--r-- | src/api.rs | 70 | ||||
-rw-r--r-- | src/command/history.rs | 10 | ||||
-rw-r--r-- | src/command/import.rs | 6 | ||||
-rw-r--r-- | src/command/login.rs | 8 | ||||
-rw-r--r-- | src/command/mod.rs | 40 | ||||
-rw-r--r-- | src/command/register.rs | 8 | ||||
-rw-r--r-- | src/command/search.rs | 5 | ||||
-rw-r--r-- | src/command/server.rs | 15 | ||||
-rw-r--r-- | src/command/stats.rs | 8 | ||||
-rw-r--r-- | src/command/sync.rs | 6 | ||||
-rw-r--r-- | src/local/api_client.rs | 95 | ||||
-rw-r--r-- | src/local/database.rs | 272 | ||||
-rw-r--r-- | src/local/encryption.rs | 108 | ||||
-rw-r--r-- | src/local/history.rs | 66 | ||||
-rw-r--r-- | src/local/import.rs | 176 | ||||
-rw-r--r-- | src/local/mod.rs | 6 | ||||
-rw-r--r-- | src/local/sync.rs | 141 | ||||
-rw-r--r-- | src/main.rs | 39 | ||||
-rw-r--r-- | src/server/auth.rs | 222 | ||||
-rw-r--r-- | src/server/database.rs | 202 | ||||
-rw-r--r-- | src/server/handlers/history.rs | 89 | ||||
-rw-r--r-- | src/server/handlers/mod.rs | 6 | ||||
-rw-r--r-- | src/server/handlers/user.rs | 140 | ||||
-rw-r--r-- | src/server/mod.rs | 23 | ||||
-rw-r--r-- | src/server/models.rs | 49 | ||||
-rw-r--r-- | src/server/router.rs | 121 | ||||
-rw-r--r-- | src/settings.rs | 172 | ||||
-rw-r--r-- | src/utils.rs | 24 |
28 files changed, 60 insertions, 2067 deletions
diff --git a/src/api.rs b/src/api.rs deleted file mode 100644 index 82ee6604..00000000 --- a/src/api.rs +++ /dev/null @@ -1,70 +0,0 @@ -use chrono::Utc; - -#[derive(Debug, Serialize, Deserialize)] -pub struct UserResponse { - pub username: String, -} - -#[derive(Debug, Serialize, Deserialize)] -pub struct RegisterRequest { - pub email: String, - pub username: String, - pub password: String, -} - -#[derive(Debug, Serialize, Deserialize)] -pub struct RegisterResponse { - pub session: String, -} - -#[derive(Debug, Serialize, Deserialize)] -pub struct LoginRequest { - pub username: String, - pub password: String, -} - -#[derive(Debug, Serialize, Deserialize)] -pub struct LoginResponse { - pub session: String, -} - -#[derive(Debug, Serialize, Deserialize)] -pub struct AddHistoryRequest { - pub id: String, - pub timestamp: chrono::DateTime<Utc>, - pub data: String, - pub hostname: String, -} - -#[derive(Debug, Serialize, Deserialize)] -pub struct CountResponse { - pub count: i64, -} - -#[derive(Debug, Serialize, Deserialize)] -pub struct SyncHistoryRequest { - pub sync_ts: chrono::DateTime<chrono::FixedOffset>, - pub history_ts: chrono::DateTime<chrono::FixedOffset>, - pub host: String, -} - -#[derive(Debug, Serialize, Deserialize)] -pub struct SyncHistoryResponse { - pub history: Vec<String>, -} - -#[derive(Debug, Serialize, Deserialize)] -pub struct ErrorResponse { - pub reason: String, -} - -impl ErrorResponse { - pub fn reply(reason: &str, status: warp::http::StatusCode) -> impl warp::Reply { - warp::reply::with_status( - warp::reply::json(&ErrorResponse { - reason: String::from(reason), - }), - status, - ) - } -} diff --git a/src/command/history.rs b/src/command/history.rs index 627efae4..2b691bac 100644 --- a/src/command/history.rs +++ b/src/command/history.rs @@ -4,10 +4,10 @@ use eyre::Result; use fork::{fork, Fork}; use structopt::StructOpt; -use crate::local::database::Database; -use crate::local::history::History; -use crate::local::sync; -use crate::settings::Settings; +use atuin_client::database::Database; +use atuin_client::history::History; +use atuin_client::settings::Settings; +use atuin_client::sync; #[derive(StructOpt)] pub enum Cmd { @@ -79,7 +79,7 @@ impl Cmd { db.update(&h)?; - if settings.local.should_sync()? { + if settings.should_sync()? { match fork() { Ok(Fork::Parent(child)) => { debug!("launched sync background process with PID {}", child); diff --git a/src/command/import.rs b/src/command/import.rs index ae927aaf..56fb30a7 100644 --- a/src/command/import.rs +++ b/src/command/import.rs @@ -5,9 +5,9 @@ use directories::UserDirs; use eyre::{eyre, Result}; use structopt::StructOpt; -use crate::local::database::Database; -use crate::local::history::History; -use crate::local::import::Zsh; +use atuin_client::database::Database; +use atuin_client::history::History; +use atuin_client::import::Zsh; use indicatif::ProgressBar; #[derive(StructOpt)] diff --git a/src/command/login.rs b/src/command/login.rs index 636ac0d3..eaeb297f 100644 --- a/src/command/login.rs +++ b/src/command/login.rs @@ -5,7 +5,7 @@ use std::io::prelude::*; use eyre::{eyre, Result}; use structopt::StructOpt; -use crate::settings::Settings; +use atuin_client::settings::Settings; #[derive(StructOpt)] #[structopt(setting(structopt::clap::AppSettings::DeriveDisplayOrder))] @@ -26,7 +26,7 @@ impl Cmd { map.insert("username", self.username.clone()); map.insert("password", self.password.clone()); - let url = format!("{}/login", settings.local.sync_address); + let url = format!("{}/login", settings.sync_address); let client = reqwest::blocking::Client::new(); let resp = client.post(url).json(&map).send()?; @@ -38,11 +38,11 @@ impl Cmd { let session = resp.json::<HashMap<String, String>>()?; let session = session["session"].clone(); - let session_path = settings.local.session_path.as_str(); + let session_path = settings.session_path.as_str(); let mut file = File::create(session_path)?; file.write_all(session.as_bytes())?; - let key_path = settings.local.key_path.as_str(); + let key_path = settings.key_path.as_str(); let mut file = File::create(key_path)?; file.write_all(&base64::decode(self.key.clone())?)?; diff --git a/src/command/mod.rs b/src/command/mod.rs index cd857e9f..6fd52613 100644 --- a/src/command/mod.rs +++ b/src/command/mod.rs @@ -1,9 +1,12 @@ +use std::path::PathBuf; + use eyre::Result; use structopt::StructOpt; -use uuid::Uuid; -use crate::local::database::Database; -use crate::settings::Settings; +use atuin_client::database::Sqlite; +use atuin_client::settings::Settings as ClientSettings; +use atuin_common::utils::uuid_v4; +use atuin_server::settings::Settings as ServerSettings; mod event; mod history; @@ -58,30 +61,33 @@ pub enum AtuinCmd { Key, } -pub fn uuid_v4() -> String { - Uuid::new_v4().to_simple().to_string() -} - impl AtuinCmd { - pub async fn run<T: Database + Send>(self, db: &mut T, settings: &Settings) -> Result<()> { + pub async fn run(self) -> Result<()> { + let client_settings = ClientSettings::new()?; + let server_settings = ServerSettings::new()?; + + let db_path = PathBuf::from(client_settings.db_path.as_str()); + + let mut db = Sqlite::new(db_path)?; + match self { - Self::History(history) => history.run(settings, db).await, - Self::Import(import) => import.run(db), - Self::Server(server) => server.run(settings).await, - Self::Stats(stats) => stats.run(db, settings), + Self::History(history) => history.run(&client_settings, &mut db).await, + Self::Import(import) => import.run(&mut db), + Self::Server(server) => server.run(&server_settings).await, + Self::Stats(stats) => stats.run(&mut db, &client_settings), Self::Init => init::init(), - Self::Search { query } => search::run(&query, db), + Self::Search { query } => search::run(&query, &mut db), - Self::Sync { force } => sync::run(settings, force, db).await, - Self::Login(l) => l.run(settings), + Self::Sync { force } => sync::run(&client_settings, force, &mut db).await, + Self::Login(l) => l.run(&client_settings), Self::Register(r) => register::run( - settings, + &client_settings, r.username.as_str(), r.email.as_str(), r.password.as_str(), ), Self::Key => { - let key = std::fs::read(settings.local.key_path.as_str())?; + let key = std::fs::read(client_settings.key_path.as_str())?; println!("{}", base64::encode(key)); Ok(()) } diff --git a/src/command/register.rs b/src/command/register.rs index 62bbeaeb..1126645a 100644 --- a/src/command/register.rs +++ b/src/command/register.rs @@ -5,7 +5,7 @@ use std::io::prelude::*; use eyre::{eyre, Result}; use structopt::StructOpt; -use crate::settings::Settings; +use atuin_client::settings::Settings; #[derive(StructOpt)] #[structopt(setting(structopt::clap::AppSettings::DeriveDisplayOrder))] @@ -26,7 +26,7 @@ pub fn run(settings: &Settings, username: &str, email: &str, password: &str) -> map.insert("email", email); map.insert("password", password); - let url = format!("{}/user/{}", settings.local.sync_address, username); + let url = format!("{}/user/{}", settings.sync_address, username); let resp = reqwest::blocking::get(url)?; if resp.status().is_success() { @@ -34,7 +34,7 @@ pub fn run(settings: &Settings, username: &str, email: &str, password: &str) -> return Ok(()); } - let url = format!("{}/register", settings.local.sync_address); + let url = format!("{}/register", settings.sync_address); let client = reqwest::blocking::Client::new(); let resp = client.post(url).json(&map).send()?; @@ -46,7 +46,7 @@ pub fn run(settings: &Settings, username: &str, email: &str, password: &str) -> let session = resp.json::<HashMap<String, String>>()?; let session = session["session"].clone(); - let path = settings.local.session_path.as_str(); + let path = settings.session_path.as_str(); let mut file = File::create(path)?; file.write_all(session.as_bytes())?; diff --git a/src/command/search.rs b/src/command/search.rs index d7b477da..773c112f 100644 --- a/src/command/search.rs +++ b/src/command/search.rs @@ -14,9 +14,10 @@ use tui::{ }; use unicode_width::UnicodeWidthStr; +use atuin_client::database::Database; +use atuin_client::history::History; + use crate::command::event::{Event, Events}; -use crate::local::database::Database; -use crate::local::history::History; const VERSION: &str = env!("CARGO_PKG_VERSION"); diff --git a/src/command/server.rs b/src/command/server.rs index a7835092..2fcf23d6 100644 --- a/src/command/server.rs +++ b/src/command/server.rs @@ -1,8 +1,8 @@ use eyre::Result; use structopt::StructOpt; -use crate::server; -use crate::settings::Settings; +use atuin_server::launch; +use atuin_server::settings::Settings; #[derive(StructOpt)] pub enum Cmd { @@ -23,13 +23,12 @@ impl Cmd { pub async fn run(&self, settings: &Settings) -> Result<()> { match self { Self::Start { host, port } => { - let host = host.as_ref().map_or( - settings.server.host.clone(), - std::string::ToString::to_string, - ); - let port = port.map_or(settings.server.port, |p| p); + let host = host + .as_ref() + .map_or(settings.host.clone(), std::string::ToString::to_string); + let port = port.map_or(settings.port, |p| p); - server::launch(settings, host, port).await + launch(settings, host, port).await } } } diff --git a/src/command/stats.rs b/src/command/stats.rs index 694484bc..0da303d7 100644 --- a/src/command/stats.rs +++ b/src/command/stats.rs @@ -8,9 +8,9 @@ use cli_table::{format::Justify, print_stdout, Cell, Style, Table}; use eyre::{eyre, Result}; use structopt::StructOpt; -use crate::local::database::Database; -use crate::local::history::History; -use crate::settings::Settings; +use atuin_client::database::Database; +use atuin_client::history::History; +use atuin_client::settings::Settings; #[derive(StructOpt)] pub enum Cmd { @@ -80,7 +80,7 @@ impl Cmd { words.join(" ") }; - let start = match settings.local.dialect.to_lowercase().as_str() { + let start = match settings.dialect.to_lowercase().as_str() { "uk" => parse_date_string(&words, Local::now(), Dialect::Uk)?, _ => parse_date_string(&words, Local::now(), Dialect::Us)?, }; diff --git a/src/command/sync.rs b/src/command/sync.rs index 88217b3c..d70b554f 100644 --- a/src/command/sync.rs +++ b/src/command/sync.rs @@ -1,8 +1,8 @@ use eyre::Result; -use crate::local::database::Database; -use crate::local::sync; -use crate::settings::Settings; +use atuin_client::database::Database; +use atuin_client::settings::Settings; +use atuin_client::sync; pub async fn run(settings: &Settings, force: bool, db: &mut (impl Database + Send)) -> Result<()> { sync::sync(settings, force, db).await?; diff --git a/src/local/api_client.rs b/src/local/api_client.rs deleted file mode 100644 index 1b64a295..00000000 --- a/src/local/api_client.rs +++ /dev/null @@ -1,95 +0,0 @@ -use chrono::Utc; -use eyre::Result; -use reqwest::header::{HeaderMap, AUTHORIZATION}; -use reqwest::Url; -use sodiumoxide::crypto::secretbox; - -use crate::api::{AddHistoryRequest, CountResponse, SyncHistoryResponse}; -use crate::local::encryption::decrypt; -use crate::local::history::History; -use crate::utils::hash_str; - -pub struct Client<'a> { - sync_addr: &'a str, - token: &'a str, - key: secretbox::Key, - client: reqwest::Client, -} - -impl<'a> Client<'a> { - pub fn new(sync_addr: &'a str, token: &'a str, key: secretbox::Key) -> Self { - Client { - sync_addr, - token, - key, - client: reqwest::Client::new(), - } - } - - pub async fn count(&self) -> Result<i64> { - let url = format!("{}/sync/count", self.sync_addr); - let url = Url::parse(url.as_str())?; - let token = format!("Token {}", self.token); - let token = token.parse()?; - - let mut headers = HeaderMap::new(); - headers.insert(AUTHORIZATION, token); - - let resp = self.client.get(url).headers(headers).send().await?; - - let count = resp.json::<CountResponse>().await?; - - Ok(count.count) - } - - pub async fn get_history( - &self, - sync_ts: chrono::DateTime<Utc>, - history_ts: chrono::DateTime<Utc>, - host: Option<String>, - ) -> Result<Vec<History>> { - let host = match host { - None => hash_str(&format!("{}:{}", whoami::hostname(), whoami::username())), - Some(h) => h, - }; - - let url = format!( - "{}/sync/history?sync_ts={}&history_ts={}&host={}", - self.sync_addr, - urlencoding::encode(sync_ts.to_rfc3339().as_str()), - urlencoding::encode(history_ts.to_rfc3339().as_str()), - host, - ); - - let resp = self - .client - .get(url) - .header(AUTHORIZATION, format!("Token {}", self.token)) - .send() - .await?; - - let history = resp.json::<SyncHistoryResponse>().await?; - let history = history - .history - .iter() - .map(|h| serde_json::from_str(h).expect("invalid base64")) - .map(|h| decrypt(&h, &self.key).expect("failed to decrypt history! check your key")) - .collect(); - - Ok(history) - } - - pub async fn post_history(&self, history: &[AddHistoryRequest]) -> Result<()> { - let url = format!("{}/history", self.sync_addr); - let url = Url::parse(url.as_str())?; - - self.client - .post(url) - .json(history) - .header(AUTHORIZATION, format!("Token {}", self.token)) - .send() - .await?; - - Ok(()) - } -} diff --git a/src/local/database.rs b/src/local/database.rs deleted file mode 100644 index abc22bb8..00000000 --- a/src/local/database.rs +++ /dev/null @@ -1,272 +0,0 @@ -use chrono::prelude::*; -use chrono::Utc; -use std::path::Path; - -use eyre::Result; - -use rusqlite::{params, Connection}; -use rusqlite::{Params, Transaction}; - -use super::history::History; - -pub trait Database { - fn save(&mut self, h: &History) -> Result<()>; - fn save_bulk(&mut self, h: &[History]) -> Result<()>; - - fn load(&self, id: &str) -> Result<History>; - fn list(&self) -> Result<Vec<History>>; - fn range(&self, from: chrono::DateTime<Utc>, to: chrono::DateTime<Utc>) - -> Result<Vec<History>>; - - fn query(&self, query: &str, params: impl Params) -> Result<Vec<History>>; - fn update(&self, h: &History) -> Result<()>; - fn history_count(&self) -> Result<i64>; - - fn first(&self) -> Result<History>; - fn last(&self) -> Result<History>; - fn before(&self, timestamp: chrono::DateTime<Utc>, count: i64) -> Result<Vec<History>>; - - fn prefix_search(&self, query: &str) -> Result<Vec<History>>; -} - -// Intended for use on a developer machine and not a sync server. -// TODO: implement IntoIterator -pub struct Sqlite { - conn: Connection, -} - -impl Sqlite { - pub fn new(path: impl AsRef<Path>) -> Result<Self> { - let path = path.as_ref(); - debug!("opening sqlite database at {:?}", path); - - let create = !path.exists(); - if create { - if let Some(dir) = path.parent() { - std::fs::create_dir_all(dir)?; - } - } - - let conn = Connection::open(path)?; - - Self::setup_db(&conn)?; - - Ok(Self { conn }) - } - - fn setup_db(conn: &Connection) -> Result<()> { - debug!("running sqlite database setup"); - - conn.execute( - "create table if not exists history ( - id text primary key, - timestamp integer not null, - duration integer not null, - exit integer not null, - command text not null, - cwd text not null, - session text not null, - hostname text not null, - - unique(timestamp, cwd, command) - )", - [], - )?; - - conn.execute( - "create table if not exists history_encrypted ( - id text primary key, - data blob not null - )", - [], - )?; - - Ok(()) - } - - fn save_raw(tx: &Transaction, h: &History) -> Result<()> { - tx.execute( - "insert or ignore into history ( - id, - timestamp, - duration, - exit, - command, - cwd, - session, - hostname - ) values (?1, ?2, ?3, ?4, ?5, ?6, ?7, ?8)", - params![ - h.id, - h.timestamp.timestamp_nanos(), - h.duration, - h.exit, - h.command, - h.cwd, - h.session, - h.hostname - ], - )?; - - Ok(()) - } -} - -impl Database for Sqlite { - fn save(&mut self, h: &History) -> Result<()> { - debug!("saving history to sqlite"); - - let tx = self.conn.transaction()?; - Self::save_raw(&tx, h)?; - tx.commit()?; - - Ok(()) - } - - fn save_bulk(&mut self, h: &[History]) -> Result<()> { - debug!("saving history to sqlite"); - - let tx = self.conn.transaction()?; - for i in h { - Self::save_raw(&tx, i)? - } - tx.commit()?; - - Ok(()) - } - - fn load(&self, id: &str) -> Result<History> { - debug!("loading history item"); - - let mut stmt = self.conn.prepare( - "select id, timestamp, duration, exit, command, cwd, session, hostname from history - where id = ?1", - )?; - - let history = stmt.query_row(params![id], |row| { - history_from_sqlite_row(Some(id.to_string()), row) - })?; - - Ok(history) - } - - fn update(&self, h: &History) -> Result<()> { - debug!("updating sqlite history"); - - self.conn.execute( - "update history - set timestamp = ?2, duration = ?3, exit = ?4, command = ?5, cwd = ?6, session = ?7, hostname = ?8 - where id = ?1", - params![h.id, h.timestamp.timestamp_nanos(), h.duration, h.exit, h.command, h.cwd, h.session, h.hostname], - )?; - - Ok(()) - } - - fn list(&self) -> Result<Vec<History>> { - debug!("listing history"); - - let mut stmt = self - .conn - .prepare("SELECT * FROM history order by timestamp asc")?; - - let history_iter = stmt.query_map(params![], |row| history_from_sqlite_row(None, row))?; - - Ok(history_iter.filter_map(Result::ok).collect()) - } - - fn range( - &self, - from: chrono::DateTime<Utc>, - to: chrono::DateTime<Utc>, - ) -> Result<Vec<History>> { - debug!("listing history from {:?} to {:?}", from, to); - - let mut stmt = self.conn.prepare( - "SELECT * FROM history where timestamp >= ?1 and timestamp <= ?2 order by timestamp asc", - )?; - - let history_iter = stmt.query_map( - params![from.timestamp_nanos(), to.timestamp_nanos()], - |row| history_from_sqlite_row(None, row), - )?; - - Ok(history_iter.filter_map(Result::ok).collect()) - } - - fn first(&self) -> Result<History> { - let mut stmt = self - .conn - .prepare("SELECT * FROM history order by timestamp asc limit 1")?; - - let history = stmt.query_row(params![], |row| history_from_sqlite_row(None, row))?; - - Ok(history) - } - - fn last(&self) -> Result<History> { - let mut stmt = self - .conn - .prepare("SELECT * FROM history order by timestamp desc limit 1")?; - - let history = stmt.query_row(params![], |row| history_from_sqlite_row(None, row))?; - - Ok(history) - } - - fn before(&self, timestamp: chrono::DateTime<Utc>, count: i64) -> Result<Vec<History>> { - let mut stmt = self - .conn - .prepare("SELECT * FROM history where timestamp < ? order by timestamp desc limit ?")?; - - let history_iter = stmt.query_map(params![timestamp.timestamp_nanos(), count], |row| { - history_from_sqlite_row(None, row) - })?; - - Ok(history_iter.filter_map(Result::ok).collect()) - } - - fn query(&self, query: &str, params: impl Params) -> Result<Vec<History>> { - let mut stmt = self.conn.prepare(query)?; - - let history_iter = stmt.query_map(params, |row| history_from_sqlite_row(None, row))?; - - Ok(history_iter.filter_map(Result::ok).collect()) - } - - fn prefix_search(&self, query: &str) -> Result<Vec<History>> { - self.query( - "select * from history where command like ?1 || '%' order by timestamp asc limit 1000", - &[query], - ) - } - - fn history_count(&self) -> Result<i64> { - let res: i64 = - self.conn - .query_row_and_then("select count(1) from history;", params![], |row| row.get(0))?; - - Ok(res) - } -} - -fn history_from_sqlite_row( - id: Option<String>, - row: &rusqlite::Row, -) -> Result<History, rusqlite::Error> { - let id = match id { - Some(id) => id, - None => row.get(0)?, - }; - - Ok(History { - id, - timestamp: Utc.timestamp_nanos(row.get(1)?), - duration: row.get(2)?, - exit: row.get(3)?, - command: row.get(4)?, - cwd: row.get(5)?, - session: row.get(6)?, - hostname: row.get(7)?, - }) -} diff --git a/src/local/encryption.rs b/src/local/encryption.rs deleted file mode 100644 index 3c1699e3..00000000 --- a/src/local/encryption.rs +++ /dev/null @@ -1,108 +0,0 @@ -// The general idea is that we NEVER send cleartext history to the server -// This way the odds of anything private ending up where it should not are -// very low -// The server authenticates via the usual username and password. This has -// nothing to do with the encryption, and is purely authentication! The client -// generates its own secret key, and encrypts all shell history with libsodium's -// secretbox. The data is then sent to the server, where it is stored. All -// clients must share the secret in order to be able to sync, as it is needed -// to decrypt - -use std::fs::File; -use std::io::prelude::*; -use std::path::PathBuf; - -use eyre::{eyre, Result}; -use sodiumoxide::crypto::secretbox; - -use crate::local::history::History; -use crate::settings::Settings; - -#[derive(Debug, Serialize, Deserialize)] -pub struct EncryptedHistory { - pub ciphertext: Vec<u8>, - pub nonce: secretbox::Nonce, -} - -// Loads the secret key, will create + save if it doesn't exist -pub fn load_key(settings: &Settings) -> Result<secretbox::Key> { - let path = settings.local.key_path.as_str(); - - if PathBuf::from(path).exists() { - let bytes = std::fs::read(path)?; - let key: secretbox::Key = rmp_serde::from_read_ref(&bytes)?; - Ok(key) - } else { - let key = secretbox::gen_key(); - let buf = rmp_serde::to_vec(&key)?; - - let mut file = File::create(path)?; - file.write_all(&buf)?; - - Ok(key) - } -} - -pub fn encrypt(history: &History, key: &secretbox::Key) -> Result<EncryptedHistory> { - // serialize with msgpack - let buf = rmp_serde::to_vec(history)?; - - let nonce = secretbox::gen_nonce(); - - let ciphertext = secretbox::seal(&buf, &nonce, key); - - Ok(EncryptedHistory { ciphertext, nonce }) -} - -pub fn decrypt(encrypted_history: &EncryptedHistory, key: &secretbox::Key) -> Result<History> { - let plaintext = secretbo |