diff --git a/deps/db-sync/deps.edn b/deps/db-sync/deps.edn index 2fc27d0477..8915499b8d 100644 --- a/deps/db-sync/deps.edn +++ b/deps/db-sync/deps.edn @@ -10,6 +10,7 @@ thheller/shadow-cljs {:mvn/version "3.3.4"} com.lambdaisland/glogi {:git/url "https://github.com/lambdaisland/glogi" :git/sha "30328a045141717aadbbb693465aed55f0904976"} + metosin/reitit {:mvn/version "0.4.2"} logseq/common {:local/root "../common"} logseq/db {:local/root "../db"}} :aliases diff --git a/deps/db-sync/package.json b/deps/db-sync/package.json index e8f1e02cfe..c91432a808 100644 --- a/deps/db-sync/package.json +++ b/deps/db-sync/package.json @@ -6,7 +6,7 @@ "dev": "cd ./worker && npx wrangler dev", "watch": "clojure -M:cljs watch db-sync", "release": "clojure -M:cljs release db-sync", - "test": "clojure -M:cljs compile db-sync-test", + "test": "clojure -M:cljs compile db-sync-test && node worker/dist/worker-test.js", "clean": "rm -rf ./worker/dist/", "deploy": "yarn clean && yarn release && cd ./worker && wrangler deploy", "deploy-staging": "yarn clean && yarn release && cd ./worker && wrangler deploy --env staging" diff --git a/deps/db-sync/src/logseq/db_sync/worker.cljs b/deps/db-sync/src/logseq/db_sync/worker.cljs index 73cf05bb44..32e9ada212 100644 --- a/deps/db-sync/src/logseq/db_sync/worker.cljs +++ b/deps/db-sync/src/logseq/db_sync/worker.cljs @@ -13,6 +13,7 @@ [logseq.db-sync.protocol :as protocol] [logseq.db-sync.snapshot :as snapshot] [logseq.db-sync.storage :as storage] + [logseq.db-sync.worker.routes :as routes] [promesa.core :as p] [shadow.cljs.modern :refer (defclass)])) @@ -823,18 +824,12 @@ (log/error :db-sync/index-db-missing {:binding "DB"})) db)) -(defn- graph-path-parts [path] - (->> (string/split path #"/") - (remove string/blank?) - (vec))) - (defn- handle-index-fetch [^js self request] (let [db (index-db self) env (.-env self) url (js/URL. (.-url request)) path (.-pathname url) - method (.-method request) - parts (graph-path-parts path)] + method (.-method request)] (try (cond (contains? #{"OPTIONS" "HEAD"} method) @@ -848,331 +843,297 @@ claims (auth-claims request env) _ (when claims (index/clj result :keywordize-keys true) - body (coerce-http-request :graphs/create body) - graph-id (str (random-uuid)) - user-id (aget claims "sub")] - (cond - (not (string? user-id)) - (unauthorized) - - (nil? body) - (bad-request "invalid body") - - :else - (p/let [{:keys [graph-name schema-version]} body - name-exists? (index/clj result :keywordize-keys true) - body (coerce-http-request :graph-members/create body) - member-id (:user-id body) - email (:email body) - role (or (:role body) "member")] + body (coerce-http-request :graphs/create body) + graph-id (str (random-uuid)) + user-id (aget claims "sub")] (cond - (nil? body) - (bad-request "invalid body") + (not (string? user-id)) + (unauthorized) - (and (not (string? member-id)) - (not (string? email))) - (bad-request "invalid user") - - :else - (p/let [manager? (index/clj result :keywordize-keys true) - body (coerce-http-request :graph-members/update body) - role (:role body)] - (cond (nil? body) (bad-request "invalid body") :else - (p/let [manager? (index/clj result :keywordize-keys true) - body (coerce-http-request :e2ee/user-keys body) - user-id (aget claims "sub")] - (cond - (not (string? user-id)) - (unauthorized) + :else + (p/let [can-access? (index/clj result :keywordize-keys true) + body (coerce-http-request :graph-members/create body) + member-id (:user-id body) + email (:email body) + role (or (:role body) "member")] + (cond + (nil? body) + (bad-request "invalid body") - (and (= method "GET") - (= ["e2ee" "user-public-key"] parts)) - (let [email (.get (.-searchParams url) "email")] - (p/let [public-key (index/ {} - (some? public-key) - (assoc :public-key public-key))))) + (and (not (string? member-id)) + (not (string? email))) + (bad-request "invalid user") - (and (= method "GET") - (= 4 (count parts)) - (= "e2ee" (first parts)) - (= "graphs" (nth parts 1)) - (= "aes-key" (nth parts 3))) - (let [graph-id (nth parts 2) - user-id (aget claims "sub")] - (cond - (not (string? user-id)) - (unauthorized) + :else + (p/let [manager? (index/ {} - (some? encrypted-aes-key) - (assoc :encrypted-aes-key encrypted-aes-key)))))))) + :graph-members/update + (let [user-id (aget claims "sub")] + (cond + (not (string? user-id)) + (unauthorized) - (and (= method "POST") - (= 4 (count parts)) - (= "e2ee" (first parts)) - (= "graphs" (nth parts 1)) - (= "aes-key" (nth parts 3))) - (let [graph-id (nth parts 2) - user-id (aget claims "sub")] - (cond - (not (string? user-id)) - (unauthorized) + (not (string? member-id)) + (bad-request "invalid user id") - :else + :else + (.then (common/read-json request) + (fn [result] + (if (nil? result) + (bad-request "missing body") + (let [body (js->clj result :keywordize-keys true) + body (coerce-http-request :graph-members/update body) + role (:role body)] + (cond + (nil? body) + (bad-request "invalid body") + + :else + (p/let [manager? (index/clj result :keywordize-keys true) - body (coerce-http-request :e2ee/graph-aes-key body)] - (if (nil? body) + body (coerce-http-request :e2ee/user-keys body) + user-id (aget claims "sub")] + (cond + (not (string? user-id)) + (unauthorized) + + (nil? body) (bad-request "invalid body") - (p/let [access? (index/clj result :keywordize-keys true) - body (coerce-http-request :e2ee/grant-access body)] - (if (nil? body) - (bad-request "invalid body") - (p/let [manager? (index/ {:ok true} - (seq @missing) - (assoc :missing-users @missing)))))))))))))) + :e2ee/user-public-key-get + (let [email (.get (.-searchParams url) "email")] + (p/let [public-key (index/ {} + (some? public-key) + (assoc :public-key public-key))))) - (and (= method "DELETE") - (= 2 (count parts)) - (= "graphs" (first parts))) - (let [graph-id (nth parts 1 nil) - user-id (aget claims "sub")] - (cond - (not (seq graph-id)) - (bad-request "missing graph id") + :e2ee/graph-aes-key-get + (let [user-id (aget claims "sub")] + (cond + (not (string? user-id)) + (unauthorized) - (not (string? user-id)) - (unauthorized) + :else + (p/let [access? (index/ {} + (some? encrypted-aes-key) + (assoc :encrypted-aes-key encrypted-aes-key)))))))) - :else - (p/let [owns? (index/clj result :keywordize-keys true) + body (coerce-http-request :e2ee/graph-aes-key body)] + (if (nil? body) + (bad-request "invalid body") + (p/let [access? (index/clj result :keywordize-keys true) + body (coerce-http-request :e2ee/grant-access body)] + (if (nil? body) + (bad-request "invalid body") + (p/let [manager? (index/ {:ok true} + (seq @missing) + (assoc :missing-users @missing)))))))))))))) + + (not-found)) + + :else + (not-found))))) (catch :default error (log/error :db-sync/index-error error) (error-response "server error" 500))))) diff --git a/deps/db-sync/src/logseq/db_sync/worker/routes.cljs b/deps/db-sync/src/logseq/db_sync/worker/routes.cljs new file mode 100644 index 0000000000..05dee697e6 --- /dev/null +++ b/deps/db-sync/src/logseq/db_sync/worker/routes.cljs @@ -0,0 +1,31 @@ +(ns logseq.db-sync.worker.routes + (:require [reitit.core :as r])) + +(def ^:private route-data + [["/graphs" + ["" {:methods {"GET" :graphs/list + "POST" :graphs/create}}] + ["/:graph-id" + ["/access" {:methods {"GET" :graphs/access}}] + ["/members" {:methods {"GET" :graph-members/list + "POST" :graph-members/create}}] + ["/members/:member-id" {:methods {"PUT" :graph-members/update + "DELETE" :graph-members/delete}}] + ["" {:methods {"DELETE" :graphs/delete}}]]] + + ["/e2ee" + ["/user-keys" {:methods {"GET" :e2ee/user-keys-get + "POST" :e2ee/user-keys-post}}] + ["/user-public-key" {:methods {"GET" :e2ee/user-public-key-get}}] + ["/graphs/:graph-id" + ["/aes-key" {:methods {"GET" :e2ee/graph-aes-key-get + "POST" :e2ee/graph-aes-key-post}}] + ["/grant-access" {:methods {"POST" :e2ee/grant-access}}]]]]) + +(def ^:private router + (r/router route-data)) + +(defn match-route [method path] + (when-let [match (r/match-by-path router path)] + (when-let [handler (get-in match [:data :methods method])] + (assoc match :handler handler)))) diff --git a/deps/db-sync/test/logseq/db_sync/worker_routes_test.cljs b/deps/db-sync/test/logseq/db_sync/worker_routes_test.cljs new file mode 100644 index 0000000000..7e3cde08df --- /dev/null +++ b/deps/db-sync/test/logseq/db_sync/worker_routes_test.cljs @@ -0,0 +1,48 @@ +(ns logseq.db-sync.worker-routes-test + (:require [cljs.test :refer [deftest is testing]] + [logseq.db-sync.worker.routes :as routes])) + +(deftest match-route-graphs-test + (testing "graphs routes" + (let [match (routes/match-route "GET" "/graphs")] + (is (= :graphs/list (:handler match)))) + (let [match (routes/match-route "POST" "/graphs")] + (is (= :graphs/create (:handler match)))) + (let [match (routes/match-route "GET" "/graphs/graph-1/access")] + (is (= :graphs/access (:handler match))) + (is (= "graph-1" (get-in match [:path-params :graph-id])))) + (let [match (routes/match-route "GET" "/graphs/graph-2/members")] + (is (= :graph-members/list (:handler match))) + (is (= "graph-2" (get-in match [:path-params :graph-id])))) + (let [match (routes/match-route "POST" "/graphs/graph-3/members")] + (is (= :graph-members/create (:handler match)))) + (let [match (routes/match-route "PUT" "/graphs/graph-4/members/user-9")] + (is (= :graph-members/update (:handler match))) + (is (= "graph-4" (get-in match [:path-params :graph-id]))) + (is (= "user-9" (get-in match [:path-params :member-id])))) + (let [match (routes/match-route "DELETE" "/graphs/graph-5")] + (is (= :graphs/delete (:handler match))) + (is (= "graph-5" (get-in match [:path-params :graph-id])))))) + +(deftest match-route-e2ee-test + (testing "e2ee routes" + (let [match (routes/match-route "GET" "/e2ee/user-keys")] + (is (= :e2ee/user-keys-get (:handler match)))) + (let [match (routes/match-route "POST" "/e2ee/user-keys")] + (is (= :e2ee/user-keys-post (:handler match)))) + (let [match (routes/match-route "GET" "/e2ee/user-public-key")] + (is (= :e2ee/user-public-key-get (:handler match)))) + (let [match (routes/match-route "GET" "/e2ee/graphs/graph-7/aes-key")] + (is (= :e2ee/graph-aes-key-get (:handler match))) + (is (= "graph-7" (get-in match [:path-params :graph-id])))) + (let [match (routes/match-route "POST" "/e2ee/graphs/graph-8/aes-key")] + (is (= :e2ee/graph-aes-key-post (:handler match))) + (is (= "graph-8" (get-in match [:path-params :graph-id])))) + (let [match (routes/match-route "POST" "/e2ee/graphs/graph-9/grant-access")] + (is (= :e2ee/grant-access (:handler match))) + (is (= "graph-9" (get-in match [:path-params :graph-id])))))) + +(deftest match-route-method-mismatch-test + (testing "method mismatch returns nil" + (is (nil? (routes/match-route "GET" "/graphs/graph-1/members/user-9"))) + (is (nil? (routes/match-route "PUT" "/e2ee/user-keys")))))