feat(sync): seal recording on finalize; encrypt-before-upload in pump; honor config flag (T9.12/T8.8)

This commit is contained in:
iamdoubz
2026-07-03 08:47:36 -05:00
parent 97bda924b3
commit 1824475cef
+37 -5
View File
@@ -545,6 +545,14 @@ pub async fn stop_recording(
// above, and only when retention is off.
if !session.retention {
let _ = std::fs::remove_file(&session.wav_path);
} else if crate::vault::is_unlocked() {
// Seal the retained recording at rest when the vault is unlocked (T8.8).
// Runs before the sync enqueue below, so any uploaded copy is ciphertext.
if let Ok(raw) = std::fs::read(&session.wav_path) {
if let Ok(sealed) = crate::vault::seal(&raw) {
let _ = std::fs::write(&session.wav_path, sealed);
}
}
}
// Sync-on-finalize (T9.5, FR-SYNC-5): enqueue configured artifacts for
@@ -1799,6 +1807,7 @@ pub struct SyncTargetConfigInput {
pub upload_recording: Option<bool>,
pub trigger_on_finalize: Option<bool>,
pub allow_plaintext_lan: Option<bool>,
pub encrypt_before_upload: Option<bool>,
}
#[tauri::command]
@@ -1842,7 +1851,7 @@ pub async fn add_sync_target(
upload_recording: config.upload_recording.unwrap_or(false),
trigger_on_finalize: config.trigger_on_finalize.unwrap_or(true),
allow_plaintext_lan: config.allow_plaintext_lan.unwrap_or(false),
encrypt_before_upload: false, // 9c (vault) is on hold
encrypt_before_upload: config.encrypt_before_upload.unwrap_or(false),
created_at: now_unix(),
};
state
@@ -1909,6 +1918,9 @@ pub async fn update_sync_target(
if let Some(v) = config.allow_plaintext_lan {
row.allow_plaintext_lan = v;
}
if let Some(v) = config.encrypt_before_upload {
row.encrypt_before_upload = v;
}
state
.store
.update_sync_target(row.clone())
@@ -2328,7 +2340,9 @@ pub(crate) async fn enqueue_meeting_sync(
async fn upload_job(
target: &dyn crate::sync::SyncTarget,
row: &crate::storage::SyncJobRow,
encrypt: bool,
) -> Result<u64, crate::sync::SyncError> {
use crate::sync::SyncError;
let remote_dir = row
.remote_path
.rsplit_once('/')
@@ -2336,9 +2350,27 @@ async fn upload_job(
.unwrap_or("");
target.ensure_dir(remote_dir).await?;
let (tx, rx) = std::sync::mpsc::channel();
target
.put(Path::new(&row.local_path), &row.remote_path, tx)
.await?;
if encrypt {
// Client-side encryption before upload (T9.12, FR-SYNC-10): seal to a
// temp file (idempotent if already sealed at rest) so the destination
// only ever holds ciphertext. Requires the vault to be unlocked.
let bytes = tokio::fs::read(&row.local_path)
.await
.map_err(|e| SyncError::Upload(e.to_string()))?;
let sealed =
crate::vault::ensure_sealed(&bytes).map_err(|e| SyncError::Upload(e.to_string()))?;
let tmp = std::env::temp_dir().join(format!("wa-enc-{}.bin", row.id));
tokio::fs::write(&tmp, &sealed)
.await
.map_err(|e| SyncError::Upload(e.to_string()))?;
let result = target.put(&tmp, &row.remote_path, tx).await;
let _ = tokio::fs::remove_file(&tmp).await;
result?;
} else {
target
.put(Path::new(&row.local_path), &row.remote_path, tx)
.await?;
}
let sent = rx
.try_iter()
.last()
@@ -2370,7 +2402,7 @@ pub(crate) async fn pump_sync(app: &AppHandle, store: &dyn crate::storage::Store
emit_sync_job(app, &job);
let outcome = match crate::sync::build_sync_target(&target) {
Ok(t) => upload_job(t.as_ref(), &job).await,
Ok(t) => upload_job(t.as_ref(), &job, target.encrypt_before_upload).await,
Err(e) => Err(e),
};
match outcome {