(ns frontend.handler.user "Provides user related handler fns like login and logout" (:require-macros [frontend.handler.user]) (:require [cljs-http.client :as http] [cljs-time.coerce :as tc] [cljs-time.core :as t] [cljs.core.async :as async :refer [ jwt (string/split ".") second (#(.decodeString ^js crypt/base64 % true)) js/JSON.parse (js->clj :keywordize-keys true) (update :cognito:username decode-username))) (defn- expired? [parsed-jwt] (some-> (* 1000 (:exp parsed-jwt)) tc/from-long (t/before? (t/now)))) (defn- almost-expired? "return true when jwt will expire after 1h" [parsed-jwt] (some-> (* 1000 (:exp parsed-jwt)) tc/from-long (t/before? (-> 1 t/hours t/from-now)))) (defn- almost-expired-or-expired? [parsed-jwt] (or (almost-expired? parsed-jwt) (expired? parsed-jwt))) (defn email [] (some-> (state/get-auth-id-token) parse-jwt :email)) (defn username [] (some-> (state/get-auth-id-token) parse-jwt :cognito:username)) (defn user-uuid [] (some-> (state/get-auth-id-token) parse-jwt :sub)) (defn logged-in? [] (some? (state/get-auth-refresh-token))) (defn- set-token-to-localstorage! ([id-token access-token] (prn :debug "set-token-to-localstorage!") (js/localStorage.setItem "id-token" id-token) (js/localStorage.setItem "access-token" access-token)) ([id-token access-token refresh-token] (prn :debug "set-token-to-localstorage!") (js/localStorage.setItem "id-token" id-token) (js/localStorage.setItem "access-token" access-token) (js/localStorage.setItem "refresh-token" refresh-token))) (defn- clear-cognito-tokens! "Clear tokens for cognito's localstorage, prefix is 'CognitoIdentityServiceProvider'" [] (let [prefix "CognitoIdentityServiceProvider."] (doseq [key (js/Object.keys js/localStorage)] (when (string/starts-with? key prefix) (js/localStorage.removeItem key))))) (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! "" "" "") (clear-cognito-tokens!)) ([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] (state/set-auth-id-token id-token) (state/set-auth-access-token access-token) (set-token-to-localstorage! id-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) (set-token-to-localstorage! id-token access-token refresh-token))) (defn- 500 (:status resp))) ;; invalid refresh-token (let [invalid-grant? (and (= 400 (:status resp)) (= (:error (:body resp)) "invalid_grant"))] (prn :debug :refresh-token-failed :status (:status resp)) (when invalid-grant? (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?))) (do (prn :debug :refresh-token-failed :status (:status resp) :body (:body resp) :error-code (:error-code resp) :error-text (:error-text resp)) nil) ; do nothing (not (http/unexceptional-status? (:status resp))) (notification/show! "exceptional status when refresh-token" :warning true) :else ; ok (when (and (:id_token (:body resp)) (:access_token (:body resp))) (set-tokens! (:id_token (:body resp)) (:access_token (:body resp))))))))) (defn restore-tokens-from-localstorage "Refresh id-token&access-token, pull latest repos, returns nil when tokens are not available." [] (println "restore-tokens-from-localstorage") (let [refresh-token (js/localStorage.getItem "refresh-token")] (when refresh-token (go (js payload))}))] (assert (= 200 (:status resp))) (let [body (js->clj (js/JSON.parse (:body resp))) access-token (get-in body ["AuthenticationResult" "AccessToken"]) id-token (get-in body ["AuthenticationResult" "IdToken"]) refresh-token (get-in body ["AuthenticationResult" "RefreshToken"])] (set-tokens! id-token access-token refresh-token) (state/pub-event! [:user/fetch-info-and-graphs]) {:id-token id-token :access-token access-token :refresh-token refresh-token}))))) (defn logout [] (clear-tokens) (state/clear-user-info!) (state/pub-event! [:user/logout])) (defn upgrade [] (let [base-upgrade-url "https://logseqdemo.lemonsqueezy.com/checkout/buy/13e194b5-c927-41a8-af58-ed1a36d6000d" user-uuid' (user-uuid) url (cond-> base-upgrade-url user-uuid' (str "?checkout[custom][user_uuid]=" (name user-uuid')))] (println " ~~~ LEMON: " url " ~~~ ") (js/window.open url))) ; (js/window.open ; "https://logseqdemo.lemonsqueezy.com/checkout/buy/13e194b5-c927-41a8-af58-ed1a36d6000d")) (defn id-token parse-jwt almost-expired-or-expired?)) (debug/pprint (str "refresh tokens... " (tc/to-string (t/now)))) ( (state/get-auth-id-token) parse-jwt expired?)) (ex-info "empty or expired token and refresh failed" {:anom :expired-token})))))) (defn (some #(when (= repo (:url %)) %) (:rtc/graphs @state/state)) :graph<->user-user-type)) (defn manager? [repo] (= (get-user-type repo) "manager")) ;; TODO: Remove if still unused #_(defn member? [repo] (= (get-user-type repo) "member")) (defn new-task--upload-user-avatar [avatar-str] (m/sp (when-let [token (state/get-auth-id-token)] (let [{:keys [status body] :as resp} (c.m/