fix: web app can't sync after merge master

This commit is contained in:
rcmerci
2026-04-21 00:34:33 +08:00
parent 32a567973a
commit 76f6faa8c1
4 changed files with 119 additions and 14 deletions

View File

@@ -1,6 +1,12 @@
(ns frontend.worker.platform
"Platform adapter contract for db-worker runtimes.")
(defn- normalize-missing-value
[value]
(if (identical? js/undefined value)
nil
value))
(def ^:private required-sections
[:env :storage :kv :broadcast :websocket :crypto :timers :sqlite])
@@ -54,7 +60,8 @@
(defn kv-get
[platform k]
(if-let [f (get-in platform [:kv :get])]
(f k)
(-> (f k)
(.then normalize-missing-value))
(throw (ex-info "platform kv/get missing" {:key k}))))
(defn kv-set!
@@ -108,7 +115,8 @@
(defn read-secret-text
[platform key]
(if-let [f (get-in platform [:crypto :read-secret-text])]
(f key)
(-> (f key)
(.then normalize-missing-value))
(throw (ex-info "platform crypto/read-secret-text missing" {:key key}))))
(defn delete-secret-text!

View File

@@ -53,18 +53,39 @@
(and (= :node runtime')
(= :electron owner-source)))))
(defn- missing-e2ee-password-ex
[data]
(ex-info "missing-e2ee-password"
(merge {:code :db-sync/missing-e2ee-password
:field :e2ee-password}
data)))
(defn- fail-missing-e2ee-password!
[data]
(fail-fast :db-sync/missing-e2ee-password
(merge {:field :e2ee-password}
(merge {:code :db-sync/missing-e2ee-password
:field :e2ee-password}
data)))
(defn- throw-missing-e2ee-password!
[data]
(throw (missing-e2ee-password-ex data)))
(defn- ensure-refresh-token!
[refresh-token]
(when-not (seq refresh-token)
(fail-missing-e2ee-password! {:reason :missing-refresh-token
:hint "Run logseq login first."})))
(defn- missing-persisted-password-error?
[error]
(let [data (ex-data error)]
(and (contains? #{:db-sync/missing-e2ee-password
:missing-e2ee-password}
(or (:code data)
(some-> error ex-message keyword)))
(= :missing-persisted-password (:reason data)))))
(defn- parse-auth-file
[text]
(when (seq text)
@@ -89,7 +110,10 @@
(defn- <save-e2ee-password
[password]
(p/let [platform' (platform/current)
refresh-token (<read-refresh-token-from-auth-file platform')
refresh-token (if (browser-runtime? platform')
(:auth/refresh-token @worker-state/*state)
(<read-refresh-token-from-auth-file platform'))
_ (ensure-refresh-token! refresh-token)
result (crypt/<encrypt-text-by-text-password refresh-token password)
text (ldb/write-transit-str result)]
(if (browser-runtime? platform')
@@ -113,8 +137,8 @@
(p/catch (fn [_]
nil))))]
(when-not (seq text)
(fail-missing-e2ee-password! {:reason :missing-persisted-password
:hint "Provide --e2ee-password to persist it."}))
(throw-missing-e2ee-password! {:reason :missing-persisted-password
:hint "Provide --e2ee-password to persist it."}))
(let [data (try
(ldb/read-transit-str text)
(catch :default _
@@ -380,11 +404,13 @@
(p/let [encrypted-private-key (ldb/read-transit-str encrypted-private-key-str)]
(-> (<decrypt-in-headless encrypted-private-key)
(p/catch (fn [headless-error]
(if (interactive-runtime?)
(if-not (interactive-runtime?)
(p/rejected headless-error)
(-> (<decrypt-with-ui-request encrypted-private-key)
(p/catch (fn [_ui-error]
(p/rejected headless-error))))
(p/rejected headless-error))))))))
(p/catch (fn [ui-error]
(if (missing-persisted-password-error? headless-error)
(p/rejected ui-error)
(p/rejected headless-error))))))))))))
(defn- <import-public-key
[public-key-str]

View File

@@ -0,0 +1,28 @@
(ns frontend.worker.platform-test
(:require [cljs.test :refer [async deftest is]]
[frontend.worker.platform :as platform]
[promesa.core :as p]))
(deftest kv-get-normalizes-undefined-to-nil-test
(async done
(-> (platform/kv-get {:kv {:get (fn [_k]
(p/resolved js/undefined))}}
"key")
(p/then (fn [value]
(is (nil? value))
(is (not (identical? js/undefined value)))))
(p/catch (fn [e]
(is false (str e))))
(p/finally done))))
(deftest read-secret-text-normalizes-undefined-to-nil-test
(async done
(-> (platform/read-secret-text {:crypto {:read-secret-text (fn [_key]
(p/resolved js/undefined))}}
"secret")
(p/then (fn [value]
(is (nil? value))
(is (not (identical? js/undefined value)))))
(p/catch (fn [e]
(is false (str e))))
(p/finally done))))

View File

@@ -99,10 +99,12 @@
(deftest save-e2ee-password-uses-secret-storage-in-browser-runtime-test
(async done
(let [platform-map {:env {:runtime :browser}}
state-prev @worker-state/*state
secret-calls (atom [])
file-calls (atom [])
auth-read-calls (atom [])
encrypt-calls (atom [])]
(reset! worker-state/*state (assoc state-prev :auth/refresh-token "refresh-from-state"))
(-> (p/with-redefs [crypt/<encrypt-text-by-text-password (fn [refresh-token password]
(swap! encrypt-calls conj [refresh-token password])
{:cipher "payload"})
@@ -123,9 +125,8 @@
(p/resolved nil))]
(#'sync-crypt/<save-e2ee-password "password"))
(p/then (fn [_]
(is (= 1 (count @auth-read-calls)))
(is (= "~/logseq/auth.json" (:path (first @auth-read-calls))))
(is (= [["refresh-from-auth-file" "password"]] @encrypt-calls))
(is (empty? @auth-read-calls))
(is (= [["refresh-from-state" "password"]] @encrypt-calls))
(is (= 1 (count @secret-calls)))
(is (= platform-map (:platform (first @secret-calls))))
(is (= "logseq-encrypted-password" (:key (first @secret-calls))))
@@ -133,7 +134,9 @@
(is (empty? @file-calls))))
(p/catch (fn [e]
(is false (str e))))
(p/finally done)))))
(p/finally (fn []
(reset! worker-state/*state state-prev)
(done)))))))
(deftest save-e2ee-password-uses-file-storage-in-node-runtime-test
(async done
@@ -352,6 +355,46 @@
(reset! worker-state/*state old-state)
(done)))))))
(deftest decrypt-private-key-browser-fallback-does-not-log-missing-persisted-password-test
(async done
(let [old-state @worker-state/*state
fail-calls (atom [])
decrypt-calls (atom [])
save-calls (atom [])]
(reset! worker-state/*state (assoc old-state :auth/refresh-token "refresh-token"))
(-> (p/with-redefs [platform/current (fn [] {:env {:runtime :browser}})
platform/read-secret-text (fn [_platform' _key]
(p/resolved nil))
platform/read-text! (fn [_platform' _path]
(p/rejected (ex-info "should-not-read-browser-file" {})))
ui-request/<request (fn [_action payload _opts]
(is (= {:reason :decrypt-user-rsa-private-key} payload))
(p/resolved {:password "ui-password"}))
sync-crypt/fail-missing-e2ee-password! (fn [data]
(swap! fail-calls conj data)
(throw (ex-info "missing-e2ee-password" data)))
ldb/read-transit-str (fn [_] :encrypted-private-key)
crypt/<decrypt-private-key (fn [password encrypted-private-key]
(swap! decrypt-calls conj [password encrypted-private-key])
(p/resolved :private-key))
crypt/<encrypt-text-by-text-password (fn [refresh-token password]
(swap! save-calls conj [:encrypt refresh-token password])
{:cipher "password-payload"})
platform/save-secret-text! (fn [_platform' key text]
(swap! save-calls conj [:save key text])
(p/resolved nil))]
(#'sync-crypt/<decrypt-private-key "encrypted-private-key-str"))
(p/then (fn [private-key]
(is (= :private-key private-key))
(is (= [["ui-password" :encrypted-private-key]] @decrypt-calls))
(is (= 2 (count @save-calls)))
(is (empty? @fail-calls))))
(p/catch (fn [e]
(is false (str e))))
(p/finally (fn []
(reset! worker-state/*state old-state)
(done)))))))
(deftest ensure-graph-aes-key-uses-platform-kv-adapters-test
(async done
(let [fetch-prev js/fetch