recovery flow v1

Signed-off-by: Dmitry Novotochinov <dmitry.novot@gmail.com>
This commit is contained in:
Dmitry Novotochinov 2019-08-01 18:49:33 +03:00
parent ba112a765b
commit b635691c25
No known key found for this signature in database
GPG Key ID: 43D1DAF5AD39C927
31 changed files with 796 additions and 218 deletions

2
.env
View File

@ -7,7 +7,7 @@ ETHEREUM_DEV_CLUSTER=1
EXTENSIONS=0
FLEET=eth.beta
GROUP_CHATS_ENABLED=1
HARDWALLET_ENABLED=0
HARDWALLET_ENABLED=1
LOG_LEVEL_STATUS_GO=info
LOG_LEVEL=debug
MAILSERVER_CONFIRMATIONS_ENABLED=1

View File

@ -6,7 +6,7 @@ ETHEREUM_DEV_CLUSTER=1
EXTENSIONS=0
FLEET=eth.beta
GROUP_CHATS_ENABLED=1
HARDWALLET_ENABLED=0
HARDWALLET_ENABLED=1
LOG_LEVEL_STATUS_GO=info
LOG_LEVEL=debug
MAILSERVER_CONFIRMATIONS_ENABLED=1

View File

@ -568,5 +568,6 @@ var TopLevel = {
"multiAccountDeriveAddresses" : function () {},
"multiAccountReset" : function () {},
"multiAccountLoadAccount" : function () {},
"multiAccountStoreAccount" : function () {}
"multiAccountStoreAccount" : function () {},
"multiAccountImportMnemonic" : function () {},
}

View File

@ -774,6 +774,25 @@ class StatusModule extends ReactContextBaseJavaModule implements LifecycleEventL
StatusThreadPoolExecutor.getInstance().execute(r);
}
@ReactMethod
public void multiAccountImportMnemonic(final String json, final Callback callback) {
Log.d(TAG, "multiAccountImportMnemonic");
if (!checkAvailability()) {
callback.invoke(false);
return;
}
Runnable r = new Runnable() {
@Override
public void run() {
String res = Statusgo.multiAccountImportMnemonic(json);
callback.invoke(res);
}
};
StatusThreadPoolExecutor.getInstance().execute(r);
}
private String createIdentifier() {
return UUID.randomUUID().toString();
}

View File

@ -384,7 +384,17 @@ RCT_EXPORT_METHOD(multiAccountStoreDerived:(NSString *)json
callback(@[result]);
}
//////////////////////////////////////////////////////////////////// MultiAccountDeriveAddresses
//////////////////////////////////////////////////////////////////// multiAccountImportMnemonic
RCT_EXPORT_METHOD(multiAccountImportMnemonic:(NSString *)json
callback:(RCTResponseSenderBlock)callback) {
#if DEBUG
NSLog(@"MultiAccountImportMnemonic() method called");
#endif
NSString *result = StatusgoMultiAccountImportMnemonic(json);
callback(@[result]);
}
//////////////////////////////////////////////////////////////////// multiAccountDeriveAddresses
RCT_EXPORT_METHOD(multiAccountDeriveAddresses:(NSString *)json
callback:(RCTResponseSenderBlock)callback) {
#if DEBUG

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 KiB

View File

@ -21,17 +21,25 @@
(defn valid-word-counts? [v]
(boolean (valid-word-counts (count v))))
(defn words-count [s]
(if (empty? s)
nil
(-> s
passphrase->words
count)))
(defn- valid-word? [s]
(re-matches #"^[A-z]+$" s))
(defn valid-words? [v]
(and (valid-word-counts? v)
(every? valid-word? v)))
(defn valid-phrase? [s]
(defn valid-length? [s]
(-> s
passphrase->words
valid-words?))
valid-word-counts?))
(defn valid-words? [s]
(->> s
passphrase->words
(every? valid-word?)))
(defn status-generated-phrase? [s]
(every? dictionary (passphrase->words s)))

View File

@ -307,13 +307,6 @@
(fn [cofx _]
(multiaccounts.recover/recover-multiaccount cofx)))
(handlers/register-handler-fx
:multiaccounts.recover.callback/recover-multiaccount-success
[(re-frame/inject-cofx :random-guid-generator)
(re-frame/inject-cofx :multiaccounts.create/get-signing-phrase)]
(fn [cofx [_ result password]]
(multiaccounts.recover/on-multiaccount-recovered cofx result password)))
;; multiaccounts login module
(handlers/register-handler-fx

View File

@ -404,18 +404,19 @@
(navigation/navigate-to-cofx :keycard-recovery-enter-mnemonic nil)))
(fx/defn start-import-flow
{:events [:recovery.ui/recover-with-keycard-pressed
{:events [:recover.ui/recover-with-keycard-pressed
:keycard.login.ui/recover-key-pressed]}
[{:keys [db] :as cofx}]
(fx/merge cofx
{:db (assoc-in db [:hardwallet :flow] :import)
:dispatch [:bottom-sheet/hide-sheet]
:hardwallet/check-nfc-enabled nil}
(navigation/navigate-to-cofx :keycard-recovery-intro nil)))
(fx/defn access-key-pressed
{:events [:multiaccounts.recover.ui/recover-multiaccount-button-pressed]}
[cofx]
(multiaccounts.recover/navigate-to-recover-multiaccount-screen cofx))
{:dispatch [:bottom-sheet/show-sheet :recover-sheet]})
(fx/defn recovery-keycard-selected
{:events [:recovery.ui/keycard-option-pressed]}
@ -425,19 +426,6 @@
:hardwallet/check-nfc-enabled nil}
(navigation/navigate-to-cofx :keycard-onboarding-intro nil)))
;NOTE to be removed when Recovery flow will be implemented
(fx/defn enter-mnemonic-next-button-pressed
{:events [:keycard.recovery.enter-mnemonic.ui/input-submitted
:keycard.recovery.enter-mnemonic.ui/next-pressed]}
[cofx]
(recovery-keycard-selected cofx))
;NOTE to be removed when Recovery flow will be implemented
(fx/defn enter-mnemonic-input-changed
{:events [:keycard.recovery.enter-mnemonic.ui/input-changed]}
[{:keys [db]} input]
{:db (assoc-in db [:hardwallet :secrets :mnemonic] input)})
(fx/defn password-option-pressed
[{:keys [db] :as cofx}]
(if (= (get-in db [:hardwallet :flow]) :create)

View File

@ -228,6 +228,9 @@
(= (get-in cofx [:db :view-id])
:create-multiaccount))
(defn recovering-multiaccount? [cofx]
(boolean (get-in cofx [:db :multiaccounts/recover])))
(defn- keycard-setup? [cofx]
(boolean (get-in cofx [:db :hardwallet :flow])))
@ -242,6 +245,7 @@
(stickers/init-stickers-packs)
(multiaccounts.update/update-sign-in-time)
#(when-not (or (creating-multiaccount? %)
(recovering-multiaccount? %)
(keycard-setup? %))
(login-only-events % address stored-pns)))))

View File

