Browse Source

feat(rtc,e2ee): grant-access support e2ee

rcmerci 3 weeks ago
parent
commit
219d9ad201

+ 2 - 2
src/main/frontend/components/e2ee.cljs

@@ -21,7 +21,7 @@
        "OK")
       (shui/button
        {:on-click (fn []
-                    (p/reject! password-promise :cancelled)
+                    (p/reject! password-promise (ex-info "cancelled" {}))
                     (shui/dialog-close!))}
        "Cancel")]]))
 
@@ -48,6 +48,6 @@
        "OK")
       (shui/button
        {:on-click (fn []
-                    (p/reject! private-key-promise :cancelled)
+                    (p/reject! private-key-promise (ex-info "input E2EE password cancelled" {}))
                     (shui/dialog-close!))}
        "Cancel")]]))

+ 1 - 1
src/main/frontend/components/header.cljs

@@ -74,7 +74,7 @@
                                             (fn []
                                               [:div.p-2.-mb-8
                                                [:h1.text-3xl.-mt-2.-ml-2 "Collaborators:"]
-                                               (settings/settings-collaboration)]))})
+                                               (settings/settings-collaboration false)]))})
 
        (when (seq online-users)
          (for [{user-email :user/email

+ 5 - 4
src/main/frontend/components/settings.cljs

@@ -1253,11 +1253,12 @@
            "Reset Password")]))]))
 
 (rum/defc settings-collaboration
-  []
+  [include-e2ee-settings?]
   [:div.panel-wrap.is-collaboration.mb-8
    (settings-rtc-members)
-   [:br]
-   (settings-rtc-e2ee)])
+   (when include-e2ee-settings?
+     [:br]
+     (settings-rtc-e2ee))])
 
 (rum/defc mcp-server-row
   [t]
@@ -1483,7 +1484,7 @@
          (settings-features)
 
          :collaboration
-         (settings-collaboration)
+         (settings-collaboration true)
 
          :ai
          (settings-ai)

+ 0 - 23
src/main/frontend/db/rtc/debug_ui.cljs

@@ -137,29 +137,6 @@
                 :on-click (fn [] (stop))}
                (shui/tabler-icon "player-stop") "stop")]])
 
-     (when (some? debug-state*)
-       [:hr]
-       [:div.flex.flex-row.items-center.gap-2
-        (ui/button "grant graph access to"
-                   {:icon "award"
-                    :on-click (fn []
-                                (let [token (state/get-auth-id-token)
-                                      user-uuid (some-> (:grant-access-to-user debug-state*) parse-uuid)
-                                      user-email (when-not user-uuid (:grant-access-to-user debug-state*))]
-                                  (when-let [graph-uuid (:graph-uuid debug-state*)]
-                                    (state/<invoke-db-worker :thread-api/rtc-grant-graph-access
-                                                             token graph-uuid
-                                                             (some-> user-uuid vector)
-                                                             (some-> user-email vector)))))})
-
-        [:b "➡️"]
-        [:input.form-input.my-2.py-1
-         {:on-change (fn [e] (swap! debug-state assoc :grant-access-to-user (util/evalue e)))
-          :on-focus (fn [e] (let [v (.-value (.-target e))]
-                              (when (= v "input email or user-uuid here")
-                                (set! (.-value (.-target e)) ""))))
-          :placeholder "input email or user-uuid here"}]])
-
      [:hr.my-2]
 
      [:div.flex.flex-row.items-center.gap-2

+ 11 - 9
src/main/frontend/handler/db_based/rtc.cljs

@@ -163,12 +163,14 @@
 
 (defn <rtc-invite-email
   [graph-uuid email]
-  (let [token (state/get-auth-id-token)]
-    (->
-     (p/do!
-      (state/<invoke-db-worker :thread-api/rtc-grant-graph-access
-                               token (str graph-uuid) [] [email])
-      (notification/show! "Invitation sent!" :success))
-     (p/catch (fn [e]
-                (notification/show! "Something wrong, please try again." :error)
-                (js/console.error e))))))
+  (let [token (state/get-auth-id-token)
+        user-uuid (user-handler/user-uuid)]
+    (when (and user-uuid token)
+      (->
+       (p/do!
+         (state/<invoke-db-worker :thread-api/rtc-grant-graph-access
+                                  token (str graph-uuid) user-uuid email)
+         (notification/show! "Invitation sent!" :success))
+       (p/catch (fn [e]
+                  (notification/show! "Something wrong, please try again." :error)
+                  (js/console.error e)))))))

