Improved:

- Confidential information has no longer stored in data.json as is.
- Synchronising progress has been shown in the notification.
- We can commit passphrases with a keyboard.
- Configuration which had not been saved yet is marked now.

Fixed:
- Hidden files have been synchronised again.

And, minor changes have been included.
This commit is contained in:
vorotamoroz
2023-01-19 18:50:06 +09:00
parent ef57fbfdda
commit c5c6deb742
6 changed files with 218 additions and 47 deletions

View File

@@ -52,7 +52,7 @@ export class LocalPouchDB extends LocalPouchDBBase {
} }
async connectRemoteCouchDB(uri: string, auth: { username: string; password: string }, disableRequestURI: boolean, passphrase: string | boolean, useDynamicIterationCount: boolean): Promise<string | { db: PouchDB.Database<EntryDoc>; info: PouchDB.Core.DatabaseInfo }> { async connectRemoteCouchDB(uri: string, auth: { username: string; password: string }, disableRequestURI: boolean, passphrase: string | false, useDynamicIterationCount: boolean): Promise<string | { db: PouchDB.Database<EntryDoc>; info: PouchDB.Core.DatabaseInfo }> {
if (!isValidRemoteCouchDBURI(uri)) return "Remote URI is not valid"; if (!isValidRemoteCouchDBURI(uri)) return "Remote URI is not valid";
if (uri.toLowerCase() != uri) return "Remote URI and database name could not contain capital letters."; if (uri.toLowerCase() != uri) return "Remote URI and database name could not contain capital letters.";
if (uri.indexOf(" ") !== -1) return "Remote URI and database name could not contain spaces."; if (uri.indexOf(" ") !== -1) return "Remote URI and database name could not contain spaces.";
@@ -154,7 +154,7 @@ export class LocalPouchDB extends LocalPouchDBBase {
}; };
const db: PouchDB.Database<EntryDoc> = new PouchDB<EntryDoc>(uri, conf); const db: PouchDB.Database<EntryDoc> = new PouchDB<EntryDoc>(uri, conf);
if (passphrase && typeof passphrase === "string") { if (passphrase !== "false" && typeof passphrase === "string") {
enableEncryption(db, passphrase, useDynamicIterationCount); enableEncryption(db, passphrase, useDynamicIterationCount);
} }
try { try {

View File

@@ -1,5 +1,5 @@
import { App, PluginSettingTab, Setting, sanitizeHTMLToDom, RequestUrlParam, requestUrl, TextAreaComponent, MarkdownRenderer, stringifyYaml } from "obsidian"; import { App, PluginSettingTab, Setting, sanitizeHTMLToDom, RequestUrlParam, requestUrl, TextAreaComponent, MarkdownRenderer, stringifyYaml } from "obsidian";
import { DEFAULT_SETTINGS, LOG_LEVEL, ObsidianLiveSyncSettings, RemoteDBSettings } from "./lib/src/types"; import { DEFAULT_SETTINGS, LOG_LEVEL, ObsidianLiveSyncSettings, ConfigPassphraseStore, RemoteDBSettings } from "./lib/src/types";
import { path2id, id2path } from "./utils"; import { path2id, id2path } from "./utils";
import { delay } from "./lib/src/utils"; import { delay } from "./lib/src/utils";
import { Semaphore } from "./lib/src/semaphore"; import { Semaphore } from "./lib/src/semaphore";
@@ -43,6 +43,9 @@ export class ObsidianLiveSyncSettingTab extends PluginSettingTab {
} }
display(): void { display(): void {
const { containerEl } = this; const { containerEl } = this;
let encrypt = this.plugin.settings.encrypt;
let passphrase = this.plugin.settings.passphrase;
let useDynamicIterationCount = this.plugin.settings.useDynamicIterationCount;
containerEl.empty(); containerEl.empty();
@@ -293,68 +296,78 @@ export class ObsidianLiveSyncSettingTab extends PluginSettingTab {
) )
); );
new Setting(containerRemoteDatabaseEl) const e2e = new Setting(containerRemoteDatabaseEl)
.setName("End to End Encryption") .setName("End to End Encryption")
.setDesc("Encrypt contents on the remote database. If you use the plugin's synchronization feature, enabling this is recommend.") .setDesc("Encrypt contents on the remote database. If you use the plugin's synchronization feature, enabling this is recommend.")
.addToggle((toggle) => .addToggle((toggle) =>
toggle.setValue(this.plugin.settings.workingEncrypt).onChange(async (value) => { toggle.setValue(encrypt).onChange(async (value) => {
if (inWizard) { if (inWizard) {
this.plugin.settings.encrypt = value; this.plugin.settings.encrypt = value;
passphrase.setDisabled(!value); passphraseSetting.setDisabled(!value);
dynamicIteration.setDisabled(!value); dynamicIteration.setDisabled(!value);
await this.plugin.saveSettings(); await this.plugin.saveSettings();
} else { } else {
this.plugin.settings.workingEncrypt = value; encrypt = value;
passphrase.setDisabled(!value); passphraseSetting.setDisabled(!value);
dynamicIteration.setDisabled(!value); dynamicIteration.setDisabled(!value);
await this.plugin.saveSettings(); await this.plugin.saveSettings();
markDirtyControl();
} }
}) })
); );
const passphrase = new Setting(containerRemoteDatabaseEl)
const markDirtyControl = () => {
passphraseSetting.controlEl.toggleClass("sls-item-dirty", passphrase != this.plugin.settings.passphrase);
e2e.controlEl.toggleClass("sls-item-dirty", encrypt != this.plugin.settings.encrypt);
dynamicIteration.controlEl.toggleClass("sls-item-dirty", useDynamicIterationCount != this.plugin.settings.useDynamicIterationCount)
}
const passphraseSetting = new Setting(containerRemoteDatabaseEl)
.setName("Passphrase") .setName("Passphrase")
.setDesc("Encrypting passphrase. If you change the passphrase of a existing database, overwriting the remote database is strongly recommended.") .setDesc("Encrypting passphrase. If you change the passphrase of a existing database, overwriting the remote database is strongly recommended.")
.addText((text) => { .addText((text) => {
text.setPlaceholder("") text.setPlaceholder("")
.setValue(this.plugin.settings.workingPassphrase) .setValue(passphrase)
.onChange(async (value) => { .onChange(async (value) => {
if (inWizard) { if (inWizard) {
this.plugin.settings.passphrase = value; this.plugin.settings.passphrase = value;
await this.plugin.saveSettings(); await this.plugin.saveSettings();
} else { } else {
this.plugin.settings.workingPassphrase = value; passphrase = value;
await this.plugin.saveSettings(); await this.plugin.saveSettings();
markDirtyControl();
} }
}); });
text.inputEl.setAttribute("type", "password"); text.inputEl.setAttribute("type", "password");
}); });
passphrase.setDisabled(!this.plugin.settings.workingEncrypt); passphraseSetting.setDisabled(!encrypt);
const dynamicIteration = new Setting(containerRemoteDatabaseEl) const dynamicIteration = new Setting(containerRemoteDatabaseEl)
.setName("Use dynamic iteration count (experimental)") .setName("Use dynamic iteration count (experimental)")
.setDesc("Balancing the encryption/decryption load against the length of the passphrase if toggled. (v0.17.5 or higher required)") .setDesc("Balancing the encryption/decryption load against the length of the passphrase if toggled. (v0.17.5 or higher required)")
.addToggle((toggle) => { .addToggle((toggle) => {
toggle.setValue(this.plugin.settings.workingUseDynamicIterationCount) toggle.setValue(useDynamicIterationCount)
.onChange(async (value) => { .onChange(async (value) => {
if (inWizard) { if (inWizard) {
this.plugin.settings.useDynamicIterationCount = value; this.plugin.settings.useDynamicIterationCount = value;
await this.plugin.saveSettings(); await this.plugin.saveSettings();
} else { } else {
this.plugin.settings.workingUseDynamicIterationCount = value; useDynamicIterationCount = value;
await this.plugin.saveSettings(); await this.plugin.saveSettings();
markDirtyControl();
} }
}); });
}) })
.setClass("wizardHidden"); .setClass("wizardHidden");
dynamicIteration.setDisabled(!this.plugin.settings.workingEncrypt); dynamicIteration.setDisabled(!encrypt);
const checkWorkingPassphrase = async (): Promise<boolean> => { const checkWorkingPassphrase = async (): Promise<boolean> => {
const settingForCheck: RemoteDBSettings = { const settingForCheck: RemoteDBSettings = {
...this.plugin.settings, ...this.plugin.settings,
encrypt: this.plugin.settings.workingEncrypt, encrypt: encrypt,
passphrase: this.plugin.settings.workingPassphrase, passphrase: passphrase,
useDynamicIterationCount: this.plugin.settings.workingUseDynamicIterationCount, useDynamicIterationCount: useDynamicIterationCount,
}; };
console.dir(settingForCheck); console.dir(settingForCheck);
const db = await this.plugin.localDatabase.connectRemoteCouchDBWithSetting(settingForCheck, this.plugin.localDatabase.isMobile); const db = await this.plugin.localDatabase.connectRemoteCouchDBWithSetting(settingForCheck, this.plugin.localDatabase.isMobile);
@@ -372,19 +385,19 @@ export class ObsidianLiveSyncSettingTab extends PluginSettingTab {
} }
}; };
const applyEncryption = async (sendToServer: boolean) => { const applyEncryption = async (sendToServer: boolean) => {
if (this.plugin.settings.workingEncrypt && this.plugin.settings.workingPassphrase == "") { if (encrypt && passphrase == "") {
Logger("If you enable encryption, you have to set the passphrase", LOG_LEVEL.NOTICE); Logger("If you enable encryption, you have to set the passphrase", LOG_LEVEL.NOTICE);
return; return;
} }
if (this.plugin.settings.workingEncrypt && !(await testCrypt())) { if (encrypt && !(await testCrypt())) {
Logger("WARNING! Your device would not support encryption.", LOG_LEVEL.NOTICE); Logger("WARNING! Your device would not support encryption.", LOG_LEVEL.NOTICE);
return; return;
} }
if (!(await checkWorkingPassphrase()) && !sendToServer) { if (!(await checkWorkingPassphrase()) && !sendToServer) {
return; return;
} }
if (!this.plugin.settings.workingEncrypt) { if (!encrypt) {
this.plugin.settings.workingPassphrase = ""; passphrase = "";
} }
this.plugin.settings.liveSync = false; this.plugin.settings.liveSync = false;
this.plugin.settings.periodicReplication = false; this.plugin.settings.periodicReplication = false;
@@ -392,11 +405,12 @@ export class ObsidianLiveSyncSettingTab extends PluginSettingTab {
this.plugin.settings.syncOnStart = false; this.plugin.settings.syncOnStart = false;
this.plugin.settings.syncOnFileOpen = false; this.plugin.settings.syncOnFileOpen = false;
this.plugin.settings.syncAfterMerge = false; this.plugin.settings.syncAfterMerge = false;
this.plugin.settings.encrypt = this.plugin.settings.workingEncrypt; this.plugin.settings.encrypt = encrypt;
this.plugin.settings.passphrase = this.plugin.settings.workingPassphrase; this.plugin.settings.passphrase = passphrase;
this.plugin.settings.useDynamicIterationCount = this.plugin.settings.workingUseDynamicIterationCount; this.plugin.settings.useDynamicIterationCount = useDynamicIterationCount;
await this.plugin.saveSettings(); await this.plugin.saveSettings();
markDirtyControl();
if (sendToServer) { if (sendToServer) {
await this.plugin.initializeDatabase(true); await this.plugin.initializeDatabase(true);
await this.plugin.markRemoteLocked(); await this.plugin.markRemoteLocked();
@@ -1326,6 +1340,45 @@ export class ObsidianLiveSyncSettingTab extends PluginSettingTab {
}) })
); );
const passphrase_options: Record<ConfigPassphraseStore, string> = {
"": "Default",
LOCALSTORAGE: "Use a custom passphrase",
ASK_AT_LAUNCH: "Ask an passphrase at every launch",
}
new Setting(containerMiscellaneousEl)
.setName("Encrypting sensitive configuration items")
.addDropdown((dropdown) =>
dropdown
.addOptions(passphrase_options)
.setValue(this.plugin.settings.configPassphraseStore)
.onChange(async (value) => {
this.plugin.settings.configPassphraseStore = value as ConfigPassphraseStore;
this.plugin.usedPassphrase = "";
confPassphraseSetting.setDisabled(this.plugin.settings.configPassphraseStore != "LOCALSTORAGE");
await this.plugin.saveSettings();
})
)
.setClass("wizardHidden");
const confPassphrase = localStorage.getItem("ls-setting-passphrase") || "";
const confPassphraseSetting = new Setting(containerMiscellaneousEl)
.setName("Passphrase of sensitive configuration items")
.setDesc("This passphrase will not be copied to another device. It will be set to `Default` until you configure it again.")
.addText((text) => {
text.setPlaceholder("")
.setValue(confPassphrase)
.onChange(async (value) => {
this.plugin.usedPassphrase = "";
localStorage.setItem("ls-setting-passphrase", value);
await this.plugin.saveSettings();
markDirtyControl();
});
text.inputEl.setAttribute("type", "password");
})
.setClass("wizardHidden");
confPassphraseSetting.setDisabled(this.plugin.settings.configPassphraseStore != "LOCALSTORAGE");
const infoApply = containerMiscellaneousEl.createEl("div", { text: `To finish setup, please select one of the presets` }); const infoApply = containerMiscellaneousEl.createEl("div", { text: `To finish setup, please select one of the presets` });
infoApply.addClass("op-warn-info"); infoApply.addClass("op-warn-info");
infoApply.addClass("wizardOnly") infoApply.addClass("wizardOnly")
@@ -1367,7 +1420,8 @@ export class ObsidianLiveSyncSettingTab extends PluginSettingTab {
pluginConfig.couchDB_URI = isCloudantURI(pluginConfig.couchDB_URI) ? "cloudant" : "self-hosted"; pluginConfig.couchDB_URI = isCloudantURI(pluginConfig.couchDB_URI) ? "cloudant" : "self-hosted";
pluginConfig.couchDB_USER = REDACTED; pluginConfig.couchDB_USER = REDACTED;
pluginConfig.passphrase = REDACTED; pluginConfig.passphrase = REDACTED;
pluginConfig.workingPassphrase = REDACTED; pluginConfig.encryptedPassphrase = REDACTED;
pluginConfig.encryptedCouchDBConnection = REDACTED;
const msgConfig = `----remote config---- const msgConfig = `----remote config----
${stringifyYaml(responseConfig)} ${stringifyYaml(responseConfig)}

View File

@@ -52,14 +52,14 @@ export class InputStringDialog extends Modal {
const { contentEl } = this; const { contentEl } = this;
contentEl.createEl("h1", { text: this.title }); contentEl.createEl("h1", { text: this.title });
// For enter to submit
new Setting(contentEl).setName(this.key).addText((text) => const formEl = contentEl.createEl("form");
new Setting(formEl).setName(this.key).addText((text) =>
text.onChange((value) => { text.onChange((value) => {
this.result = value; this.result = value;
}) })
); );
new Setting(formEl).addButton((btn) =>
new Setting(contentEl).addButton((btn) =>
btn btn
.setButtonText("Ok") .setButtonText("Ok")
.setCta() .setCta()

Submodule src/lib updated: 7be1dad0be...14fecd2411

View File

@@ -1,6 +1,6 @@
import { debounce, Notice, Plugin, TFile, addIcon, TFolder, normalizePath, TAbstractFile, Editor, MarkdownView, PluginManifest, App } from "obsidian"; import { debounce, Notice, Plugin, TFile, addIcon, TFolder, normalizePath, TAbstractFile, Editor, MarkdownView, PluginManifest, App } from "obsidian";
import { Diff, DIFF_DELETE, DIFF_EQUAL, DIFF_INSERT, diff_match_patch } from "diff-match-patch"; import { Diff, DIFF_DELETE, DIFF_EQUAL, DIFF_INSERT, diff_match_patch } from "diff-match-patch";
import { EntryDoc, LoadedEntry, ObsidianLiveSyncSettings, diff_check_result, diff_result_leaf, EntryBody, LOG_LEVEL, VER, DEFAULT_SETTINGS, diff_result, FLAGMD_REDFLAG, SYNCINFO_ID, InternalFileEntry } from "./lib/src/types"; import { EntryDoc, LoadedEntry, ObsidianLiveSyncSettings, diff_check_result, diff_result_leaf, EntryBody, LOG_LEVEL, VER, DEFAULT_SETTINGS, diff_result, FLAGMD_REDFLAG, SYNCINFO_ID, InternalFileEntry, SALT_OF_PASSPHRASE, ConfigPassphraseStore, CouchDBConnection } from "./lib/src/types";
import { PluginDataEntry, PERIODIC_PLUGIN_SWEEP, PluginList, DevicePluginList, InternalFileInfo, queueItem, FileInfo } from "./types"; import { PluginDataEntry, PERIODIC_PLUGIN_SWEEP, PluginList, DevicePluginList, InternalFileInfo, queueItem, FileInfo } from "./types";
import { getDocData, isDocContentSame } from "./lib/src/utils"; import { getDocData, isDocContentSame } from "./lib/src/utils";
import { Logger } from "./lib/src/logger"; import { Logger } from "./lib/src/logger";
@@ -10,7 +10,7 @@ import { ConflictResolveModal } from "./ConflictResolveModal";
import { ObsidianLiveSyncSettingTab } from "./ObsidianLiveSyncSettingTab"; import { ObsidianLiveSyncSettingTab } from "./ObsidianLiveSyncSettingTab";
import { DocumentHistoryModal } from "./DocumentHistoryModal"; import { DocumentHistoryModal } from "./DocumentHistoryModal";
import { applyPatch, clearAllPeriodic, clearAllTriggers, clearTrigger, disposeMemoObject, generatePatchObj, id2path, isObjectMargeApplicable, isSensibleMargeApplicable, memoIfNotExist, memoObject, path2id, retrieveMemoObject, setTrigger, tryParseJSON } from "./utils"; import { applyPatch, clearAllPeriodic, clearAllTriggers, clearTrigger, disposeMemoObject, generatePatchObj, id2path, isObjectMargeApplicable, isSensibleMargeApplicable, memoIfNotExist, memoObject, path2id, retrieveMemoObject, setTrigger, tryParseJSON } from "./utils";
import { decrypt, encrypt } from "./lib/src/e2ee_v2"; import { decrypt, encrypt, tryDecrypt } from "./lib/src/e2ee_v2";
const isDebug = false; const isDebug = false;
@@ -398,11 +398,11 @@ export default class ObsidianLiveSyncPlugin extends Plugin {
const configURIBase = "obsidian://setuplivesync?settings="; const configURIBase = "obsidian://setuplivesync?settings=";
this.addCommand({ this.addCommand({
id: "livesync-copysetupuri", id: "livesync-copysetupuri",
name: "Copy setup URI", name: "Copy the setup URI",
callback: async () => { callback: async () => {
const encryptingPassphrase = await askString(this.app, "Encrypt your settings", "Passphrase", ""); const encryptingPassphrase = await askString(this.app, "Encrypt your settings", "The passphrase to encrypt the setup URI", "");
if (encryptingPassphrase === false) return; if (encryptingPassphrase === false) return;
const setting = { ...this.settings }; const setting = { ...this.settings, configPassphraseStore: "", encryptedCouchDBConnection: "", encryptedPassphrase: "" };
const keys = Object.keys(setting) as (keyof ObsidianLiveSyncSettings)[]; const keys = Object.keys(setting) as (keyof ObsidianLiveSyncSettings)[];
for (const k of keys) { for (const k of keys) {
if (JSON.stringify(k in setting ? setting[k] : "") == JSON.stringify(k in DEFAULT_SETTINGS ? DEFAULT_SETTINGS[k] : "*")) { if (JSON.stringify(k in setting ? setting[k] : "") == JSON.stringify(k in DEFAULT_SETTINGS ? DEFAULT_SETTINGS[k] : "*")) {
@@ -417,11 +417,11 @@ export default class ObsidianLiveSyncPlugin extends Plugin {
}); });
this.addCommand({ this.addCommand({
id: "livesync-copysetupurifull", id: "livesync-copysetupurifull",
name: "Copy setup URI (Full)", name: "Copy the setup URI (Full)",
callback: async () => { callback: async () => {
const encryptingPassphrase = await askString(this.app, "Encrypt your settings", "Passphrase", ""); const encryptingPassphrase = await askString(this.app, "Encrypt your settings", "The passphrase to encrypt the setup URI", "");
if (encryptingPassphrase === false) return; if (encryptingPassphrase === false) return;
const setting = { ...this.settings }; const setting = { ...this.settings, configPassphraseStore: "", encryptedCouchDBConnection: "", encryptedPassphrase: "" };
const encryptedSetting = encodeURIComponent(await encrypt(JSON.stringify(setting), encryptingPassphrase, false)); const encryptedSetting = encodeURIComponent(await encrypt(JSON.stringify(setting), encryptingPassphrase, false));
const uri = `${configURIBase}${encryptedSetting}`; const uri = `${configURIBase}${encryptedSetting}`;
await navigator.clipboard.writeText(uri); await navigator.clipboard.writeText(uri);
@@ -430,7 +430,7 @@ export default class ObsidianLiveSyncPlugin extends Plugin {
}); });
this.addCommand({ this.addCommand({
id: "livesync-opensetupuri", id: "livesync-opensetupuri",
name: "Open setup URI", name: "Open the setup URI",
callback: async () => { callback: async () => {
const setupURI = await askString(this.app, "Easy setup", "Set up URI", `${configURIBase}aaaaa`); const setupURI = await askString(this.app, "Easy setup", "Set up URI", `${configURIBase}aaaaa`);
if (setupURI === false) return; if (setupURI === false) return;
@@ -446,16 +446,20 @@ export default class ObsidianLiveSyncPlugin extends Plugin {
const setupWizard = async (confString: string) => { const setupWizard = async (confString: string) => {
try { try {
const oldConf = JSON.parse(JSON.stringify(this.settings)); const oldConf = JSON.parse(JSON.stringify(this.settings));
const encryptingPassphrase = await askString(this.app, "Passphrase", "Passphrase for your settings", ""); const encryptingPassphrase = await askString(this.app, "Passphrase", "The passphrase to decrypt your setup URI", "");
if (encryptingPassphrase === false) return; if (encryptingPassphrase === false) return;
const newConf = await JSON.parse(await decrypt(confString, encryptingPassphrase, false)); const newConf = await JSON.parse(await decrypt(confString, encryptingPassphrase, false));
if (newConf) { if (newConf) {
const result = await askYesNo(this.app, "Importing LiveSync's conf, OK?"); const result = await askYesNo(this.app, "Importing LiveSync's conf, OK?");
if (result == "yes") { if (result == "yes") {
const newSettingW = Object.assign({}, DEFAULT_SETTINGS, newConf); const newSettingW = Object.assign({}, DEFAULT_SETTINGS, newConf) as ObsidianLiveSyncSettings;
this.localDatabase.closeReplication(); this.localDatabase.closeReplication();
this.settings.suspendFileWatching = true; this.settings.suspendFileWatching = true;
console.dir(newSettingW); console.dir(newSettingW);
// Back into the default method once.
newSettingW.configPassphraseStore = "";
newSettingW.encryptedPassphrase = "";
newSettingW.encryptedCouchDBConnection = "";
const setupJustImport = "Just import setting"; const setupJustImport = "Just import setting";
const setupAsNew = "Set it up as secondary or subsequent device"; const setupAsNew = "Set it up as secondary or subsequent device";
const setupAgain = "Reconfigure and reconstitute the data"; const setupAgain = "Reconfigure and reconstitute the data";
@@ -464,9 +468,11 @@ export default class ObsidianLiveSyncPlugin extends Plugin {
const setupType = await askSelectString(this.app, "How would you like to set it up?", [setupAsNew, setupAgain, setupJustImport, setupManually]); const setupType = await askSelectString(this.app, "How would you like to set it up?", [setupAsNew, setupAgain, setupJustImport, setupManually]);
if (setupType == setupJustImport) { if (setupType == setupJustImport) {
this.settings = newSettingW; this.settings = newSettingW;
this.usedPassphrase = "";
await this.saveSettings(); await this.saveSettings();
} else if (setupType == setupAsNew) { } else if (setupType == setupAsNew) {
this.settings = newSettingW; this.settings = newSettingW;
this.usedPassphrase = "";
await this.saveSettings(); await this.saveSettings();
await this.resetLocalOldDatabase(); await this.resetLocalOldDatabase();
await this.resetLocalDatabase(); await this.resetLocalDatabase();
@@ -478,6 +484,8 @@ export default class ObsidianLiveSyncPlugin extends Plugin {
if (await askSelectString(this.app, "Do you really want to do this?", ["Cancel", confirm]) != confirm) { if (await askSelectString(this.app, "Do you really want to do this?", ["Cancel", confirm]) != confirm) {
return; return;
} }
this.settings = newSettingW;
this.usedPassphrase = "";
await this.saveSettings(); await this.saveSettings();
await this.resetLocalOldDatabase(); await this.resetLocalOldDatabase();
await this.resetLocalDatabase(); await this.resetLocalDatabase();
@@ -494,6 +502,7 @@ export default class ObsidianLiveSyncPlugin extends Plugin {
if (keepLocalDB == "yes" && keepRemoteDB == "yes") { if (keepLocalDB == "yes" && keepRemoteDB == "yes") {
// nothing to do. so peaceful. // nothing to do. so peaceful.
this.settings = newSettingW; this.settings = newSettingW;
this.usedPassphrase = "";
await this.saveSettings(); await this.saveSettings();
const replicate = await askYesNo(this.app, "Unlock and replicate?"); const replicate = await askYesNo(this.app, "Unlock and replicate?");
if (replicate == "yes") { if (replicate == "yes") {
@@ -513,6 +522,7 @@ export default class ObsidianLiveSyncPlugin extends Plugin {
} }
let initDB; let initDB;
this.settings = newSettingW; this.settings = newSettingW;
this.usedPassphrase = "";
await this.saveSettings(); await this.saveSettings();
if (keepLocalDB == "no") { if (keepLocalDB == "no") {
this.resetLocalOldDatabase(); this.resetLocalOldDatabase();
@@ -715,11 +725,93 @@ export default class ObsidianLiveSyncPlugin extends Plugin {
return await this.localDatabase.initializeDatabase(); return await this.localDatabase.initializeDatabase();
} }
usedPassphrase = "";
getPassphrase(settings: ObsidianLiveSyncSettings) {
const methods: Record<ConfigPassphraseStore, (() => Promise<string | false>)> = {
"": () => Promise.resolve("*"),
"LOCALSTORAGE": () => Promise.resolve(localStorage.getItem("ls-setting-passphrase") ?? false),
"ASK_AT_LAUNCH": () => askString(this.app, "Passphrase", "passphrase", "")
}
const method = settings.configPassphraseStore;
const methodFunc = method in methods ? methods[method] : methods[""];
return methodFunc();
}
async decryptConfigurationItem(encrypted: string, passphrase: string) {
const dec = await tryDecrypt(encrypted, passphrase + SALT_OF_PASSPHRASE, false);
if (dec) {
this.usedPassphrase = passphrase;
return dec;
}
return false;
}
tryDecodeJson(encoded: string | false): object | false {
try {
if (!encoded) return false;
return JSON.parse(encoded);
} catch (ex) {
return false;
}
}
async encryptConfigurationItem(src: string, settings: ObsidianLiveSyncSettings) {
if (this.usedPassphrase != "") {
return await encrypt(src, this.usedPassphrase + SALT_OF_PASSPHRASE, false);
}
const passphrase = await this.getPassphrase(settings);
if (passphrase === false) {
Logger("Could not determine passphrase to save data.json! You probably make the configuration sure again!", LOG_LEVEL.URGENT);
return "";
}
const dec = await encrypt(src, passphrase + SALT_OF_PASSPHRASE, false);
if (dec) {
this.usedPassphrase = passphrase;
return dec;
}
return "";
}
async loadSettings() { async loadSettings() {
this.settings = Object.assign({}, DEFAULT_SETTINGS, await this.loadData()); const settings = Object.assign({}, DEFAULT_SETTINGS, await this.loadData()) as ObsidianLiveSyncSettings;
this.settings.workingEncrypt = this.settings.encrypt; const passphrase = await this.getPassphrase(settings);
this.settings.workingPassphrase = this.settings.passphrase; if (passphrase === false) {
Logger("Could not determine passphrase for reading data.json! DO NOT synchronize with the remote before making sure your configuration is!", LOG_LEVEL.URGENT);
} else {
if (settings.encryptedCouchDBConnection) {
const keys = ["couchDB_URI", "couchDB_USER", "couchDB_PASSWORD", "couchDB_DBNAME"] as (keyof CouchDBConnection)[];
const decrypted = this.tryDecodeJson(await this.decryptConfigurationItem(settings.encryptedCouchDBConnection, passphrase)) as CouchDBConnection;
if (decrypted) {
for (const key of keys) {
if (key in decrypted) {
settings[key] = decrypted[key]
}
}
} else {
Logger("Could not decrypt passphrase for reading data.json! DO NOT synchronize with the remote before making sure your configuration is!", LOG_LEVEL.URGENT);
for (const key of keys) {
settings[key] = "";
}
}
}
if (settings.encrypt && settings.encryptedPassphrase) {
const encrypted = settings.encryptedPassphrase;
const decrypted = await this.decryptConfigurationItem(encrypted, passphrase);
if (decrypted) {
settings.passphrase = decrypted;
} else {
Logger("Could not decrypt passphrase for reading data.json! DO NOT synchronize with the remote before making sure your configuration is!", LOG_LEVEL.URGENT);
settings.passphrase = "";
}
}
}
this.settings = settings;
if ("workingEncrypt" in this.settings) delete this.settings.workingEncrypt;
if ("workingPassphrase" in this.settings) delete this.settings.workingPassphrase;
// Delete this feature to avoid problems on mobile. // Delete this feature to avoid problems on mobile.
this.settings.disableRequestURI = true; this.settings.disableRequestURI = true;
@@ -751,7 +843,29 @@ export default class ObsidianLiveSyncPlugin extends Plugin {
const lsKey = "obsidian-live-sync-vaultanddevicename-" + this.getVaultName(); const lsKey = "obsidian-live-sync-vaultanddevicename-" + this.getVaultName();
localStorage.setItem(lsKey, this.deviceAndVaultName || ""); localStorage.setItem(lsKey, this.deviceAndVaultName || "");
await this.saveData(this.settings); const settings = { ...this.settings };
if (this.usedPassphrase == "" && !await this.getPassphrase(settings)) {
Logger("Could not determine passphrase for saving data.json! Our data.json have insecure items!", LOG_LEVEL.NOTICE);
} else {
if (settings.couchDB_PASSWORD != "" || settings.couchDB_URI != "" || settings.couchDB_USER != "" || settings.couchDB_DBNAME) {
const connectionSetting: CouchDBConnection = {
couchDB_DBNAME: settings.couchDB_DBNAME,
couchDB_PASSWORD: settings.couchDB_PASSWORD,
couchDB_URI: settings.couchDB_URI,
couchDB_USER: settings.couchDB_USER,
};
settings.encryptedCouchDBConnection = await this.encryptConfigurationItem(JSON.stringify(connectionSetting), settings);
settings.couchDB_PASSWORD = "";
settings.couchDB_DBNAME = "";
settings.couchDB_URI = "";
settings.couchDB_USER = "";
}
if (settings.encrypt && settings.passphrase != "") {
settings.encryptedPassphrase = await this.encryptConfigurationItem(settings.passphrase, settings);
settings.passphrase = "";
}
}
await this.saveData(settings);
this.localDatabase.settings = this.settings; this.localDatabase.settings = this.settings;
this.triggerRealizeSettingSyncMode(); this.triggerRealizeSettingSyncMode();
} }
@@ -1020,7 +1134,6 @@ export default class ObsidianLiveSyncPlugin extends Plugin {
} }
recentProcessedInternalFiles = [] as string[]; recentProcessedInternalFiles = [] as string[];
async watchVaultRawEventsAsync(path: string) { async watchVaultRawEventsAsync(path: string) {
const stat = await this.app.vault.adapter.stat(path); const stat = await this.app.vault.adapter.stat(path);
// sometimes folder is coming. // sometimes folder is coming.
if (stat && stat.type != "file") return; if (stat && stat.type != "file") return;

View File

@@ -248,3 +248,7 @@ div.sls-setting-menu-btn {
.sls-setting:not(.isWizard) .wizardOnly { .sls-setting:not(.isWizard) .wizardOnly {
display: none; display: none;
} }
.sls-item-dirty::before {
content: "✏";
}