@ -283,11 +283,23 @@
(fx/defn verify-multiaccount
[{:keys [db] :as cofx} {:keys [realm-error]}]
(fx/merge cofx
{:db (-> db
(assoc :node/on-ready :verify-multiaccount)
(assoc :realm-error realm-error))}
(node/initialize nil)))
(if (get-in db [:multiaccounts/recover])
(fx/merge cofx
{:db (-> db
(update :multiaccounts/recover assoc
:processing? false
:password ""
:password-confirmation ""
:password-error :recover-password-invalid)
(update :multiaccounts/recover dissoc
:password-valid?))
:node/stop nil}
(navigation/navigate-to-cofx :recover-multiaccount-enter-password nil))
(fx/merge cofx
{:db (-> db
(assoc :node/on-ready :verify-multiaccount)
(assoc :realm-error realm-error))}
(node/initialize nil))))
(fx/defn unknown-realm-error
[cofx {:keys [realm-error erase-button]}]

View File

@ -20,11 +20,14 @@
(defn check-phrase-errors [recovery-phrase]
(cond (string/blank? recovery-phrase) :required-field
(not (mnemonic/valid-phrase? recovery-phrase)) :recovery-phrase-invalid))
(not (mnemonic/valid-words? recovery-phrase)) :recovery-phrase-invalid
(not (mnemonic/valid-length? recovery-phrase)) :recovery-phrase-wrong-length
(not (mnemonic/status-generated-phrase? recovery-phrase)) :recovery-phrase-unknown-words))
(defn check-phrase-warnings [recovery-phrase]
(when (not (mnemonic/status-generated-phrase? recovery-phrase))
:recovery-phrase-unknown-words))
(cond (string/blank? recovery-phrase) :required-field
(not (mnemonic/valid-words? recovery-phrase)) :recovery-phrase-invalid
(not (mnemonic/status-generated-phrase? recovery-phrase)) :recovery-phrase-unknown-words))
(defn recover-multiaccount! [masked-passphrase password]
(status/recover-multiaccount
@ -42,22 +45,31 @@
(fx/defn set-phrase
[{:keys [db]} masked-recovery-phrase]
(let [recovery-phrase (security/safe-unmask-data masked-recovery-phrase)]
{:db (update db :multiaccounts/recover assoc
:passphrase (string/lower-case recovery-phrase)
:passphrase-valid? (not (check-phrase-errors recovery-phrase)))}))
(fx/merge
{:db (update db :multiaccounts/recover assoc
:passphrase (string/lower-case recovery-phrase)
:passphrase-error nil
:next-button-disabled? (or (empty? recovery-phrase)
(not (mnemonic/valid-length? recovery-phrase))))})))
(fx/defn validate-phrase
[{:keys [db]}]
(let [recovery-phrase (get-in db [:multiaccounts/recover :passphrase])]
{:db (update db :multiaccounts/recover assoc
:passphrase-error (check-phrase-errors recovery-phrase)
:passphrase-warning (check-phrase-warnings recovery-phrase))}))
:passphrase-error (check-phrase-errors recovery-phrase))}))
(fx/defn validate-phrase-for-warnings
[{:keys [db]}]
(let [recovery-phrase (get-in db [:multiaccounts/recover :passphrase])]
{:db (update db :multiaccounts/recover assoc
:passphrase-error (check-phrase-warnings recovery-phrase))}))
(fx/defn set-password
[{:keys [db]} masked-password]
(let [password (security/safe-unmask-data masked-password)]
{:db (update db :multiaccounts/recover assoc
:password password
:password-error nil
:password-valid? (not (check-password-errors password)))}))
(fx/defn validate-password
@ -66,52 +78,51 @@
{:db (assoc-in db [:multiaccounts/recover :password-error] (check-password-errors password))}))
(fx/defn validate-recover-result
[{:keys [db] :as cofx} {:keys [error pubkey address walletAddress walletPubKey chatAddress chatPubKey]} password]
(if (empty? error)
(let [multiaccount-address (-> address
(string/lower-case)
(string/replace-first "0x" ""))
keycard-multiaccount? (boolean (get-in db [:multiaccounts/multiaccounts multiaccount-address :keycard-instance-uid]))]
(if keycard-multiaccount?
;; trying to recover multiaccount created with keycard
{:db (-> db
(update :multiaccounts/recover assoc
:processing? false
:passphrase-error :recover-keycard-multiaccount-not-supported)
(update :multiaccounts/recover dissoc
:passphrase-valid?))
:node/stop nil}
(let [multiaccount {:derived {constants/path-whisper-keyword {:publicKey chatPubKey
:address chatAddress}
constants/path-default-wallet-keyword {:publicKey walletPubKey
:address walletAddress}}
:address address
:mnemonic ""}]
(multiaccounts.create/on-multiaccount-created
cofx multiaccount password {:seed-backed-up? true}))))
{:db (-> db
(update :multiaccounts/recover assoc
:processing? false
:password ""
:password-error :recover-password-invalid)
(update :multiaccounts/recover dissoc
:password-valid?))
:node/stop nil}))
[{:keys [db] :as cofx} password]
(let [multiaccount (get-in db [:multiaccounts/recover :root-key])
multiaccount-address (-> (:address multiaccount)
(string/lower-case)
(string/replace-first "0x" ""))
keycard-multiaccount? (boolean (get-in db [:multiaccounts/multiaccounts multiaccount-address :keycard-instance-uid]))]
(if keycard-multiaccount?
;; trying to recover multiaccount created with keycard
{:db (-> db
(update :multiaccounts/recover assoc
:processing? false
:passphrase-error :recover-keycard-multiaccount-not-supported)
(update :multiaccounts/recover dissoc
:passphrase-valid?))
:node/stop nil}
(let [multiaccount' (assoc multiaccount :derived (get-in db [:multiaccounts/recover :derived]))]
(multiaccounts.create/on-multiaccount-created
cofx multiaccount' password {:seed-backed-up? true})))))
(fx/defn on-multiaccount-recovered
[cofx result password]
(let [data (types/json->clj result)]
(validate-recover-result cofx data password)))
{:events [:multiaccounts.recover.callback/recover-multiaccount-success]
:interceptors [(re-frame/inject-cofx :random-guid-generator)
(re-frame/inject-cofx :multiaccounts.create/get-signing-phrase)]}
[cofx password]
(validate-recover-result cofx password))
(fx/defn multiaccount-store-derived
[{:keys [db]}]
(let [id (get-in db [:multiaccounts/recover :root-key :id])
password (get-in db [:multiaccounts/recover :password])]
(status/multiaccount-store-derived
id
[constants/path-whisper constants/path-default-wallet]
password
#(re-frame/dispatch [:multiaccounts.recover.callback/recover-multiaccount-success password]))))
(fx/defn recover-multiaccount
[{:keys [db random-guid-generator] :as cofx}]
(fx/merge
cofx
{:db (-> db
(assoc-in [:multiaccounts/recover :processing?] true)
(assoc :node/on-ready :recover-multiaccount)
(assoc :multiaccounts/new-installation-id (random-guid-generator)))}
(node/initialize nil)))
(let [{:keys [password passphrase]} (:multiaccounts/recover db)]
(fx/merge cofx
{:db (-> db
(assoc-in [:multiaccounts/recover :processing?] true)
(assoc :multiaccounts/new-installation-id (random-guid-generator)))
:multiaccounts.recover/recover-multiaccount
[(security/mask-data passphrase) password]})))
(fx/defn recover-multiaccount-with-checks [{:keys [db] :as cofx}]
(let [{:keys [passphrase processing?]} (:multiaccounts/recover db)]
@ -127,9 +138,164 @@
(fx/defn navigate-to-recover-multiaccount-screen [{:keys [db] :as cofx}]
(fx/merge cofx
{:db (dissoc db :multiaccounts/recover)}
(navigation/navigate-to-cofx :recover nil)))
(navigation/navigate-to-cofx :recover-multiaccount nil)))
(re-frame/reg-fx
:multiaccounts.recover/recover-multiaccount
(fn [[masked-passphrase password]]
(recover-multiaccount! masked-passphrase password)))
(re-frame/reg-fx
:multiaccounts.recover/import-mnemonic
(fn [{:keys [passphrase password]}]
(status-im.native-module.core/multiaccount-import-mnemonic
passphrase
password
(fn [result]
(re-frame/dispatch [:multiaccounts.recover/import-mnemonic-success result])))))
(re-frame/reg-fx
:multiaccounts.recover/derive-addresses
(fn [{:keys [account-id paths]}]
(status-im.native-module.core/multiaccount-derive-addresses
account-id
paths
(fn [result]
(re-frame/dispatch [:multiaccounts.recover/derive-addresses-success result])))))
(fx/defn on-import-mnemonic-success
{:events [:multiaccounts.recover/import-mnemonic-success]}
[{:keys [db] :as cofx} result]
(let [{:keys [id] :as data} (types/json->clj result)]
{:db (assoc-in db [:multiaccounts/recover :root-key] data)
:multiaccounts.recover/derive-addresses {:account-id id
:paths [constants/path-default-wallet
constants/path-whisper]}}))
(fx/defn on-derive-addresses-success
{:events [:multiaccounts.recover/derive-addresses-success]}
[{:keys [db] :as cofx} result]
(let [data (types/json->clj result)]
(fx/merge cofx
{:db (assoc-in db [:multiaccounts/recover :derived] data)}
(navigation/navigate-to-cofx :recover-multiaccount-success nil))))
(fx/defn re-encrypt-pressed
{:events [:recover.success.ui/re-encrypt-pressed]}
[{:keys [db] :as cofx}]
(fx/merge cofx
{:db (assoc-in db [:intro-wizard :selected-storage-type] :default)}
(navigation/navigate-to-cofx :recover-multiaccount-select-storage nil)))
(fx/defn enter-phrase-pressed
{:events [:recover.ui/enter-phrase-pressed]}
[{:keys [db] :as cofx}]
(fx/merge cofx
{:db (assoc db :multiaccounts/recover {:next-button-disabled? true})
:dispatch [:bottom-sheet/hide-sheet]}
(navigation/navigate-to-cofx :recover-multiaccount-enter-phrase nil)))
(fx/defn prepare-to-recover
[{:keys [db random-guid-generator] :as cofx}]
(fx/merge cofx
{:db (-> db
(assoc :node/on-ready :import-mnemonic)
(assoc :multiaccounts/new-installation-id (random-guid-generator)))}
(node/initialize nil)))
(fx/defn import-mnemonic
[{:keys [db]}]
(let [{:keys [password passphrase]} (:multiaccounts/recover db)]
{:multiaccounts.recover/import-mnemonic
{:passphrase passphrase
:password password}}))
(fx/defn proceed-to-import-mnemonic
[{:keys [db] :as cofx}]
(let [{:keys [passphrase]} (:multiaccounts/recover db)
node-started? (= :started (:node/status db))]
(when (mnemonic/valid-length? passphrase)
(if node-started?
(import-mnemonic cofx)
(prepare-to-recover cofx)))))
(fx/defn enter-phrase-next-button-pressed
{:events [:recover.enter-passphrase.ui/input-submitted
:recover.enter-passphrase.ui/next-pressed]
:interceptors [(re-frame/inject-cofx :random-guid-generator)]}
[{:keys [db] :as cofx}]
(fx/merge cofx
(proceed-to-import-mnemonic)))
(fx/defn cancel-pressed
{:events [:recover.ui/cancel-pressed]}
[cofx]
(navigation/navigate-back cofx))
(fx/defn select-storage-next-pressed
{:events [:recover.select-storage.ui/next-pressed]
:interceptors [(re-frame/inject-cofx :random-guid-generator)]}
[{:keys [db] :as cofx}]
(let [storage-type (get-in db [:intro-wizard :selected-storage-type])]
(if (= storage-type :advanced)
{:dispatch [:recovery.ui/keycard-option-pressed]})
(navigation/navigate-to-cofx cofx :recover-multiaccount-enter-password nil)))
(fx/defn proceed-to-password-confirm
[{:keys [db] :as cofx}]
(when (nil? (get-in db [:multiaccounts/recover :password-error]))
(navigation/navigate-to-cofx cofx :recover-multiaccount-confirm-password nil)))
(fx/defn enter-password-next-button-pressed
{:events [:recover.enter-password.ui/input-submitted
:recover.enter-password.ui/next-pressed]}
[{:keys [db] :as cofx}]
(fx/merge cofx
(validate-password)
(proceed-to-password-confirm)))
(fx/defn confirm-password-next-button-pressed
{:events [:recover.confirm-password.ui/input-submitted
:recover.confirm-password.ui/next-pressed]
:interceptors [(re-frame/inject-cofx :random-guid-generator)]}
[{:keys [db] :as cofx}]
(let [{:keys [password password-confirmation]} (:multiaccounts/recover db)]
(if (= password password-confirmation)
(fx/merge cofx
{:db (assoc db :intro-wizard nil)}
(multiaccount-store-derived)
(navigation/navigate-to-cofx :keycard-welcome nil))
{:db (assoc-in db [:multiaccounts/recover :password-error] :password_error1)})))
(fx/defn count-words
[{:keys [db]}]
(let [passphrase (get-in db [:multiaccounts/recover :passphrase])]
{:db (assoc-in db [:multiaccounts/recover :words-count]
(mnemonic/words-count passphrase))}))
(fx/defn run-validation
[{:keys [db] :as cofx}]
(let [passphrase (get-in db [:multiaccounts/recover :passphrase])]
(when (= (last passphrase) " ")
(fx/merge cofx
(validate-phrase-for-warnings)))))
(fx/defn enter-phrase-input-changed
{:events [:recover.enter-passphrase.ui/input-changed]}
[cofx input]
(fx/merge cofx
(set-phrase input)
(count-words)
(run-validation)))
(fx/defn enter-password-input-changed
{:events [:recover.enter-password.ui/input-changed]}
[cofx input]
(set-password cofx input))
(fx/defn confirm-password-input-changed
{:events [:recover.confirm-password.ui/input-changed]}
[{:keys [db]} input]
{:db (-> db
(assoc-in [:multiaccounts/recover :password-confirmation] input)
(assoc-in [:multiaccounts/recover :password-error] nil))})

View File

@ -40,6 +40,9 @@
(defn multiaccount-generate-and-derive-addresses [n mnemonic-length paths callback]
(native-module/multiaccount-generate-and-derive-addresses n mnemonic-length paths callback))
(defn multiaccount-import-mnemonic [mnemonic password callback]
(native-module/multiaccount-import-mnemonic mnemonic password callback))
(defn login [address password main-account watch-addresses callback]
(native-module/login address password main-account watch-addresses callback))

View File

@ -106,6 +106,14 @@
on-result)))
(defn multiaccount-import-mnemonic [mnemonic password on-result]
(when (and @node-started (status))
(.multiAccountImportMnemonic (status)
(types/clj->json {:mnemonicPhrase mnemonic
:Bip39Passphrase password})
on-result)))
(defn login [address password main-account watch-addresses on-result]
(when (and @node-started (status))
(.login (status)

View File

@ -18,6 +18,8 @@
:keycard-lock (js-require/js-require "./resources/images/ui/keycard-lock.png")
:keycard (js-require/js-require "./resources/images/ui/keycard.png")
:keycard-logo (js-require/js-require "./resources/images/ui/keycard-logo.png")
:keycard-logo-blue (js-require/js-require "./resources/images/ui/keycard-logo-blue.png")
:keycard-logo-gray (js-require/js-require "./resources/images/ui/keycard-logo-gray.png")
:keycard-key (js-require/js-require "./resources/images/ui/keycard-key.png")
:keycard-empty (js-require/js-require "./resources/images/ui/keycard-empty.png")
:keycard-phone (js-require/js-require "./resources/images/ui/keycard-phone.png")

View File

@ -13,7 +13,8 @@
[status-im.utils.fx :as fx]
[status-im.utils.security :as security]
[status-im.utils.types :as types]
[taoensso.timbre :as log]))
[taoensso.timbre :as log]
[status-im.multiaccounts.recover.core :as multiaccounts.recover]))
(fx/defn status-node-started
[{db :db :as cofx}]
@ -38,11 +39,8 @@
:create-multiaccount
(fn [_]
{:multiaccounts.create/create-multiaccount (select-keys create [:id :password])})
:recover-multiaccount
(fn [{:keys [db]}]
(let [{:keys [password passphrase]} (:multiaccounts/recover db)]
{:multiaccounts.recover/recover-multiaccount
[(security/mask-data passphrase) password]}))
:import-mnemonic
(multiaccounts.recover/import-mnemonic)
:create-keycard-multiaccount
(hardwallet/create-keycard-multiaccount)
:start-onboarding

View File

@ -3,16 +3,20 @@
[status-im.ui.components.common.common :refer [list-separator]]
[status-im.ui.components.icons.vector-icons :as vi]
[status-im.ui.components.react :as rn]
[status-im.ui.components.colors :as colors]))
[status-im.ui.components.colors :as colors]
[status-im.ui.components.react :as react]
[status-im.react-native.resources :as resources]))
(defn action-button [{:keys [label accessibility-label icon icon-opts on-press label-style cyrcle-color]}]
(defn action-button [{:keys [label accessibility-label icon icon-opts image image-opts on-press label-style cyrcle-color]}]
[rn/touchable-highlight (merge {:on-press on-press
:underlay-color (colors/alpha colors/gray 0.15)}
(when accessibility-label
{:accessibility-label accessibility-label}))
[rn/view {:style st/action-button}
[rn/view {:style (st/action-button-icon-container cyrcle-color)}
[vi/icon icon icon-opts]]
(if image
[react/image (assoc image-opts :source (resources/get-image image))]
[vi/icon icon icon-opts])]
[rn/view st/action-button-label-container
[rn/text {:style (merge st/action-button-label label-style)}
label]]]])

