[WIP] Encryption for secure values (#183)

This commit is contained in:
Gregory Schier
2025-04-15 07:18:26 -07:00
committed by GitHub
parent e114a85c39
commit 2e55a1bd6d
208 changed files with 4063 additions and 28698 deletions

View File

@@ -53,9 +53,11 @@ impl<'a> DbContext<'a> {
where
M: Into<AnyModel> + Clone + UpsertModelInfo,
{
let (order_by_col, order_by_dir) = M::order_by();
let (sql, params) = Query::select()
.from(M::table_name())
.column(Asterisk)
.order_by(order_by_col, order_by_dir)
.build_rusqlite(SqliteQueryBuilder);
let mut stmt = self.conn.resolve().prepare(sql.as_str())?;
let items = stmt.query_map(&*params.as_params(), M::from_row)?;
@@ -72,18 +74,21 @@ impl<'a> DbContext<'a> {
M: Into<AnyModel> + Clone + UpsertModelInfo,
{
// TODO: Figure out how to do this conditional builder better
let (order_by_col, order_by_dir) = M::order_by();
let (sql, params) = if let Some(limit) = limit {
Query::select()
.from(M::table_name())
.column(Asterisk)
.cond_where(Expr::col(col).eq(value))
.limit(limit)
.order_by(order_by_col, order_by_dir)
.build_rusqlite(SqliteQueryBuilder)
} else {
Query::select()
.from(M::table_name())
.column(Asterisk)
.cond_where(Expr::col(col).eq(value))
.order_by(order_by_col, order_by_dir)
.build_rusqlite(SqliteQueryBuilder)
};

View File

@@ -20,7 +20,7 @@ pub enum Error {
#[error("Model error: {0}")]
GenericError(String),
#[error("Row not found")]
RowNotFound,

View File

@@ -20,7 +20,7 @@ use tokio::sync::mpsc;
mod commands;
mod connection_or_tx;
mod db_context;
pub mod db_context;
pub mod error;
pub mod models;
pub mod queries;

View File

@@ -3,10 +3,11 @@ use crate::models::HttpRequestIden::{
Authentication, AuthenticationType, Body, BodyType, CreatedAt, Description, FolderId, Headers,
Method, Name, SortPriority, UpdatedAt, Url, UrlParameters, WorkspaceId,
};
use crate::util::{generate_prefixed_id, UpdateSource};
use crate::util::{UpdateSource, generate_prefixed_id};
use chrono::{NaiveDateTime, Utc};
use rusqlite::Row;
use sea_query::{enum_def, IntoIden, IntoTableRef, SimpleExpr};
use sea_query::Order::Desc;
use sea_query::{IntoColumnRef, IntoIden, IntoTableRef, Order, SimpleExpr, enum_def};
use serde::{Deserialize, Deserializer, Serialize};
use serde_json::Value;
use std::collections::BTreeMap;
@@ -124,6 +125,10 @@ impl UpsertModelInfo for Settings {
panic!("Settings does not have unique IDs")
}
fn order_by() -> (impl IntoColumnRef, Order) {
(SettingsIden::CreatedAt, Desc)
}
fn get_id(&self) -> String {
self.id.clone()
}
@@ -209,6 +214,7 @@ pub struct Workspace {
pub updated_at: NaiveDateTime,
pub name: String,
pub description: String,
pub encryption_key_challenge: Option<String>,
// Settings
#[serde(default = "default_true")]
@@ -231,6 +237,10 @@ impl UpsertModelInfo for Workspace {
generate_prefixed_id("wk")
}
fn order_by() -> (impl IntoColumnRef, Order) {
(WorkspaceIden::CreatedAt, Desc)
}
fn get_id(&self) -> String {
self.id.clone()
}
@@ -245,6 +255,7 @@ impl UpsertModelInfo for Workspace {
(UpdatedAt, upsert_date(source, self.updated_at)),
(Name, self.name.trim().into()),
(Description, self.description.into()),
(EncryptionKeyChallenge, self.encryption_key_challenge.into()),
(SettingFollowRedirects, self.setting_follow_redirects.into()),
(SettingRequestTimeout, self.setting_request_timeout.into()),
(SettingValidateCertificates, self.setting_validate_certificates.into()),
@@ -256,6 +267,7 @@ impl UpsertModelInfo for Workspace {
WorkspaceIden::UpdatedAt,
WorkspaceIden::Name,
WorkspaceIden::Description,
WorkspaceIden::EncryptionKeyChallenge,
WorkspaceIden::SettingRequestTimeout,
WorkspaceIden::SettingFollowRedirects,
WorkspaceIden::SettingRequestTimeout,
@@ -274,6 +286,7 @@ impl UpsertModelInfo for Workspace {
updated_at: row.get("updated_at")?,
name: row.get("name")?,
description: row.get("description")?,
encryption_key_challenge: row.get("encryption_key_challenge")?,
setting_follow_redirects: row.get("setting_follow_redirects")?,
setting_request_timeout: row.get("setting_request_timeout")?,
setting_validate_certificates: row.get("setting_validate_certificates")?,
@@ -281,16 +294,11 @@ impl UpsertModelInfo for Workspace {
}
}
impl Workspace {
pub fn new(name: String) -> Self {
Self {
name,
model: "workspace".to_string(),
setting_validate_certificates: true,
setting_follow_redirects: true,
..Default::default()
}
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, Default, TS)]
#[serde(default, rename_all = "camelCase")]
#[ts(export, export_to = "gen_models.ts")]
pub struct EncryptedKey {
pub encrypted_key: String,
}
#[derive(Debug, Clone, Serialize, Deserialize, Default, TS)]
@@ -304,6 +312,7 @@ pub struct WorkspaceMeta {
pub workspace_id: String,
pub created_at: NaiveDateTime,
pub updated_at: NaiveDateTime,
pub encryption_key: Option<EncryptedKey>,
pub setting_sync_dir: Option<String>,
}
@@ -320,6 +329,10 @@ impl UpsertModelInfo for WorkspaceMeta {
generate_prefixed_id("wm")
}
fn order_by() -> (impl IntoColumnRef, Order) {
(WorkspaceMetaIden::CreatedAt, Desc)
}
fn get_id(&self) -> String {
self.id.clone()
}
@@ -333,6 +346,7 @@ impl UpsertModelInfo for WorkspaceMeta {
(CreatedAt, upsert_date(source, self.created_at)),
(UpdatedAt, upsert_date(source, self.updated_at)),
(WorkspaceId, self.workspace_id.into()),
(EncryptionKey, self.encryption_key.map(|e| serde_json::to_string(&e).unwrap()).into()),
(SettingSyncDir, self.setting_sync_dir.into()),
])
}
@@ -340,6 +354,7 @@ impl UpsertModelInfo for WorkspaceMeta {
fn update_columns() -> Vec<impl IntoIden> {
vec![
WorkspaceMetaIden::UpdatedAt,
WorkspaceMetaIden::EncryptionKey,
WorkspaceMetaIden::SettingSyncDir,
]
}
@@ -348,12 +363,14 @@ impl UpsertModelInfo for WorkspaceMeta {
where
Self: Sized,
{
let encryption_key: Option<String> = row.get("encryption_key")?;
Ok(Self {
id: row.get("id")?,
workspace_id: row.get("workspace_id")?,
model: row.get("model")?,
created_at: row.get("created_at")?,
updated_at: row.get("updated_at")?,
encryption_key: encryption_key.map(|e| serde_json::from_str(&e).unwrap()),
setting_sync_dir: row.get("setting_sync_dir")?,
})
}
@@ -413,6 +430,10 @@ impl UpsertModelInfo for CookieJar {
generate_prefixed_id("cj")
}
fn order_by() -> (impl IntoColumnRef, Order) {
(CookieJarIden::CreatedAt, Desc)
}
fn get_id(&self) -> String {
self.id.clone()
}
@@ -486,6 +507,10 @@ impl UpsertModelInfo for Environment {
generate_prefixed_id("ev")
}
fn order_by() -> (impl IntoColumnRef, Order) {
(EnvironmentIden::CreatedAt, Desc)
}
fn get_id(&self) -> String {
self.id.clone()
}
@@ -575,6 +600,10 @@ impl UpsertModelInfo for Folder {
generate_prefixed_id("fl")
}
fn order_by() -> (impl IntoColumnRef, Order) {
(FolderIden::CreatedAt, Desc)
}
fn get_id(&self) -> String {
self.id.clone()
}
@@ -673,7 +702,7 @@ pub struct HttpRequest {
#[serde(default = "default_http_method")]
pub method: String,
pub name: String,
pub sort_priority: f32,
pub sort_priority: f64,
pub url: String,
pub url_parameters: Vec<HttpUrlParameter>,
}
@@ -691,6 +720,10 @@ impl UpsertModelInfo for HttpRequest {
generate_prefixed_id("rq")
}
fn order_by() -> (impl IntoColumnRef, Order) {
(HttpResponseIden::CreatedAt, Desc)
}
fn get_id(&self) -> String {
self.id.to_string()
}
@@ -745,21 +778,21 @@ impl UpsertModelInfo for HttpRequest {
Ok(Self {
id: r.get("id")?,
model: r.get("model")?,
sort_priority: r.get("sort_priority")?,
workspace_id: r.get("workspace_id")?,
created_at: r.get("created_at")?,
updated_at: r.get("updated_at")?,
url: r.get("url")?,
url_parameters: serde_json::from_str(url_parameters.as_str()).unwrap_or_default(),
method: r.get("method")?,
authentication: serde_json::from_str(authentication.as_str()).unwrap_or_default(),
authentication_type: r.get("authentication_type")?,
body: serde_json::from_str(body.as_str()).unwrap_or_default(),
body_type: r.get("body_type")?,
description: r.get("description")?,
authentication: serde_json::from_str(authentication.as_str()).unwrap_or_default(),
authentication_type: r.get("authentication_type")?,
headers: serde_json::from_str(headers.as_str()).unwrap_or_default(),
folder_id: r.get("folder_id")?,
headers: serde_json::from_str(headers.as_str()).unwrap_or_default(),
method: r.get("method")?,
name: r.get("name")?,
sort_priority: r.get("sort_priority")?,
url: r.get("url")?,
url_parameters: serde_json::from_str(url_parameters.as_str()).unwrap_or_default(),
})
}
}
@@ -814,6 +847,10 @@ impl UpsertModelInfo for WebsocketConnection {
generate_prefixed_id("wc")
}
fn order_by() -> (impl IntoColumnRef, Order) {
(WebsocketConnectionIden::CreatedAt, Desc)
}
fn get_id(&self) -> String {
self.id.clone()
}
@@ -924,6 +961,10 @@ impl UpsertModelInfo for WebsocketRequest {
generate_prefixed_id("wr")
}
fn order_by() -> (impl IntoColumnRef, Order) {
(WebsocketRequestIden::CreatedAt, Desc)
}
fn get_id(&self) -> String {
self.id.clone()
}
@@ -1045,6 +1086,10 @@ impl UpsertModelInfo for WebsocketEvent {
generate_prefixed_id("we")
}
fn order_by() -> (impl IntoColumnRef, Order) {
(WebsocketEventIden::CreatedAt, Desc)
}
fn get_id(&self) -> String {
self.id.clone()
}
@@ -1158,6 +1203,10 @@ impl UpsertModelInfo for HttpResponse {
generate_prefixed_id("rs")
}
fn order_by() -> (impl IntoColumnRef, Order) {
(HttpResponseIden::CreatedAt, Desc)
}
fn get_id(&self) -> String {
self.id.clone()
}
@@ -1286,6 +1335,10 @@ impl UpsertModelInfo for GrpcRequest {
generate_prefixed_id("gr")
}
fn order_by() -> (impl IntoColumnRef, Order) {
(GrpcRequestIden::CreatedAt, Desc)
}
fn get_id(&self) -> String {
self.id.clone()
}
@@ -1409,6 +1462,10 @@ impl UpsertModelInfo for GrpcConnection {
generate_prefixed_id("gc")
}
fn order_by() -> (impl IntoColumnRef, Order) {
(GrpcConnectionIden::CreatedAt, Desc)
}
fn get_id(&self) -> String {
self.id.clone()
}
@@ -1525,6 +1582,10 @@ impl UpsertModelInfo for GrpcEvent {
generate_prefixed_id("ge")
}
fn order_by() -> (impl IntoColumnRef, Order) {
(GrpcEventIden::CreatedAt, Desc)
}
fn get_id(&self) -> String {
self.id.clone()
}
@@ -1612,6 +1673,10 @@ impl UpsertModelInfo for Plugin {
generate_prefixed_id("pg")
}
fn order_by() -> (impl IntoColumnRef, Order) {
(PluginIden::CreatedAt, Desc)
}
fn get_id(&self) -> String {
self.id.clone()
}
@@ -1690,6 +1755,10 @@ impl UpsertModelInfo for SyncState {
generate_prefixed_id("ss")
}
fn order_by() -> (impl IntoColumnRef, Order) {
(SyncStateIden::CreatedAt, Desc)
}
fn get_id(&self) -> String {
self.id.clone()
}
@@ -1769,6 +1838,10 @@ impl UpsertModelInfo for KeyValue {
generate_prefixed_id("kv")
}
fn order_by() -> (impl IntoColumnRef, Order) {
(KeyValueIden::CreatedAt, Desc)
}
fn get_id(&self) -> String {
self.id.clone()
}
@@ -1919,13 +1992,18 @@ impl<'de> Deserialize<'de> for AnyModel {
Some(m) if m == "key_value" => AnyModel::KeyValue(fv(value).unwrap()),
Some(m) if m == "plugin" => AnyModel::Plugin(fv(value).unwrap()),
Some(m) if m == "settings" => AnyModel::Settings(fv(value).unwrap()),
Some(m) if m == "websocket_connection" => AnyModel::WebsocketConnection(fv(value).unwrap()),
Some(m) if m == "websocket_connection" => {
AnyModel::WebsocketConnection(fv(value).unwrap())
}
Some(m) if m == "websocket_event" => AnyModel::WebsocketEvent(fv(value).unwrap()),
Some(m) if m == "websocket_request" => AnyModel::WebsocketRequest(fv(value).unwrap()),
Some(m) if m == "workspace" => AnyModel::Workspace(fv(value).unwrap()),
Some(m) if m == "workspace_meta" => AnyModel::WorkspaceMeta(fv(value).unwrap()),
Some(m) => {
return Err(serde::de::Error::custom(format!("Failed to deserialize AnyModel {}", m)));
return Err(serde::de::Error::custom(format!(
"Failed to deserialize AnyModel {}",
m
)));
}
None => {
return Err(serde::de::Error::custom("Missing or invalid model"));
@@ -1963,6 +2041,7 @@ pub trait UpsertModelInfo {
fn table_name() -> impl IntoTableRef;
fn id_column() -> impl IntoIden + Eq + Clone;
fn generate_id() -> String;
fn order_by() -> (impl IntoColumnRef, Order);
fn get_id(&self) -> String;
fn insert_values(
self,

View File

@@ -26,7 +26,6 @@ impl<'a> DbContext<'a> {
pub fn get_or_create_workspace_meta(
&self,
workspace_id: &str,
source: &UpdateSource,
) -> Result<WorkspaceMeta> {
let workspace_meta = self.get_workspace_meta(workspace_id);
if let Some(workspace_meta) = workspace_meta {
@@ -38,7 +37,7 @@ impl<'a> DbContext<'a> {
..Default::default()
};
self.upsert_workspace_meta(&workspace_meta, source)
self.upsert_workspace_meta(&workspace_meta, &UpdateSource::Background)
}
pub fn upsert_workspace_meta(

View File

@@ -1,15 +1,17 @@
use crate::connection_or_tx::ConnectionOrTx;
use crate::db_context::DbContext;
use crate::error::Error::GenericError;
use crate::error::Result;
use crate::util::ModelPayload;
use r2d2::Pool;
use r2d2_sqlite::SqliteConnectionManager;
use rusqlite::TransactionBehavior;
use std::sync::{Arc, Mutex};
use tauri::{Manager, Runtime};
use tauri::{Manager, Runtime, State};
use tokio::sync::mpsc;
pub trait QueryManagerExt<'a, R> {
fn db_manager(&'a self) -> State<'a, QueryManager>;
fn db(&'a self) -> DbContext<'a>;
fn with_db<F, T>(&'a self, func: F) -> T
where
@@ -20,6 +22,10 @@ pub trait QueryManagerExt<'a, R> {
}
impl<'a, R: Runtime, M: Manager<R>> QueryManagerExt<'a, R> for M {
fn db_manager(&'a self) -> State<'a, QueryManager> {
self.state::<QueryManager>()
}
fn db(&'a self) -> DbContext<'a> {
let qm = self.state::<QueryManager>();
qm.inner().connect()
@@ -42,7 +48,7 @@ impl<'a, R: Runtime, M: Manager<R>> QueryManagerExt<'a, R> for M {
}
}
#[derive(Clone)]
#[derive(Debug, Clone)]
pub struct QueryManager {
pool: Arc<Mutex<Pool<SqliteConnectionManager>>>,
events_tx: mpsc::Sender<ModelPayload>,
@@ -91,9 +97,12 @@ impl QueryManager {
func(&db_context)
}
pub fn with_tx<F, T>(&self, func: F) -> Result<T>
pub fn with_tx<T, E>(
&self,
func: impl FnOnce(&DbContext) -> std::result::Result<T, E>,
) -> std::result::Result<T, E>
where
F: FnOnce(&DbContext) -> Result<T>,
E: From<crate::error::Error>,
{
let mut conn = self
.pool
@@ -112,11 +121,13 @@ impl QueryManager {
match func(&db_context) {
Ok(val) => {
tx.commit()?;
tx.commit()
.map_err(|e| GenericError(format!("Failed to commit transaction {e:?}")))?;
Ok(val)
}
Err(e) => {
tx.rollback()?;
tx.rollback()
.map_err(|e| GenericError(format!("Failed to rollback transaction {e:?}")))?;
Err(e)
}
}

View File

@@ -1,4 +1,3 @@
use std::collections::BTreeMap;
use crate::error::Result;
use crate::models::{AnyModel, Environment, Folder, GrpcRequest, HttpRequest, UpsertModelInfo, WebsocketRequest, Workspace, WorkspaceIden};
use crate::query_manager::QueryManagerExt;
@@ -6,6 +5,7 @@ use chrono::{NaiveDateTime, Utc};
use log::warn;
use nanoid::nanoid;
use serde::{Deserialize, Serialize};
use std::collections::BTreeMap;
use tauri::{AppHandle, Listener, Runtime, WebviewWindow};
use ts_rs::TS;
@@ -14,6 +14,10 @@ pub fn generate_prefixed_id(prefix: &str) -> String {
}
pub fn generate_id() -> String {
generate_id_of_length(10)
}
pub fn generate_id_of_length(n: usize) -> String {
let alphabet: [char; 57] = [
'2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j',
'k', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', 'A', 'B', 'C',
@@ -21,7 +25,7 @@ pub fn generate_id() -> String {
'X', 'Y', 'Z',
];
nanoid!(10, &alphabet)
nanoid!(n, &alphabet)
}
#[derive(Debug, Clone, Serialize, Deserialize, TS)]
@@ -101,8 +105,9 @@ pub struct WorkspaceExport {
pub resources: BatchUpsertResult,
}
#[derive(Default, Debug, Deserialize, Serialize)]
#[derive(Default, Debug, Deserialize, Serialize, TS)]
#[serde(default, rename_all = "camelCase")]
#[ts(export, export_to = "gen_util.ts")]
pub struct BatchUpsertResult {
pub workspaces: Vec<Workspace>,
pub environments: Vec<Environment>,