13 KiB
Sync E2EE Password Persistence Implementation Plan
Goal: Persist and consume sync E2EE passwords only as refresh-token-encrypted data across CLI, desktop app, web app, and db-worker-node.
Architecture: Keep password encryption and verification logic in frontend.worker.sync.crypt as the single source of truth.
Architecture: Use runtime-specific persistence behind worker platform boundaries, with IndexedDB-backed secret storage on browser runtime and <data-dir>/e2ee-password file storage on node runtime.
Architecture: Route CLI sync start --e2ee-password and sync download --e2ee-password, plus desktop UI password input, through the same worker-side validation and persistence flow.
Tech Stack: ClojureScript, Promesa, db-worker platform adapters, babashka.cli command parsing, Electron renderer-to-worker UI request protocol.
Related: Builds on docs/agent-guide/047-logseq-cli-sync-command.md, docs/agent-guide/055-logseq-cli-login-logout.md, and docs/agent-guide/074-db-worker-node-invoke-main-thread-refactor.md.
Problem statement
Current behavior is inconsistent across runtimes and command paths.
frontend.worker.sync.crypt/<save-e2ee-password encrypts password text by refresh-token, but password sourcing and persistence locations differ by flow.
CLI still accepts and persists plain :e2ee-password in cli.edn, which conflicts with secure persistence requirements.
Browser runtime currently persists non-native password file via OPFS (e2ee-password) instead of IndexedDB.
CLI headless fallback currently includes config password and can return :ui-interaction-required rather than a direct e2ee-password not found outcome.
Desktop interactive flow can decrypt via UI path, but the verify-and-persist behavior is not defined as an explicit shared worker API reused by CLI.
The table below summarizes current state and required change.
| Area | Current state | Required state |
|---|---|---|
| CLI config | `sync config set | get |
| Password crypto key | Worker encrypt/decrypt helpers already accept refresh-token, but CLI fallback still depends on config and auth-token candidates. |
Use refresh-token as the only password crypto key source for persisted password. |
| Browser persistence | Non-native flow reads/writes OPFS file e2ee-password. |
Store encrypted password in IndexedDB. |
| Node persistence | Non-native flow reads/writes <data-dir>/e2ee-password. |
Keep <data-dir>/e2ee-password as canonical node location. |
| CLI UX | sync start/status does not provide a dedicated missing-password UX contract. |
If password is unavailable, sync start/status should report e2ee-password not found with actionable hint. |
| CLI input path | No sync start --e2ee-password or sync download --e2ee-password option. |
Add --e2ee-password to sync start and sync download, verify it, encrypt it, then persist it. |
| Desktop UI reuse | UI decrypt path exists, but shared CLI-equivalent verify/persist contract is not explicit. | Desktop missing-password interaction should call the same worker verify-and-persist flow as CLI. |
Target architecture
CLI / Desktop UI / Web runtime
|
| password input (optional)
v
frontend.worker.sync.crypt
- verify password against user encrypted private key
- encrypt/decrypt persisted password with refresh-token
- return typed missing-password errors
|
+--> Browser runtime: IndexedDB secret slot
|
+--> Node runtime: <data-dir>/e2ee-password
Testing Plan
I will add worker unit tests in src/test/frontend/worker/sync/crypt_test.cljs for runtime-specific storage routing, refresh-token-only encryption key usage, and typed missing-password behavior in CLI-headless mode.
I will add CLI command tests in src/test/logseq/cli/command/sync_test.cljs for sync start --e2ee-password, sync download --e2ee-password, deprecation of config key handling, and missing-password reporting in sync start/sync download/sync status paths.
I will add CLI config tests in src/test/logseq/cli/command/sync_test.cljs and src/test/logseq/cli/format_test.cljs to verify :e2ee-password is no longer treated as file config and to verify updated user-facing output.
I will add desktop UI request tests in src/test/frontend/handler/db_based/sync_test.cljs or nearby handler tests to verify missing-password UI roundtrip invokes the shared worker verify-and-persist API instead of bespoke flow-only logic.
I will run targeted tests first, then run bb dev:lint-and-test for final validation.
NOTE: I will write all tests before I add any implementation behavior.
Implementation plan
Phase 1. Define shared worker password vault behavior.
-
Add failing tests in
src/test/frontend/worker/sync/crypt_test.cljsthat describe a new shared worker API for verify-and-persist password. -
Add failing tests that enforce refresh-token-only persisted password decrypt path in headless mode and reject config-only fallback.
-
Implement a single worker helper in
src/main/frontend/worker/sync/crypt.cljsthat verifies a candidate password by decrypting user encrypted private key and then persists encrypted password payload. -
Expose the helper through a thread API that can be invoked by both CLI and desktop flows.
-
Remove
:e2ee-passwordconfig candidate usage fromcrypt.cljsheadless candidate logic. -
Ensure CLI-headless path does not mask missing-password with
:ui-interaction-requiredwhen no interactive channel exists. -
Add typed error payload and hint contract for missing persisted password.
-
Run worker crypt tests and confirm failing cases are now passing.
Phase 2. Enforce browser and node persistence locations.
-
Add failing tests for browser runtime persistence using IndexedDB secret storage rather than OPFS file route.
-
Add failing tests for node runtime persistence at
<data-dir>/e2ee-password. -
Update
src/main/frontend/worker/sync/crypt.cljsstorage branching to select persistence backend by runtime (:browservs:node) rather than legacy native URL heuristics for this password feature. -
Keep node runtime read/write via
platform/read-text!andplatform/write-text!with pathe2ee-password. -
Keep browser runtime read/write via
platform/read-secret-textandplatform/save-secret-text!so storage lands in IndexedDB-backed kv store. -
Add migration fallback in browser path to read legacy OPFS
e2ee-passwordonce, then re-save to IndexedDB, then remove legacy file. -
Add clear/delete behavior that correctly removes persisted password from both runtime backends.
-
Run
frontend.worker.sync.crypt-testto verify storage routing and migration behavior.
Phase 3. Deprecate cli.edn :e2ee-password.
-
Add failing CLI tests in
src/test/logseq/cli/command/sync_test.cljsthat rejectsync config set|get|unset e2ee-password. -
Add failing config tests in
src/test/logseq/cli/command/sync_test.cljsshowing:e2ee-passwordin oldcli.ednis ignored and sanitized out without any deprecation warning. -
Remove
e2ee-passwordfromconfig-key-mapand sync config command examples insrc/main/logseq/cli/command/sync.cljs. -
Remove
:e2ee-passwordfromsync-configpayload generated by CLI for worker runtime config. -
Add
:e2ee-passwordintoremoved-config-keysinsrc/main/logseq/cli/config.cljsso persisted file config is automatically sanitized. -
Update
src/main/logseq/cli/format.cljsandsrc/test/logseq/cli/format_test.cljsfor changed sync-config output expectations. -
Run sync command and format tests to verify deprecation behavior.
Phase 4. Add CLI --e2ee-password on sync start and sync download, and enforce missing-password errors.
-
Add failing parse/build-action tests in
src/test/logseq/cli/command/sync_test.cljsfor--e2ee-passwordon bothsync startandsync download. -
Add failing execute tests that both commands invoke worker verify-and-persist before start or download work begins.
-
Add CLI auth helper test coverage in
src/test/logseq/cli/command/sync_test.cljsfor resolving refresh-token fromauth.jsonin runtime command execution path. -
Extend
src/main/logseq/cli/command/sync.cljscommand spec to include--e2ee-passwordforsync startandsync download. -
Add CLI-side runtime auth context helper usage so both flows have id-token and refresh-token available at execution time.
-
When option is provided, call worker shared verify-and-persist API and fail fast on invalid password.
-
In
sync startandsync downloadexecution paths, plussync statusreporting path, map missing-password diagnostics to explicite2ee-password not founderrors with actionable hint text. -
Ensure
sync statusreturns error status and non-zero exit code when e2ee password is required but missing. -
Keep machine-readable error code stable for automation.
-
Run sync command tests and relevant integration tests.
Phase 5. Reuse CLI flow from desktop missing-password interaction.
-
Add failing UI request tests for missing persisted password path in desktop interactive runtime.
-
In
src/main/frontend/worker/sync/crypt.cljs, route interactive fallback to request password input, then call the same shared verify-and-persist helper used by CLI. -
Update
src/main/frontend/worker/ui_request.cljsandsrc/main/frontend/handler/worker.cljsaction contract so UI request returns password input and worker does verification/decryption centrally. -
Update desktop password modal flow in
src/main/frontend/handler/events/rtc.cljsandsrc/main/frontend/components/e2ee.cljsonly as needed to collect password and forward it to worker. -
Remove duplicate validation logic from UI-side code paths after worker shared flow is active.
-
Run frontend handler/component tests and existing e2ee-related tests.
Phase 6. Documentation and acceptance.
-
Update
docs/cli/logseq-cli.mdto removesync config ... e2ee-passwordreferences. -
Document
sync start --e2ee-passwordandsync download --e2ee-passwordusage, plus missing-password troubleshooting forsync start/sync download/sync status. -
Document persistence locations explicitly.
-
Browser runtime stores encrypted password in IndexedDB.
-
Node runtime stores encrypted password at
<data-dir>/e2ee-password. -
Add migration note for users with legacy
cli.edn :e2ee-passwordand legacy browser OPFS password file. -
Run a final lint and test pass.
-
Confirm no docs still recommend
cli.edn :e2ee-password.
Edge cases to cover
Expired id-token with valid refresh-token in CLI should still allow password verification and persistence after auth refresh.
Missing refresh-token in auth.json should produce a login-required error before any password persistence attempt.
Wrong --e2ee-password should fail verification without overwriting previously stored encrypted password.
Node file exists but is corrupted transit payload should return typed missing/invalid-password diagnostics and preserve existing UX hints.
Browser migration should handle missing legacy OPFS file without error noise.
Desktop interactive flow cancellation should not persist empty password and should keep prior stored password untouched.
Multiple sync start invocations should avoid race conditions that overwrite password storage with stale data.
Verification commands
Run targeted worker tests.
bb dev:test -v frontend.worker.sync.crypt-test
Run targeted CLI sync tests.
bb dev:test -v logseq.cli.command.sync-test
Run CLI format tests.
bb dev:test -v logseq.cli.format-test
Run CLI command tests around sync/auth/config behavior.
bb dev:test -v logseq.cli.command.sync-test
Run full lint and test suite before merge.
bb dev:lint-and-test
Testing Details
The key behavior assertions are that password persistence is always encrypted with refresh-token, storage backend is runtime-correct, and user-facing CLI/desktop UX is deterministic when password is missing or invalid.
Tests will validate command outcomes and worker behavior contracts instead of only checking internal data structures.
Implementation Details
- Introduce one shared worker API for verify-and-persist E2EE password and reuse it from CLI and desktop.
- Remove
:e2ee-passwordfrom CLI file config lifecycle and sync config subcommands. - Keep refresh-token as the sole crypto key for persisted password encryption and decryption.
- Move browser persistence to IndexedDB secret storage and keep node persistence at
<data-dir>/e2ee-password. - Add legacy migration read path from browser OPFS password file to IndexedDB.
- Add explicit missing-password diagnostics for CLI
sync start/sync download/sync status, withsync statusreturning error when password is required but missing. - Add
sync start --e2ee-passwordandsync download --e2ee-passwordoptions with verification-before-persist semantics. - Keep
:e2ee-passworddeprecation behavior as silent ignore with no compatibility warning. - Preserve machine-readable error codes and CLI structured output compatibility.
- Update user docs and troubleshooting guidance to match deprecation and new option flow.
Question
No open questions.
Decisions locked.
Add --e2ee-password to sync start and sync download only.
Ignore cli.edn :e2ee-password silently with no compatibility path.
Return error for missing password in sync status.