+ 16 - 11
src/main/frontend/worker/rtc/core.cljs

@@ -483,13 +483,20 @@
                         :schema-version (str major-schema-version)})))
 
 (defn new-task--grant-access-to-others
-  [token graph-uuid & {:keys [target-user-uuids target-user-emails]}]
-  (let [{:keys [get-ws-create-task]} (gen-get-ws-create-map--memoized (ws-util/get-ws-url token))]
-    (ws-util/send&recv get-ws-create-task
-                       (cond-> {:action "grant-access"
-                                :graph-uuid graph-uuid}
-                         target-user-uuids (assoc :target-user-uuids target-user-uuids)
-                         target-user-emails (assoc :target-user-emails target-user-emails)))))
+  [token graph-uuid user-uuid target-user-email]
+  (m/sp
+    (let [{:keys [get-ws-create-task]} (gen-get-ws-create-map--memoized (ws-util/get-ws-url token))
+          encrypted-aes-key
+          (m/? (rtc-crypt/task--encrypt-graph-aes-key-by-other-user-public-key
+                get-ws-create-task graph-uuid user-uuid target-user-email))
+          resp (m/? (ws-util/send&recv get-ws-create-task
+                                       (cond-> {:action "grant-access"
+                                                :graph-uuid graph-uuid
+                                                :target-user-email+encrypted-aes-key-coll
+                                                [{:user/email target-user-email
+                                                  :encrypted-aes-key (ldb/write-transit-str encrypted-aes-key)}]})))]
+      (when (:ex-data resp)
+        (throw (ex-info (:ex-message resp) (:ex-data resp)))))))
 
 (defn new-task--get-block-content-versions
   "Return a task that return map [:ex-data :ex-message :versions]"
@@ -612,10 +619,8 @@
   (rtc-toggle-remote-profile))
 
 (def-thread-api :thread-api/rtc-grant-graph-access
-  [token graph-uuid target-user-uuids target-user-emails]
-  (new-task--grant-access-to-others token graph-uuid
-                                    :target-user-uuids target-user-uuids
-                                    :target-user-emails target-user-emails))
+  [token graph-uuid user-uuid target-user-email]
+  (new-task--grant-access-to-others token graph-uuid user-uuid target-user-email))
 
 (def-thread-api :thread-api/rtc-get-graphs
   [token]

+ 68 - 27
src/main/frontend/worker/rtc/crypt.cljs

@@ -43,6 +43,12 @@
     (assert (some? user-uuid))
     (str "user-rsa-key-pair###" user-uuid)))
 
