Browse Source

feat(login): update login & refresh tokens logic

rcmerci 3 years ago
parent
commit
8a9ef0e4cf

+ 27 - 21
src/electron/electron/core.cljs

@@ -2,7 +2,7 @@
   (:require [electron.handler :as handler]
             [electron.search :as search]
             [electron.updater :refer [init-updater]]
-            [electron.utils :refer [*win mac? linux? logger get-win-from-sender restore-user-fetch-agent]]
+            [electron.utils :refer [*win mac? linux? logger get-win-from-sender restore-user-fetch-agent send-to-renderer]]
             [clojure.string :as string]
             [promesa.core :as p]
             [cljs-bean.core :as bean]
@@ -18,7 +18,8 @@
             [electron.exceptions :as exceptions]
             ["/electron/utils" :as utils]
             ["electron-context-menu" :as init-context-menu]
-            [goog.object :as gobj]))
+            [goog.object :as gobj]
+            [clojure.string :as string]))
 
 (defonce LSP_SCHEME "logseq")
 (defonce FILE_LSP_SCHEME "lsp")
@@ -45,32 +46,37 @@
   (.setAsDefaultProtocolClient app LSP_SCHEME)
 
   (.registerFileProtocol
-    protocol "assets"
-    (fn [^js request callback]
-      (let [url (.-url request)
-            path (string/replace url "assets://" "")
-            path (js/decodeURIComponent path)]
-        (callback #js {:path path}))))
+   protocol "assets"
+   (fn [^js request callback]
+     (let [url (.-url request)
+           path (string/replace url "assets://" "")
+           path (js/decodeURIComponent path)]
+       (callback #js {:path path}))))
 
   (.registerFileProtocol
-    protocol FILE_LSP_SCHEME
-    (fn [^js request callback]
-      (let [url (.-url request)
-            url' ^js (js/URL. url)
-            [_ ROOT] (if (string/starts-with? url PLUGIN_URL)
-                         [PLUGIN_URL PLUGINS_ROOT]
-                         [STATIC_URL js/__dirname])
+   protocol FILE_LSP_SCHEME
+   (fn [^js request callback]
+     (let [url (.-url request)
+           url' ^js (js/URL. url)
+           [_ ROOT] (if (string/starts-with? url PLUGIN_URL)
+                      [PLUGIN_URL PLUGINS_ROOT]
+                      [STATIC_URL js/__dirname])
 
-            path' (.-pathname url')
-            path' (js/decodeURIComponent path')
-            path' (.join path ROOT path')]
+           path' (.-pathname url')
+           path' (js/decodeURIComponent path')
+           path' (.join path ROOT path')]
 
-        (callback #js {:path path'}))))
+       (callback #js {:path path'}))))
 
   (.on app "open-url"
        (fn [event url]
-         (prn {:url url
-               :event event})))
+         (.info logger "open-url" (str {:url url
+                                        :event event}))
+
+         (let [parsed-url (js/URL. url)]
+           (when (and (= "logseq:" (.-protocol parsed-url))
+                      (= "auth-callback" (.-host parsed-url)))
+             (send-to-renderer "loginCallback" (.get (.-searchParams parsed-url) "code"))))))
 
   #(do
      (.unregisterProtocol protocol FILE_LSP_SCHEME)

+ 8 - 3
src/main/electron/listener.cljs

@@ -11,7 +11,9 @@
             [electron.ipc :as ipc]
             [frontend.ui :as ui]
             [frontend.handler.notification :as notification]
-            [frontend.handler.repo :as repo-handler]))
+            [frontend.handler.repo :as repo-handler]
+            [frontend.handler.user :as user]
+            [frontend.db.persist :as db-persist]))
 
 (defn persist-dbs!
   []
@@ -56,7 +58,6 @@
                      (fn []
                        (state/pub-event! [:modal/set-git-username-and-email])))
 
-
   (js/window.apis.on "getCurrentGraph"
                      (fn []
                        (when-let [graph (state/get-current-repo)]
@@ -92,7 +93,11 @@
                              handlers {:before     before-f
                                        :on-success after-f
                                        :on-error   error-f}]
-                         (repo-handler/persist-db! repo handlers)))))
+                         (repo-handler/persist-db! repo handlers))))
+
+  (js/window.apis.on "loginCallback"
+                     (fn [code]
+                       (user/login-callback code))))
 
 (defn listen!
   []

+ 11 - 10
src/main/frontend/components/header.cljs

@@ -30,15 +30,16 @@
                    (route-handler/go-to-journals!))}
      (ui/icon "home" {:style {:fontSize ui/icon-size}})]))
 