View File

@ -31,6 +31,7 @@
{:padding-horizontal 16
:padding-vertical 9
:background-color color
:elevation 2
:border-radius 8})
(def bottom-tooltip-text-container

View File

@ -19,7 +19,8 @@
[status-im.ui.components.toolbar.view :as toolbar]
[status-im.i18n :as i18n]
[status-im.ui.components.status-bar.view :as status-bar]
[status-im.constants :as constants]))
[status-im.constants :as constants]
[status-im.utils.config :as config]))
(defn dots-selector [{:keys [on-press n selected color]}]
[react/view {:style (styles/dot-selector n)}
@ -128,20 +129,26 @@
(utils/get-shortened-address public-key)]]
[radio/radio selected?]]]))])
(defn storage-entry [{:keys [type icon icon-width icon-height title desc]} selected-storage-type]
(defn storage-entry [{:keys [type icon icon-width icon-height
image image-selected image-width image-height
title desc]} selected-storage-type]
(let [selected? (= type selected-storage-type)]
[react/view
[react/view {:style {:padding-top 14 :padding-bottom 4}}
[react/text {:style (assoc styles/wizard-text :text-align :left :margin-left 16)}
(i18n/label type)]]
[react/touchable-highlight
{:on-press #(re-frame/dispatch [:intro-wizard/on-key-storage-selected type])}
{:on-press #(re-frame/dispatch [:intro-wizard/on-key-storage-selected (if config/hardwallet-enabled? type :default)])}
[react/view (assoc (styles/list-item selected?)
:align-items :flex-start
:padding-top 20
:padding-bottom 12)
[vector-icons/icon icon {:color (if selected? colors/blue colors/gray)
:width icon-width :height icon-height}]
(if image
[react/image
{:source (resources/get-image (if selected? image-selected image))
:style {:width image-width :height image-height}}]
[vector-icons/icon icon {:color (if selected? colors/blue colors/gray)
:width icon-width :height icon-height}])
[react/view {:style {:margin-horizontal 16 :flex 1}}
[react/text {:style (assoc styles/wizard-text :font-weight "500" :color colors/black :text-align :left)}
(i18n/label title)]
@ -157,12 +164,13 @@
:icon-height 24
:title :this-device
:desc :this-device-desc}
{:type :advanced
:icon :main-icons/keycard-logo
:icon-width 13
:icon-height 22
:title :keycard
:desc :keycard-desc}]]
{:type :advanced
:image :keycard-logo-gray
:image-selected :keycard-logo-blue
:image-width 24
:image-height 24
:title :keycard
:desc :keycard-desc}]]
[react/view {:style {:flex 1
:justify-content :flex-end
;; We have to align top storage entry

View File

@ -397,58 +397,3 @@
:label (i18n/label :t/next)
:disabled? (empty? input-word)
:forward? true}]]]]])))
;NOTE temporary screen, to be removed after Recovery will be implemented
(defview enter-mnemonic []
(letsubs [mnemonic [:hardwallet-mnemonic]]
[react/view styles/container
[toolbar/toolbar
{:transparent? true
:style {:margin-top 32}}
[toolbar/nav-text
{:handler #(re-frame/dispatch [:keycard.onboarding.ui/cancel-pressed])
:style {:padding-left 21}}
(i18n/label :t/cancel)]
[react/text {:style {:color colors/gray}}
(i18n/label :t/step-i-of-n {:step "1"
:number "2"})]]
[react/view {:flex 1
:flex-direction :column
:justify-content :space-between
:align-items :center}
[react/view {:flex-direction :column
:align-items :center}
[react/view {:margin-top 16}
[react/text {:style {:typography :header
:text-align :center}}
"Enter your recovery phrase"]]
[react/view {:margin-top 16
:width "85%"
:align-items :center}
[react/text {:style {:color colors/gray
:text-align :center}}
"Enter your recovery phrase, separate the words by single spaces"]]]
[react/view
[text-input/text-input-with-label
{:on-change-text #(re-frame/dispatch [:keycard.recovery.enter-mnemonic.ui/input-changed %])
:auto-focus true
:on-submit-editing #(re-frame/dispatch [:keycard.recovery.enter-mnemonic.ui/input-submitted])
:placeholder nil
:height 125
:multiline true
:auto-correct false
:container {:background-color :white}
:style {:background-color :white
:typography :header}}]]
[react/view {:flex-direction :row
:justify-content :space-between
:align-items :center
:width "100%"
:height 86}
[react/view]
[react/view {:margin-right 20}
[components.common/bottom-button
{:on-press #(re-frame/dispatch [:keycard.recovery.enter-mnemonic.ui/next-pressed])
:label (i18n/label :t/next)
:disabled? (empty? mnemonic)
:forward? true}]]]]]))

