fix(cli-e2e): disable keychain only in e2e runs

This commit is contained in:
Tienson Qin
2026-04-22 15:31:13 +08:00
parent 80a99e0ad0
commit fd6bdff4cc
5 changed files with 132 additions and 63 deletions

View File

@@ -14,6 +14,7 @@
text)))
(def template-pattern #"\{\{([^}]+)\}\}")
(def ^:private e2e-env {"CLI_E2E_TEST" "1"})
(defn- render-string
[template context]
@@ -165,6 +166,7 @@
[command context {:keys [run-command stdin allow-failure phase step-index step-total case-id]}]
(run-command {:cmd (render-string command context)
:dir (paths/repo-root)
:env e2e-env
:stdin (some-> stdin (render-string context))
:phase phase
:step-index step-index

View File

@@ -1,12 +1,8 @@
(ns frontend.handler.e2ee
"rtc E2EE related fns"
(:require [electron.ipc :as ipc]
[frontend.common.crypt :as crypt]
[frontend.common.thread-api :refer [def-thread-api]]
[frontend.mobile.secure-storage :as secure-storage]
[frontend.state :as state]
[frontend.util :as util]
[lambdaisland.glogi :as log]
[promesa.core :as p]))
(def ^:private save-op :keychain/save-e2ee-password)
@@ -70,35 +66,3 @@
(if (native-storage-supported?)
(<keychain-delete! key)
(p/resolved nil)))
(def-thread-api :thread-api/request-e2ee-password
[]
(p/let [password-promise (state/pub-event! [:rtc/request-e2ee-password])
password password-promise]
{:password password}))
(defn- <decrypt-user-e2ee-private-key
[encrypted-private-key]
(->
(p/let [private-key-promise (state/pub-event! [:rtc/decrypt-user-e2ee-private-key encrypted-private-key])
private-key private-key-promise]
(crypt/<export-private-key private-key))
(p/catch (fn [e]
(log/error :<decrypt-user-e2ee-private-key e)
e))))
(def-thread-api :thread-api/decrypt-user-e2ee-private-key
[encrypted-private-key]
(<decrypt-user-e2ee-private-key encrypted-private-key))
(def-thread-api :thread-api/native-save-e2ee-password
[encrypted-text]
(<native-save-secret! "logseq-encrypted-password" encrypted-text))
(def-thread-api :thread-api/native-get-e2ee-password
[]
(<native-get-secret "logseq-encrypted-password"))
(def-thread-api :thread-api/native-delete-e2ee-password
[]
(<native-delete-secret! "logseq-encrypted-password"))

View File

@@ -59,23 +59,17 @@
[k value]
(idb/set-item! k value))
(def ^:private secret-prefix "worker-secret###")
(defn- secret-key
[key]
(str secret-prefix key))
(defn- save-secret-text!
[key text]
(kv-set! (secret-key key) text))
(kv-set! key text))
(defn- read-secret-text
[key]
(kv-get (secret-key key)))
(kv-get key))
(defn- delete-secret-text!
[key]
(kv-set! (secret-key key) nil))
(kv-set! key nil))
(defn- install-opfs-pool
[sqlite pool-name]

View File

@@ -327,43 +327,62 @@
[state]
(transit/write kv-transit-writer state))
(def ^:private secret-prefix "worker-secret###")
(def ^:private keychain-service "Logseq E2EE")
(defn- secret-key
[key]
(str secret-prefix key))
(defn- keychain-account
[key]
(secret-key key))
(defn- <save-secret-text!
[kv key text]
(-> (p/let [_ (.setPassword ^js keytar keychain-service (keychain-account key) text)]
(-> (p/let [_ (.setPassword ^js keytar keychain-service key text)]
nil)
(p/catch (fn [e]
(log/warn :db-worker/keychain-save-failed {:error e
:key key})
((:set! kv) (secret-key key) text)))))
((:set! kv) key text)))))
(defn- <read-secret-text
[kv key]
(-> (p/let [secret (.getPassword ^js keytar keychain-service (keychain-account key))]
(-> (p/let [secret (.getPassword ^js keytar keychain-service key)]
secret)
(p/catch (fn [e]
(log/warn :db-worker/keychain-read-failed {:error e
:key key})
((:get kv) (secret-key key))))))
((:get kv) key)))))
(defn- <delete-secret-text!
[kv key]
(-> (p/let [_ (.deletePassword ^js keytar keychain-service (keychain-account key))]
(-> (p/let [_ (.deletePassword ^js keytar keychain-service key)]
nil)
(p/catch (fn [e]
(log/warn :db-worker/keychain-delete-failed {:error e
:key key})
((:set! kv) (secret-key key) nil)))))
((:set! kv) key nil)))))
(defn- truthy-env?
[value]
(contains? #{"1" "true" "yes" "on"}
(string/lower-case (string/trim (str (or value ""))))))
(defn- use-keychain-for-owner?
[owner-source]
(not (and (= :cli owner-source)
(truthy-env? (gobj/get (.-env js/process) "CLI_E2E_TEST")))))
(defn- <save-secret-text-by-owner!
[kv owner-source key text]
(if (use-keychain-for-owner? owner-source)
(<save-secret-text! kv key text)
((:set! kv) key text)))
(defn- <read-secret-text-by-owner
[kv owner-source key]
(if (use-keychain-for-owner? owner-source)
(<read-secret-text kv key)
((:get kv) key)))
(defn- <delete-secret-text-by-owner!
[kv owner-source key]
(if (use-keychain-for-owner? owner-source)
(<delete-secret-text! kv key)
((:set! kv) key nil)))
(defn- kv-store
[data-dir]
@@ -435,9 +454,9 @@
:backup-db (fn [^js db path]
(.backup db path))}
:crypto {:save-secret-text! (fn [key text]
(<save-secret-text! kv key text))
(<save-secret-text-by-owner! kv owner-source key text))
:read-secret-text (fn [key]
(<read-secret-text kv key))
(<read-secret-text-by-owner kv owner-source key))
:delete-secret-text! (fn [key]
(<delete-secret-text! kv key))}
(<delete-secret-text-by-owner! kv owner-source key))}
:timers {:set-interval! (fn [f ms] (js/setInterval f ms))}})))

