Bläddra i källkod

enhance(sync): check token not expired before api-calls (#7267)

* enhance(sync): check token not expired before api-calls

* fix(sync): ensure id-token exists when calling user/user-uuid

* enhance(sync): add <wrap-ensure-id&access-token, remove refresh-token-loop

* fix: check exception before using the result of <user-uuid

* fix: notify users ex message instead of ex info

Co-authored-by: Tienson Qin <[email protected]>
rcmerci 2 år sedan
förälder
incheckning
decd47c8a9

+ 7 - 3
src/main/frontend/components/file_sync.cljs

@@ -370,9 +370,13 @@
                                            (state/pub-event! [:file-sync/onboarding-tip :unavailable])
 
                                            ;; current graph belong to other user, do nothing
-                                           (and (first @graphs-txid)
-                                                (not (fs-sync/check-graph-belong-to-current-user (user-handler/user-uuid)
-                                                                                                 (first @graphs-txid))))
+                                           (let [user-uuid (async/<! (user-handler/<user-uuid))
+                                                 user-uuid (when-not (instance? ExceptionInfo user-uuid) user-uuid)]
+                                             (and (first @graphs-txid)
+                                                  user-uuid
+                                                  (not (fs-sync/check-graph-belong-to-current-user
+                                                        user-uuid
+                                                        (first @graphs-txid)))))
                                            nil
 
                                            (and synced-file-graph?

+ 194 - 202
src/main/frontend/fs/sync.cljs

@@ -278,8 +278,8 @@
   "max retry count is 5.
   *stop: volatile var, stop retry-request when it's true,
           and return :stop"
-  ([api-name body token refresh-token-fn *stop] (<request* api-name body token refresh-token-fn 0 *stop))
-  ([api-name body token refresh-token-fn retry-count *stop]
+  ([api-name body token *stop] (<request* api-name body token 0 *stop))
+  ([api-name body token retry-count *stop]
    (go
      (if (and *stop @*stop (contains? stoppable-apis api-name))
        :stop
@@ -291,8 +291,7 @@
              (throw (js/Error. :file-sync-request))
              (do (println "will retry after" (min 60000 (* 1000 retry-count)) "ms")
                  (<! (timeout (min 60000 (* 1000 retry-count))))
-                 (let [token (<! (refresh-token-fn))]
-                   (<! (<request* api-name body token refresh-token-fn (inc retry-count) *stop)))))
+                 (<! (<request* api-name body token (inc retry-count) *stop))))
            (:resp resp)))))))
 
 (defn <request [api-name & args]
@@ -721,8 +720,7 @@
   (<upload-graph-encrypt-keys [this graph-uuid public-key encrypted-private-key]))
 
 (defprotocol IToken
-  (<get-token [this])
-  (<refresh-token [this]))
+  (<get-token [this]))
 
 
 (defn <case-different-local-file-exist?
@@ -767,14 +765,9 @@
 
 (deftype RSAPI [^:mutable graph-uuid' ^:mutable private-key' ^:mutable public-key']
   IToken
-  (<get-token [this]
-    (go
-      (or (state/get-auth-id-token)
-          (<! (<refresh-token this)))))
-  (<refresh-token [_]
-    (go
-      (<! (user/<refresh-id-token&access-token))
-      (state/get-auth-id-token)))
+  (<get-token [_this]
+    (user/<wrap-ensure-id&access-token
+     (state/get-auth-id-token)))
 
   IRSAPI
   (rsapi-ready? [_ graph-uuid] (and (= graph-uuid graph-uuid') private-key' public-key'))
@@ -855,14 +848,9 @@
 
 (deftype ^:large-vars/cleanup-todo CapacitorAPI [^:mutable graph-uuid' ^:mutable private-key ^:mutable public-key']
   IToken
-  (<get-token [this]
-    (go
-      (or (state/get-auth-id-token)
-          (<! (<refresh-token this)))))
-  (<refresh-token [_]
-    (go
-      (<! (user/<refresh-id-token&access-token))
-      (state/get-auth-id-token)))
+  (<get-token [_this]
+    (user/<wrap-ensure-id&access-token
+     (state/get-auth-id-token)))
 
   IRSAPI
   (rsapi-ready? [_ graph-uuid] (and (= graph-uuid graph-uuid') private-key public-key'))
@@ -1128,7 +1116,7 @@
 
   (<request [this api-name body]
     (go
-      (let [resp (<! (<request api-name body (<! (<get-token this)) #(<refresh-token this) *stopped?))]
+      (let [resp (<! (<request api-name body (<! (<get-token this)) *stopped?))]
         (if (http/unexceptional-status? (:status resp))
           (get-resp-json-body resp)
           (let [exp (ex-info "request failed"
@@ -1147,186 +1135,189 @@
     (.<request this "update_files" {:GraphUUID graph-uuid :TXId txid :Files files}))
 
   IToken
-  (<get-token [this]
-    (go
-      (or (state/get-auth-id-token)
-          (<! (<refresh-token this)))))
-
-  (<refresh-token [_]
-    (go
-      (<! (user/<refresh-id-token&access-token))
-      (state/get-auth-id-token))))
+  (<get-token [_this]
+    (user/<wrap-ensure-id&access-token
+     (state/get-auth-id-token))))
 
 (extend-type RemoteAPI
   IRemoteAPI
-  (<user-info [this] (.<request this "user_info" {}))
+  (<user-info [this]
+    (user/<wrap-ensure-id&access-token
+     (<! (.<request this "user_info" {}))))
   (<get-remote-all-files-meta [this graph-uuid]
-    (go
-      (let [file-meta-list      (transient #{})
-            encrypted-path-list (transient [])
-            exp-r
-            (<!
-             (go-loop [continuation-token nil]
-               (let [r (<! (.<request this "get_all_files"
-                                      (into
-                                       {}
-                                       (remove (comp nil? second)
-                                               {:GraphUUID graph-uuid :ContinuationToken continuation-token}))))]
-                 (if (instance? ExceptionInfo r)
-                   r
-                   (let [next-continuation-token (:NextContinuationToken r)
-                         objs                    (:Objects r)]
-                     (apply conj! encrypted-path-list (map (comp remove-user-graph-uuid-prefix :Key) objs))
-                     (apply conj! file-meta-list
-                       (map
-                         #(hash-map :checksum (:checksum %)
-                                    :encrypted-path (remove-user-graph-uuid-prefix (:Key %))
-                                    :size (:Size %)
-                                    :last-modified (:LastModified %))
-                         objs))
-                     (when-not (empty? next-continuation-token)
-                       (recur next-continuation-token)))))))]
-        (if (instance? ExceptionInfo exp-r)
-          exp-r
-          (let [file-meta-list*      (persistent! file-meta-list)
-                encrypted-path-list* (persistent! encrypted-path-list)
-                path-list-or-exp     (<! (<decrypt-fnames rsapi graph-uuid encrypted-path-list*))]
-            (if (instance? ExceptionInfo path-list-or-exp)
-              path-list-or-exp
-              (let [encrypted-path->path-map (zipmap encrypted-path-list* path-list-or-exp)]
-                (set
-                 (mapv
-                  #(->FileMetadata (:size %)
-                                   (:checksum %)
-                                   (get encrypted-path->path-map (:encrypted-path %))
-                                   (:encrypted-path %)
-                                   (:last-modified %)
-                                   true nil)
-                  file-meta-list*)))))))))
+    (user/<wrap-ensure-id&access-token
+     (let [file-meta-list      (transient #{})
+           encrypted-path-list (transient [])
+           exp-r
+           (<!
+            (go-loop [continuation-token nil]
+              (let [r (<! (.<request this "get_all_files"
+                                     (into
+                                      {}
+                                      (remove (comp nil? second)
+                                              {:GraphUUID graph-uuid :ContinuationToken continuation-token}))))]
+                (if (instance? ExceptionInfo r)
+                  r
+                  (let [next-continuation-token (:NextContinuationToken r)
+                        objs                    (:Objects r)]
+                    (apply conj! encrypted-path-list (map (comp remove-user-graph-uuid-prefix :Key) objs))
+                    (apply conj! file-meta-list
+                           (map
+                            #(hash-map :checksum (:checksum %)
+                                       :encrypted-path (remove-user-graph-uuid-prefix (:Key %))
+                                       :size (:Size %)
+                                       :last-modified (:LastModified %))
+                            objs))
+                    (when-not (empty? next-continuation-token)
+                      (recur next-continuation-token)))))))]
+       (if (instance? ExceptionInfo exp-r)
+         exp-r
+         (let [file-meta-list*      (persistent! file-meta-list)
+               encrypted-path-list* (persistent! encrypted-path-list)
+               path-list-or-exp     (<! (<decrypt-fnames rsapi graph-uuid encrypted-path-list*))]
+           (if (instance? ExceptionInfo path-list-or-exp)
+             path-list-or-exp
+             (let [encrypted-path->path-map (zipmap encrypted-path-list* path-list-or-exp)]
+               (set
+                (mapv
+                 #(->FileMetadata (:size %)
+                                  (:checksum %)
+                                  (get encrypted-path->path-map (:encrypted-path %))
+                                  (:encrypted-path %)
+                                  (:last-modified %)
+                                  true nil)
+                 file-meta-list*)))))))))
 
   (<get-remote-files-meta [this graph-uuid filepaths]
     {:pre [(coll? filepaths)]}
-    (go
-      (let [encrypted-paths* (<! (<encrypt-fnames rsapi graph-uuid filepaths))
-            r                (<! (.<request this "get_files_meta" {:GraphUUID graph-uuid :Files encrypted-paths*}))]
-        (if (instance? ExceptionInfo r)
-          r
-          (let [encrypted-paths (mapv :FilePath r)
-                paths-or-exp    (<! (<decrypt-fnames rsapi graph-uuid encrypted-paths))]
-            (if (instance? ExceptionInfo paths-or-exp)
-              paths-or-exp
-              (let [encrypted-path->path-map (zipmap encrypted-paths paths-or-exp)]
-                (into #{}
-                      (comp
-                       (filter #(not= "filepath too long" (:Error %)))
-                       (map #(->FileMetadata (:Size %)
-                                             (:Checksum %)
-                                             (get encrypted-path->path-map (:FilePath %))
-                                             (:FilePath %)
-                                             (:LastModified %)
-                                             true nil)))
-                      r))))))))
+    (user/<wrap-ensure-id&access-token
+     (let [encrypted-paths* (<! (<encrypt-fnames rsapi graph-uuid filepaths))
+           r                (<! (.<request this "get_files_meta" {:GraphUUID graph-uuid :Files encrypted-paths*}))]
+       (if (instance? ExceptionInfo r)
+         r
+         (let [encrypted-paths (mapv :FilePath r)
+               paths-or-exp    (<! (<decrypt-fnames rsapi graph-uuid encrypted-paths))]
+           (if (instance? ExceptionInfo paths-or-exp)
+             paths-or-exp
+             (let [encrypted-path->path-map (zipmap encrypted-paths paths-or-exp)]
+               (into #{}
+                     (comp
+                      (filter #(not= "filepath too long" (:Error %)))
+                      (map #(->FileMetadata (:Size %)
+                                            (:Checksum %)
+                                            (get encrypted-path->path-map (:FilePath %))
+                                            (:FilePath %)
+                                            (:LastModified %)
+                                            true nil)))
+                     r))))))))
 
   (<get-remote-graph [this graph-name-opt graph-uuid-opt]
     {:pre [(or graph-name-opt graph-uuid-opt)]}
-    (.<request this "get_graph" (cond-> {}
-                                  (seq graph-name-opt)
-                                  (assoc :GraphName graph-name-opt)
-                                  (seq graph-uuid-opt)
-                                  (assoc :GraphUUID graph-uuid-opt))))
+    (user/<wrap-ensure-id&access-token
+     (<! (.<request this "get_graph" (cond-> {}
+                                       (seq graph-name-opt)
+                                       (assoc :GraphName graph-name-opt)
+                                       (seq graph-uuid-opt)
+                                       (assoc :GraphUUID graph-uuid-opt))))))
 
   (<get-remote-file-versions [this graph-uuid filepath]
-    (go
-      (let [encrypted-path (first (<! (<encrypt-fnames rsapi graph-uuid [filepath])))]
-        (<! (.<request this "get_file_version_list" {:GraphUUID graph-uuid :File encrypted-path})))))
+    (user/<wrap-ensure-id&access-token
+     (let [encrypted-path (first (<! (<encrypt-fnames rsapi graph-uuid [filepath])))]
+       (<! (.<request this "get_file_version_list" {:GraphUUID graph-uuid :File encrypted-path})))))
 
   (<list-remote-graphs [this]
-    (.<request this "list_graphs"))
+    (user/<wrap-ensure-id&access-token
+     (<! (.<request this "list_graphs"))))
 
   (<get-deletion-logs [this graph-uuid from-txid]
-    (go
-      (let [r (<! (.<request this "get_deletion_log" {:GraphUUID graph-uuid :FromTXId from-txid}))]
-        (if (instance? ExceptionInfo r)
-          r
-          (let [txns-with-encrypted-paths (mapv #(update % :path remove-user-graph-uuid-prefix) (:Transactions r))
-                encrypted-paths           (mapv :path txns-with-encrypted-paths)
-                encrypted-path->path-map
-                (zipmap
-                 encrypted-paths
-                 (<! (<decrypt-fnames rsapi graph-uuid encrypted-paths)))
-                txns
-                (mapv
-                 (fn [txn] (update txn :path #(get encrypted-path->path-map %)))
-                 txns-with-encrypted-paths)]
-            txns)))))
+    (user/<wrap-ensure-id&access-token
+     (let [r (<! (.<request this "get_deletion_log" {:GraphUUID graph-uuid :FromTXId from-txid}))]
+       (if (instance? ExceptionInfo r)
+         r
+         (let [txns-with-encrypted-paths (mapv #(update % :path remove-user-graph-uuid-prefix) (:Transactions r))
+               encrypted-paths           (mapv :path txns-with-encrypted-paths)
+               encrypted-path->path-map
+               (zipmap
+                encrypted-paths
+                (<! (<decrypt-fnames rsapi graph-uuid encrypted-paths)))
+               txns
+               (mapv
+                (fn [txn] (update txn :path #(get encrypted-path->path-map %)))
+                txns-with-encrypted-paths)]
+           txns)))))
 
   (<get-diff [this graph-uuid from-txid]
     ;; TODO: path in transactions should be relative path(now s3 key, which includes graph-uuid and user-uuid)
-    (go
-      (let [r (<! (.<request this "get_diff" {:GraphUUID graph-uuid :FromTXId from-txid}))]
-        (if (instance? ExceptionInfo r)
-          r
-          (let [txns-with-encrypted-paths (sort-by :TXId (:Transactions r))
-                txns-with-encrypted-paths*
-                (mapv
-                 (fn [txn]
-                   (assoc txn :TXContent
-                          (mapv
-                           (fn [[to-path from-path checksum]]
-                             [(remove-user-graph-uuid-prefix to-path)
-                              (some-> from-path remove-user-graph-uuid-prefix)
-                              checksum])
-                           (:TXContent txn))))
-                 txns-with-encrypted-paths)
+    (user/<wrap-ensure-id&access-token
+     (let [r (<! (.<request this "get_diff" {:GraphUUID graph-uuid :FromTXId from-txid}))]
+       (if (instance? ExceptionInfo r)
+         r
+         (let [txns-with-encrypted-paths (sort-by :TXId (:Transactions r))
+               txns-with-encrypted-paths*
+               (mapv
+                (fn [txn]
+                  (assoc txn :TXContent
+                         (mapv
+                          (fn [[to-path from-path checksum]]
+                            [(remove-user-graph-uuid-prefix to-path)
+                             (some-> from-path remove-user-graph-uuid-prefix)
+                             checksum])
+                          (:TXContent txn))))
+                txns-with-encrypted-paths)
+               encrypted-paths
+               (mapcat
+                (fn [txn]
+                  (remove
+                   #(or (nil? %) (not (string/starts-with? % "e.")))
+                   (mapcat
+                    (fn [[to-path from-path _checksum]] [to-path from-path])
+                    (:TXContent txn))))
+                txns-with-encrypted-paths*)
+               encrypted-path->path-map
+               (zipmap
                 encrypted-paths
-                (mapcat
-                 (fn [txn]
-                   (remove
-                    #(or (nil? %) (not (string/starts-with? % "e.")))
-                    (mapcat
-                     (fn [[to-path from-path _checksum]] [to-path from-path])
-                     (:TXContent txn))))
-                 txns-with-encrypted-paths*)
-                encrypted-path->path-map
-                (zipmap
-                 encrypted-paths
-                 (<! (<decrypt-fnames rsapi graph-uuid encrypted-paths)))
-                txns
-                (mapv
-                 (fn [txn]
-                   (assoc
-                    txn :TXContent
-                    (mapv
-                     (fn [[to-path from-path checksum]]
-                       [(get encrypted-path->path-map to-path to-path)
-                        (some->> from-path (get encrypted-path->path-map))
-                        checksum])
-                     (:TXContent txn))))
-                 txns-with-encrypted-paths*)]
-            [txns
-             (:TXId (last txns))
-             (:TXId (first txns))])))))
+                (<! (<decrypt-fnames rsapi graph-uuid encrypted-paths)))
+               txns
+               (mapv
+                (fn [txn]
+                  (assoc
+                   txn :TXContent
+                   (mapv
+                    (fn [[to-path from-path checksum]]
+                      [(get encrypted-path->path-map to-path to-path)
+                       (some->> from-path (get encrypted-path->path-map))
+                       checksum])
+                    (:TXContent txn))))
+                txns-with-encrypted-paths*)]
+           [txns
+            (:TXId (last txns))
+            (:TXId (first txns))])))))
 
   (<create-graph [this graph-name]
-    (.<request this "create_graph" {:GraphName graph-name}))
+    (user/<wrap-ensure-id&access-token
+     (<! (.<request this "create_graph" {:GraphName graph-name}))))
 
   (<delete-graph [this graph-uuid]
-    (.<request this "delete_graph" {:GraphUUID graph-uuid}))
+    (user/<wrap-ensure-id&access-token
+     (<! (.<request this "delete_graph" {:GraphUUID graph-uuid}))))
 
   (<get-graph-salt [this graph-uuid]
-    (.<request this "get_graph_salt" {:GraphUUID graph-uuid}))
+    (user/<wrap-ensure-id&access-token
+     (<! (.<request this "get_graph_salt" {:GraphUUID graph-uuid}))))
 
   (<create-graph-salt [this graph-uuid]
-    (.<request this "create_graph_salt" {:GraphUUID graph-uuid}))
+    (user/<wrap-ensure-id&access-token
+     (<! (.<request this "create_graph_salt" {:GraphUUID graph-uuid}))))
 
   (<get-graph-encrypt-keys [this graph-uuid]
-    (.<request this "get_graph_encrypt_keys" {:GraphUUID graph-uuid}))
+    (user/<wrap-ensure-id&access-token
+     (<! (.<request this "get_graph_encrypt_keys" {:GraphUUID graph-uuid}))))
 
   (<upload-graph-encrypt-keys [this graph-uuid public-key encrypted-private-key]
-    (.<request this "upload_graph_encrypt_keys" {:GraphUUID             graph-uuid
-                                                 :public-key            public-key
-                                                 :encrypted-private-key encrypted-private-key})))
-
+    (user/<wrap-ensure-id&access-token
+     (<! (.<request this "upload_graph_encrypt_keys" {:GraphUUID             graph-uuid
+                                                      :public-key            public-key
+                                                      :encrypted-private-key encrypted-private-key})))))
 
 (def remoteapi (->RemoteAPI nil))
 
@@ -3049,43 +3040,44 @@
     (when (false? @*sync-entered?)
       (reset! *sync-entered? true)
       (let [*sync-state                 (atom (sync-state))
-            current-user-uuid           (user/user-uuid)
+            current-user-uuid           (<! (user/<user-uuid))
             ;; put @graph-uuid & get-current-repo together,
             ;; prevent to get older repo dir and current graph-uuid.
             _                           (<! (p->c (persist-var/-load graphs-txid)))
             [user-uuid graph-uuid txid] @graphs-txid
             txid                        (or txid 0)
             repo                        (state/get-current-repo)]
-        (when (and repo
-                   (graph-sync-off? repo) @network-online-cursor
-                   user-uuid graph-uuid txid
-                   (user/logged-in?)
-                   (not (config/demo-graph? repo)))
-          (try
-            (when-let [sm (sync-manager-singleton current-user-uuid graph-uuid
-                                                  (config/get-repo-dir repo) repo
-                                                  txid *sync-state)]
-              (when (check-graph-belong-to-current-user current-user-uuid user-uuid)
-                (if-not (<! (<check-remote-graph-exists graph-uuid)) ; remote graph has been deleted
-                  (clear-graphs-txid! repo)
-                  (do
-                    (state/set-file-sync-state graph-uuid @*sync-state)
-                    (state/set-file-sync-manager graph-uuid sm)
-
-                    ;; update global state when *sync-state changes
-                    (add-watch *sync-state ::update-global-state
-                               (fn [_ _ _ n]
-                                 (state/set-file-sync-state graph-uuid n)))
-
-                    (state/set-state! [:file-sync/graph-state :current-graph-uuid] graph-uuid)
-
-                    (.start sm)
-
-                    (offer! remote->local-full-sync-chan true)
-                    (offer! full-sync-chan true)))))
-            (catch :default e
-              (prn "Sync start error: ")
-              (log/error :exception e))))
+        (when-not (instance? ExceptionInfo current-user-uuid)
+          (when (and repo
+                     (graph-sync-off? repo) @network-online-cursor
+                     user-uuid graph-uuid txid
+                     (user/logged-in?)
+                     (not (config/demo-graph? repo)))
+            (try
+              (when-let [sm (sync-manager-singleton current-user-uuid graph-uuid
+                                                    (config/get-repo-dir repo) repo
+                                                    txid *sync-state)]
+                (when (check-graph-belong-to-current-user current-user-uuid user-uuid)
+                  (if-not (<! (<check-remote-graph-exists graph-uuid)) ; remote graph has been deleted
+                    (clear-graphs-txid! repo)
+                    (do
+                      (state/set-file-sync-state graph-uuid @*sync-state)
+                      (state/set-file-sync-manager graph-uuid sm)
+
+                      ;; update global state when *sync-state changes
+                      (add-watch *sync-state ::update-global-state
+                                 (fn [_ _ _ n]
+                                   (state/set-file-sync-state graph-uuid n)))
+
+                      (state/set-state! [:file-sync/graph-state :current-graph-uuid] graph-uuid)
+
+                      (.start sm)
+
+                      (offer! remote->local-full-sync-chan true)
+                      (offer! full-sync-chan true)))))
+              (catch :default e
+                (prn "Sync start error: ")
+                (log/error :exception e)))))
         (reset! *sync-entered? false)))))
 
 ;;; ### some add-watches

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

@@ -236,7 +236,6 @@
     (el/listen!))
   (persist-var/load-vars)
   (user-handler/restore-tokens-from-localstorage)
-  (user-handler/refresh-tokens-loop)
   (js/setTimeout instrument! (* 60 1000)))
 
 (defn stop! []

+ 33 - 25
src/main/frontend/handler/file_sync.cljs

@@ -44,28 +44,33 @@
   [name]
   (go
     (let [r* (<! (sync/<create-graph sync/remoteapi name))
-          r (if (instance? ExceptionInfo r*) r* (:GraphUUID r*))]
-      (if (and (not (instance? ExceptionInfo r))
-               (string? r))
-        (let [tx-info [0 r (user/user-uuid) (state/get-current-repo)]]
-          (<! (apply sync/<update-graphs-txid! tx-info))
-          (swap! refresh-file-sync-component not)
-          tx-info)
-        (do
-          (state/set-state! [:ui/loading? :graph/create-remote?] false)
-          (cond
-           ;; already processed this exception by events
-           ;; - :file-sync/storage-exceed-limit
-           ;; - :file-sync/graph-count-exceed-limit
-           (or (sync/storage-exceed-limit? r)
-               (sync/graph-count-exceed-limit? r))
-           nil
+          user-uuid-or-exp (<! (user/<user-uuid))
+          r (if (instance? ExceptionInfo r*) r*
+                (if (instance? ExceptionInfo user-uuid-or-exp)
+                  user-uuid-or-exp
+                  (:GraphUUID r*)))]
+      (when-not (instance? ExceptionInfo user-uuid-or-exp)
+        (if (and (not (instance? ExceptionInfo r))
+                 (string? r))
+          (let [tx-info [0 r user-uuid-or-exp (state/get-current-repo)]]
+            (<! (apply sync/<update-graphs-txid! tx-info))
+            (swap! refresh-file-sync-component not)
+            tx-info)
+          (do
+            (state/set-state! [:ui/loading? :graph/create-remote?] false)
+            (cond
+              ;; already processed this exception by events
+              ;; - :file-sync/storage-exceed-limit
+              ;; - :file-sync/graph-count-exceed-limit
+              (or (sync/storage-exceed-limit? r)
+                  (sync/graph-count-exceed-limit? r))
+              nil
 
-           (contains? #{400 404} (get-in (ex-data r) [:err :status]))
-           (notification/show! (str "Create graph failed: already existed graph: " name) :warning true nil 4000)
+              (contains? #{400 404} (get-in (ex-data r) [:err :status]))
+              (notification/show! (str "Create graph failed: already existed graph: " name) :warning true nil 4000)
 
-           :else
-           (notification/show! (str "Create graph failed:" r) :warning true nil 4000)))))))
+              :else
+              (notification/show! (str "Create graph failed: " (ex-message r)) :warning true nil 4000))))))))
 
 (defn <delete-graph
   [graph-uuid]
@@ -100,11 +105,14 @@
 (defn init-graph [graph-uuid]
   (go
     (let [repo (state/get-current-repo)
-          user-uuid (user/user-uuid)]
-      (state/set-state! :sync-graph/init? true)
-      (<! (sync/<update-graphs-txid! 0 graph-uuid user-uuid repo))
-      (swap! refresh-file-sync-component not)
-      (state/pub-event! [:graph/switch repo {:persist? false}]))))
+          user-uuid-or-exp (<! (user/<user-uuid))]
+      (if (instance? ExceptionInfo user-uuid-or-exp)
+        (notification/show! (ex-message user-uuid-or-exp) :error)
+        (do
+          (state/set-state! :sync-graph/init? true)
+          (<! (sync/<update-graphs-txid! 0 graph-uuid user-uuid-or-exp repo))
+          (swap! refresh-file-sync-component not)
+          (state/pub-event! [:graph/switch repo {:persist? false}]))))))
 
 (defn download-version-file
   ([graph-uuid file-uuid version-uuid]

+ 9 - 0
src/main/frontend/handler/user.clj

@@ -0,0 +1,9 @@
+(ns frontend.handler.user
+  "Macros.")
+
+(defmacro <wrap-ensure-id&access-token
+  [& body]
+  `(cljs.core.async/go
+     (if-some [exp# (cljs.core.async/<! (<ensure-id&access-token))]
+       exp#
+       (do ~@body))))

+ 53 - 30
src/main/frontend/handler/user.cljs

@@ -1,5 +1,6 @@
 (ns frontend.handler.user
   "Provides user related handler fns like login and logout"
+  (:require-macros [frontend.handler.user])
   (:require [frontend.config :as config]
             [frontend.handler.config :as config-handler]
             [frontend.state :as state]
@@ -8,7 +9,7 @@
             [cljs-time.core :as t]
             [cljs-time.coerce :as tc]
             [cljs-http.client :as http]
-            [cljs.core.async :as async :refer [go go-loop <! timeout]]))
+            [cljs.core.async :as async :refer [go <!]]))
 
 (defn set-preferred-format!
   [format]
@@ -57,19 +58,14 @@
    parse-jwt
    :email))
 
-(defn user-uuid []
+(defn- user-uuid []
   (some->
    (state/get-auth-id-token)
    parse-jwt
    :sub))
 
 (defn logged-in? []
-  (boolean
-   (some->
-    (state/get-auth-id-token)
-    parse-jwt
-    expired?
-    not)))
+  (some? (state/get-auth-refresh-token)))
 
 (defn- set-token-to-localstorage!
   ([id-token access-token]
@@ -83,12 +79,19 @@
    (js/localStorage.setItem "refresh-token" refresh-token)))
 
 (defn- clear-tokens
-  []
-  (state/set-auth-id-token nil)
-  (state/set-auth-access-token nil)
-  (state/set-auth-refresh-token nil)
-  (set-token-to-localstorage! "" "" ""))
-
+  ([]
+   (state/set-auth-id-token nil)
+   (state/set-auth-access-token nil)
+   (state/set-auth-refresh-token nil)
+   (set-token-to-localstorage! "" "" ""))
+  ([except-refresh-token?]
+   (state/set-auth-id-token nil)
+   (state/set-auth-access-token nil)
+   (when-not except-refresh-token?
+     (state/set-auth-refresh-token nil))
+   (if except-refresh-token?
+     (set-token-to-localstorage! "" "")
+     (set-token-to-localstorage! "" "" ""))))
 
 (defn- set-tokens!
   ([id-token access-token]
@@ -109,8 +112,25 @@
     (when-let [refresh-token (state/get-auth-refresh-token)]
       (let [resp (<! (http/get (str "https://" config/API-DOMAIN "/auth_refresh_token?refresh_token=" refresh-token)
                                {:with-credentials? false}))]
+        (cond
+          (and (<= 400 (:status resp))
+               (> 500 (:status resp)))
+          ;; invalid refresh-token
+          (clear-tokens)
+
+          ;; e.g. api return 500, server internal error
+          ;; we shouldn't clear tokens if they aren't expired yet
+          ;; the `refresh-tokens-loop` will retry soon
+          (and (not (http/unexceptional-status? (:status resp)))
+               (not (-> (state/get-auth-id-token) parse-jwt expired?)))
+          nil                           ; do nothing
+
+          (not (http/unexceptional-status? (:status resp)))
+          (clear-tokens true)
+
+          :else                         ; ok
         (when (and (:id_token (:body resp)) (:access_token (:body resp)))
-          (set-tokens! (:id_token (:body resp)) (:access_token (:body resp))))))))
+          (set-tokens! (:id_token (:body resp)) (:access_token (:body resp)))))))))
 
 (defn restore-tokens-from-localstorage
   "Restore id-token, access-token, refresh-token from localstorage,
@@ -148,22 +168,25 @@
   (clear-tokens)
   (state/pub-event! [:user/logout]))
 
+(defn <ensure-id&access-token
+  []
+  (go
+    (when (or (nil? (state/get-auth-id-token))
+              (-> (state/get-auth-id-token) parse-jwt almost-expired-or-expired?))
+      (debug/pprint (str "refresh tokens... " (tc/to-string (t/now))))
+      (<! (<refresh-id-token&access-token))
+      (when (or (nil? (state/get-auth-id-token))
+                (-> (state/get-auth-id-token) parse-jwt expired?))
+        (ex-info "empty or expired token and refresh failed" {})))))
+
+(defn <user-uuid
+  []
+  (go
+    (if-some [exp (<! (<ensure-id&access-token))]
+      exp
+      (user-uuid))))
 
-
-;;; refresh tokens loop
-(def stop-refresh false)
-(defn refresh-tokens-loop []
-  (debug/pprint "start refresh-tokens-loop")
-  (go-loop []
-    (<! (timeout 60000))
-    (when (state/get-auth-refresh-token)
-      (let [id-token (state/get-auth-id-token)]
-        (when (or (nil? id-token)
-                  (-> id-token (parse-jwt) (almost-expired-or-expired?)))
-          (debug/pprint (str "refresh tokens... " (tc/to-string(t/now))))
-          (<! (<refresh-id-token&access-token)))))
-    (when-not stop-refresh
-      (recur))))
+;;; user groups
 
 (defn alpha-user?
   []