View File

@ -105,10 +105,12 @@
[react/i18n-text {:style styles/processing :key :processing}]])
[react/view {:style styles/bottom-button-container}
[components.common/button
{:label (i18n/label :t/access-key)
{:label (i18n/label :t/access-key)
:button-style styles/bottom-button
:background? false
:on-press #(re-frame/dispatch [:multiaccounts.recover.ui/recover-multiaccount-button-pressed])}]
:background? false
:on-press #(do
(react/dismiss-keyboard!)
(re-frame/dispatch [:multiaccounts.recover.ui/recover-multiaccount-button-pressed]))}]
[components.common/button
{:label (i18n/label :t/submit)
:button-style styles/bottom-button

View File

@ -16,7 +16,17 @@
[status-im.ui.components.common.common :as components.common]
[status-im.utils.security :as security]
[status-im.utils.platform :as platform]
[clojure.string :as string]))
[clojure.string :as string]
[status-im.ui.components.action-button.styles :as action-button.styles]
[status-im.ui.components.action-button.action-button :as action-button]
[status-im.ui.components.colors :as colors]
[status-im.utils.gfycat.core :as gfy]
[status-im.utils.identicon :as identicon]
[status-im.ui.components.radio :as radio]
[status-im.ui.components.icons.vector-icons :as vector-icons]
[status-im.ui.screens.intro.views :as intro.views]
[status-im.utils.utils :as utils]
[status-im.constants :as constants]))
(defview passphrase-input [passphrase error warning]
(letsubs [input-ref (reagent/atom nil)]
@ -94,3 +104,343 @@
:label (i18n/label :t/sign-in)
:disabled? disabled?
:on-press sign-in}]])])))
(defn bottom-sheet-view []
[react/view {:flex 1 :flex-direction :row}
[react/view action-button.styles/actions-list
[action-button/action-button
{:label (i18n/label :t/enter-seed-phrase)
:accessibility-label :enter-seed-phrase-button
:icon :main-icons/text
:icon-opts {:color colors/blue}
:on-press #(re-frame/dispatch [:recover.ui/enter-phrase-pressed])}]
[action-button/action-button
{:label (i18n/label :t/recover-with-keycard)
:label-style (if config/hardwallet-enabled? {} {:color colors/gray})
:accessibility-label :recover-with-keycard-button
:image :keycard-logo-blue
:image-opts {:style {:width 24 :height 24}}
:on-press #(when config/hardwallet-enabled?
(re-frame/dispatch [:recover.ui/recover-with-keycard-pressed]))}]]])
(def bottom-sheet
{:content bottom-sheet-view
:content-height 130})
(defview enter-phrase []
(letsubs [{:keys [passphrase
processing?
passphrase-error
words-count
next-button-disabled?]} [:get-recover-multiaccount]]
[react/keyboard-avoiding-view {:flex 1
:justify-content :space-between
:background-color colors/white}
[toolbar/toolbar
{:transparent? true
:style {:margin-top 32}}
[toolbar/nav-text
{:handler #(re-frame/dispatch [:recover.ui/cancel-pressed])
:style {:padding-left 21}}
(i18n/label :t/cancel)]
[react/text {:style {:color colors/gray}}
(i18n/label :t/step-i-of-n {:step "1"
:number "2"})]]
[react/view {:flex 1
:flex-direction :column
:justify-content :space-between
:align-items :center}
[react/view {:flex-direction :column
:align-items :center}
[react/view {:margin-top 16}
[react/text {:style {:typography :header
:text-align :center}}
(i18n/label :t/multiaccounts-recover-enter-phrase-title)]]
[react/view {:margin-top 16}
[text-input/text-input-with-label
{:on-change-text #(re-frame/dispatch [:recover.enter-passphrase.ui/input-changed (security/mask-data %)])
:auto-focus true
:on-submit-editing #(re-frame/dispatch [:recover.enter-passphrase.ui/input-submitted])
:error (when passphrase-error (i18n/label passphrase-error))
:placeholder nil
:height 120
:multiline true
:auto-correct false
:container {:background-color :white
:min-width "50%"}
:style {:background-color :white
:text-align :center
:font-size 16
:font-weight "700"}}]]
[react/view {:align-items :center}
(when words-count
[react/view {:flex-direction :row
:height 11
:align-items :center}
(when-not next-button-disabled?
[vector-icons/tiny-icon :tiny-icons/tiny-check])
[react/text {:style {:font-size 14
:padding-left 4
:text-align :center
:color colors/black}}
(i18n/label-pluralize words-count :t/words-n)]])]
(when next-button-disabled?
[react/view {:margin-top 17
:align-items :center}
[react/text {:style {:color colors/black
:font-size 14
:text-align :center}}
(i18n/label :t/multiaccounts-recover-enter-phrase-text)]])]
(when processing?
[react/view
[react/activity-indicator {:size :large
:animating true}]
[react/text {:style {:color colors/gray
:margin-top 8}}
(i18n/label :t/processing)]])
[react/view {:flex-direction :row
:justify-content :space-between
:align-items :center
:width "100%"
:height 86}
(when-not processing?
[react/view])
(when-not processing?
[react/view {:margin-right 20}
[components.common/bottom-button
{:on-press #(re-frame/dispatch [:recover.enter-passphrase.ui/next-pressed])
:label (i18n/label :t/next)
:disabled? next-button-disabled?
:forward? true}]])]]]))
(defview success []
(letsubs [multiaccount [:get-recover-multiaccount]]
(let [pubkey (get-in multiaccount [:derived constants/path-whisper-keyword :publicKey])]
[react/view {:flex 1
:justify-content :space-between
:background-color colors/white}
[toolbar/toolbar
{:transparent? true
:style {:margin-top 32}}
nil
nil]
[react/view {:flex 1
:flex-direction :column
:justify-content :space-between
:align-items :center}
[react/view {:flex-direction :column
:align-items :center}
[react/view {:margin-top 16}
[react/text {:style {:typography :header
:text-align :center}}
(i18n/label :t/keycard-recovery-success-header)]]
[react/view {:margin-top 16
:width "85%"
:align-items :center}
[react/text {:style {:color colors/gray
:text-align :center}}
(i18n/label :t/recovery-success-text)]]]
[react/view {:flex-direction :column
:flex 1
:justify-content :center
:align-items :center}
[react/view {:margin-horizontal 16
:flex-direction :column}
[react/view {:justify-content :center
:align-items :center
:margin-bottom 11}
[react/image {:source {:uri (identicon/identicon pubkey)}
:style {:width 61
:height 61
:border-radius 30
:border-width 1
:border-color (colors/alpha colors/black 0.1)}}]]
[react/text {:style {:text-align :center
:color colors/black
:font-weight "500"}
:number-of-lines 1
:ellipsize-mode :middle}
(gfy/generate-gfy pubkey)]
[react/text {:style {:text-align :center
:margin-top 4
:color colors/gray
:font-family "monospace"}
:number-of-lines 1
:ellipsize-mode :middle}
(utils/get-shortened-address pubkey)]]]
[react/view {:margin-bottom 50}
[react/touchable-highlight
{:on-press #(re-frame/dispatch [:recover.success.ui/re-encrypt-pressed])}
[react/view {:background-color colors/gray-background
:align-items :center
:justify-content :center
:flex-direction :row
:width 193
:height 44
:border-radius 10}
[react/text {:style {:color colors/blue}}
(i18n/label :t/re-encrypt-key)]]]]]])))
(defview select-storage []
(letsubs [{:keys [selected-storage-type]} [:intro-wizard]
{view-height :height} [:dimensions/window]]
[react/view {:flex 1
:justify-content :space-between
:background-color colors/white}
[toolbar/toolbar
{:transparent? true
:style {:margin-top 32}}
[toolbar/nav-text
{:handler #(re-frame/dispatch [:recover.ui/cancel-pressed])
:style {:padding-left 21}}
(i18n/label :t/cancel)]
nil]
[react/view {:flex 1
:justify-content :space-between}
[react/view {:flex-direction :column
:align-items :center}
[react/view {:margin-top 16}
[react/text {:style {:typography :header
:text-align :center}}
(i18n/label :t/intro-wizard-title3)]]
[react/view {:margin-top 16
:width "85%"
:align-items :center}
[react/text {:style {:color colors/gray
:text-align :center}}
(i18n/label :t/intro-wizard-text3)]]]
[intro.views/select-key-storage {:selected-storage-type (if config/hardwallet-enabled? selected-storage-type :default)} view-height]
[react/view {:flex-direction :row
:justify-content :space-between
:align-items :center
:width "100%"
:height 86}
[react/view components.styles/flex]
[react/view {:margin-right 20}
[components.common/bottom-button
{:on-press #(re-frame/dispatch [:recover.select-storage.ui/next-pressed])
:forward? true}]]]]]))
(defview enter-password []
(letsubs [{:keys [password password-error]} [:get-recover-multiaccount]]
[react/keyboard-avoiding-view {:flex 1
:justify-content :space-between
:background-color colors/white}
[toolbar/toolbar
{:transparent? true
:style {:margin-top 32}}
[toolbar/nav-text
{:handler #(re-frame/dispatch [:recover.ui/cancel-pressed])
:style {:padding-left 21}}
(i18n/label :t/cancel)]
[react/text {:style {:color colors/gray}}
(i18n/label :t/step-i-of-n {:step "1"
:number "2"})]]
[react/view {:flex 1
:flex-direction :column
:justify-content :space-between
:align-items :center}
[react/view {:flex-direction :column
:align-items :center}
[react/view {:margin-top 16}
[react/text {:style {:typography :header
:text-align :center}}
(i18n/label :t/intro-wizard-title-alt4)]]
[react/view {:margin-top 16
:width "85%"
:align-items :center}
[react/text {:style {:color colors/gray
:text-align :center}}
(i18n/label :t/password-description)]]
[react/view {:margin-top 16}
[text-input/text-input-with-label
{:on-change-text #(re-frame/dispatch [:recover.enter-password.ui/input-changed (security/mask-data %)])
:auto-focus true
:on-submit-editing #(re-frame/dispatch [:recover.enter-password.ui/input-submitted])
:secure-text-entry true
:error (when password-error (i18n/label password-error))
:placeholder nil
:height 125
:multiline false
:auto-correct false
:container {:background-color :white
:min-width "50%"}
:style {:background-color :white
:width 200
:text-align :center
:font-size 20
:font-weight "700"}}]]]
[react/view {:flex-direction :row
:justify-content :space-between
:align-items :center
:width "100%"
:height 86}
[react/view]
[react/view {:margin-right 20}
[components.common/bottom-button
{:on-press #(re-frame/dispatch [:recover.enter-password.ui/next-pressed])
:label (i18n/label :t/next)
:disabled? (empty? password)
:forward? true}]]]]]))
(defview confirm-password []
(letsubs [{:keys [password-confirmation password-error]} [:get-recover-multiaccount]]
[react/keyboard-avoiding-view {:flex 1
:justify-content :space-between
:background-color colors/white}
[toolbar/toolbar
{:transparent? true
:style {:margin-top 32}}
[toolbar/nav-text
{:handler #(re-frame/dispatch [:recover.ui/cancel-pressed])
:style {:padding-left 21}}
(i18n/label :t/cancel)]
[react/text {:style {:color colors/gray}}
(i18n/label :t/step-i-of-n {:step "1"
:number "2"})]]
[react/view {:flex 1
:flex-direction :column
:justify-content :space-between
:align-items :center}
[react/view {:flex-direction :column
:align-items :center}
[react/view {:margin-top 16}
[react/text {:style {:typography :header
:text-align :center}}
(i18n/label :t/intro-wizard-title-alt5)]]
[react/view {:margin-top 16
:width "85%"
:align-items :center}
[react/text {:style {:color colors/gray
:text-align :center}}
(i18n/label :t/password-description)]]
[react/view {:margin-top 16}
[text-input/text-input-with-label
{:on-change-text #(re-frame/dispatch [:recover.confirm-password.ui/input-changed %])
:auto-focus true
:on-submit-editing #(re-frame/dispatch [:recover.confirm-password.ui/input-submitted])
:error (when password-error (i18n/label password-error))
:secure-text-entry true
:placeholder nil
:height 125
:multiline false
:auto-correct false
:container {:background-color :white
:min-width "50%"}
:style {:background-color :white
:width 200
:text-align :center
:font-size 20
:font-weight "700"}}]]]
[react/view {:flex-direction :row
:justify-content :space-between
:align-items :center
:width "100%"
:height 86}
[react/view]
[react/view {:margin-right 20}
[components.common/bottom-button
{:on-press #(re-frame/dispatch [:recover.confirm-password.ui/next-pressed])
:label (i18n/label :t/next)
:disabled? (empty? password-confirmation)
:forward? true}]]]]]))

View File

@ -5,7 +5,12 @@
#{:login
:progress
:create-multiaccount
:recover
:recover-multiaccount
:recover-multiaccount-enter-phrase
:recover-multiaccount-select-storage
:recover-multiaccount-enter-password
:recover-multiaccount-confirm-password
:recover-multiaccount-success
:multiaccounts
:intro
:intro-wizard
@ -32,7 +37,6 @@
:keycard-onboarding-recovery-phrase
:keycard-onboarding-recovery-phrase-confirm-word1
:keycard-onboarding-recovery-phrase-confirm-word2
:keycard-recovery-enter-mnemonic
:keycard-recovery-intro
:keycard-recovery-start
:keycard-recovery-pair
@ -46,7 +50,12 @@
:screens (cond-> [:login
:progress
:create-multiaccount
:recover
:recover-multiaccount
:recover-multiaccount-enter-phrase
:recover-multiaccount-select-storage
:recover-multiaccount-enter-password
:recover-multiaccount-confirm-password
:recover-multiaccount-success
:multiaccounts]
config/hardwallet-enabled?
@ -85,7 +94,6 @@
:keycard-onboarding-recovery-phrase
:keycard-onboarding-recovery-phrase-confirm-word1
:keycard-onboarding-recovery-phrase-confirm-word2
:keycard-recovery-enter-mnemonic
:keycard-recovery-intro
:keycard-recovery-start
:keycard-recovery-pair

View File

@ -3,7 +3,7 @@
[status-im.ui.screens.about-app.views :as about-app]
[status-im.ui.screens.multiaccounts.create.views :as multiaccounts.create]
[status-im.ui.screens.multiaccounts.login.views :as login]
[status-im.ui.screens.multiaccounts.recover.views :as recover]
[status-im.ui.screens.multiaccounts.recover.views :as multiaccounts.recover]
[status-im.ui.screens.multiaccounts.views :as multiaccounts]
[status-im.ui.screens.add-new.new-chat.views :as new-chat]
[status-im.ui.screens.add-new.new-public-chat.view :as new-public-chat]
@ -71,7 +71,12 @@
{:login login/login
:progress progress/progress
:create-multiaccount multiaccounts.create/create-multiaccount
:recover recover/recover
:recover-multiaccount multiaccounts.recover/recover
:recover-multiaccount-enter-phrase multiaccounts.recover/enter-phrase
:recover-multiaccount-select-storage multiaccounts.recover/select-storage
:recover-multiaccount-enter-password multiaccounts.recover/enter-password
:recover-multiaccount-confirm-password multiaccounts.recover/confirm-password
:recover-multiaccount-success multiaccounts.recover/success
:multiaccounts multiaccounts/multiaccounts
:intro intro/intro
:intro-wizard intro/wizard
@ -94,7 +99,6 @@
:keycard-onboarding-recovery-phrase keycard.onboarding/recovery-phrase
:keycard-onboarding-recovery-phrase-confirm-word1 keycard.onboarding/recovery-phrase-confirm-word
:keycard-onboarding-recovery-phrase-confirm-word2 keycard.onboarding/recovery-phrase-confirm-word
:keycard-recovery-enter-mnemonic keycard.onboarding/enter-mnemonic
:keycard-pairing keycard/pairing
:keycard-nfc-on keycard/nfc-on
:keycard-connection-lost keycard/connection-lost

View File

@ -18,6 +18,7 @@
[status-im.ui.screens.routing.core :as routing]
[status-im.ui.screens.signing.views :as signing]
[status-im.ui.screens.popover.views :as popover]
[status-im.ui.screens.multiaccounts.recover.views :as recover.views]
[status-im.utils.dimensions :as dimensions]
status-im.ui.screens.wallet.collectibles.etheremon.views
status-im.ui.screens.wallet.collectibles.cryptostrikers.views
@ -59,7 +60,10 @@
(merge home.sheet/private-chat-actions)
(= view :group-chat-actions)
(merge home.sheet/group-chat-actions))]
(merge home.sheet/group-chat-actions)
(= view :recover-sheet)
(merge recover.views/bottom-sheet))]
[bottom-sheet/bottom-sheet opts])))