View File

@@ -1,5 +1,6 @@
(ns frontend.worker.platform-node-test
(:require ["fs" :as fs]
["keytar" :as keytar]
["path" :as node-path]
[cljs.test :refer [async deftest is testing]]
[clojure.string :as string]
@@ -76,6 +77,95 @@
(is false (str "unexpected error: " e))))
(p/finally done)))))
(deftest node-platform-cli-owner-bypasses-keychain-in-cli-e2e-test
(async done
(let [data-dir (node-helper/create-tmp-dir "platform-node-cli-secrets")
process-env (.-env js/process)
original-cli-e2e-test (gobj/get process-env "CLI_E2E_TEST")
calls (atom {:save 0 :read 0 :delete 0})
original-save (gobj/get keytar "setPassword")
original-read (gobj/get keytar "getPassword")
original-delete (gobj/get keytar "deletePassword")]
(gobj/set process-env "CLI_E2E_TEST" "1")
(gobj/set keytar "setPassword" (fn [& _]
(swap! calls update :save inc)
(js/Promise.resolve true)))
(gobj/set keytar "getPassword" (fn [& _]
(swap! calls update :read inc)
(js/Promise.resolve nil)))
(gobj/set keytar "deletePassword" (fn [& _]
(swap! calls update :delete inc)
(js/Promise.resolve true)))
(-> (p/let [platform (platform-node/node-platform {:data-dir data-dir
:owner-source :cli})
crypto (:crypto platform)
kv (:kv platform)
_ ((:save-secret-text! crypto) "secret-key" "secret-value")
kv-value ((:get kv) "secret-key")
secret-value ((:read-secret-text crypto) "secret-key")
_ ((:delete-secret-text! crypto) "secret-key")
kv-cleared ((:get kv) "secret-key")]
(is (= "secret-value" kv-value))
(is (= "secret-value" secret-value))
(is (nil? kv-cleared))
(is (= {:save 0 :read 0 :delete 0} @calls)))
(p/catch (fn [e]
(is false (str "unexpected error: " e))))
(p/finally (fn []
(gobj/set keytar "setPassword" original-save)
(gobj/set keytar "getPassword" original-read)
(gobj/set keytar "deletePassword" original-delete)
(if (some? original-cli-e2e-test)
(gobj/set process-env "CLI_E2E_TEST" original-cli-e2e-test)
(gobj/remove process-env "CLI_E2E_TEST"))
(done)))))))
(deftest node-platform-cli-owner-uses-keychain-when-keychain-present
(async done
(let [data-dir (node-helper/create-tmp-dir "platform-node-cli-secrets-keychain")
process-env (.-env js/process)
original-cli-e2e-test (gobj/get process-env "CLI_E2E_TEST")
calls (atom {:save 0 :read 0 :delete 0})
secrets (atom {})
original-save (gobj/get keytar "setPassword")
original-read (gobj/get keytar "getPassword")
original-delete (gobj/get keytar "deletePassword")]
(gobj/remove process-env "CLI_E2E_TEST")
(gobj/set keytar "setPassword" (fn [_service key value]
(swap! calls update :save inc)
(swap! secrets assoc key value)
(js/Promise.resolve true)))
(gobj/set keytar "getPassword" (fn [_service key]
(swap! calls update :read inc)
(js/Promise.resolve (get @secrets key))))
(gobj/set keytar "deletePassword" (fn [_service key]
(swap! calls update :delete inc)
(swap! secrets dissoc key)
(js/Promise.resolve true)))
(-> (p/let [platform (platform-node/node-platform {:data-dir data-dir
:owner-source :cli})
crypto (:crypto platform)
kv (:kv platform)
_ ((:save-secret-text! crypto) "secret-key" "secret-value")
kv-value ((:get kv) "secret-key")
secret-value ((:read-secret-text crypto) "secret-key")
_ ((:delete-secret-text! crypto) "secret-key")
deleted-value ((:read-secret-text crypto) "secret-key")]
(is (nil? kv-value))
(is (= "secret-value" secret-value))
(is (nil? deleted-value))
(is (= {:save 1 :read 2 :delete 1} @calls)))
(p/catch (fn [e]
(is false (str "unexpected error: " e))))
(p/finally (fn []
(gobj/set keytar "setPassword" original-save)
(gobj/set keytar "getPassword" original-read)
(gobj/set keytar "deletePassword" original-delete)
(if (some? original-cli-e2e-test)
(gobj/set process-env "CLI_E2E_TEST" original-cli-e2e-test)
(gobj/remove process-env "CLI_E2E_TEST"))
(done)))))))
(deftest kv-store-preserves-uint8array-values-across-reloads-test
(async done
(let [data-dir (node-helper/create-tmp-dir "platform-node-kv-store")