diff --git a/apps/desktop/src/components/editor/EditorSettingsDialog.vue b/apps/desktop/src/components/editor/EditorSettingsDialog.vue
index 80f30ed9f2..076ce39ecc 100644
--- a/apps/desktop/src/components/editor/EditorSettingsDialog.vue
+++ b/apps/desktop/src/components/editor/EditorSettingsDialog.vue
@@ -52,11 +52,14 @@ import {
aiTestConnection,
checkMcpServerStatus,
installMcpServer,
+ forgetWebdavSyncSecretsPassphrase,
forgetWebdavSavedPassword,
listSystemFonts,
+ saveWebdavSyncSecretsPreference,
saveWebdavSavedPassword,
webdavPasswordStatus,
webdavSyncDownload,
+ webdavSyncSecretsStatus,
webdavSyncTest,
webdavSyncUpload,
type AiModelInfo,
@@ -1278,13 +1281,14 @@ const webdavHasSavedPassword = ref(false);
const webdavRemotePath = ref(localStorage.getItem("dbx-webdav-remote-path") || DEFAULT_WEB_DAV_REMOTE_PATH);
const webdavSyncSecrets = ref(false);
const webdavSecretsPassphrase = ref("");
+const webdavHasSavedSecretsPassphrase = ref(false);
const webdavAutoUploadEnabled = ref(localStorage.getItem("dbx-webdav-auto-upload-enabled") === "true");
const webdavAutoUploadIntervalMinutes = ref(Number(localStorage.getItem("dbx-webdav-auto-upload-interval-minutes") || String(DEFAULT_WEB_DAV_AUTO_UPLOAD_INTERVAL_MINUTES)));
const webdavBusy = ref<"" | "test" | "upload" | "download">("");
const webdavMessage = ref("");
const webdavError = ref(false);
-const webdavReady = computed(() => !!webdavEndpoint.value.trim() && !webdavBusy.value && (!webdavSyncSecrets.value || !!webdavSecretsPassphrase.value.trim()));
+const webdavReady = computed(() => !!webdavEndpoint.value.trim() && !webdavBusy.value && (!webdavSyncSecrets.value || !!webdavSecretsPassphrase.value.trim() || webdavHasSavedSecretsPassphrase.value));
function currentWebDavConfig(): WebDavConfig {
return {
@@ -1320,6 +1324,7 @@ async function runWebDavAction(kind: "test" | "upload" | "download", action: ()
try {
rememberWebDavFields();
await applyWebDavPasswordPreference();
+ await applyWebDavSyncSecretsPreference();
setWebDavResult(await action());
} catch (e: any) {
setWebDavResult(e?.message || String(e), true);
@@ -1356,6 +1361,40 @@ async function applyWebDavPasswordPreference() {
}
}
+async function refreshWebDavSyncSecretsStatus() {
+ try {
+ const status = await webdavSyncSecretsStatus();
+ webdavSyncSecrets.value = status.enabled;
+ webdavHasSavedSecretsPassphrase.value = status.hasSavedPassphrase;
+ } catch {
+ webdavSyncSecrets.value = false;
+ webdavHasSavedSecretsPassphrase.value = false;
+ }
+}
+
+async function applyWebDavSyncSecretsPreference() {
+ const passphrase = webdavSecretsPassphrase.value.trim();
+ if (!webdavSyncSecrets.value) {
+ await saveWebdavSyncSecretsPreference(false);
+ return;
+ }
+ await saveWebdavSyncSecretsPreference(true, passphrase || undefined);
+ if (passphrase) {
+ webdavHasSavedSecretsPassphrase.value = true;
+ webdavSecretsPassphrase.value = "";
+ }
+}
+
+async function clearWebDavSyncSecretsPassphrase() {
+ try {
+ await forgetWebdavSyncSecretsPassphrase();
+ webdavHasSavedSecretsPassphrase.value = false;
+ webdavSecretsPassphrase.value = "";
+ } catch (e: any) {
+ setWebDavResult(e?.message || String(e), true);
+ }
+}
+
async function testWebDav() {
await runWebDavAction("test", async () => {
await webdavSyncTest(currentWebDavConfig());
@@ -1425,7 +1464,9 @@ watch(
editDebugLoggingEnabled.value = settingsStore.desktopSettings.debug_logging_enabled;
editSidebarTablePageSize.value = settingsStore.desktopSettings.sidebar_table_page_size ?? DEFAULT_SIDEBAR_TABLE_PAGE_SIZE;
webdavPassword.value = "";
+ webdavSecretsPassphrase.value = "";
await refreshWebDavPasswordStatus();
+ await refreshWebDavSyncSecretsStatus();
syncAiEditState();
if (!isWeb && activeSettingsTab.value === "mcp") void refreshMcpStatus();
if (!isWeb && activeSettingsTab.value === "ai" && aiIsCodexCli.value) void ensureCodexMcpStatus();
@@ -3173,7 +3214,21 @@ onUnmounted(cleanupPreviewEditor);
-
+
{{ t("settings.syncSecretsPassphraseDescription") }}
diff --git a/apps/desktop/src/lib/backend/api.ts b/apps/desktop/src/lib/backend/api.ts
index 877b99615d..697165a295 100644
--- a/apps/desktop/src/lib/backend/api.ts
+++ b/apps/desktop/src/lib/backend/api.ts
@@ -223,6 +223,9 @@ export const webdavSyncTest = forward("webdavSyncTest");
export const webdavPasswordStatus = forward("webdavPasswordStatus");
export const saveWebdavSavedPassword = forward("saveWebdavSavedPassword");
export const forgetWebdavSavedPassword = forward("forgetWebdavSavedPassword");
+export const webdavSyncSecretsStatus = forward("webdavSyncSecretsStatus");
+export const saveWebdavSyncSecretsPreference = forward("saveWebdavSyncSecretsPreference");
+export const forgetWebdavSyncSecretsPassphrase = forward("forgetWebdavSyncSecretsPassphrase");
export const webdavSyncUpload = forward("webdavSyncUpload");
export const webdavSyncDownload = forward("webdavSyncDownload");
export const saveAiConversation = forward("saveAiConversation");
diff --git a/apps/desktop/src/lib/backend/http.ts b/apps/desktop/src/lib/backend/http.ts
index 693799e656..f689c1992b 100644
--- a/apps/desktop/src/lib/backend/http.ts
+++ b/apps/desktop/src/lib/backend/http.ts
@@ -1095,6 +1095,11 @@ export interface WebDavPasswordStatus {
hasSavedPassword: boolean;
}
+export interface WebDavSyncSecretsStatus {
+ enabled: boolean;
+ hasSavedPassphrase: boolean;
+}
+
export async function webdavSyncTest(config: WebDavConfig): Promise {
return post("/api/cloud-sync/webdav/test", { config });
}
@@ -1111,6 +1116,18 @@ export async function forgetWebdavSavedPassword(config: WebDavConfig): Promise {
+ return post("/api/cloud-sync/webdav/sync-secrets-status", {});
+}
+
+export async function saveWebdavSyncSecretsPreference(enabled: boolean, passphrase?: string): Promise {
+ return post("/api/cloud-sync/webdav/save-sync-secrets-preference", { enabled, passphrase });
+}
+
+export async function forgetWebdavSyncSecretsPassphrase(): Promise {
+ return post("/api/cloud-sync/webdav/forget-sync-secrets-passphrase", {});
+}
+
export async function webdavSyncUpload(config: WebDavConfig, editorSettings?: unknown, secretsPassphrase?: string): Promise {
return post("/api/cloud-sync/webdav/upload", { config, editorSettings, secretsPassphrase });
}
diff --git a/apps/desktop/src/lib/backend/tauri.ts b/apps/desktop/src/lib/backend/tauri.ts
index 41f81af92a..e3aad86622 100644
--- a/apps/desktop/src/lib/backend/tauri.ts
+++ b/apps/desktop/src/lib/backend/tauri.ts
@@ -190,6 +190,11 @@ export interface WebDavPasswordStatus {
hasSavedPassword: boolean;
}
+export interface WebDavSyncSecretsStatus {
+ enabled: boolean;
+ hasSavedPassphrase: boolean;
+}
+
export interface QueryPagination {
limit: number;
offset: number;
@@ -428,6 +433,18 @@ export async function forgetWebdavSavedPassword(config: WebDavConfig): Promise {
+ return invoke("webdav_sync_secrets_status");
+}
+
+export async function saveWebdavSyncSecretsPreference(enabled: boolean, passphrase?: string): Promise {
+ return invoke("save_webdav_sync_secrets_preference", { enabled, passphrase });
+}
+
+export async function forgetWebdavSyncSecretsPassphrase(): Promise {
+ return invoke("forget_webdav_sync_secrets_passphrase");
+}
+
export async function webdavSyncUpload(config: WebDavConfig, editorSettings?: unknown, secretsPassphrase?: string): Promise {
return invoke("webdav_sync_upload", { config, editorSettings, secretsPassphrase });
}
diff --git a/crates/dbx-core/src/cloud_sync.rs b/crates/dbx-core/src/cloud_sync.rs
index cbc72e8b76..8b9f989a27 100644
--- a/crates/dbx-core/src/cloud_sync.rs
+++ b/crates/dbx-core/src/cloud_sync.rs
@@ -11,9 +11,10 @@ use sha2::{Digest, Sha256};
use crate::ai::AiConfig;
use crate::connection_secrets::{
- MQ_AUTH_API_KEY_VALUE_KEY, MQ_AUTH_CLIENT_SECRET_KEY, MQ_AUTH_PASSWORD_KEY, MQ_AUTH_TOKEN_KEY, MQ_TOKEN_SIGNING_KEY,
+ MQ_AUTH_API_KEY_VALUE_KEY, MQ_AUTH_CLIENT_SECRET_KEY, MQ_AUTH_PASSWORD_KEY, MQ_AUTH_TOKEN_KEY,
+ MQ_TOKEN_SIGNING_KEY, NACOS_AUTH_PASSWORD_KEY,
};
-use crate::models::connection::{ConnectionConfig, TransportLayerConfig};
+use crate::models::connection::{ConnectionConfig, DatabaseType, TransportLayerConfig};
use crate::saved_sql::SavedSqlLibrary;
use crate::storage::{DesktopSettings, Storage};
@@ -31,6 +32,7 @@ const SECRET_KEYS: &[&str] = &[
MQ_AUTH_API_KEY_VALUE_KEY,
MQ_AUTH_CLIENT_SECRET_KEY,
MQ_TOKEN_SIGNING_KEY,
+ NACOS_AUTH_PASSWORD_KEY,
];
const SSH_TUNNEL_SECRET_PREFIX: &str = "ssh_tunnels.";
const TRANSPORT_LAYER_SECRET_PREFIX: &str = "transport_layers.";
@@ -50,6 +52,13 @@ pub struct WebDavPasswordStatus {
pub has_saved_password: bool,
}
+#[derive(Debug, Clone, Copy, Serialize, Deserialize)]
+#[serde(rename_all = "camelCase")]
+pub struct WebDavSyncSecretsStatus {
+ pub enabled: bool,
+ pub has_saved_passphrase: bool,
+}
+
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct SyncSnapshot {
@@ -143,6 +152,21 @@ pub async fn build_sync_snapshot(
})
}
+pub async fn build_sync_snapshot_with_saved_secrets(
+ storage: &Storage,
+ app_version: impl Into,
+ editor_settings: Option,
+ secrets_passphrase: Option<&str>,
+) -> Result {
+ match normalized_passphrase(secrets_passphrase) {
+ Some(passphrase) => build_sync_snapshot(storage, app_version, editor_settings, Some(passphrase)).await,
+ None => {
+ let saved_passphrase = resolve_webdav_sync_secrets_passphrase(storage).await?;
+ build_sync_snapshot(storage, app_version, editor_settings, saved_passphrase.as_deref()).await
+ }
+ }
+}
+
pub async fn apply_sync_snapshot(
storage: &Storage,
snapshot: &SyncSnapshot,
@@ -214,6 +238,46 @@ pub async fn resolve_webdav_password(storage: &Storage, config: &mut WebDavConfi
Ok(())
}
+pub async fn webdav_sync_secrets_status(storage: &Storage) -> Result {
+ Ok(WebDavSyncSecretsStatus {
+ enabled: storage.load_webdav_sync_secrets_enabled().await?,
+ has_saved_passphrase: storage.load_webdav_sync_secrets_passphrase_blob().await?.is_some(),
+ })
+}
+
+pub async fn save_webdav_sync_secrets_preference(
+ storage: &Storage,
+ enabled: bool,
+ passphrase: Option<&str>,
+) -> Result<(), String> {
+ let normalized = normalized_passphrase(passphrase);
+ let blob = match normalized {
+ Some(passphrase) => {
+ let secret = storage.load_or_create_local_device_secret().await?;
+ let blob = encrypt_text_with_secret(passphrase, &secret)?;
+ Some(serde_json::to_value(blob).map_err(|e| e.to_string())?)
+ }
+ None => None,
+ };
+ storage.save_webdav_sync_secrets_preference(enabled, blob.as_ref()).await
+}
+
+pub async fn forget_webdav_sync_secrets_passphrase(storage: &Storage) -> Result<(), String> {
+ storage.delete_webdav_sync_secrets_passphrase_blob().await
+}
+
+pub async fn resolve_webdav_sync_secrets_passphrase(storage: &Storage) -> Result