Browse Source

Merge branch 'encryption' of git://github.com/kanru/logseq into kanru-encryption

Tienson Qin 5 years ago
parent
commit
4aa2d8aaf6

+ 2 - 0
package.json

@@ -57,6 +57,8 @@
     },
     "dependencies": {
         "chokidar": "^3.5.1",
+        "@kanru/rage-wasm": "^0.1.4",
+        "bip39": "^3.0.3",
         "codemirror": "^5.58.1",
         "diff": "5.0.0",
         "diff-match-patch": "^1.0.5",

+ 78 - 0
src/main/frontend/components/encryption.cljs

@@ -0,0 +1,78 @@
+(ns frontend.components.encryption
+  (:require [rum.core :as rum]
+            [frontend.encrypt :as e]
+            [frontend.util :as util :refer-macros [profile]]
+            [frontend.context.i18n :as i18n]
+            [frontend.db.utils :as db-utils]
+            [clojure.string :as string]
+            [frontend.state :as state]))
+
+(rum/defcs encryption-dialog-inner <
+  (rum/local false ::reveal-secret-phrase?)
+  [state repo-url close-fn]
+  (let [reveal-secret-phrase? (get state ::reveal-secret-phrase?)
+        secret-phrase (e/get-mnemonic repo-url)
+        public-key (e/get-public-key repo-url)
+        private-key (e/get-secret-key repo-url)]
+    (rum/with-context [[t] i18n/*tongue-context*]
+      [:div
+       [:div.sm:flex.sm:items-start
+        [:div.mt-3.text-center.sm:mt-0.sm:text-left
+         [:h3#modal-headline.text-lg.leading-6.font-medium.text-gray-900
+          "This graph is encrypted"]]]
+
+       [:div.mt-1
+        [:div.max-w-2xl.rounded-md.shadow-sm.sm:max-w-xl
+         [:div.cursor-pointer.block.w-full.rounded-sm.p-2.text-gray-900
+          {:on-click (fn []
+                       (when (not @reveal-secret-phrase?)
+                         (reset! reveal-secret-phrase? true)))}
+          (if @reveal-secret-phrase?
+            [:div
+             [:div.font-medium.text-gray-900 "Secret Phrase:"]
+             [:div secret-phrase]
+             [:div.font-medium.text-gray-900 "Public Key:"]
+             [:div public-key]
+             [:div.font-medium.text-gray-900 "Private Key:"]
+             [:div private-key]]
+            "click to view the secret phrase")]]]
+
+       [:div.mt-5.sm:mt-4.sm:flex.sm:flex-row-reverse
+        [:span.mt-3.flex.w-full.rounded-md.shadow-sm.sm:mt-0.sm:w-auto
+         [:button.inline-flex.justify-center.w-full.rounded-md.border.border-gray-300.px-4.py-2.bg-white.text-base.leading-6.font-medium.text-gray-700.shadow-sm.hover:text-gray-500.focus:outline-none.focus:border-blue-300.focus:shadow-outline-blue.transition.ease-in-out.duration-150.sm:text-sm.sm:leading-5
+          {:type "button"
+           :on-click close-fn}
+          (t :close)]]]])))
+
+(defn encryptioin-dialog
+  [repo-url]
+  (fn [close-fn]
+    (encryption-dialog-inner repo-url close-fn)))
+
+(rum/defcs encryption-setup-dialog-inner
+  [state repo-url close-fn]
+  (rum/with-context [[t] i18n/*tongue-context*]
+    [:div
+     [:div.sm:flex.sm:items-start
+      [:div.mt-3.text-center.sm:mt-0.sm:text-left
+       [:h3#modal-headline.text-lg.leading-6.font-medium.text-gray-900
+        "Create encrypted graph?"]]]
+
+     [:div.mt-5.sm:mt-4.sm:flex.sm:flex-row-reverse
+      [:span.flex.w-full.rounded-md.shadow-sm.sm:ml-3.sm:w-auto
+       [:button.inline-flex.justify-center.w-full.rounded-md.border.border-transparent.px-4.py-2.bg-indigo-600.text-base.leading-6.font-medium.text-white.shadow-sm.hover:bg-indigo-500.focus:outline-none.focus:border-indigo-700.focus:shadow-outline-indigo.transition.ease-in-out.duration-150.sm:text-sm.sm:leading-5
+        {:type "button"
+         :on-click (fn []
+                     (e/generate-mnemonic-and-save repo-url)
+                     (close-fn))}
+        (t :yes)]]
+      [:span.mt-3.flex.w-full.rounded-md.shadow-sm.sm:mt-0.sm:w-auto
+       [:button.inline-flex.justify-center.w-full.rounded-md.border.border-gray-300.px-4.py-2.bg-white.text-base.leading-6.font-medium.text-gray-700.shadow-sm.hover:text-gray-500.focus:outline-none.focus:border-blue-300.focus:shadow-outline-blue.transition.ease-in-out.duration-150.sm:text-sm.sm:leading-5
+        {:type "button"
+         :on-click close-fn}
+        (t :no)]]]]))
+
+(defn encryption-setup-dialog
+  [repo-url]
+  (fn [close-fn]
+    (encryption-setup-dialog-inner repo-url close-fn)))

+ 19 - 12
src/main/frontend/components/repo.cljs

@@ -4,6 +4,7 @@
             [frontend.ui :as ui]
             [frontend.state :as state]
             [frontend.db :as db]
+            [frontend.encrypt :as e]
             [frontend.handler.repo :as repo-handler]
             [frontend.handler.common :as common-handler]
             [frontend.handler.route :as route-handler]
@@ -16,6 +17,7 @@
             [frontend.version :as version]
             [frontend.components.commit :as commit]
             [frontend.components.svg :as svg]
+            [frontend.components.encryption :as encryption]
             [frontend.context.i18n :as i18n]
             [clojure.string :as string]
             [clojure.string :as str]))
@@ -60,18 +62,23 @@
                       :href url}
                   (db/get-repo-path url)])
                [:div.controls
-                [:a.control {:title (if local?
-                                      "Sync with the local directory"
-                                      "Clone again and re-index the db")
-                             :on-click (fn []
-                                         (if local?
-                                           (nfs-handler/rebuild-index! url
-                                                                       repo-handler/create-today-journal!)
-                                           (repo-handler/rebuild-index! url))
-                                         (js/setTimeout
-                                          (fn []
-                                            (route-handler/redirect! {:to :home}))
-                                          500))}
+                (when (e/encrypted-db? url)
+                  [:a.control {:title "Show encryption information about this graph"
+                               :on-click (fn []
+                                           (state/set-modal! (encryption/encryptioin-dialog url)))}
+                   "🔐"])
+                [:a.control.ml-4 {:title (if local?
+                                           "Sync with the local directory"
+                                           "Clone again and re-index the db")
+                                  :on-click (fn []
+                                              (if local?
+                                                (nfs-handler/rebuild-index! url
+                                                                            repo-handler/create-today-journal!)
+                                                (repo-handler/rebuild-index! url))
+                                              (js/setTimeout
+                                               (fn []
+                                                 (route-handler/redirect! {:to :home}))
+                                               500))}
                  "Re-index"]
                 [:a.control.ml-4 {:title "Clone again and re-index the db"
                                   :on-click (fn []

+ 2 - 0
src/main/frontend/db_schema.cljs

@@ -15,6 +15,8 @@
   {:schema/version  {}
    :db/type         {}
    :db/ident        {:db/unique :db.unique/identity}
+   :db/encrypted?    {}
+   :db/secret-phrase {}
 
    ;; user
    :me/name  {}

+ 2 - 0
src/main/frontend/dicts.cljs

@@ -300,8 +300,10 @@ title: How to take dummy notes?
         :more-options "More options"
         :to "to"
         :yes "Yes"
+        :no "No"
         :submit "Submit"
         :cancel "Cancel"
+        :close "Close"
         :re-index "Re-index"
         :export-json "Export as JSON"
         :unlink "unlink"

+ 82 - 0
src/main/frontend/encrypt.cljs

@@ -0,0 +1,82 @@
+(ns frontend.encrypt
+  (:require [frontend.utf8 :as utf8]
+            [frontend.db.utils :as db-utils]
+            [frontend.db :as db]
+            [frontend.state :as state]
+            [clojure.string :as str]
+            ["bip39" :as bip39]
+            ["buffer" :as buffer]
+            ["@kanru/rage-wasm" :as rage]))
+
+(defonce age-pem-header-line "-----BEGIN AGE ENCRYPTED FILE-----")
+(defonce age-version-line "age-encryption.org/v1")
+
+(defn content-encrypted?
+  [content]
+  (or (str/starts-with? content age-pem-header-line)
+      (str/starts-with? content age-version-line)))
+
+(defn encrypted-db?
+  [repo-url]
+  (db-utils/get-key-value repo-url :db/encrypted?))
+
+(defn get-mnemonic
+  [repo-url]
+  (db-utils/get-key-value repo-url :db/secret-phrase))
+
+(defn- save-mnemonic
+  [repo-url mnemonic]
+  (db/set-key-value repo-url :db/secret-phrase mnemonic)
+  (db/set-key-value repo-url :db/encrypted? true))
+
+(defn- generate-mnemonic
+  []
+  (bip39/generateMnemonic 256))
+
+(defn generate-mnemonic-and-save
+  [repo-url]
+  (when-not (get-mnemonic repo-url)
+    (let [mnemonic (generate-mnemonic)]
+      (save-mnemonic repo-url mnemonic))))
+
+(defn- derive-key-from-mnemonic
+  [mnemonic]
+  (let [entropy (-> (bip39/mnemonicToEntropy mnemonic)
+                    (buffer/Buffer.from "hex"))
+        keys (rage/keygen_from_random_bytes entropy)]
+    keys))
+
+(defn get-public-key
+  [repo-url]
+  (second (derive-key-from-mnemonic (get-mnemonic repo-url))))
+
+(defn get-secret-key
+  [repo-url]
+  (first (derive-key-from-mnemonic (get-mnemonic repo-url))))
+
+(defn encrypt
+  ([content]
+   (encrypt (state/get-current-repo) content))
+  ([repo-url content]
+   (cond
+     (encrypted-db? repo-url)
+     (let [content (utf8/encode content)
+           public-key (get-public-key repo-url)
+           encrypted (rage/encrypt_with_x25519 public-key content true)]
+       (utf8/decode encrypted))
+     :else
+     content)))
+
+(defn decrypt
+  ([content]
+   (decrypt (state/get-current-repo) content))
+  ([repo-url content]
+   (cond
+     (and (encrypted-db? repo-url)
+          (content-encrypted? content))
+     (let [content (utf8/encode content)
+           secret-key (get-secret-key repo-url)
+           decrypted (rage/decrypt_with_x25519 secret-key content)]
+       (utf8/decode decrypted))
+     :else
+     content)))

+ 17 - 13
src/main/frontend/fs.cljs

@@ -10,7 +10,8 @@
             [frontend.fs.node :as node]
             [frontend.db :as db]
             [cljs-bean.core :as bean]
-            [frontend.state :as state]))
+            [frontend.state :as state]
+            [frontend.encrypt :as encrypt]))
 
 (defonce nfs-record (nfs/->Nfs))
 (defonce bfs-record (bfs/->Bfs))
@@ -58,21 +59,24 @@
 
 (defn read-file
   [dir path]
-  (protocol/read-file (get-fs dir) dir path))
+  (p/chain (protocol/read-file (get-fs dir) dir path)
+           encrypt/decrypt))
 
 (defn write-file!
   [repo dir path content opts]
-  (->
-   (do
-     (protocol/write-file! (get-fs dir) repo dir path content opts)
-     (db/set-file-last-modified-at! repo (config/get-file-path repo path) (js/Date.)))
-   (p/catch (fn [error]
-              (log/error :file/write-failed? {:dir dir
-                                              :path path
-                                              :error error})
-              ;; Disable this temporarily
-              ;; (js/alert "Current file can't be saved! Please copy its content to your local file system and click the refresh button.")
-))))
+  (when content
+    (let [content (encrypt/encrypt content)]
+      (->
+       (do
+         (protocol/write-file! (get-fs dir) repo dir path content opts)
+         (db/set-file-last-modified-at! repo (config/get-file-path repo path) (js/Date.)))
+       (p/catch (fn [error]
+                  (log/error :file/write-failed? {:dir dir
+                                                  :path path
+                                                  :error error})
+                  ;; Disable this temporarily
+                  ;; (js/alert "Current file can't be saved! Please copy its content to your local file system and click the refresh button.")
+))))))
 
 (defn rename!
   [repo old-path new-path]

+ 144 - 0
src/main/frontend/fs.cljs.~fbf461699dd0b25d7110bc6ff3d4f788913a10f4~

@@ -0,0 +1,144 @@
+(ns frontend.fs
+  (:require [frontend.util :as util :refer-macros [profile]]
+            [frontend.config :as config]
+            [clojure.string :as string]
+            [promesa.core :as p]
+            [lambdaisland.glogi :as log]
+            [frontend.fs.protocol :as protocol]
+            [frontend.fs.nfs :as nfs]
+            [frontend.fs.bfs :as bfs]
+            [frontend.fs.node :as node]
+            [frontend.db :as db]
+            [cljs-bean.core :as bean]
+            [frontend.state :as state]))
+
+(defonce nfs-record (nfs/->Nfs))
+(defonce bfs-record (bfs/->Bfs))
+(defonce node-record (node/->Node))
+
+(defn local-db?
+  [dir]
+  (and (string? dir)
+       (config/local-db? (subs dir 1))))
+
+(defn get-fs
+  [dir]
+  (let [bfs-local? (or (string/starts-with? dir "/local")
+                       (string/starts-with? dir "local"))
+        current-repo (state/get-current-repo)
+        git-repo? (and current-repo
+                       (string/starts-with? current-repo "https://"))]
+    (cond
+      (and (util/electron?) (not bfs-local?) (not git-repo?))
+      node-record
+
+      (local-db? dir)
+      nfs-record
+
+      :else
+      bfs-record)))
+
+(defn mkdir!
+  [dir]
+  (protocol/mkdir! (get-fs dir) dir))
+
+(defn readdir
+  [dir]
+  (protocol/readdir (get-fs dir) dir))
+
+(defn unlink!
+  [path opts]
+  (protocol/unlink! (get-fs path) path opts))
+
+(defn rmdir!
+  "Remove the directory recursively.
+   Warning: only run it for browser cache."
+  [dir]
+  (protocol/rmdir! (get-fs dir) dir))
+
+(defn read-file
+  [dir path]
+  (protocol/read-file (get-fs dir) dir path))
+
+(defn write-file!
+  [repo dir path content opts]
+  (->
+   (do
+     (protocol/write-file! (get-fs dir) repo dir path content opts)
+     (db/set-file-last-modified-at! repo (config/get-file-path repo path) (js/Date.)))
+   (p/catch (fn [error]
+              (log/error :file/write-failed? {:dir dir
+                                              :path path
+                                              :error error})
+              ;; Disable this temporarily
+              ;; (js/alert "Current file can't be saved! Please copy its content to your local file system and click the refresh button.")
+))))
+
+(defn rename!
+  [repo old-path new-path]
+  (cond
+    ; See https://github.com/isomorphic-git/lightning-fs/issues/41
+    (= old-path new-path)
+    (p/resolved nil)
+
+    :else
+    (protocol/rename! (get-fs old-path) repo old-path new-path)))
+
+(defn stat
+  [dir path]
+  (protocol/stat (get-fs dir) dir path))
+
+(defn open-dir
+  [ok-handler]
+  (let [record (if (util/electron?) node-record nfs-record)]
+    (p/let [result (protocol/open-dir record ok-handler)]
+      (if (util/electron?)
+        (let [[dir & paths] (bean/->clj result)]
+          [(:path dir) paths])
+        result))))
+
+(defn get-files
+  [path-or-handle ok-handler]
+  (let [record (if (util/electron?) node-record nfs-record)]
+    (p/let [result (protocol/get-files record path-or-handle ok-handler)]
+      (if (util/electron?)
+        (let [result (bean/->clj result)]
+          (rest result))
+        result))))
+
+(defn watch-dir!
+  [dir]
+  (protocol/watch-dir! node-record dir))
+
+(defn mkdir-if-not-exists
+  [dir]
+  (->
+   (when dir
+     (util/p-handle
+      (stat dir nil)
+      (fn [_stat])
+      (fn [error]
+        (mkdir! dir))))
+   (p/catch (fn [_error] nil))))
+
+(defn create-if-not-exists
+  ([repo dir path]
+   (create-if-not-exists repo dir path ""))
+  ([repo dir path initial-content]
+   (let [path (if (util/starts-with? path "/")
+                path
+                (str "/" path))]
+     (->
+      (p/let [stat (stat dir path)]
+        true)
+      (p/catch
+       (fn [_error]
+         (p/let [_ (write-file! repo dir path initial-content nil)]
+           false)))))))
+
+(defn file-exists?
+  [dir path]
+  (util/p-handle
+   (stat dir path)
+   (fn [_stat] true)
+   (fn [_e] false)))

+ 1 - 0
src/main/frontend/handler/common.cljs

@@ -7,6 +7,7 @@
             [frontend.text :as text]
             [frontend.git :as git]
             [frontend.db :as db]
+            [frontend.encrypt :as e]
             [lambdaisland.glogi :as log]
             [cljs.reader :as reader]
             [frontend.spec :as spec]

+ 5 - 2
src/main/frontend/handler/repo.cljs

@@ -26,7 +26,8 @@
             [frontend.dicts :as dicts]
             [frontend.spec :as spec]
             [goog.dom :as gdom]
-            [goog.object :as gobj]))
+            [goog.object :as gobj]
+            [frontend.components.encryption :as encryption]))
 
 ;; Project settings should be checked in two situations:
 ;; 1. User changes the config.edn directly in logseq.com (fn: alter-file)
@@ -196,7 +197,9 @@
         (when-let [content (some #(when (= (:file/path %) config-file)
                                     (:file/content %)) files)]
           (file-handler/restore-config! repo-url content true))))
-    (when first-clone? (create-default-files! repo-url))
+    (when first-clone?
+      (create-default-files! repo-url)
+      (state/set-modal! (encryption/encryption-setup-dialog repo-url)))
     (when re-render?
       (ui-handler/re-render-root! re-render-opts))
     (state/set-importing-to-db! false)))

+ 4 - 3
src/main/frontend/handler/web/nfs.cljs

@@ -19,7 +19,8 @@
             [frontend.db :as db]
             [frontend.db.model :as db-model]
             [frontend.config :as config]
-            [lambdaisland.glogi :as log]))
+            [lambdaisland.glogi :as log]
+            [frontend.encrypt :as encrypt]))
 
 (defn remove-ignore-files
   [files]
@@ -145,7 +146,7 @@
                          (p/let [content (if nfs?
                                            (.text (:file/file file))
                                            (:file/content file))]
-                           (assoc file :file/content content))) markup-files))
+                           (assoc file :file/content (encrypt/decrypt content)))) markup-files))
            (p/then (fn [result]
                      (let [files (map #(dissoc % :file/file) result)]
                        (repo-handler/start-repo-db-if-not-exists! repo {:db-type :local-native-fs})
@@ -240,7 +241,7 @@
                         (p/let [content (if nfs?
                                           (.text (:file/file file))
                                           (:file/content file))]
-                          (assoc file :file/content content)))) added-or-modified))
+                          (assoc file :file/content (encrypt/decrypt content))))) added-or-modified))
         (p/then (fn [result]
                   (let [files (map #(dissoc % :file/file :file/handle) result)
                         non-modified? (fn [file]

File diff suppressed because it is too large
+ 316 - 318
yarn.lock


Some files were not shown because too many files changed in this diff