View File

@ -6,7 +6,21 @@ class PassphraseInput(BaseEditBox):
def __init__(self, driver):
super(PassphraseInput, self).__init__(driver)
self.locator = self.Locator.accessibility_id("enter-12-words")
self.locator = self.Locator.xpath_selector("//android.widget.EditText")
class EnterSeedPhraseButton(BaseButton):
def __init__(self, driver):
super(EnterSeedPhraseButton, self).__init__(driver)
self.locator = self.Locator.accessibility_id("enter-seed-phrase-button")
class ReencryptYourKeyButton(BaseButton):
def __init__(self, driver):
super(ReencryptYourKeyButton, self).__init__(driver)
self.locator = self.Locator.xpath_selector("//android.widget.TextView[@text='Re-encrypt your key']")
class ConfirmRecoverAccess(BaseButton):
@ -72,7 +86,9 @@ class RecoverAccessView(SignInView):
self.driver = driver
self.passphrase_input = PassphraseInput(self.driver)
self.enter_seed_phrase_button = EnterSeedPhraseButton(self.driver)
self.confirm_recover_access = ConfirmRecoverAccess(self.driver)
self.reencrypt_your_key_button = ReencryptYourKeyButton(self.driver)
self.warnings = Warnings(self.driver)
self.confirm_phrase_button = ConfirmPhraseButton(self.driver)
self.cancel_button = CancelPhraseButton(self.driver)

