mirror of
https://github.com/vrtmrz/obsidian-livesync.git
synced 2026-05-11 18:21:50 +00:00
feat: Chunk ID namespace is now separated from the E2EE passphrase by introducing userHashSalt.
This commit is contained in:
2
src/lib
2
src/lib
Submodule src/lib updated: 97530553a6...16ed161ffa
@@ -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();
|
||||
});
|
||||
});
|
||||
|
||||
24
updates.md
24
updates.md
@@ -3,6 +3,30 @@ Since 19th July, 2025 (beta1 in 0.25.0-beta1, 13th July, 2025)
|
||||
|
||||
The head note of 0.25 is now in [updates_old.md](https://github.com/vrtmrz/obsidian-livesync/blob/main/updates_old.md). Because 0.25 got a lot of updates, thankfully, compatibility is kept and we do not need breaking changes! In other words, when get enough stabled. The next version will be v1.0.0. Even though it my hope.
|
||||
|
||||
## Unreleased
|
||||
|
||||
### Improved
|
||||
|
||||
- Chunk ID namespace is now separated from the E2EE passphrase by introducing `userHashSalt`.
|
||||
- Chunk ID hashing now prefers `userHashSalt` when present, and falls back to the legacy passphrase-derived seed for compatibility.
|
||||
- New setup now generates `userHashSalt` automatically if it is missing.
|
||||
- `rebuildEverything` now generates `userHashSalt` only when it is missing, as a migration path for existing vaults.
|
||||
- Setup URI / QR settings round-trip now preserves `userHashSalt`.
|
||||
|
||||
### Behaviour and safety
|
||||
|
||||
- `userHashSalt` has been added to tweak-value mismatch detection so devices can notice and resolve mismatched chunk-ID namespace settings.
|
||||
- `userHashSalt` mismatch is treated as compatible but potentially lossy (inefficient), not hard-incompatible.
|
||||
- Mismatch dialogues now mask `userHashSalt` values to avoid exposing the raw value in UI.
|
||||
|
||||
### Tests
|
||||
|
||||
- Added and updated unit tests for:
|
||||
- `HashManager` (`userHashSalt` priority and differing-salt behaviour).
|
||||
- `SetupManager` (generation only when missing, preserving existing value).
|
||||
- `Rebuilder` (generation only when missing, no regeneration when present).
|
||||
- `processSetting` setup URI round-trip and secure-field handling.
|
||||
|
||||
## 0.25.60
|
||||
|
||||
29th April, 2026
|
||||
|
||||
Reference in New Issue
Block a user