Change password inside new settings (#19474)
* feat: added change-password screen Signed-off-by: Lungu Cristian <lungucristian95@gmail.com> * fix: pw verification error not shown Signed-off-by: Lungu Cristian <lungucristian95@gmail.com> * feat: added changing password with confirmation and loading Signed-off-by: Lungu Cristian <lungucristian95@gmail.com> * fix: adjusted flow Signed-off-by: Lungu Cristian <lungucristian95@gmail.com> * fix: added minimum waiting time when loading Signed-off-by: Lungu Cristian <lungucristian95@gmail.com> * ref: moved events to change-password Signed-off-by: Lungu Cristian <lungucristian95@gmail.com> * fix: added styles where missing Signed-off-by: Lungu Cristian <lungucristian95@gmail.com> * ref: moved header out Signed-off-by: Lungu Cristian <lungucristian95@gmail.com> * ref: moved forms into separate namespaces Signed-off-by: Lungu Cristian <lungucristian95@gmail.com> * fix: linter promesa alias issue Signed-off-by: Lungu Cristian <lungucristian95@gmail.com> * feat: added password-tips quo-component Signed-off-by: Lungu Cristian <lungucristian95@gmail.com> * fix: validation message Signed-off-by: Lungu Cristian <lungucristian95@gmail.com> * fix: removed bottom-sheet event Signed-off-by: Lungu Cristian <lungucristian95@gmail.com> * removed temp file Signed-off-by: Lungu Cristian <lungucristian95@gmail.com> * fix: addressed review comments Signed-off-by: Lungu Cristian <lungucristian95@gmail.com> * fix: addressed @seanstrom's review comments Signed-off-by: Lungu Cristian <lungucristian95@gmail.com> * fix: address @ilmotta's review comments Signed-off-by: Lungu Cristian <lungucristian95@gmail.com> * fix: addressed @vkjr's review comments Signed-off-by: Lungu Cristian <lungucristian95@gmail.com> * fix: buttom button alignment with keyboard Signed-off-by: Lungu Cristian <lungucristian95@gmail.com> * fix: addressed review comments Signed-off-by: Lungu Cristian <lungucristian95@gmail.com> * fix: keyboard behavior Signed-off-by: Lungu Cristian <lungucristian95@gmail.com> * fix: navigation to loader Signed-off-by: Lungu Cristian <lungucristian95@gmail.com> * fix: use-theme usage * fix: button alignment due to alert banner --------- Signed-off-by: Lungu Cristian <lungucristian95@gmail.com>
This commit is contained in:
parent
245b3fcf57
commit
161ba2713f
|
@ -2,6 +2,7 @@
|
|||
(:require
|
||||
["react-native" :as react-native]
|
||||
[clojure.string :as string]
|
||||
[native-module.utils :as native-utils]
|
||||
[react-native.platform :as platform]
|
||||
[taoensso.timbre :as log]
|
||||
[utils.transforms :as types]))
|
||||
|
@ -543,13 +544,21 @@
|
|||
(when platform/android?
|
||||
(.resetKeyboardInputCursor ^js (ui-helper) input selection)))
|
||||
|
||||
;; passwords are hashed
|
||||
(defn reset-password
|
||||
[key-uid current-password# new-password# callback]
|
||||
(log/debug "[native-module] change-database-password")
|
||||
(init-keystore
|
||||
key-uid
|
||||
#(.reEncryptDbAndKeystore ^js (encryption) key-uid current-password# new-password# callback)))
|
||||
([key-uid current-password-hashed new-password-hashed]
|
||||
(native-utils/promisify-native-module-call reset-password
|
||||
key-uid
|
||||
current-password-hashed
|
||||
new-password-hashed))
|
||||
([key-uid current-password-hashed new-password-hashed callback]
|
||||
(log/debug "[native-module] change-database-password")
|
||||
(init-keystore
|
||||
key-uid
|
||||
#(.reEncryptDbAndKeystore ^js (encryption)
|
||||
key-uid
|
||||
current-password-hashed
|
||||
new-password-hashed
|
||||
callback))))
|
||||
|
||||
(defn convert-to-keycard-account
|
||||
[{:keys [key-uid] :as multiaccount-data} settings current-password# new-password callback]
|
||||
|
|
|
@ -0,0 +1,23 @@
|
|||
(ns native-module.utils
|
||||
(:require
|
||||
[clojure.string :as string]
|
||||
[promesa.core :as promesa]
|
||||
[utils.transforms :as types]))
|
||||
|
||||
(defn- promisify-callback
|
||||
[res rej]
|
||||
(fn [result]
|
||||
(let [native-error (let [{:keys [error]} (types/json->clj result)]
|
||||
(when-not (string/blank? error)
|
||||
error))]
|
||||
(if native-error
|
||||
(rej (ex-info "Native module call error" {:error native-error}))
|
||||
(res result)))))
|
||||
|
||||
(defn promisify-native-module-call
|
||||
[f & args]
|
||||
(promesa/create
|
||||
(fn [res rej]
|
||||
(->> [(promisify-callback res rej)]
|
||||
(concat args)
|
||||
(apply f)))))
|
|
@ -0,0 +1,6 @@
|
|||
(ns quo.components.password.password-tips.style)
|
||||
|
||||
(def password-tips
|
||||
{:flex-direction :row
|
||||
:margin-horizontal 20
|
||||
:justify-content :space-between})
|
|
@ -0,0 +1,30 @@
|
|||
(ns quo.components.password.password-tips.view
|
||||
(:require [quo.components.password.password-tips.style :as style]
|
||||
[quo.components.password.tips.view :as tips]
|
||||
[react-native.core :as rn]
|
||||
[schema.core :as schema]
|
||||
[utils.i18n :as i18n]))
|
||||
|
||||
(def ?schema
|
||||
[:=>
|
||||
[:cat
|
||||
[:map {:closed true}
|
||||
[:lower-case? :boolean]
|
||||
[:upper-case? :boolean]
|
||||
[:numbers? :boolean]
|
||||
[:symbols? :boolean]]
|
||||
[:any]]])
|
||||
|
||||
(defn- view-internal
|
||||
[{:keys [lower-case? upper-case? numbers? symbols?]}]
|
||||
[rn/view {:style style/password-tips}
|
||||
[tips/view {:completed? lower-case?}
|
||||
(i18n/label :t/password-creation-tips-1)]
|
||||
[tips/view {:completed? upper-case?}
|
||||
(i18n/label :t/password-creation-tips-2)]
|
||||
[tips/view {:completed? numbers?}
|
||||
(i18n/label :t/password-creation-tips-3)]
|
||||
[tips/view {:completed? symbols?}
|
||||
(i18n/label :t/password-creation-tips-4)]])
|
||||
|
||||
(def view (schema/instrument #'view-internal ?schema))
|
|
@ -37,18 +37,17 @@
|
|||
[{:keys [title title-accessibility-label input counter-top counter-bottom
|
||||
title-right title-right-props]
|
||||
avatar-props :avatar}]
|
||||
(let [avatar-props (when avatar-props
|
||||
(assoc avatar-props :size :size-32))
|
||||
title-props (assoc title-right-props
|
||||
:title title
|
||||
:right title-right
|
||||
:accessibility-label title-accessibility-label)]
|
||||
(let [title-props (assoc title-right-props
|
||||
:title title
|
||||
:right title-right
|
||||
:accessibility-label title-accessibility-label)]
|
||||
[rn/view {:style style/header}
|
||||
[rn/view {:style style/header-title}
|
||||
(when avatar-props
|
||||
(if (:group? avatar-props)
|
||||
[group-avatar/view avatar-props]
|
||||
[channel-avatar/view avatar-props]))
|
||||
(let [avatar-props (assoc avatar-props :size :size-32)]
|
||||
(if (:group? avatar-props)
|
||||
[group-avatar/view avatar-props]
|
||||
[channel-avatar/view avatar-props])))
|
||||
[standard-title/view title-props]]
|
||||
(when (= input :recovery-phrase)
|
||||
[header-counter counter-top counter-bottom])]))
|
||||
|
|
|
@ -111,6 +111,7 @@
|
|||
quo.components.numbered-keyboard.numbered-keyboard.view
|
||||
quo.components.onboarding.small-option-card.view
|
||||
quo.components.overlay.view
|
||||
quo.components.password.password-tips.view
|
||||
quo.components.password.tips.view
|
||||
quo.components.profile.collectible-list-item.view
|
||||
quo.components.profile.collectible.view
|
||||
|
@ -356,6 +357,7 @@
|
|||
|
||||
;;;; Password
|
||||
(def tips quo.components.password.tips.view/view)
|
||||
(def password-tips quo.components.password.password-tips.view/view)
|
||||
|
||||
;;;; Profile
|
||||
(def collectible quo.components.profile.collectible.view/collectible)
|
||||
|
|
|
@ -80,7 +80,7 @@
|
|||
|
||||
(defn save-user-password!
|
||||
[key-uid password]
|
||||
(keychain/save-credentials key-uid key-uid (security/safe-unmask-data password) #()))
|
||||
(keychain/save-credentials key-uid key-uid (security/safe-unmask-data password)))
|
||||
|
||||
(defn get-user-password!
|
||||
[key-uid callback]
|
||||
|
|
|
@ -123,11 +123,20 @@
|
|||
(def ^:const profile-pictures-visibility-none 3)
|
||||
|
||||
(def ^:const min-password-length 6)
|
||||
(def ^:const new-password-min-length 10)
|
||||
(def ^:const max-group-chat-participants 20)
|
||||
(def ^:const max-group-chat-name-length 24)
|
||||
(def ^:const default-number-of-messages 20)
|
||||
(def ^:const default-number-of-pin-messages 3)
|
||||
|
||||
(def ^:const password-tips [:lower-case? :upper-case? :numbers? :symbols?])
|
||||
(def ^:const strength-status
|
||||
{1 :very-weak
|
||||
2 :weak
|
||||
3 :okay
|
||||
4 :strong
|
||||
5 :very-strong})
|
||||
|
||||
(def ^:const mailserver-password "status-offline-inbox")
|
||||
|
||||
(def ^:const send-transaction-failed-parse-response 1)
|
||||
|
|
|
@ -6,6 +6,7 @@
|
|||
[react-native.core :as rn]
|
||||
[react-native.safe-area :as safe-area]
|
||||
[reagent.core :as reagent]
|
||||
[status-im.constants :as constants]
|
||||
[status-im.contexts.onboarding.create-password.style :as style]
|
||||
[utils.i18n :as i18n]
|
||||
[utils.re-frame :as rf]
|
||||
|
@ -80,18 +81,11 @@
|
|||
:on-focus on-input-focus
|
||||
:on-blur on-blur-repeat-password}]]))
|
||||
|
||||
(def strength-status
|
||||
{1 :very-weak
|
||||
2 :weak
|
||||
3 :okay
|
||||
4 :strong
|
||||
5 :very-strong})
|
||||
|
||||
(defn help
|
||||
[{{:keys [lower-case? upper-case? numbers? symbols?]} :validations
|
||||
password-strength :password-strength}]
|
||||
[rn/view
|
||||
[quo/strength-divider {:type (strength-status password-strength :info)}
|
||||
[quo/strength-divider {:type (constants/strength-status password-strength :info)}
|
||||
(i18n/label :t/password-creation-tips-title)]
|
||||
[rn/view {:style style/password-tips}
|
||||
[quo/tips {:completed? lower-case?}
|
||||
|
@ -109,16 +103,16 @@
|
|||
utils.string/has-upper-case?
|
||||
utils.string/has-numbers?
|
||||
utils.string/has-symbols?
|
||||
#(utils.string/at-least-n-chars? % 10))]
|
||||
#(utils.string/at-least-n-chars? % constants/new-password-min-length))]
|
||||
(->> password
|
||||
(validations)
|
||||
(zipmap [:lower-case? :upper-case? :numbers? :symbols? :long-enough?]))))
|
||||
validations
|
||||
(zipmap (conj constants/password-tips :long-enough?)))))
|
||||
|
||||
(defn calc-password-strength
|
||||
[validations]
|
||||
(->> (vals validations)
|
||||
(filter true?)
|
||||
(count)))
|
||||
count))
|
||||
|
||||
(defn password-form
|
||||
[]
|
||||
|
|
|
@ -136,6 +136,7 @@
|
|||
numbered-keyboard]
|
||||
[status-im.contexts.preview.quo.onboarding.small-option-card :as
|
||||
small-option-card]
|
||||
[status-im.contexts.preview.quo.password.password-tips :as password-tips]
|
||||
[status-im.contexts.preview.quo.password.tips :as tips]
|
||||
[status-im.contexts.preview.quo.profile.collectible :as collectible]
|
||||
[status-im.contexts.preview.quo.profile.collectible-list-item :as collectible-list-item]
|
||||
|
@ -425,7 +426,9 @@
|
|||
:onboarding [{:name :small-option-card
|
||||
:component small-option-card/view}]
|
||||
:password [{:name :tips
|
||||
:component tips/view}]
|
||||
:component tips/view}
|
||||
{:name :password-tips
|
||||
:component password-tips/view}]
|
||||
:profile [{:name :collectible
|
||||
:component collectible/view}
|
||||
{:name :collectible-list-item
|
||||
|
|
|
@ -0,0 +1,29 @@
|
|||
(ns status-im.contexts.preview.quo.password.password-tips
|
||||
(:require
|
||||
[quo.core :as quo]
|
||||
[quo.foundations.colors :as colors]
|
||||
[reagent.core :as reagent]
|
||||
[status-im.constants :as constant]
|
||||
[status-im.contexts.preview.quo.preview :as preview]))
|
||||
|
||||
(def init-state
|
||||
(reduce (fn [acc tip] (assoc acc tip false)) {} constant/password-tips))
|
||||
|
||||
(defn- make-tip-descriptor
|
||||
[tip]
|
||||
{:key tip
|
||||
:type :boolean})
|
||||
|
||||
(def descriptor
|
||||
(map make-tip-descriptor constant/password-tips))
|
||||
|
||||
(defn view
|
||||
[]
|
||||
(let [state (reagent/atom init-state)]
|
||||
(fn []
|
||||
[preview/preview-container
|
||||
{:state state
|
||||
:descriptor descriptor
|
||||
:component-container-style {:padding 20
|
||||
:background-color colors/neutral-95}}
|
||||
[quo/password-tips @state]])))
|
|
@ -23,7 +23,7 @@
|
|||
:blur? true
|
||||
:action :arrow}
|
||||
{:title (i18n/label :t/password)
|
||||
:on-press #(rf/dispatch [:open-modal :settings-password])
|
||||
:on-press #(rf/dispatch [:open-modal :screen/settings-password])
|
||||
:image-props :i/password
|
||||
:image :icon
|
||||
:blur? true
|
||||
|
|
|
@ -0,0 +1,17 @@
|
|||
(ns status-im.contexts.profile.settings.screens.password.change-password.effects
|
||||
(:require [native-module.core :as native-module]
|
||||
[promesa.core :as promesa]
|
||||
[status-im.common.keychain.events :as keychain]
|
||||
[utils.re-frame :as rf]
|
||||
[utils.security.core :as security]))
|
||||
|
||||
(rf/reg-fx :effects.change-password/change-password
|
||||
(fn [{:keys [key-uid old-password new-password on-success on-fail]}]
|
||||
(let [hash-masked-password (fn [password]
|
||||
(-> password security/hash-masked-password security/safe-unmask-data))
|
||||
old-password-hashed (hash-masked-password old-password)
|
||||
new-password-hashed (hash-masked-password new-password)]
|
||||
(-> (native-module/reset-password key-uid old-password-hashed new-password-hashed)
|
||||
(promesa/then #(keychain/save-user-password! key-uid new-password-hashed))
|
||||
(promesa/then on-success)
|
||||
(promesa/catch on-fail)))))
|
|
@ -0,0 +1,68 @@
|
|||
(ns status-im.contexts.profile.settings.screens.password.change-password.events
|
||||
(:require [status-im.contexts.profile.settings.screens.password.change-password.effects]
|
||||
[taoensso.timbre :as log]
|
||||
[utils.re-frame :as rf]
|
||||
[utils.security.core :as security]))
|
||||
|
||||
(rf/reg-event-fx :change-password/verify-old-password
|
||||
(fn [_ [entered-password]]
|
||||
(let [hashed-password (-> entered-password
|
||||
security/hash-masked-password
|
||||
security/safe-unmask-data)]
|
||||
{:json-rpc/call [{:method "accounts_verifyPassword"
|
||||
:params [hashed-password]
|
||||
:on-success (fn [valid?]
|
||||
(rf/dispatch [:change-password/password-verify-success valid?
|
||||
entered-password]))
|
||||
:on-error (fn [error]
|
||||
(log/error "accounts_verifyPassword error"
|
||||
{:error error
|
||||
:event :password-settings/change-password}))}]})))
|
||||
(rf/reg-event-fx :change-password/password-verify-success
|
||||
(fn [{:keys [db]} [valid? old-password]]
|
||||
{:db (if valid?
|
||||
(-> db
|
||||
(assoc-in [:settings/change-password :old-password] old-password)
|
||||
(assoc-in [:settings/change-password :current-step] :new-password))
|
||||
(assoc-in db [:settings/change-password :verify-error] true))}))
|
||||
|
||||
(rf/reg-event-fx :change-password/reset-error
|
||||
(fn [{:keys [db]}]
|
||||
{:db (assoc-in db [:settings/change-password :verify-error] false)}))
|
||||
|
||||
(rf/reg-event-fx :change-password/reset
|
||||
(fn [{:keys [db]}]
|
||||
{:db (assoc db :settings/change-password {})}))
|
||||
|
||||
(rf/reg-event-fx :change-password/confirm-new-password
|
||||
(fn [{:keys [db]} [new-password]]
|
||||
{:db (assoc-in db [:settings/change-password :new-password] new-password)
|
||||
:fx [[:dispatch [:change-password/submit]]]}))
|
||||
|
||||
(rf/reg-event-fx :change-password/submit
|
||||
(fn [{:keys [db]}]
|
||||
(let [key-uid (get-in db [:profile/profile :key-uid])
|
||||
{:keys [new-password old-password]} (get db :settings/change-password)]
|
||||
{:db (assoc-in db [:settings/change-password :loading?] true)
|
||||
:fx [[:dispatch [:dismiss-keyboard]]
|
||||
[:dispatch [:open-modal :screen/change-password-loading]]
|
||||
[:effects.change-password/change-password
|
||||
{:key-uid key-uid
|
||||
:old-password old-password
|
||||
:new-password new-password
|
||||
:on-success (fn []
|
||||
(rf/dispatch [:change-password/submit-success]))
|
||||
:on-fail (fn [error]
|
||||
(rf/dispatch [:change-password/submit-fail error]))}]]})))
|
||||
|
||||
(rf/reg-event-fx :change-password/submit-fail
|
||||
(fn [_ [error]]
|
||||
(log/error "failed to change the password"
|
||||
{:error error
|
||||
:event :change-password/submit})
|
||||
{:fx [[:dispatch [:change-password/reset]]
|
||||
[:dispatch [:navigate-back]]]}))
|
||||
|
||||
(rf/reg-event-fx :change-password/submit-success
|
||||
(fn [{:keys [db]}]
|
||||
{:db (assoc-in db [:settings/change-password :loading?] false)}))
|
|
@ -0,0 +1,19 @@
|
|||
(ns status-im.contexts.profile.settings.screens.password.change-password.header
|
||||
(:require [quo.core :as quo]
|
||||
[react-native.core :as rn]
|
||||
[status-im.contexts.profile.settings.screens.password.change-password.style :as style]
|
||||
[utils.i18n :as i18n]))
|
||||
|
||||
(defn view
|
||||
[]
|
||||
[rn/view {:style style/heading}
|
||||
[quo/text
|
||||
{:style style/heading-title
|
||||
:weight :semi-bold
|
||||
:size :heading-1}
|
||||
(i18n/label :t/change-password)]
|
||||
[quo/text
|
||||
{:style style/heading-subtitle
|
||||
:weight :regular
|
||||
:size :paragraph-1}
|
||||
(i18n/label :t/change-password-description)]])
|
|
@ -0,0 +1,54 @@
|
|||
(ns status-im.contexts.profile.settings.screens.password.change-password.loading
|
||||
(:require [quo.core :as quo]
|
||||
[react-native.core :as rn]
|
||||
[react-native.safe-area :as safe-area]
|
||||
[status-im.contexts.profile.settings.screens.password.change-password.style :as style]
|
||||
[utils.i18n :as i18n]
|
||||
[utils.re-frame :as rf]))
|
||||
|
||||
;; NOTE: The duration of the password change depends on the device hardware, so to avoid
|
||||
;; quick changes in UI when mounted, we're waiting for a bit.
|
||||
(def ^:private minimum-loading-time 5000)
|
||||
|
||||
(defn- handle-logout
|
||||
[]
|
||||
(rf/dispatch [:multiaccounts.logout.ui/logout-pressed])
|
||||
(rf/dispatch [:change-password/reset]))
|
||||
|
||||
(defn view
|
||||
[]
|
||||
(let [insets (safe-area/get-insets)
|
||||
[minimum-loading-timeout-done?
|
||||
set-minimum-loading-timeout-done] (rn/use-state false)
|
||||
loading? (rf/sub [:settings/change-password-loading])
|
||||
done? (and (not loading?) minimum-loading-timeout-done?)]
|
||||
(rn/use-mount (fn []
|
||||
(js/setTimeout
|
||||
#(set-minimum-loading-timeout-done true)
|
||||
minimum-loading-time)))
|
||||
[quo/overlay {:type :shell}
|
||||
[rn/view
|
||||
{:key :change-password-loading
|
||||
:style (style/loading-container insets)}
|
||||
[quo/page-nav]
|
||||
[quo/page-top
|
||||
{:description :text
|
||||
:title (if done?
|
||||
(i18n/label :t/change-password-done-header)
|
||||
(i18n/label :t/change-password-loading-header))
|
||||
:description-text (if done?
|
||||
(i18n/label :t/change-password-done-description)
|
||||
(i18n/label :t/change-password-loading-description))}]
|
||||
[rn/view
|
||||
{:style style/loading-content}
|
||||
(when-not done?
|
||||
[quo/information-box
|
||||
{:type :error
|
||||
:style {:margin-top 12}
|
||||
:icon :i/info}
|
||||
(i18n/label :t/change-password-loading-warning)])]
|
||||
(when done?
|
||||
[quo/logout-button
|
||||
{:container-style style/logout-container
|
||||
:on-press handle-logout}])]]))
|
||||
|
|
@ -0,0 +1,157 @@
|
|||
(ns status-im.contexts.profile.settings.screens.password.change-password.new-password-form
|
||||
(:require
|
||||
[clojure.string :as string]
|
||||
[quo.core :as quo]
|
||||
[quo.foundations.colors :as colors]
|
||||
[react-native.core :as rn]
|
||||
[status-im.constants :as constant]
|
||||
[status-im.contexts.profile.settings.screens.password.change-password.events]
|
||||
[status-im.contexts.profile.settings.screens.password.change-password.header :as header]
|
||||
[status-im.contexts.profile.settings.screens.password.change-password.style :as style]
|
||||
[utils.i18n :as i18n]
|
||||
[utils.re-frame :as rf]
|
||||
[utils.security.core :as security]
|
||||
[utils.string :as utils.string]))
|
||||
|
||||
(defn- password-with-hint
|
||||
[{{:keys [text status shown?]} :hint :as input-props}]
|
||||
[:<>
|
||||
[quo/input
|
||||
(-> input-props
|
||||
(dissoc :hint)
|
||||
(assoc :type :password
|
||||
:blur? true))]
|
||||
[rn/view {:style style/info-message}
|
||||
(when shown?
|
||||
[quo/info-message
|
||||
(cond-> {:type status
|
||||
:size :default}
|
||||
(not= :success status) (assoc :icon :i/info)
|
||||
(= :success status) (assoc :icon :i/positive-state)
|
||||
(= :default status) (assoc :text-color colors/white-70-blur
|
||||
:icon-color colors/white-70-blur))
|
||||
text])]])
|
||||
|
||||
(defn- calc-password-strength
|
||||
[validations]
|
||||
(->> (vals validations)
|
||||
(filter true?)
|
||||
count))
|
||||
|
||||
(defn- help
|
||||
[{:keys [validations]}]
|
||||
(let [{:keys [lower-case? upper-case? numbers? symbols?]} validations
|
||||
password-strength (calc-password-strength validations)]
|
||||
[rn/view
|
||||
[quo/strength-divider {:type (constant/strength-status password-strength :info)}
|
||||
(i18n/label :t/password-creation-tips-title)]
|
||||
[rn/view {:style style/password-tips}
|
||||
[quo/tips {:completed? lower-case?}
|
||||
(i18n/label :t/password-creation-tips-1)]
|
||||
[quo/tips {:completed? upper-case?}
|
||||
(i18n/label :t/password-creation-tips-2)]
|
||||
[quo/tips {:completed? numbers?}
|
||||
(i18n/label :t/password-creation-tips-3)]
|
||||
[quo/tips {:completed? symbols?}
|
||||
(i18n/label :t/password-creation-tips-4)]]]))
|
||||
|
||||
(defn- password-validations
|
||||
[password]
|
||||
{:lower-case? (utils.string/has-lower-case? password)
|
||||
:upper-case? (utils.string/has-upper-case? password)
|
||||
:numbers? (utils.string/has-numbers? password)
|
||||
:symbols? (utils.string/has-symbols? password)})
|
||||
|
||||
(defn view
|
||||
[]
|
||||
(let [customization-color (rf/sub [:profile/customization-color])
|
||||
[password set-password] (rn/use-state "")
|
||||
[repeat-password set-repeat-password] (rn/use-state "")
|
||||
[disclaimer-accepted? set-disclaimer-accepted] (rn/use-state false)
|
||||
[focused? set-focused] (rn/use-state false)
|
||||
[show-validation? set-show-validation] (rn/use-state false)
|
||||
|
||||
;; validations
|
||||
not-blank? (complement string/blank?)
|
||||
validations (password-validations password)
|
||||
long-enough? (utils.string/at-least-n-chars?
|
||||
password
|
||||
constant/new-password-min-length)
|
||||
empty-password? (string/blank? password)
|
||||
same-passwords? (and (not empty-password?)
|
||||
(= password repeat-password))
|
||||
meet-requirements? (and (not empty-password?)
|
||||
long-enough?
|
||||
same-passwords?
|
||||
disclaimer-accepted?)
|
||||
error? (and show-validation?
|
||||
(not same-passwords?)
|
||||
(not empty-password?))
|
||||
|
||||
;; handlers
|
||||
on-change-password (fn [new-value]
|
||||
(set-password new-value)
|
||||
(when (and (not-blank? new-value)
|
||||
(= (count new-value)
|
||||
(count repeat-password)))
|
||||
(set-show-validation true)))
|
||||
on-change-repeat-password (fn [new-value]
|
||||
(set-repeat-password new-value)
|
||||
(when (and (not-blank? new-value)
|
||||
(= (count new-value)
|
||||
(count password)))
|
||||
(set-show-validation true)))
|
||||
on-blur-repeat-password (fn []
|
||||
(if empty-password?
|
||||
(set-show-validation false)
|
||||
(set-show-validation true)))
|
||||
on-input-focus (fn [] (set-focused true))
|
||||
on-disclaimer-change (fn []
|
||||
(set-disclaimer-accepted
|
||||
(not disclaimer-accepted?)))
|
||||
on-submit (fn []
|
||||
(rf/dispatch
|
||||
[:change-password/confirm-new-password
|
||||
(security/mask-data password)]))]
|
||||
[:<>
|
||||
[rn/scroll-view {:style style/form-container}
|
||||
[header/view]
|
||||
[password-with-hint
|
||||
{:hint {:text (i18n/label :t/password-creation-hint)
|
||||
:status (if long-enough? :success :default)
|
||||
:shown? true}
|
||||
:placeholder (i18n/label :t/change-password-new-password-placeholder)
|
||||
:label (i18n/label :t/change-password-new-password-label)
|
||||
:on-change-text on-change-password
|
||||
:on-focus on-input-focus
|
||||
:auto-focus true}]
|
||||
[rn/view {:style style/space-between-inputs}]
|
||||
[password-with-hint
|
||||
{:hint {:text (if same-passwords?
|
||||
(i18n/label :t/password-creation-match)
|
||||
(i18n/label :t/password-creation-dont-match))
|
||||
:status (if same-passwords? :success :error)
|
||||
:shown? (and (not empty-password?)
|
||||
show-validation?)}
|
||||
:error? error?
|
||||
:placeholder (i18n/label :t/change-password-repeat-password-placeholder)
|
||||
:on-change-text on-change-repeat-password
|
||||
:on-focus on-input-focus
|
||||
:on-blur on-blur-repeat-password}]]
|
||||
[rn/view {:style style/bottom-part}
|
||||
(when same-passwords?
|
||||
[rn/view {:style style/disclaimer-container}
|
||||
[quo/disclaimer
|
||||
{:blur? true
|
||||
:on-change on-disclaimer-change
|
||||
:checked? disclaimer-accepted?}
|
||||
(i18n/label :t/password-creation-disclaimer)]])
|
||||
(when (and focused? (not same-passwords?))
|
||||
[help
|
||||
{:validations validations}])
|
||||
[rn/view {:style style/button-container}
|
||||
[quo/button
|
||||
{:disabled? (not meet-requirements?)
|
||||
:customization-color customization-color
|
||||
:on-press on-submit}
|
||||
(i18n/label :t/password-creation-confirm)]]]]))
|
|
@ -0,0 +1,63 @@
|
|||
(ns status-im.contexts.profile.settings.screens.password.change-password.old-password-form
|
||||
(:require
|
||||
[clojure.string :as string]
|
||||
[quo.core :as quo]
|
||||
[react-native.core :as rn]
|
||||
[status-im.constants :as constant]
|
||||
[status-im.contexts.profile.settings.screens.password.change-password.events]
|
||||
[status-im.contexts.profile.settings.screens.password.change-password.header :as header]
|
||||
[status-im.contexts.profile.settings.screens.password.change-password.style :as style]
|
||||
[utils.i18n :as i18n]
|
||||
[utils.re-frame :as rf]
|
||||
[utils.security.core :as security]
|
||||
[utils.string :as utils.string]))
|
||||
|
||||
(defn view
|
||||
[]
|
||||
(let [error (rf/sub [:settings/change-password-error])
|
||||
customization-color (rf/sub [:profile/customization-color])
|
||||
[password set-password] (rn/use-state "")
|
||||
on-change-password (fn [new-value]
|
||||
(when error
|
||||
(rf/dispatch [:change-password/reset-error]))
|
||||
(set-password new-value))
|
||||
meet-requirements? (and ((complement string/blank?) password)
|
||||
(utils.string/at-least-n-chars? password
|
||||
constant/min-password-length))
|
||||
on-submit (fn []
|
||||
(when meet-requirements?
|
||||
(rf/dispatch
|
||||
[:change-password/verify-old-password
|
||||
(security/mask-data password)])))]
|
||||
[:<>
|
||||
[rn/scroll-view {:content-container-style style/form-container}
|
||||
[header/view]
|
||||
[quo/input
|
||||
{:placeholder (i18n/label :t/change-password-old-password-placeholder)
|
||||
:label (i18n/label :t/change-password-old-password-label)
|
||||
:on-change-text on-change-password
|
||||
:auto-focus true
|
||||
:type :password
|
||||
:blur? true}]
|
||||
[rn/view
|
||||
{:style style/error-container}
|
||||
(when error
|
||||
[quo/info-message
|
||||
{:type :error
|
||||
:size :default
|
||||
:icon :i/info}
|
||||
(i18n/label :t/oops-wrong-password)])]
|
||||
[quo/information-box
|
||||
{:type :error
|
||||
:style style/warning-container
|
||||
:icon :i/info}
|
||||
(i18n/label :t/change-password-confirm-warning)]]
|
||||
[rn/view
|
||||
{:style (assoc style/bottom-part
|
||||
:margin-horizontal 20
|
||||
:margin-top 12)}
|
||||
[quo/button
|
||||
{:disabled? (not meet-requirements?)
|
||||
:customization-color customization-color
|
||||
:on-press on-submit}
|
||||
(i18n/label :t/continue)]]]))
|
|
@ -0,0 +1,60 @@
|
|||
(ns status-im.contexts.profile.settings.screens.password.change-password.style
|
||||
(:require
|
||||
[quo.foundations.colors :as colors]
|
||||
[react-native.safe-area :as safe-area]))
|
||||
|
||||
(def form-container
|
||||
{:margin-horizontal 20})
|
||||
|
||||
(def heading
|
||||
{:margin-bottom 20
|
||||
:margin-top 12})
|
||||
|
||||
(def heading-subtitle {:color colors/white})
|
||||
|
||||
(def heading-title (assoc heading-subtitle :margin-bottom 8))
|
||||
|
||||
(def info-message
|
||||
{:margin-top 8})
|
||||
|
||||
(def space-between-inputs {:height 16})
|
||||
|
||||
(def error-container
|
||||
{:margin-top 8
|
||||
:flex-direction :row
|
||||
:align-items :center})
|
||||
|
||||
(def warning-container
|
||||
{:margin-top 12})
|
||||
|
||||
(defn loading-container
|
||||
[{:keys [top bottom]}]
|
||||
{:flex 1
|
||||
:justify-content :space-between
|
||||
:padding-top top
|
||||
:padding-bottom bottom})
|
||||
|
||||
(def loading-content
|
||||
{:flex 1
|
||||
:padding-horizontal 20})
|
||||
|
||||
(def logout-container
|
||||
{:margin-vertical 12
|
||||
:margin-horizontal 20})
|
||||
|
||||
(def password-tips
|
||||
{:flex-direction :row
|
||||
:margin-horizontal 20
|
||||
:justify-content :space-between})
|
||||
|
||||
(def bottom-part
|
||||
{:margin-bottom (- (safe-area/get-bottom) 12)
|
||||
:justify-content :flex-end})
|
||||
|
||||
(def disclaimer-container
|
||||
{:margin-horizontal 20
|
||||
:margin-vertical 4})
|
||||
|
||||
(def button-container
|
||||
{:margin-top 12
|
||||
:margin-horizontal 20})
|
|
@ -0,0 +1,40 @@
|
|||
(ns status-im.contexts.profile.settings.screens.password.change-password.view
|
||||
(:require
|
||||
[quo.core :as quo]
|
||||
[react-native.core :as rn]
|
||||
[react-native.platform :as platform]
|
||||
[react-native.safe-area :as safe-area]
|
||||
[status-im.contexts.profile.settings.screens.password.change-password.events]
|
||||
[status-im.contexts.profile.settings.screens.password.change-password.new-password-form :as
|
||||
new-password-form]
|
||||
[status-im.contexts.profile.settings.screens.password.change-password.old-password-form :as
|
||||
old-password-form]
|
||||
[utils.re-frame :as rf]))
|
||||
|
||||
(defn- navigate-back
|
||||
[]
|
||||
(rf/dispatch [:navigate-back]))
|
||||
|
||||
(defn view
|
||||
[]
|
||||
(let [{:keys [top]} (safe-area/get-insets)
|
||||
alert-banners-top-margin (rf/sub [:alert-banners/top-margin])
|
||||
current-step (rf/sub [:settings/change-password-current-step])]
|
||||
(rn/use-unmount #(rf/dispatch [:change-password/reset]))
|
||||
[quo/overlay {:type :shell}
|
||||
[rn/pressable
|
||||
{:key :change-password
|
||||
:on-press rn/dismiss-keyboard!
|
||||
:accessible false
|
||||
:style {:flex 1}}
|
||||
[quo/page-nav
|
||||
{:margin-top top
|
||||
:background :blur
|
||||
:icon-name :i/arrow-left
|
||||
:on-press navigate-back}]
|
||||
[rn/keyboard-avoiding-view
|
||||
{:style {:flex 1}
|
||||
:keyboardVerticalOffset (if platform/ios? alert-banners-top-margin 0)}
|
||||
(condp = current-step
|
||||
:old-password [old-password-form/view]
|
||||
:new-password [new-password-form/view])]]]))
|
|
@ -2,8 +2,6 @@
|
|||
(:require [quo.core :as quo]
|
||||
[quo.theme :as quo.theme]
|
||||
[status-im.common.biometric.utils :as biometric]
|
||||
[status-im.common.not-implemented :as not-implemented]
|
||||
[status-im.config :as config]
|
||||
[status-im.constants :as constants]
|
||||
[utils.i18n :as i18n]
|
||||
[utils.re-frame :as rf]))
|
||||
|
@ -47,13 +45,12 @@
|
|||
|
||||
(defn- get-change-password-item
|
||||
[]
|
||||
(when config/show-not-implemented-features?
|
||||
{:title (i18n/label :t/change-password)
|
||||
:on-press not-implemented/alert
|
||||
:blur? true
|
||||
:image :icon
|
||||
:image-props :i/password
|
||||
:action :arrow}))
|
||||
{:title (i18n/label :t/change-password)
|
||||
:on-press #(rf/dispatch [:open-modal :screen/change-password])
|
||||
:blur? true
|
||||
:image :icon
|
||||
:image-props :i/password
|
||||
:action :arrow})
|
||||
|
||||
(defn- navigate-back
|
||||
[]
|
||||
|
|
|
@ -40,6 +40,7 @@
|
|||
:registry {}
|
||||
:visibility-status-updates {}
|
||||
:stickers/packs-pending #{}
|
||||
:settings/change-password {}
|
||||
:keycard {:nfc-enabled? false
|
||||
:pin {:original []
|
||||
:confirmation []
|
||||
|
|
|
@ -52,6 +52,9 @@
|
|||
[status-im.contexts.profile.settings.screens.messages.blocked-users.view :as
|
||||
settings.blocked-users]
|
||||
[status-im.contexts.profile.settings.screens.messages.view :as settings.messages]
|
||||
[status-im.contexts.profile.settings.screens.password.change-password.loading :as
|
||||
change-password-loading]
|
||||
[status-im.contexts.profile.settings.screens.password.change-password.view :as change-password]
|
||||
[status-im.contexts.profile.settings.screens.password.view :as settings-password]
|
||||
[status-im.contexts.profile.settings.view :as settings]
|
||||
[status-im.contexts.settings.wallet.saved-addresses.view :as saved-addresses-settings]
|
||||
|
@ -485,7 +488,7 @@
|
|||
|
||||
;; Settings
|
||||
|
||||
{:name :settings-password
|
||||
{:name :screen/settings-password
|
||||
:options options/transparent-modal-screen-options
|
||||
:component settings-password/view}
|
||||
|
||||
|
@ -503,7 +506,20 @@
|
|||
|
||||
{:name :screen/settings-blocked-users
|
||||
:options options/transparent-modal-screen-options
|
||||
:component settings.blocked-users/view}]
|
||||
:component settings.blocked-users/view}
|
||||
|
||||
{:name :screen/change-password
|
||||
:options (assoc options/transparent-modal-screen-options :theme :dark)
|
||||
:component change-password/view}
|
||||
|
||||
{:name :screen/change-password-loading
|
||||
:options (assoc
|
||||
options/transparent-modal-screen-options
|
||||
:theme :dark
|
||||
:popGesture false
|
||||
:hardwareBackButton {:dismissModalOnPress false
|
||||
:popStackOnPress false})
|
||||
:component change-password-loading/view}]
|
||||
|
||||
[{:name :shell
|
||||
:options {:theme :dark}}]
|
||||
|
|
|
@ -13,6 +13,7 @@
|
|||
status-im.subs.onboarding
|
||||
status-im.subs.pairing
|
||||
status-im.subs.profile
|
||||
status-im.subs.settings
|
||||
status-im.subs.shell
|
||||
status-im.subs.wallet.activities
|
||||
status-im.subs.wallet.collectibles
|
||||
|
@ -167,6 +168,9 @@
|
|||
;;biometrics
|
||||
(reg-root-key-sub :biometrics :biometrics)
|
||||
|
||||
;;settings
|
||||
(reg-root-key-sub :settings/change-password :settings/change-password)
|
||||
|
||||
;;debug
|
||||
(when js/goog.DEBUG
|
||||
(reg-root-key-sub :dev/previewed-component :dev/previewed-component))
|
||||
|
|
|
@ -0,0 +1,17 @@
|
|||
(ns status-im.subs.settings
|
||||
(:require [re-frame.core :as rf]))
|
||||
|
||||
(rf/reg-sub :settings/change-password-current-step
|
||||
:<- [:settings/change-password]
|
||||
(fn [change-password]
|
||||
(or (get change-password :current-step) :old-password)))
|
||||
|
||||
(rf/reg-sub :settings/change-password-loading
|
||||
:<- [:settings/change-password]
|
||||
(fn [change-password]
|
||||
(get change-password :loading?)))
|
||||
|
||||
(rf/reg-sub :settings/change-password-error
|
||||
:<- [:settings/change-password]
|
||||
(fn [change-password]
|
||||
(get change-password :verify-error)))
|
|
@ -146,6 +146,19 @@
|
|||
"change-logging-enabled": "Are you sure you want to {{enable}} logging?",
|
||||
"change-passcode": "Change Passcode",
|
||||
"change-password": "Change password",
|
||||
"change-password-confirm-description": "Your data must now be re-encrypted with your new password. Please do not quit the app or turn off your device.",
|
||||
"change-password-confirm-warning": "Re-encrypting data after changing password may take up to 3 min. Closing the app or locking phone while re-encryption is in progress will lead to data corruption and loss of your Status profile.",
|
||||
"change-password-description": "Change password used to log in to Status and sign transactions",
|
||||
"change-password-loading-header": "Keep app open on screen!",
|
||||
"change-password-loading-description": "Data is now being re-encrypted with your new password, this may take up to 3min.",
|
||||
"change-password-done-header": "Re-encryption complete!",
|
||||
"change-password-done-description": "Please log out Status and log in using your new password",
|
||||
"change-password-loading-warning": "Do not quit the app or turn off your device while in-progress.\n \nClosing the app or locking your phone while re-encryption is taking place will lead to data corruption and the loss of your Status profile.",
|
||||
"change-password-new-password-label": "New password",
|
||||
"change-password-new-password-placeholder": "Enter new password",
|
||||
"change-password-old-password-label": "Current password",
|
||||
"change-password-old-password-placeholder": "Enter current password",
|
||||
"change-password-repeat-password-placeholder": "Repeat new password",
|
||||
"change-pin": "Change 6-digit passcode",
|
||||
"change-puk": "Change 12-digit PUK",
|
||||
"change-pairing": "Change pairing code",
|
||||
|
@ -1211,7 +1224,9 @@
|
|||
"puk-mismatch": "Wrong PUK code",
|
||||
"quiet-days": "{{quiet-days}} days",
|
||||
"quiet-hours": "{{quiet-hours}} hours",
|
||||
"re-encrypt": "Re-encrypt",
|
||||
"re-encrypt-key": "Re-encrypt your keys",
|
||||
"re-encrypt-data": "Re-encrypt your data",
|
||||
"receive": "Receive",
|
||||
"receive-transaction": "Receive transaction",
|
||||
"recent": "Recent",
|
||||
|
|
Loading…
Reference in New Issue