mirror of
https://github.com/vrtmrz/obsidian-livesync.git
synced 2026-05-14 19:41:16 +00:00
feat: Chunk ID namespace is now separated from the E2EE passphrase by introducing userHashSalt.
This commit is contained in:
@@ -6,6 +6,7 @@ import {
|
||||
SuffixDatabaseName,
|
||||
} from "../../../lib/src/common/types.ts";
|
||||
import { Logger } from "../../../lib/src/common/logger.ts";
|
||||
import { generateUserHashSalt } from "../../../lib/src/common/utils.ts";
|
||||
import { LiveSyncSetting as Setting } from "./LiveSyncSetting.ts";
|
||||
import type { ObsidianLiveSyncSettingTab } from "./ObsidianLiveSyncSettingTab.ts";
|
||||
import type { PageFunctions } from "./SettingPane.ts";
|
||||
@@ -156,6 +157,42 @@ export function panePatches(this: ObsidianLiveSyncSettingTab, paneEl: HTMLElemen
|
||||
await this.core.localDatabase._prepareHashFunctions();
|
||||
});
|
||||
});
|
||||
|
||||
void addPanel(paneEl, "Chunk ID Namespace").then((paneEl) => {
|
||||
paneEl.createDiv({
|
||||
text: "Manage the Chunk ID Namespace Salt (userHashSalt). This value is used as a seed for generating chunk IDs. If you change this value, chunk IDs will be regenerated and you must rebuild the database.",
|
||||
cls: "op-warn-info",
|
||||
});
|
||||
|
||||
new Setting(paneEl)
|
||||
.autoWireText("userHashSalt", { holdValue: true })
|
||||
.setClass("wizardHidden")
|
||||
.addApplyButton(["userHashSalt"]);
|
||||
|
||||
new Setting(paneEl)
|
||||
.setName("Generate New Salt")
|
||||
.setDesc(
|
||||
"Generate a new random salt for the Chunk ID namespace. After generating, a database rebuild is strongly recommended."
|
||||
)
|
||||
.addButton((button) => {
|
||||
button
|
||||
.setButtonText("Generate New Salt")
|
||||
.setCta()
|
||||
.onClick(async () => {
|
||||
const confirmed = await this.core.confirm.askYesNo(
|
||||
"Generating a new salt will invalidate existing chunk IDs. Until you rebuild the database, deduplication will be inefficient. Are you sure to generate a new salt now?"
|
||||
);
|
||||
if (confirmed) {
|
||||
const newSalt = generateUserHashSalt();
|
||||
this.editingSettings.userHashSalt = newSalt;
|
||||
await this.saveSettings(["userHashSalt"]);
|
||||
Logger(`New Chunk ID Namespace Salt generated.`, LOG_LEVEL_NOTICE);
|
||||
this.requestUpdate();
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
void addPanel(paneEl, "Edge case addressing (Behaviour)").then((paneEl) => {
|
||||
new Setting(paneEl).autoWireToggle("doNotSuspendOnFetching");
|
||||
new Setting(paneEl).setClass("wizardHidden").autoWireToggle("doNotDeleteFolder");
|
||||
|
||||
@@ -7,7 +7,7 @@ import {
|
||||
REMOTE_MINIO,
|
||||
REMOTE_P2P,
|
||||
} from "../../lib/src/common/types.ts";
|
||||
import { generatePatchObj, isObjectDifferent } from "../../lib/src/common/utils.ts";
|
||||
import { generatePatchObj, isObjectDifferent, generateUserHashSalt } from "../../lib/src/common/utils.ts";
|
||||
import Intro from "./SetupWizard/dialogs/Intro.svelte";
|
||||
import SelectMethodNewUser from "./SetupWizard/dialogs/SelectMethodNewUser.svelte";
|
||||
import SelectMethodExisting from "./SetupWizard/dialogs/SelectMethodExisting.svelte";
|
||||
@@ -328,6 +328,9 @@ export class SetupManager extends AbstractModule {
|
||||
}
|
||||
if (confirm) {
|
||||
extra();
|
||||
if (userMode === UserMode.NewUser && !newConf.userHashSalt) {
|
||||
newConf.userHashSalt = generateUserHashSalt();
|
||||
}
|
||||
await this.applySetting(newConf, userMode);
|
||||
if (userMode === UserMode.NewUser) {
|
||||
// For new users, schedule a rebuild everything.
|
||||
|
||||
@@ -154,4 +154,47 @@ describe("SetupManager", () => {
|
||||
);
|
||||
expect(setting.currentSettings().activeConfigurationId).toBe("legacy-couchdb");
|
||||
});
|
||||
|
||||
it("onConfirmApplySettingsFromWizard should generate userHashSalt for NewUser when absent", async () => {
|
||||
const { manager, setting, dialogManager, core } = createSetupManager();
|
||||
const randomSpy = vi.spyOn(globalThis.crypto, "getRandomValues").mockImplementation((array) => {
|
||||
const target = array as Uint8Array;
|
||||
for (let i = 0; i < target.length; i++) {
|
||||
target[i] = 0xab;
|
||||
}
|
||||
return array;
|
||||
});
|
||||
dialogManager.openWithExplicitCancel.mockResolvedValueOnce(true);
|
||||
|
||||
await manager.onConfirmApplySettingsFromWizard(
|
||||
{
|
||||
...setting.currentSettings(),
|
||||
userHashSalt: "",
|
||||
},
|
||||
UserMode.NewUser
|
||||
);
|
||||
|
||||
expect(setting.currentSettings().userHashSalt).toBe("abababababababababababababababab");
|
||||
expect(core.rebuilder.scheduleRebuild).toHaveBeenCalledTimes(1);
|
||||
randomSpy.mockRestore();
|
||||
});
|
||||
|
||||
it("onConfirmApplySettingsFromWizard should keep existing userHashSalt for NewUser", async () => {
|
||||
const { manager, setting, dialogManager, core } = createSetupManager();
|
||||
const randomSpy = vi.spyOn(globalThis.crypto, "getRandomValues");
|
||||
dialogManager.openWithExplicitCancel.mockResolvedValueOnce(true);
|
||||
|
||||
await manager.onConfirmApplySettingsFromWizard(
|
||||
{
|
||||
...setting.currentSettings(),
|
||||
userHashSalt: "00112233445566778899aabbccddeeff",
|
||||
},
|
||||
UserMode.NewUser
|
||||
);
|
||||
|
||||
expect(setting.currentSettings().userHashSalt).toBe("00112233445566778899aabbccddeeff");
|
||||
expect(randomSpy).not.toHaveBeenCalled();
|
||||
expect(core.rebuilder.scheduleRebuild).toHaveBeenCalledTimes(1);
|
||||
randomSpy.mockRestore();
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user