View File

@ -34,15 +34,15 @@ class RecoverAccountPasswordInput(BaseEditBox):
class CreatePasswordInput(BaseEditBox):
def __init__(self, driver):
super(CreatePasswordInput, self).__init__(driver)
self.locator = self.Locator.xpath_selector("//android.widget.TextView[@text='Create a password']"
"/following-sibling::android.widget.EditText")
self.locator = self.Locator.xpath_selector("//android.widget.TextView[@text='Create a password']/.."
"//android.widget.EditText")
class ConfirmYourPasswordInput(BaseEditBox):
def __init__(self, driver):
super(ConfirmYourPasswordInput, self).__init__(driver)
self.locator = self.Locator.xpath_selector("//android.widget.TextView[@text='Confirm your password']"
"/following-sibling::android.widget.EditText")
self.locator = self.Locator.xpath_selector("//android.widget.TextView[@text='Confirm your password']/.."
"//android.widget.EditText")
class SignInButton(BaseButton):
@ -198,11 +198,16 @@ class SignInView(BaseView):
recover_access_view = self.add_existing_multiaccount_button.click()
else:
recover_access_view = self.access_key_button.click()
recover_access_view.enter_seed_phrase_button.click()
recover_access_view.passphrase_input.click()
recover_access_view.passphrase_input.set_value(passphrase)
recover_access_view.recover_account_password_input.click()
recover_access_view.recover_account_password_input.set_value(password)
recover_access_view.sign_in_button.click_until_presence_of_element(recover_access_view.home_button)
recover_access_view.next_button.click()
recover_access_view.reencrypt_your_key_button.click()
recover_access_view.next_button.click()
recover_access_view.create_password_input.set_value(password)
recover_access_view.next_button.click()
recover_access_view.confirm_your_password_input.set_value(password)
recover_access_view.next_button.click_until_presence_of_element(recover_access_view.home_button)
return self.get_home_view()
def sign_in(self, password=common_password):

