encryption.cljs 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241
  1. (ns frontend.components.encryption
  2. (:require [clojure.string :as string]
  3. [frontend.context.i18n :refer [t]]
  4. [frontend.handler.notification :as notification]
  5. [frontend.fs.sync :as sync]
  6. [frontend.state :as state]
  7. [frontend.ui :as ui]
  8. [frontend.util :as util]
  9. [frontend.config :as config]
  10. [cljs.core.async :as async]
  11. [rum.core :as rum]))
  12. (rum/defc show-password-cp
  13. [*show-password?]
  14. [:div.flex.flex-row.items-center
  15. [:label.px-1 {:for "show-password"}
  16. (ui/checkbox {:checked? @*show-password?
  17. :on-change (fn [e]
  18. (reset! *show-password? (util/echecked? e)))
  19. :id "show-password"})
  20. [:span.text-sm.ml-1.opacity-80.select-none.px-1 "Show password"]]])
  21. (rum/defcs ^:large-vars/cleanup-todo input-password-inner < rum/reactive
  22. (rum/local "" ::password)
  23. (rum/local "" ::pw-confirm)
  24. (rum/local false ::pw-confirm-focused?)
  25. (rum/local false ::show-password?)
  26. {:will-mount (fn [state]
  27. ;; try to close tour tips
  28. (some->> (state/sub :file-sync/jstour-inst)
  29. (.complete))
  30. state)}
  31. [state repo-url close-fn {:keys [type GraphName GraphUUID init-graph-keys after-input-password]}]
  32. (let [*password (get state ::password)
  33. *pw-confirm (get state ::pw-confirm)
  34. *pw-confirm-focused? (get state ::pw-confirm-focused?)
  35. *show-password? (get state ::show-password?)
  36. *input-ref-0 (rum/create-ref)
  37. *input-ref-1 (rum/create-ref)
  38. remote-pw? (= type :input-pwd-remote)
  39. loading? (state/sub [:ui/loading? :set-graph-password])
  40. pw-strength (when (and init-graph-keys
  41. (not (string/blank? @*password)))
  42. (util/check-password-strength @*password))
  43. can-submit? #(if init-graph-keys
  44. (and (>= (count @*password) 6)
  45. (>= (:id pw-strength) 1))
  46. true)
  47. set-remote-graph-pwd-result (state/sub [:file-sync/set-remote-graph-password-result])
  48. submit-handler
  49. (fn []
  50. (let [value @*password]
  51. (cond
  52. (string/blank? value)
  53. nil
  54. (and init-graph-keys (not= @*password @*pw-confirm))
  55. (notification/show! "The passwords are not matched." :error)
  56. :else
  57. (case type
  58. (:create-pwd-remote :input-pwd-remote)
  59. (do
  60. (state/set-state! [:ui/loading? :set-graph-password] true)
  61. (state/set-state! [:file-sync/set-remote-graph-password-result] {})
  62. (async/go
  63. (let [persist-r (async/<! (sync/encrypt+persist-pwd! @*password GraphUUID))]
  64. (if (instance? js/Error persist-r)
  65. (js/console.error persist-r)
  66. (when (fn? after-input-password)
  67. (after-input-password @*password)
  68. ;; TODO: it's better if based on sync state
  69. (when init-graph-keys
  70. (js/setTimeout #(state/pub-event! [:file-sync/maybe-onboarding-show :sync-learn]) 10000)))))))))))
  71. cancel-handler
  72. (fn []
  73. (state/set-state! [:file-sync/set-remote-graph-password-result] {})
  74. (close-fn))
  75. enter-handler
  76. (fn [^js e]
  77. (when-let [^js input (and e (= 13 (.-which e)) (.-target e))]
  78. (when-not (string/blank? (.-value input))
  79. (let [input-0? (= (util/safe-lower-case (.-placeholder input)) "password")]
  80. (if init-graph-keys
  81. ;; setup mode
  82. (if input-0?
  83. (.select (rum/deref *input-ref-1))
  84. (submit-handler))
  85. ;; unlock mode
  86. (submit-handler))))))]
  87. [:div.encryption-password.max-w-2xl.-mb-2
  88. [:div.cp__file-sync-related-normal-modal
  89. [:div.flex.justify-center.pb-4 [:span.icon-wrap (ui/icon "lock-access" {:size 28})]]
  90. [:div.mt-3.text-center.sm:mt-0.sm:text-left
  91. [:h1#modal-headline.text-2xl.font-bold.text-center
  92. (if init-graph-keys
  93. (if remote-pw?
  94. "Secure this remote graph!"
  95. "Encrypt this graph")
  96. (if remote-pw?
  97. "Unlock this remote graph!"
  98. "Decrypt this graph"))]]
  99. ;; decrypt remote graph with one password
  100. (when (and remote-pw? (not init-graph-keys))
  101. [:<>
  102. [:div.folder-tip.flex.flex-col.items-center
  103. [:h3
  104. [:span.flex.space-x-2.leading-none.pb-1
  105. (ui/icon "cloud-lock" {:size 20})
  106. [:span GraphName]
  107. [:span.scale-75 (ui/icon "arrow-right")]
  108. [:span (ui/icon "folder")]]]
  109. [:h4.px-2.-mb-1.5 (config/get-string-repo-dir repo-url)]]
  110. [:div.input-hints.text-sm.py-2.px-3.rounded.mb-2.mt-2.flex.items-center
  111. (if-let [display-str (:fail set-remote-graph-pwd-result)]
  112. [:<>
  113. [:span.flex.pr-1.text-error (ui/icon "alert-circle" {:class "text-md mr-1"})]
  114. [:span.text-error display-str]]
  115. [:<>
  116. [:span.flex.pr-1 (ui/icon "bulb" {:class "text-md mr-1"})]
  117. [:span "Please enter the password for this graph to continue syncing."]])]])
  118. ;; secure this remote graph
  119. (when (and remote-pw? init-graph-keys)
  120. (let [pattern-ok? #(>= (count @*password) 6)]
  121. [:<>
  122. [:h2.text-center.opacity-70.text-sm.py-2
  123. "Each graph you want to synchronize via Logseq needs its own password for end-to-end encryption."]
  124. [:div.input-hints.text-sm.py-2.px-3.rounded.mb-3.mt-4.flex.items-center
  125. (if (or (not (string/blank? @*password))
  126. (not (string/blank? @*pw-confirm)))
  127. (if (or (not (pattern-ok?))
  128. (not= @*password @*pw-confirm))
  129. [:span.flex.pr-1.text-error (ui/icon "alert-circle" {:class "text-md mr-1"})]
  130. [:span.flex.pr-1.text-success (ui/icon "circle-check" {:class "text-md mr-1"})])
  131. [:span.flex.pr-1 (ui/icon "bulb" {:class "text-md mr-1"})])
  132. (if (not (string/blank? @*password))
  133. (if-not (pattern-ok?)
  134. [:span "Password can't be less than 6 characters"]
  135. (if (not (string/blank? @*pw-confirm))
  136. (if (not= @*pw-confirm @*password)
  137. [:span "Password fields are not matching!"]
  138. [:span "Password fields are matching!"])
  139. [:span "Enter your chosen password again!"]))
  140. [:span "Choose a strong and hard to guess password!"])
  141. ]
  142. ;; password strength checker
  143. (when-not (string/blank? @*password)
  144. [:<>
  145. [:div.input-hints.text-sm.py-2.px-3.rounded.mb-2.-mt-1.5.flex.items-center.sm:space-x-3.strength-wrap
  146. (let [included-set (set (:contains pw-strength))]
  147. (for [i ["lowercase" "uppercase" "number" "symbol"]
  148. :let [included? (contains? included-set i)]]
  149. [:span.strength-item
  150. {:key i
  151. :class (when included? "included")}
  152. (ui/icon (if included? "check" "x") {:class "mr-1"})
  153. [:span.capitalize i]
  154. ]))]
  155. [:div.input-pw-strength
  156. [:div.indicator.flex
  157. (for [i (range 4)
  158. :let [title (get ["Too weak" "Weak" "Medium" "Strong"] i)]]
  159. [:i {:key i
  160. :title title
  161. :class (when (>= (int (:id pw-strength)) i) "active")} i])]]])]))
  162. [:input.form-input.block.w-full.sm:text-sm.sm:leading-5.my-2
  163. {:type (if @*show-password? "text" "password")
  164. :ref *input-ref-0
  165. :placeholder "Password"
  166. :auto-focus true
  167. :disabled loading?
  168. :on-key-up enter-handler
  169. :on-change (fn [^js e]
  170. (reset! *password (util/evalue e))
  171. (when (:fail set-remote-graph-pwd-result)
  172. (state/set-state! [:file-sync/set-remote-graph-password-result] {})))}]
  173. (when init-graph-keys
  174. [:input.form-input.block.w-full.sm:text-sm.sm:leading-5.my-2
  175. {:type (if @*show-password? "text" "password")
  176. :ref *input-ref-1
  177. :placeholder "Re-enter the password"
  178. :on-focus #(reset! *pw-confirm-focused? true)
  179. :on-blur #(reset! *pw-confirm-focused? false)
  180. :disabled loading?
  181. :on-key-up enter-handler
  182. :on-change (fn [^js e]
  183. (reset! *pw-confirm (util/evalue e)))}])
  184. (show-password-cp *show-password?)
  185. (when init-graph-keys
  186. [:div.init-remote-pw-tips.space-x-4.pt-2.hidden.sm:flex
  187. [:div.flex-1.flex.items-center
  188. [:span.px-3.flex (ui/icon "key")]
  189. [:p.dark:text-gray-100
  190. [:span "Please make sure you "]
  191. "remember the password you have set, "
  192. [:span "and we recommend you "]
  193. "keep a secure backup "
  194. [:span "of the password."]]]
  195. [:div.flex-1.flex.items-center
  196. [:span.px-3.flex (ui/icon "lock")]
  197. [:p.dark:text-gray-100
  198. "If you lose your password, all of your data in the cloud can’t be decrypted. "
  199. [:span "You will still be able to access the local version of your graph."]]]])]
  200. [:div.mt-5.sm:mt-4.flex.justify-center.sm:justify-end.space-x-3
  201. (ui/button (t :cancel) :background "gray" :disabled loading? :class "opacity-60" :on-click cancel-handler)
  202. (ui/button [:span.inline-flex.items-center.leading-none
  203. [:span (t :submit)]
  204. (when loading?
  205. [:span.ml-1 (ui/loading "" {:class "w-4 h-4"})])]
  206. :disabled (or (not (can-submit?)) loading?)
  207. :on-click submit-handler)]]))
  208. (defn input-password
  209. ([repo-url close-fn] (input-password repo-url close-fn {:type :local}))
  210. ([repo-url close-fn opts]
  211. (fn [close-fn']
  212. (let [close-fn' (if (fn? close-fn)
  213. #(do (close-fn %)
  214. (close-fn'))
  215. close-fn')]
  216. (input-password-inner repo-url close-fn' opts)))))