+(defn- <import-public-key-transit-str
+  "Return js/CryptoKey"
+  [public-key-transit-str]
+  (when-let [exported-public-key (ldb/read-transit-str public-key-transit-str)]
+    (crypt/<import-public-key exported-public-key)))
+
 (defn task--upload-user-rsa-key-pair
   "Uploads the user's RSA key pair to the server."
   [get-ws-create-task user-uuid public-key encrypted-private-key & {:keys [reset-private-key]
@@ -65,22 +71,30 @@
   Return {:public-key CryptoKey, :encrypted-private-key [array,array,array]}
   Return nil if not exists"
   [get-ws-create-task user-uuid]
-  (letfn [(task--import-public-key [public-key-transit-str]
-            (m/sp
-              (when-let [exported-public-key (ldb/read-transit-str public-key-transit-str)]
-                (c.m/<? (crypt/<import-public-key exported-public-key)))))]
-    (m/sp
-      (let [response (m/? (ws-util/send&recv get-ws-create-task
-                                             {:action "fetch-user-rsa-key-pair"
-                                              :user-uuid user-uuid}))]
-        (if (:ex-data response)
-          (throw (ex-info (:ex-message response)
-                          (assoc (:ex-data response)
-                                 :type :rtc.exception/fetch-user-rsa-key-pair-error)))
-          (let [{:keys [public-key encrypted-private-key]} response]
-            (when (and public-key encrypted-private-key)
-              {:public-key (m/? (task--import-public-key public-key))
-               :encrypted-private-key (ldb/read-transit-str encrypted-private-key)})))))))
+  (m/sp
+    (let [response (m/? (ws-util/send&recv get-ws-create-task
+                                           {:action "fetch-user-rsa-key-pair"
+                                            :user-uuid user-uuid}))]
+      (if (:ex-data response)
+        (throw (ex-info (:ex-message response)
+                        (assoc (:ex-data response)
+                               :type :rtc.exception/fetch-user-rsa-key-pair-error)))
+        (let [{:keys [public-key encrypted-private-key]} response]
+          (when (and public-key encrypted-private-key)
+            {:public-key (c.m/<? (<import-public-key-transit-str public-key))
+             :encrypted-private-key (ldb/read-transit-str encrypted-private-key)}))))))
+
+(defn- task--remote-fetch-graph-encrypted-aes-key
+  "Return nil if not exists."
+  [get-ws-create-task graph-uuid]
+  (m/sp
+    (let [response (m/? (ws-util/send&recv get-ws-create-task
+                                           {:action "fetch-graph-encrypted-aes-key"
+                                            :graph-uuid graph-uuid}))]
+      (if (:ex-data response)
+        (throw (ex-info (:ex-message response) (assoc (:ex-data response)
+                                                      :type :rtc.exception/fetch-graph-aes-key-error)))
+        (ldb/read-transit-str (:encrypted-aes-key response))))))
 
 (defn task--fetch-graph-aes-key
   "Fetches the AES key for a graph, from indexeddb or server.
@@ -90,17 +104,10 @@
     (let [encrypted-aes-key (c.m/<? (<get-item (graph-encrypted-aes-key-idb-key graph-uuid)))]
       (if encrypted-aes-key
         (c.m/<? (crypt/<decrypt-aes-key private-key encrypted-aes-key))
-        (let [response (m/? (ws-util/send&recv get-ws-create-task
-                                               {:action "fetch-graph-encrypted-aes-key"
-                                                :graph-uuid graph-uuid}))]
-          (if (:ex-data response)
-            (throw (ex-info (:ex-message response) (assoc (:ex-data response)
-                                                          :type :rtc.exception/fetch-graph-aes-key-error)))
-            (let [{:keys [encrypted-aes-key]} response]
-              (when-let [encrypted-aes-key* (ldb/read-transit-str encrypted-aes-key)]
-                (let [aes-key (c.m/<? (crypt/<decrypt-aes-key private-key encrypted-aes-key*))]
-                  (c.m/<? (<set-item! (graph-encrypted-aes-key-idb-key graph-uuid) encrypted-aes-key*))
-                  aes-key)))))))))
+        (when-let [encrypted-aes-key (m/? (task--remote-fetch-graph-encrypted-aes-key get-ws-create-task graph-uuid))]
+          (let [aes-key (c.m/<? (crypt/<decrypt-aes-key private-key encrypted-aes-key))]
+            (c.m/<? (<set-item! (graph-encrypted-aes-key-idb-key graph-uuid) encrypted-aes-key))
+            aes-key))))))
 
 (defn task--persist-graph-encrypted-aes-key
   [graph-uuid encrypted-aes-key]
@@ -123,6 +130,7 @@
        :private-key private-key})))
 
 (defn task--get-aes-key
+  "Return nil if not exists"
   [get-ws-create-task user-uuid graph-uuid]
   (m/sp
     (let [{:keys [_public-key private-key]} (m/? (task--get-decrypted-rsa-key-pair get-ws-create-task user-uuid))]
@@ -139,6 +147,39 @@
       (m/? (task--upload-user-rsa-key-pair get-ws-create-task user-uuid public-key new-encrypted-private-key
                                            :reset-private-key true)))))
 
