feat(sync): seal recording on finalize; encrypt-before-upload in pump; honor config flag (T9.12/T8.8)
This commit is contained in:
@@ -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 {
|
||||
|
||||
Reference in New Issue
Block a user