mirror of
https://github.com/atuinsh/atuin.git
synced 2025-01-13 17:58:54 +01:00
Aaaaaand they're encrypted now too
This commit is contained in:
parent
8f0eda013e
commit
d8e7e2c9d2
@ -7,7 +7,8 @@ create table if not exists records (
|
||||
timestamp integer not null,
|
||||
tag text not null,
|
||||
version text not null,
|
||||
data blob not null
|
||||
data blob not null,
|
||||
cek blob not null
|
||||
);
|
||||
|
||||
create index host_idx on records (host);
|
||||
|
@ -1,3 +0,0 @@
|
||||
-- store content encryption keys in the record
|
||||
alter table records
|
||||
add column cek text;
|
@ -9,7 +9,7 @@ use reqwest::{
|
||||
StatusCode, Url,
|
||||
};
|
||||
|
||||
use atuin_common::record::Record;
|
||||
use atuin_common::record::{EncryptedData, Record};
|
||||
use atuin_common::{
|
||||
api::{
|
||||
AddHistoryRequest, CountResponse, DeleteHistoryRequest, ErrorResponse, IndexResponse,
|
||||
@ -200,7 +200,7 @@ impl<'a> Client<'a> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn post_records(&self, records: &[Record]) -> Result<()> {
|
||||
pub async fn post_records(&self, records: &[Record<EncryptedData>]) -> Result<()> {
|
||||
let url = format!("{}/record", self.sync_addr);
|
||||
let url = Url::parse(url.as_str())?;
|
||||
|
||||
@ -215,7 +215,7 @@ impl<'a> Client<'a> {
|
||||
tag: String,
|
||||
start: Option<Uuid>,
|
||||
count: u64,
|
||||
) -> Result<Vec<Record>> {
|
||||
) -> Result<Vec<Record<EncryptedData>>> {
|
||||
let url = format!(
|
||||
"{}/record/next?host={}&tag={}&count={}",
|
||||
self.sync_addr, host, tag, count
|
||||
@ -230,7 +230,7 @@ impl<'a> Client<'a> {
|
||||
|
||||
let resp = self.client.get(url).send().await?;
|
||||
|
||||
let records = resp.json::<Vec<Record>>().await?;
|
||||
let records = resp.json::<Vec<Record<EncryptedData>>>().await?;
|
||||
|
||||
Ok(records)
|
||||
}
|
||||
|
@ -151,7 +151,7 @@ impl KvStore {
|
||||
}
|
||||
|
||||
if let Some(parent) = decrypted.parent {
|
||||
record = store.get(parent.as_str()).await?;
|
||||
record = store.get(parent).await?;
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
|
@ -6,6 +6,7 @@ use rusty_paseto::core::{
|
||||
ImplicitAssertion, Key as DataKey, Local as LocalPurpose, Paseto, PasetoNonce, Payload, V4,
|
||||
};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use uuid::Uuid;
|
||||
|
||||
/// Use PASETO V4 Local encryption using the additional data as an implicit assertion.
|
||||
#[allow(non_camel_case_types)]
|
||||
@ -158,10 +159,10 @@ struct AtuinFooter {
|
||||
// This cannot be changed, otherwise it breaks the authenticated encryption.
|
||||
#[derive(Debug, Copy, Clone, Serialize)]
|
||||
struct Assertions<'a> {
|
||||
id: &'a str,
|
||||
id: &'a Uuid,
|
||||
version: &'a str,
|
||||
tag: &'a str,
|
||||
host: &'a str,
|
||||
host: &'a Uuid,
|
||||
}
|
||||
|
||||
impl<'a> From<AdditionalData<'a>> for Assertions<'a> {
|
||||
|
@ -21,7 +21,7 @@ pub trait Store {
|
||||
records: impl Iterator<Item = &Record<EncryptedData>> + Send + Sync,
|
||||
) -> Result<()>;
|
||||
|
||||
async fn get(&self, id: &str) -> Result<Record<EncryptedData>>;
|
||||
async fn get(&self, id: Uuid) -> Result<Record<EncryptedData>>;
|
||||
async fn len(&self, host: Uuid, tag: &str) -> Result<u64>;
|
||||
|
||||
/// Get the record that follows this record
|
||||
|
@ -119,7 +119,6 @@ async fn sync_upload(
|
||||
// remote tail = current local tail
|
||||
|
||||
let mut record = Some(store.get(start).await.unwrap());
|
||||
record = store.next(&record.unwrap()).await?;
|
||||
|
||||
let mut buf = Vec::with_capacity(upload_page_size);
|
||||
|
||||
|
@ -8,7 +8,7 @@ use uuid::Uuid;
|
||||
#[derive(Clone, Debug, PartialEq)]
|
||||
pub struct DecryptedData(pub Vec<u8>);
|
||||
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
|
||||
pub struct EncryptedData {
|
||||
pub data: String,
|
||||
pub content_encryption_key: String,
|
||||
@ -54,10 +54,10 @@ pub struct Record<Data> {
|
||||
/// Extra data from the record that should be encoded in the data
|
||||
#[derive(Debug, Copy, Clone)]
|
||||
pub struct AdditionalData<'a> {
|
||||
pub id: &'a str,
|
||||
pub id: &'a Uuid,
|
||||
pub version: &'a str,
|
||||
pub tag: &'a str,
|
||||
pub host: &'a str,
|
||||
pub host: &'a Uuid,
|
||||
}
|
||||
|
||||
impl<Data> Record<Data> {
|
||||
|
@ -13,7 +13,10 @@ use self::{
|
||||
models::{History, NewHistory, NewSession, NewUser, Session, User},
|
||||
};
|
||||
use async_trait::async_trait;
|
||||
use atuin_common::{record::Record, utils::get_days_from_month};
|
||||
use atuin_common::{
|
||||
record::{EncryptedData, Record},
|
||||
utils::get_days_from_month,
|
||||
};
|
||||
use chrono::{Datelike, TimeZone};
|
||||
use chronoutil::RelativeDuration;
|
||||
use serde::{de::DeserializeOwned, Serialize};
|
||||
@ -56,7 +59,7 @@ pub trait Database: Sized + Clone + Send + Sync + 'static {
|
||||
async fn delete_history(&self, user: &User, id: String) -> DbResult<()>;
|
||||
async fn deleted_history(&self, user: &User) -> DbResult<Vec<String>>;
|
||||
|
||||
async fn add_records(&self, user: &User, record: &[Record]) -> DbResult<()>;
|
||||
async fn add_records(&self, user: &User, record: &[Record<EncryptedData>]) -> DbResult<()>;
|
||||
async fn next_records(
|
||||
&self,
|
||||
user: &User,
|
||||
@ -64,7 +67,7 @@ pub trait Database: Sized + Clone + Send + Sync + 'static {
|
||||
tag: String,
|
||||
start: Option<Uuid>,
|
||||
count: u64,
|
||||
) -> DbResult<Vec<Record>>;
|
||||
) -> DbResult<Vec<Record<EncryptedData>>>;
|
||||
|
||||
// Return the tail record ID for each store, so (HostID, Tag, TailRecordID)
|
||||
async fn tail_records(&self, user: &User) -> DbResult<Vec<(Uuid, String, Uuid)>>;
|
||||
|
@ -7,7 +7,8 @@ create table records (
|
||||
timestamp bigint not null, -- not a timestamp type, as those do not have nanosecond precision
|
||||
version text not null,
|
||||
tag text not null, -- what is this? history, kv, whatever. Remember clients get a log per tag per host
|
||||
data bytea not null, -- store the actual history data, encrypted. I don't wanna know!
|
||||
data text not null, -- store the actual history data, encrypted. I don't wanna know!
|
||||
cek text not null,
|
||||
|
||||
user_id bigint not null, -- allow multiple users
|
||||
created_at timestamp not null default current_timestamp
|
||||
|
@ -1,7 +1,7 @@
|
||||
use std::str::FromStr;
|
||||
|
||||
use async_trait::async_trait;
|
||||
use atuin_common::record::Record;
|
||||
use atuin_common::record::{EncryptedData, Record};
|
||||
use atuin_server_database::models::{History, NewHistory, NewSession, NewUser, Session, User};
|
||||
use atuin_server_database::{Database, DbError, DbResult};
|
||||
use futures_util::TryStreamExt;
|
||||
@ -335,7 +335,7 @@ impl Database for Postgres {
|
||||
}
|
||||
|
||||
#[instrument(skip_all)]
|
||||
async fn add_records(&self, user: &User, records: &[Record]) -> DbResult<()> {
|
||||
async fn add_records(&self, user: &User, records: &[Record<EncryptedData>]) -> DbResult<()> {
|
||||
let mut tx = self.pool.begin().await.map_err(fix_error)?;
|
||||
|
||||
for i in records {
|
||||
@ -343,8 +343,8 @@ impl Database for Postgres {
|
||||
|
||||
sqlx::query(
|
||||
"insert into records
|
||||
(id, client_id, host, parent, timestamp, version, tag, data, user_id)
|
||||
values ($1, $2, $3, $4, $5, $6, $7, $8, $9)
|
||||
(id, client_id, host, parent, timestamp, version, tag, data, cek, user_id)
|
||||
values ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10)
|
||||
on conflict do nothing
|
||||
",
|
||||
)
|
||||
@ -355,7 +355,8 @@ impl Database for Postgres {
|
||||
.bind(i.timestamp as i64) // throwing away some data, but i64 is still big in terms of time
|
||||
.bind(&i.version)
|
||||
.bind(&i.tag)
|
||||
.bind(&i.data)
|
||||
.bind(&i.data.data)
|
||||
.bind(&i.data.content_encryption_key)
|
||||
.bind(user.id)
|
||||
.execute(&mut tx)
|
||||
.await
|
||||
@ -375,7 +376,7 @@ impl Database for Postgres {
|
||||
tag: String,
|
||||
start: Option<Uuid>,
|
||||
count: u64,
|
||||
) -> DbResult<Vec<Record>> {
|
||||
) -> DbResult<Vec<Record<EncryptedData>>> {
|
||||
tracing::debug!("{:?} - {:?} - {:?}", host, tag, start);
|
||||
let mut ret = Vec::with_capacity(count as usize);
|
||||
let mut parent = start;
|
||||
@ -402,7 +403,7 @@ impl Database for Postgres {
|
||||
|
||||
match record {
|
||||
Ok(record) => {
|
||||
let record: Record = record.into();
|
||||
let record: Record<EncryptedData> = record.into();
|
||||
ret.push(record.clone());
|
||||
|
||||
parent = Some(record.id);
|
||||
|
@ -1,12 +1,12 @@
|
||||
use ::sqlx::{FromRow, Result};
|
||||
use atuin_common::record::Record;
|
||||
use atuin_common::record::{EncryptedData, Record};
|
||||
use atuin_server_database::models::{History, Session, User};
|
||||
use sqlx::{postgres::PgRow, Row};
|
||||
|
||||
pub struct DbUser(pub User);
|
||||
pub struct DbSession(pub Session);
|
||||
pub struct DbHistory(pub History);
|
||||
pub struct DbRecord(pub Record);
|
||||
pub struct DbRecord(pub Record<EncryptedData>);
|
||||
|
||||
impl<'a> FromRow<'a, PgRow> for DbUser {
|
||||
fn from_row(row: &'a PgRow) -> Result<Self> {
|
||||
@ -47,6 +47,11 @@ impl<'a> ::sqlx::FromRow<'a, PgRow> for DbRecord {
|
||||
fn from_row(row: &'a PgRow) -> ::sqlx::Result<Self> {
|
||||
let timestamp: i64 = row.try_get("timestamp")?;
|
||||
|
||||
let data = EncryptedData {
|
||||
data: row.try_get("data")?,
|
||||
content_encryption_key: row.try_get("cek")?,
|
||||
};
|
||||
|
||||
Ok(Self(Record {
|
||||
id: row.try_get("client_id")?,
|
||||
host: row.try_get("host")?,
|
||||
@ -54,13 +59,13 @@ impl<'a> ::sqlx::FromRow<'a, PgRow> for DbRecord {
|
||||
timestamp: timestamp as u64,
|
||||
version: row.try_get("version")?,
|
||||
tag: row.try_get("tag")?,
|
||||
data: row.try_get("data")?,
|
||||
data,
|
||||
}))
|
||||
}
|
||||
}
|
||||
|
||||
impl Into<Record> for DbRecord {
|
||||
fn into(self) -> Record {
|
||||
impl Into<Record<EncryptedData>> for DbRecord {
|
||||
fn into(self) -> Record<EncryptedData> {
|
||||
Record { ..self.0 }
|
||||
}
|
||||
}
|
||||
|
@ -8,13 +8,13 @@ use super::{ErrorResponse, ErrorResponseStatus, RespExt};
|
||||
use crate::router::{AppState, UserAuth};
|
||||
use atuin_server_database::Database;
|
||||
|
||||
use atuin_common::record::{Record, RecordIndex};
|
||||
use atuin_common::record::{EncryptedData, Record, RecordIndex};
|
||||
|
||||
#[instrument(skip_all, fields(user.id = user.id))]
|
||||
pub async fn post<DB: Database>(
|
||||
UserAuth(user): UserAuth,
|
||||
state: State<AppState<DB>>,
|
||||
Json(records): Json<Vec<Record>>,
|
||||
Json(records): Json<Vec<Record<EncryptedData>>>,
|
||||
) -> Result<(), ErrorResponseStatus<'static>> {
|
||||
let State(AppState { database, settings }) = state;
|
||||
|
||||
@ -26,7 +26,7 @@ pub async fn post<DB: Database>(
|
||||
|
||||
let too_big = records
|
||||
.iter()
|
||||
.any(|r| r.data.len() >= settings.max_record_size || settings.max_record_size == 0);
|
||||
.any(|r| r.data.data.len() >= settings.max_record_size || settings.max_record_size == 0);
|
||||
|
||||
if too_big {
|
||||
return Err(
|
||||
@ -84,7 +84,7 @@ pub async fn next<DB: Database>(
|
||||
params: Query<NextParams>,
|
||||
UserAuth(user): UserAuth,
|
||||
state: State<AppState<DB>>,
|
||||
) -> Result<Json<Vec<Record>>, ErrorResponseStatus<'static>> {
|
||||
) -> Result<Json<Vec<Record<EncryptedData>>>, ErrorResponseStatus<'static>> {
|
||||
let State(AppState { database, settings }) = state;
|
||||
let params = params.0;
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user