-(rum/defc login
-  [logged?]
+(rum/defc login < rum/reactive
+  []
   (rum/with-context [[t] i18n/*tongue-context*]
-    (when (and (not logged?)
-               (not config/publishing?))
+    (when-not config/publishing?
+      (if (user-handler/logged?)
+        [:span.text-sm.font-medium (user-handler/email)]
 
-      [:a.button.text-sm.font-medium.block {:on-click (fn []
-                                                        (js/window.open "https://logseq-test.auth.us-east-2.amazoncognito.com/oauth2/authorize?client_id=4fi79en9aurclkb92e25hmu9ts&response_type=code&scope=email+openid+phone&redirect_uri=logseq%3A%2F%2Ftest"))}
-       [:span (t :login)]])))
+        [:a.button.text-sm.font-medium.block {:on-click (fn []
+                                                          (js/window.open "https://logseq-test.auth.us-east-2.amazoncognito.com/oauth2/authorize?client_id=4fi79en9aurclkb92e25hmu9ts&response_type=code&scope=email+openid+phone&redirect_uri=logseq%3A%2F%2Fauth-callback"))}
+         [:span (t :login)]]))))
 
 (rum/defc left-menu-button < rum/reactive
   [{:keys [on-click]}]
@@ -50,7 +51,7 @@
 
 (rum/defc dropdown-menu < rum/reactive
   [{:keys [current-repo t]}]
-  (let [logged? (state/logged?)
+  (let [logged? (user-handler/logged?)
         page-menu (page-menu/page-menu nil)
         page-menu-and-hr (when (seq page-menu)
                            (concat page-menu [{:hr true}]))]
@@ -140,7 +141,7 @@
          (svg/reload 16) [:strong (t :updater/quit-and-install)]]]])))
 
 (rum/defc header < rum/reactive
-  [{:keys [open-fn current-repo logged? me default-home new-block-mode]}]
+  [{:keys [open-fn current-repo default-home new-block-mode]}]
   (let [repos (->> (state/sub [:me :repos])
                    (remove #(= (:url %) config/local-repo)))
         electron-mac? (and util/mac? (util/electron?))
@@ -179,7 +180,7 @@
      [:div.r.flex
       (when (and (not (mobile-util/is-native-platform?))
                  (not (util/electron?)))
-        (login logged?))
+        (login))
 
       (when plugin-handler/lsp-enabled?
         (plugins/hook-ui-items :toolbar))

+ 4 - 2
src/main/frontend/components/sidebar.cljs

@@ -19,6 +19,7 @@
             [frontend.handler.editor :as editor-handler]
             [frontend.handler.route :as route-handler]
             [frontend.handler.page :as page-handler]
+            [frontend.handler.user :as user-handler]
             [frontend.mixins :as mixins]
             [frontend.modules.shortcut.data-helper :as shortcut-dh]
             [frontend.state :as state]
@@ -347,7 +348,9 @@
         current-repo (state/sub :git/current-repo)
         loading-files? (when current-repo (state/sub [:repo/loading-files? current-repo]))
         journals-length (state/sub :journals-length)
-        latest-journals (db/get-latest-journals (state/get-current-repo) journals-length)]
+        latest-journals (db/get-latest-journals (state/get-current-repo) journals-length)
+        preferred-format (state/sub [:me :preferred_format])
+        logged? (user-handler/logged?)]
     [:div
      (cond
        (and default-home
@@ -451,7 +454,6 @@
         right-sidebar-blocks (state/sub-right-sidebar-blocks)
         route-name (get-in route-match [:data :name])
         global-graph-pages? (= :graph route-name)
-        logged? (:name me)
         db-restoring? (state/sub :db/restoring?)
         indexeddb-support? (state/sub :indexeddb/support?)
         page? (= :page route-name)

+ 12 - 9
src/main/frontend/fs/sync.cljs

@@ -7,7 +7,8 @@
             [clojure.string :as string]
             [frontend.state :as state]
             [frontend.config :as config]
-            [frontend.fs.macro :refer [err? err->]]))
+            [frontend.fs.macro :refer [err? err->]]
+            [frontend.handler.user :as user]))
 
 (def ws-addr "wss://og96xf1si7.execute-api.us-east-2.amazonaws.com/production?graphuuid=%s")
 
@@ -69,7 +70,7 @@
          (do
            (println "will retry after" (min 60000 (* 1000 retry-count)) "ms")
            (<! (timeout (min 60000 (* 1000 retry-count))))
-           (let [token (refresh-token-fn)]
+           (let [token (<! (refresh-token-fn))]
              (<! (request api-name body token refresh-token-fn (inc retry-count)))))
          (:resp resp))))))
 
@@ -190,18 +191,20 @@
 
 (def rsapi (->MockRSAPI))
 
-(deftype RemoteAPI [^:mutable token]
+(deftype RemoteAPI []
   Object
   (get-token [this]
-    (or token (.refresh-token this)))
+    (go
+      (or (state/get-auth-access-token)
+          (<! (.refresh-token this)))))
   (refresh-token [_]
-    ;; TODO
-    (set! token "<id-token>")
-    token)
+    (go
+      (<! (user/refresh-id-token&access-token))
+      (state/get-auth-access-token)))
   (request [this api-name body]
     (let [c (chan)]
       (go
-        (let [resp (<! (request api-name body (.get-token this) #(.refresh-token this)))]
+        (let [resp (<! (request api-name body (<! (.get-token this)) #(.refresh-token this)))]
           (if (http/unexceptional-status? (:status resp))
             (get-resp-json-body resp)
             {:err resp :body (get-resp-json-body resp)})))))
@@ -237,7 +240,7 @@
   (create-graph [this graph-name]
     (.request this "create_graph" {:GraphName graph-name})))
 
-(def remoteapi (->RemoteAPI nil))
+(def remoteapi (->RemoteAPI))
 
 (defn- remote-graph-exists? [graph-uuid]
   "200: true

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

@@ -18,6 +18,7 @@
             [frontend.handler.page :as page-handler]
             [frontend.handler.repo :as repo-handler]
             [frontend.handler.ui :as ui-handler]
+            [frontend.handler.user :as user-handler]
             [frontend.extensions.srs :as srs]
             [frontend.mobile.core :as mobile]
             [frontend.mobile.util :as mobile-util]
@@ -240,6 +241,7 @@
     (when (util/electron?)
       (el/listen!))
     (persist-var/load-vars)
+    (user-handler/refresh-tokens-loop)
     (js/setTimeout instrument! (* 60 1000))))
 
 (defn stop! []

+ 117 - 13
src/main/frontend/handler/user.cljs

@@ -6,6 +6,7 @@
             [frontend.idb :as idb]
             [frontend.state :as state]
             [frontend.util :as util]
+            [frontend.debug :as debug]
             [lambdaisland.glogi :as log]
             [promesa.core :as p]))
 
@@ -25,27 +26,30 @@
   (when format
     (config-handler/set-config! :preferred-format format)
     (state/set-preferred-format! format)
-    (when (:name (:me @state/state))
-      (when (state/logged?)
-        (util/post (str config/api "set_preferred_format")
-                   {:preferred_format (name format)}
-                   (fn [_result]
-                     (notification/show! "Format set successfully!" :success))
-                   (fn [_e]))))))
+    ;; (when (:name (:me @state/state))
+    ;;   (when (state/logged?)
+    ;;     (util/post (str config/api "set_preferred_format")
+    ;;                {:preferred_format (name format)}
+    ;;                (fn [_result]
+    ;;                  (notification/show! "Format set successfully!" :success))
+    ;;                (fn [_e]))))
+    ))
 
 (defn set-preferred-workflow!
   [workflow]
   (when workflow
     (config-handler/set-config! :preferred-workflow workflow)
     (state/set-preferred-workflow! workflow)
-    (when (:name (:me @state/state))
-      (util/post (str config/api "set_preferred_workflow")
-                 {:preferred_workflow (name workflow)}
-                 (fn [_result]
-                   (notification/show! "Workflow set successfully!" :success))
-                 (fn [_e])))))
+    ;; (when (:name (:me @state/state))
+    ;;   (util/post (str config/api "set_preferred_workflow")
+    ;;              {:preferred_workflow (name workflow)}
+    ;;              (fn [_result]
+    ;;                (notification/show! "Workflow set successfully!" :success))
+    ;;              (fn [_e])))
+    ))
 
 (defn sign-out!
+  {:deprecated "-"}
   ([]
    (sign-out! true))
   ([confirm?]
@@ -67,3 +71,103 @@
                    (sign-out! false))
                  (fn [error]
                    (log/error :user/delete-account-failed error)))))
+
+
+
+;;; userinfo, token, login/logout, ...
+
+(defn- parse-jwt [jwt]
+  (some-> jwt
+          (string/split ".")
+          (second)
+          (js/atob)
+          (js/JSON.parse)
+          (js->clj :keywordize-keys true)))
+
+(defn- expired? [parsed-jwt]
+  (some->
+   (* 1000 (:exp parsed-jwt))
+   (tc/from-long)
+   (t/before? (t/now))))
+
+(defn- almost-expired? [parsed-jwt]
+  "return true when jwt will expire after 1h"
+  (some->
+   (* 1000 (:exp parsed-jwt))
+   (tc/from-long)
+   (t/before? (-> 1 t/hours t/from-now))))
+
+(defn email []
+  (some->
+   (state/get-auth-id-token)
+   (parse-jwt)
+   (:email)))
+
+(defn logged? []
+  (boolean
+   (some->
+    (state/get-auth-id-token)
+    (parse-jwt)
+    (expired?)
+    (not))))
+
+(defn- clear-tokens []
+  (state/set-auth-id-token nil)
+  (state/set-auth-access-token nil)
+  (state/set-auth-refresh-token nil))
+
+(defn- set-tokens!
+  ([id-token access-token]
+   (state/set-auth-id-token id-token)
+   (state/set-auth-access-token access-token))
+  ([id-token access-token refresh-token]
+   (state/set-auth-id-token id-token)
+   (state/set-auth-access-token access-token)
+   (state/set-auth-refresh-token refresh-token)))
+
+(defn login-callback [code]
+  (go
+    (let [resp (<! (http/get (str "https://api.logseq.com/auth_callback?code=" code)))]
+      (if (= 200 (:status resp))
+        (-> resp
+            (:body)
+            (js/JSON.parse)
+            (js->clj :keywordize-keys true)
+            (as-> $ (set-tokens! (:id_token $) (:access_token $) (:refresh_token $))))
+        (debug/pprint "login-callback" resp)))))
+
+(defn refresh-id-token&access-token []
+  "refresh id-token and access-token, if refresh_token expired, clear all tokens
+   return true if success, else false"
+  (when-let [refresh-token (state/get-auth-refresh-token)]
+    (go
+      (let [resp (<! (http/get (str "https://api.logseq.com/auth_refresh_token?refresh_token=" refresh-token)))]
+        (if (= 400 (:status resp))
+          ;; invalid refresh_token
+          (do
+            (clear-tokens)
+            false)
+          (do
+            (->
+             resp
+             (as-> $ (and (http/unexceptional-status? (:status $)) $))
+             (:body)
+             (js/JSON.parse)
+             (js->clj :keywordize-keys true)
+             (as-> $ (set-tokens! (:id_token $) (:access_token $))))
+            true))))))
+
+;;; refresh tokens loop
+(def stop-refresh false)
+(defn refresh-tokens-loop []
+  (debug/pprint "start refresh-tokens-loop")
+  (go-loop []
+    (<! (timeout 60000))
+    (when-some [refresh-token (state/get-auth-refresh-token)]
+      (let [id-token (state/get-auth-id-token)]
+        (when (or (nil? id-token)
+                  (-> id-token (parse-jwt) (almost-expired?)))
+          (debug/pprint (str "refresh tokens... " (tc/to-string(t/now))))
+          (refresh-id-token&access-token))))
+    (when-not stop-refresh
+      (recur))))

+ 30 - 0
src/main/frontend/state.cljs

@@ -204,6 +204,13 @@
      :srs/mode?                             false
 
      :srs/cards-due-count                   nil
+
+     :reactive/query-dbs                    {}
+
+     ;; login, userinfo, token, ...
+     :auth/refresh-token                    nil
+     :auth/access-token                     nil
+     :auth/id-token                         nil
      })))
 
 ;; block uuid -> {content(String) -> ast}
@@ -1048,6 +1055,7 @@
 
 (defn logged?
   "Whether the user has logged in."
+  {:deprecated "-"}
   []
   (some? (get-name)))
 
@@ -1617,6 +1625,7 @@
     (->> (sub :sidebar/blocks)
          (filter #(= (first %) current-repo)))))
 
+
 (defn toggle-collapsed-block!
   [block-id]
   (let [current-repo (get-current-repo)]
@@ -1640,3 +1649,24 @@
   (and (editing?)
        ;; config
        (:custom-query? (last (get-editor-args)))))
+
+(defn set-auth-id-token
+  [id-token]
+  (set-state! :auth/id-token id-token))
+
+(defn set-auth-refresh-token
+  [refresh-token]
+  (set-state! :auth/refresh-token refresh-token))
+
+(defn set-auth-access-token
+  [access-token]
+  (set-state! :auth/access-token access-token))
+
+(defn get-auth-id-token []
+  (:auth/id-token @state))
+
+(defn get-auth-access-token []
+  (:auth/access-token @state))
+
+(defn get-auth-refresh-token []
+  (:auth/refresh-token @state))