user.cljs 7.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235
  1. (ns frontend.handler.user
  2. "Provides user related handler fns like login and logout"
  3. (:require-macros [frontend.handler.user])
  4. (:require [frontend.config :as config]
  5. [frontend.handler.config :as config-handler]
  6. [frontend.state :as state]
  7. [frontend.debug :as debug]
  8. [clojure.string :as string]
  9. [cljs-time.core :as t]
  10. [cljs-time.coerce :as tc]
  11. [cljs-http.client :as http]
  12. [cljs.core.async :as async :refer [go <!]]
  13. [goog.crypt.Sha256]
  14. [goog.crypt.Hmac]
  15. [goog.crypt :as crypt]))
  16. (defn set-preferred-format!
  17. [format]
  18. (when format
  19. (config-handler/set-config! :preferred-format format)
  20. (state/set-preferred-format! format)))
  21. (defn set-preferred-workflow!
  22. [workflow]
  23. (when workflow
  24. (config-handler/set-config! :preferred-workflow workflow)
  25. (state/set-preferred-workflow! workflow)))
  26. ;;; userinfo, token, login/logout, ...
  27. (defn- parse-jwt [jwt]
  28. (some-> jwt
  29. (string/split ".")
  30. second
  31. js/atob
  32. js/JSON.parse
  33. (js->clj :keywordize-keys true)))
  34. (defn- expired? [parsed-jwt]
  35. (some->
  36. (* 1000 (:exp parsed-jwt))
  37. tc/from-long
  38. (t/before? (t/now))))
  39. (defn- almost-expired?
  40. "return true when jwt will expire after 1h"
  41. [parsed-jwt]
  42. (some->
  43. (* 1000 (:exp parsed-jwt))
  44. tc/from-long
  45. (t/before? (-> 1 t/hours t/from-now))))
  46. (defn- almost-expired-or-expired?
  47. [parsed-jwt]
  48. (or (almost-expired? parsed-jwt)
  49. (expired? parsed-jwt)))
  50. (defn email []
  51. (some->
  52. (state/get-auth-id-token)
  53. parse-jwt
  54. :email))
  55. (defn user-uuid []
  56. (some->
  57. (state/get-auth-id-token)
  58. parse-jwt
  59. :sub))
  60. (defn logged-in? []
  61. (some? (state/get-auth-refresh-token)))
  62. (defn- set-token-to-localstorage!
  63. ([id-token access-token]
  64. (prn :debug "set-token-to-localstorage!")
  65. (js/localStorage.setItem "id-token" id-token)
  66. (js/localStorage.setItem "access-token" access-token))
  67. ([id-token access-token refresh-token]
  68. (prn :debug "set-token-to-localstorage!")
  69. (js/localStorage.setItem "id-token" id-token)
  70. (js/localStorage.setItem "access-token" access-token)
  71. (js/localStorage.setItem "refresh-token" refresh-token)))
  72. (defn- clear-tokens
  73. ([]
  74. (state/set-auth-id-token nil)
  75. (state/set-auth-access-token nil)
  76. (state/set-auth-refresh-token nil)
  77. (set-token-to-localstorage! "" "" ""))
  78. ([except-refresh-token?]
  79. (state/set-auth-id-token nil)
  80. (state/set-auth-access-token nil)
  81. (when-not except-refresh-token?
  82. (state/set-auth-refresh-token nil))
  83. (if except-refresh-token?
  84. (set-token-to-localstorage! "" "")
  85. (set-token-to-localstorage! "" "" ""))))
  86. (defn- set-tokens!
  87. ([id-token access-token]
  88. (state/set-auth-id-token id-token)
  89. (state/set-auth-access-token access-token)
  90. (set-token-to-localstorage! id-token access-token))
  91. ([id-token access-token refresh-token]
  92. (state/set-auth-id-token id-token)
  93. (state/set-auth-access-token access-token)
  94. (state/set-auth-refresh-token refresh-token)
  95. (set-token-to-localstorage! id-token access-token refresh-token)))
  96. (defn <refresh-id-token&access-token
  97. "Refresh id-token and access-token"
  98. []
  99. (go
  100. (when-let [refresh-token (state/get-auth-refresh-token)]
  101. (let [resp (<! (http/get (str "https://" config/API-DOMAIN "/auth_refresh_token?refresh_token=" refresh-token)
  102. {:with-credentials? false}))]
  103. (cond
  104. (and (<= 400 (:status resp))
  105. (> 500 (:status resp)))
  106. ;; invalid refresh-token
  107. (clear-tokens)
  108. ;; e.g. api return 500, server internal error
  109. ;; we shouldn't clear tokens if they aren't expired yet
  110. ;; the `refresh-tokens-loop` will retry soon
  111. (and (not (http/unexceptional-status? (:status resp)))
  112. (not (-> (state/get-auth-id-token) parse-jwt expired?)))
  113. nil ; do nothing
  114. (not (http/unexceptional-status? (:status resp)))
  115. (clear-tokens true)
  116. :else ; ok
  117. (when (and (:id_token (:body resp)) (:access_token (:body resp)))
  118. (set-tokens! (:id_token (:body resp)) (:access_token (:body resp)))))))))
  119. (defn restore-tokens-from-localstorage
  120. "Refresh id-token&access-token, pull latest repos, returns nil when tokens are not available."
  121. []
  122. (println "restore-tokens-from-localstorage")
  123. (let [refresh-token (js/localStorage.getItem "refresh-token")]
  124. (when refresh-token
  125. (go
  126. (<! (<refresh-id-token&access-token))
  127. ;; refresh remote graph list by pub login event
  128. (when (user-uuid) (state/pub-event! [:user/fetch-info-and-graphs]))))))
  129. (defn ^:export login-callback [code]
  130. (state/set-state! [:ui/loading? :login] true)
  131. (go
  132. (let [resp (<! (http/get (str "https://" config/API-DOMAIN "/auth_callback?code=" code)
  133. {:with-credentials? false}))]
  134. (if (= 200 (:status resp))
  135. (-> resp
  136. :body
  137. (as-> $ (set-tokens! (:id_token $) (:access_token $) (:refresh_token $)))
  138. (#(state/pub-event! [:user/fetch-info-and-graphs])))
  139. (debug/pprint "login-callback" resp)))))
  140. (defn ^:export login-with-username-password-e2e
  141. [username password client-id client-secret]
  142. (let [text-encoder (new js/TextEncoder)
  143. key (.encode text-encoder client-secret)
  144. hasher (new crypt/Sha256)
  145. hmacer (new crypt/Hmac hasher key)
  146. secret-hash (.encodeByteArray ^js crypt/base64 (.getHmac hmacer (str username client-id)))
  147. payload {"AuthParameters" {"USERNAME" username,
  148. "PASSWORD" password,
  149. "SECRET_HASH" secret-hash}
  150. "AuthFlow" "USER_PASSWORD_AUTH",
  151. "ClientId" client-id}
  152. headers {"X-Amz-Target" "AWSCognitoIdentityProviderService.InitiateAuth",
  153. "Content-Type" "application/x-amz-json-1.1"}]
  154. (go
  155. (let [resp (<! (http/post config/COGNITO-IDP {:headers headers
  156. :body (js/JSON.stringify (clj->js payload))}))]
  157. (assert (= 200 (:status resp)))
  158. (let [body (js->clj (js/JSON.parse (:body resp)))
  159. access-token (get-in body ["AuthenticationResult" "AccessToken"])
  160. id-token (get-in body ["AuthenticationResult" "IdToken"])
  161. refresh-token (get-in body ["AuthenticationResult" "RefreshToken"])]
  162. (set-tokens! id-token access-token refresh-token)
  163. (state/pub-event! [:user/fetch-info-and-graphs])
  164. {:id-token id-token :access-token access-token :refresh-token refresh-token})))))
  165. (defn logout []
  166. (clear-tokens)
  167. (state/pub-event! [:user/logout]))
  168. (defn <ensure-id&access-token
  169. []
  170. (go
  171. (when (or (nil? (state/get-auth-id-token))
  172. (-> (state/get-auth-id-token) parse-jwt almost-expired-or-expired?))
  173. (debug/pprint (str "refresh tokens... " (tc/to-string (t/now))))
  174. (<! (<refresh-id-token&access-token))
  175. (when (or (nil? (state/get-auth-id-token))
  176. (-> (state/get-auth-id-token) parse-jwt expired?))
  177. (ex-info "empty or expired token and refresh failed" {})))))
  178. (defn <user-uuid
  179. []
  180. (go
  181. (if-some [exp (<! (<ensure-id&access-token))]
  182. exp
  183. (user-uuid))))
  184. ;;; user groups
  185. (defn alpha-user?
  186. []
  187. (or config/dev?
  188. (contains? (state/user-groups) "alpha-tester")))
  189. (defn beta-user?
  190. []
  191. (or config/dev?
  192. (contains? (state/user-groups) "beta-tester")))
  193. (defn alpha-or-beta-user?
  194. []
  195. (or (alpha-user?) (beta-user?)))
  196. (defonce feature-matrix {:file-sync :beta
  197. :whiteboard :beta})
  198. (defn feature-available?
  199. [feature]
  200. (or config/dev?
  201. (when (logged-in?)
  202. (case (feature feature-matrix)
  203. :beta (alpha-or-beta-user?)
  204. :alpha (alpha-user?)
  205. false))))