+(defn- task--fetch-user-rsa-public-key
+  "Fetches the user's RSA public-key from server.
+  Return js/CryptoKey.
+  Return nil if not exists"
+  [get-ws-create-task user-email]
+  (m/sp
+    (let [response (m/? (ws-util/send&recv get-ws-create-task
+                                           {:action "fetch-user-rsa-public-key"
+                                            :user/email user-email}))]
+      (if (:ex-data response)
+        (throw (ex-info (:ex-message response)
+                        (assoc (:ex-data response)
+                               :type :rtc.exception/fetch-user-rsa-public-key-error)))
+        (let [{:keys [public-key]} response]
+          (when public-key
+            (c.m/<? (<import-public-key-transit-str public-key))))))))
+
+(defn task--encrypt-graph-aes-key-by-other-user-public-key
+  "Return encrypted-aes-key,
+  which is decrypted by current user's private-key, then other-user's public-key"
+  [get-ws-create-task graph-uuid user-uuid other-user-email]
+  (m/sp
+    (when-let [graph-aes-key (m/? (task--get-aes-key get-ws-create-task user-uuid graph-uuid))]
+      (let [{:keys [public-key] :as response}
+            (m/? (ws-util/send&recv get-ws-create-task
+                                    {:action "fetch-user-rsa-key-pair"
+                                     :user-email other-user-email}))]
+        (if (:ex-data response)
+          (throw (ex-info (:ex-message response) (:ex-data response)))
+          (when public-key
+            (let [public-key* (c.m/<? (<import-public-key-transit-str public-key))]
+              (c.m/<? (crypt/<encrypt-aes-key public-key* graph-aes-key)))))))))
+
 (def-thread-api :thread-api/get-user-rsa-key-pair
   [token user-uuid]
   (m/sp

+ 1 - 0
src/main/frontend/worker/rtc/exception.cljs

@@ -28,6 +28,7 @@ the server will put it to s3 and return its presigned-url to clients."}
   :rtc.exception/ws-timeout {:doc "websocket timeout"}
 
   :rtc.exception/fetch-user-rsa-key-pair-error {:doc "Failed to fetch user RSA key pair from server"}
+  :rtc.exception/fetch-user-rsa-public-key-error {:doc "Failed to fetch user RSA public-key from server"}
   :rtc.exception/fetch-graph-aes-key-error {:doc "Failed to fetch graph AES key from server"}
   :rtc.exception/upload-user-rsa-key-pair-error {:doc "Failed to upload user RSA key pair to server"}
   :rtc.exception/not-found-user-rsa-key-pair {:doc "user rsa-key-pair not found"}

+ 12 - 3
src/main/frontend/worker/rtc/malli_schema.cljs

@@ -252,6 +252,9 @@
      ["fetch-graph-encrypted-aes-key"
       [:map
        [:encrypted-aes-key [:maybe :string]]]]
+     ["fetch-user-rsa-public-key"
+       [:map
+        [:public-key [:maybe :string]]]]
      ["upload-user-rsa-key-pair"
       [:map
        [:public-key :string]
@@ -321,8 +324,11 @@
       ["grant-access"
        [:map
         [:graph-uuid :uuid]
-        [:target-user-uuids {:optional true} [:sequential :uuid]]
-        [:target-user-emails {:optional true} [:sequential :string]]]]
+        [:target-user-email+encrypted-aes-key-coll
+         [:sequential
+          [:map
+           [:user/email :string]
+           [:encrypted-aes-key [:maybe :string]]]]]]]
       ["get-users-info"
        [:map
         [:graph-uuid :uuid]]]
@@ -403,7 +409,10 @@
         [:user-uuid :uuid]]]
       ["fetch-graph-encrypted-aes-key"
        [:map
-        [:graph-uuid :uuid]]]])))
+        [:graph-uuid :uuid]]]
+      ["fetch-user-rsa-public-key"
+       [:map
+        [:user/email :string]]]])))
 
 (def data-to-ws-encoder (m/encoder data-to-ws-schema (mt/transformer
                                                       mt/string-transformer