View File

@ -1,16 +1,22 @@
(ns status-im.test.ethereum.mnemonic
(:require [cljs.test :refer-macros [deftest is testing]]
[status-im.ethereum.mnemonic :as mnemonic]))
[status-im.ethereum.mnemonic :as mnemonic]
[clojure.string :as string]))
(deftest valid-length?
(is (not (mnemonic/valid-length? "rate rate")))
(is (not (mnemonic/valid-length? (string/join " " (repeat 13 "rate")))))
(is (not (mnemonic/valid-length? (string/join " " (repeat 16 "rate")))))
(is (mnemonic/valid-length? (string/join " " (repeat 12 "rate"))))
(is (mnemonic/valid-length? (string/join " " (repeat 15 "rate"))))
(is (mnemonic/valid-length? (string/join " " (repeat 18 "rate"))))
(is (mnemonic/valid-length? (string/join " " (repeat 21 "rate"))))
(is (mnemonic/valid-length? (string/join " " (repeat 24 "rate")))))
(deftest valid-words?
(is (not (mnemonic/valid-words? ["rate" "rate"])))
(is (not (mnemonic/valid-words? ["rate" "rate" "rate" "rate" "rate" "rate" "rate" "rate" "rate" "rate" "rate" "rate?"])))
(is (mnemonic/valid-words? ["rate" "rate" "rate" "rate" "rate" "rate" "rate" "rate" "rate" "rate" "rate" "rate"])))
(deftest valid-phrase
(is (not (mnemonic/valid-phrase? "rate rate")))
(is (not (mnemonic/valid-phrase? "rate rate rate rate rate rate rate rate rate rate rate rate?")))
(is (mnemonic/valid-phrase? "rate rate rate rate rate rate rate rate rate rate rate rate")))
(is (not (mnemonic/valid-words? "rate! rate")))
(is (not (mnemonic/valid-words? "rate rate rate rate rate rate rate rate rate rate rate rate?")))
(is (mnemonic/valid-words? "rate rate rate rate rate rate rate rate rate rate rate rate")))
(deftest passphrase->words?
(is (= ["one" "two" "three" "for" "five" "six" "seven" "height" "nine" "ten" "eleven" "twelve"]

View File

@ -21,15 +21,15 @@
(is (= :required-field (models/check-phrase-errors nil)))
(is (= :required-field (models/check-phrase-errors " ")))
(is (= :required-field (models/check-phrase-errors " \t\n ")))
(is (= :recovery-phrase-invalid (models/check-phrase-errors "phrase with four words")))
(is (= :recovery-phrase-invalid (models/check-phrase-errors "phrase with five cool words")))
(is (= :recovery-phrase-wrong-length (models/check-phrase-errors "phrase with four words")))
(is (= :recovery-phrase-wrong-length (models/check-phrase-errors "phrase with five cool words")))
(is (nil? (models/check-phrase-errors "monkey monkey monkey monkey monkey monkey monkey monkey monkey monkey monkey monkey")))
(is (nil? (models/check-phrase-errors (string/join " " (repeat 15 "monkey")))))
(is (nil? (models/check-phrase-errors (string/join " " (repeat 18 "monkey")))))
(is (nil? (models/check-phrase-errors (string/join " " (repeat 24 "monkey")))))
(is (= :recovery-phrase-invalid (models/check-phrase-errors (string/join " " (repeat 14 "monkey")))))
(is (= :recovery-phrase-invalid (models/check-phrase-errors (string/join " " (repeat 11 "monkey")))))
(is (= :recovery-phrase-invalid (models/check-phrase-errors (string/join " " (repeat 19 "monkey")))))
(is (= :recovery-phrase-wrong-length (models/check-phrase-errors (string/join " " (repeat 14 "monkey")))))
(is (= :recovery-phrase-wrong-length (models/check-phrase-errors (string/join " " (repeat 11 "monkey")))))
(is (= :recovery-phrase-wrong-length (models/check-phrase-errors (string/join " " (repeat 19 "monkey")))))
(is (= :recovery-phrase-invalid (models/check-phrase-errors "monkey monkey monkey 12345 monkey adf+123 monkey monkey monkey monkey monkey monkey")))
;;NOTE(goranjovic): the following check should be ok because we sanitize extra whitespace
(is (nil? (models/check-phrase-errors " monkey monkey monkey\t monkey monkey monkey monkey monkey monkey monkey monkey monkey \t "))))
@ -42,44 +42,49 @@
;;;; handlers
(deftest set-phrase
(is (= {:db {:multiaccounts/recover {:passphrase "game buzz method pretty olympic fat quit display velvet unveil marine crater"
:passphrase-valid? true}}}
(is (= {:db {:multiaccounts/recover {:passphrase "game buzz method pretty olympic fat quit display velvet unveil marine crater"
:passphrase-error nil
:next-button-disabled? false}}}
(models/set-phrase {:db {}} (security/mask-data "game buzz method pretty olympic fat quit display velvet unveil marine crater"))))
(is (= {:db {:multiaccounts/recover {:passphrase "game buzz method pretty olympic fat quit display velvet unveil marine crater"
:passphrase-valid? true}}}
(is (= {:db {:multiaccounts/recover {:passphrase "game buzz method pretty olympic fat quit display velvet unveil marine crater"
:passphrase-error nil
:next-button-disabled? false}}}
(models/set-phrase {:db {}} (security/mask-data "Game buzz method pretty Olympic fat quit DISPLAY velvet unveil marine crater"))))
(is (= {:db {:multiaccounts/recover {:passphrase "game buzz method pretty zeus fat quit display velvet unveil marine crater"
:passphrase-valid? true}}}
(is (= {:db {:multiaccounts/recover {:passphrase "game buzz method pretty zeus fat quit display velvet unveil marine crater"
:passphrase-error nil
:next-button-disabled? false}}}
(models/set-phrase {:db {}} (security/mask-data "game buzz method pretty zeus fat quit display velvet unveil marine crater"))))
(is (= {:db {:multiaccounts/recover {:passphrase " game\t buzz method pretty olympic fat quit\t display velvet unveil marine crater "
:passphrase-valid? true}}}
(is (= {:db {:multiaccounts/recover {:passphrase " game\t buzz method pretty olympic fat quit\t display velvet unveil marine crater "
:passphrase-error nil
:next-button-disabled? false}}}
(models/set-phrase {:db {}} (security/mask-data " game\t buzz method pretty olympic fat quit\t display velvet unveil marine crater "))))
(is (= {:db {:multiaccounts/recover {:passphrase "game buzz method pretty 1234 fat quit display velvet unveil marine crater"
:passphrase-valid? false}}}
(is (= {:db {:multiaccounts/recover {:passphrase "game buzz method pretty 1234 fat quit display velvet unveil marine crater"
:passphrase-error nil
:next-button-disabled? false}}}
(models/set-phrase {:db {}} (security/mask-data "game buzz method pretty 1234 fat quit display velvet unveil marine crater")))))
(deftest validate-phrase
(is (= {:db {:multiaccounts/recover {:passphrase-error nil
:passphrase-warning nil
:passphrase "game buzz method pretty olympic fat quit display velvet unveil marine crater"}}}
(models/validate-phrase {:db {:multiaccounts/recover {:passphrase "game buzz method pretty olympic fat quit display velvet unveil marine crater"}}})))
(is (= {:db {:multiaccounts/recover {:passphrase-error nil
:passphrase-warning :recovery-phrase-unknown-words
:passphrase "game buzz method pretty zeus fat quit display velvet unveil marine crater"}}}
(is (= {:db {:multiaccounts/recover {:passphrase-error :recovery-phrase-unknown-words
:passphrase "game buzz method pretty zeus fat quit display velvet unveil marine crater"}}}
(models/validate-phrase {:db {:multiaccounts/recover {:passphrase "game buzz method pretty zeus fat quit display velvet unveil marine crater"}}})))
(is (= {:db {:multiaccounts/recover {:passphrase-error :recovery-phrase-invalid
:passphrase-warning :recovery-phrase-unknown-words
:passphrase "game buzz method pretty 1234 fat quit display velvet unveil marine crater"}}}
(models/validate-phrase {:db {:multiaccounts/recover {:passphrase "game buzz method pretty 1234 fat quit display velvet unveil marine crater"}}}))))
(deftest set-password
(is (= {:db {:multiaccounts/recover {:password " "
:password-error nil
:password-valid? false}}}
(models/set-password {:db {}} (security/mask-data " "))))
(is (= {:db {:multiaccounts/recover {:password "abc"
:password-error nil
:password-valid? false}}}
(models/set-password {:db {}} (security/mask-data "abc"))))
(is (= {:db {:multiaccounts/recover {:password "thisisapaswoord"
:password-error nil
:password-valid? true}}}
(models/set-password {:db {}} (security/mask-data "thisisapaswoord")))))
@ -99,18 +104,15 @@
:db {:multiaccounts/recover
{:passphrase "game buzz method pretty zeus fat quit display velvet unveil marine crater"
:password "thisisapaswoord"}}})]
(is (contains? new-cofx :node/start))
(is (= "random" (get-in new-cofx [:db :multiaccounts/new-installation-id])))
(is (= :recover-multiaccount (get-in new-cofx [:db :node/on-ready])))))
(is (not (nil? (get new-cofx :multiaccounts.recover/recover-multiaccount))))))
(deftest recover-multiaccount-with-checks
(let [new-cofx (models/recover-multiaccount-with-checks {:random-guid-generator (constantly "random")
:db {:multiaccounts/recover
{:passphrase "game buzz method pretty olympic fat quit display velvet unveil marine crater"
:password "thisisapaswoord"}}})]
(is (contains? new-cofx :node/start))
(is (= "random" (get-in new-cofx [:db :multiaccounts/new-installation-id])))
(is (= :recover-multiaccount (get-in new-cofx [:db :node/on-ready]))))
(is (= "random" (get-in new-cofx [:db :multiaccounts/new-installation-id]))))
(let [new-cofx (models/recover-multiaccount-with-checks {:random-guid-generator (constantly "random")
:db {:multiaccounts/recover
{:passphrase "game buzz method pretty zeus fat quit display velvet unveil marine crater"

View File

@ -302,7 +302,6 @@
"save-password": "Save password",
"submit": "Submit",
"recover-key": "Recover key",
"processing": "Processing...",
"currency-display-name-kes": "Kenyan Shilling",
"view-etheremon": "View in Etheremon",
"wallet-transaction-fee-details": "Gas limit caps the units of gas spent on the transaction. Gas price sets the price per unit of gas. Increasing gas price can make your transaction faster.",
@ -736,6 +735,7 @@
"browser-not-secure": "Connection is not secure! Do not sign transactions or send personal data on this site.",
"welcome-to-status-description": "Here you can chat with people in a secure private chat, browse and interact with DApps.",
"recovery-phrase-invalid": "Recovery phrase is invalid",
"recovery-phrase-wrong-length": "Please make sure the phrase you enter has 12, 15, 18, 21, or 24 words.",
"currency-display-name-cny": "China Yuan Renminbi",
"clear-history-confirmation-content": "Are you sure you want to clear this chat history?",
"mailserver-reconnect": "Could not connect to mailserver. Tap to reconnect",
@ -1269,5 +1269,16 @@
"account-color": "Account color",
"to-encrypt-enter-password": "To encrypt the account please enter your password",
"accounts": "Accounts",
"add-account-incorrect-password": "Password seems to be incorrect. Enter the password you use to unlock the app."
"add-account-incorrect-password": "Password seems to be incorrect. Enter the password you use to unlock the app.",
"remember-me": "Remember me",
"enter-seed-phrase": "Enter Seed phrase",
"recover-with-keycard": "Recover with Keycard",
"multiaccounts-recover-enter-phrase-title": "Enter your seed phrase",
"multiaccounts-recover-enter-phrase-text": "Enter 12, 15, 18, 21 or 24 words.\nSeperate words by a single space.",
"words-n": {
"one": "1 word",
"other": "{{count}} words"
},
"re-encrypt-key": "Re-encrypt your key",
"recovery-success-text": "You will have to create a new code or password to re-encrypt your key"
}