multiaccounts refactoring S3, refactor keychain and touchid, move and refactor create/recover/login profile (#16448)

* multiaccounts refactoring S3 E1, refactor keychain and touchid,simplify app init flow, refactor biometric flow
* S3 E2 move and refactor create/recover/login methods
This commit is contained in:
flexsurfer 2023-07-06 11:25:57 +02:00 committed by GitHub
parent 229a806f12
commit 4decba8d00
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
51 changed files with 818 additions and 2035 deletions

View File

@ -833,6 +833,37 @@ void _SaveAccountAndLogin(const FunctionCallbackInfo<Value>& args) {
} }
void _CreateAccountAndLogin(const FunctionCallbackInfo<Value>& args) {
Isolate* isolate = args.GetIsolate();
Local<Context> context = isolate->GetCurrentContext();
if (args.Length() != 1) {
// Throw an Error that is passed back to JavaScript
isolate->ThrowException(Exception::TypeError(
String::NewFromUtf8Literal(isolate, "Wrong number of arguments for SaveAccountAndLogin")));
return;
}
// Check the argument types
if (!args[0]->IsString()) {
isolate->ThrowException(Exception::TypeError(
String::NewFromUtf8Literal(isolate, "Wrong argument type for 'settingsJSON'")));
return;
}
String::Utf8Value arg0Obj(isolate, args[0]->ToString(context).ToLocalChecked());
char *arg0 = *arg0Obj;
// Call exported Go function, which returns a C string
char *c = CreateAccountAndLogin(arg0);
Local<String> ret = String::NewFromUtf8(isolate, c).ToLocalChecked();
args.GetReturnValue().Set(ret);
delete c;
}
void _GenerateAlias(const FunctionCallbackInfo<Value>& args) { void _GenerateAlias(const FunctionCallbackInfo<Value>& args) {
Isolate* isolate = args.GetIsolate(); Isolate* isolate = args.GetIsolate();
Local<Context> context = isolate->GetCurrentContext(); Local<Context> context = isolate->GetCurrentContext();
@ -1929,6 +1960,7 @@ void init(Local<Object> exports) {
NODE_SET_METHOD(exports, "hashMessage", _HashMessage); NODE_SET_METHOD(exports, "hashMessage", _HashMessage);
NODE_SET_METHOD(exports, "resetChainData", _ResetChainData); NODE_SET_METHOD(exports, "resetChainData", _ResetChainData);
NODE_SET_METHOD(exports, "saveAccountAndLogin", _SaveAccountAndLogin); NODE_SET_METHOD(exports, "saveAccountAndLogin", _SaveAccountAndLogin);
NODE_SET_METHOD(exports, "createAccountAndLogin", _CreateAccountAndLogin);
NODE_SET_METHOD(exports, "generateAlias", _GenerateAlias); NODE_SET_METHOD(exports, "generateAlias", _GenerateAlias);
NODE_SET_METHOD(exports, "validateMnemonic", _ValidateMnemonic); NODE_SET_METHOD(exports, "validateMnemonic", _ValidateMnemonic);
NODE_SET_METHOD(exports, "multiformatSerializePublicKey", _MultiformatSerializePublicKey); NODE_SET_METHOD(exports, "multiformatSerializePublicKey", _MultiformatSerializePublicKey);

View File

@ -0,0 +1,78 @@
(ns react-native.keychain
(:require ["react-native-keychain" :as react-native-keychain]
[clojure.string :as string]
[taoensso.timbre :as log]))
;; ********************************************************************************
;; Storing / Retrieving a user password to/from Keychain
;; ********************************************************************************
;;
;; We are using set/get/reset internet credentials there because they are bound
;; to an address (`server`) property.
(defn enum-val
[enum-name value-name]
(get-in (js->clj ^js react-native-keychain) [enum-name value-name]))
;; We need a more strict access mode for keychain entries that save user password.
;; iOS
;; see this article for more details:
;; https://developer.apple.com/documentation/security/keychain_services/keychain_items/restricting_keychain_item_accessibility?language=objc
(def keychain-restricted-availability
;; From Apple's documentation:
;; > The kSecAttrAccessible attribute enables you to control item availability
;; > relative to the lock state of the device.
;; > It also lets you specify eligibility for restoration to a new device.
;; > If the attribute ends with the string ThisDeviceOnly,
;; > the item can be restored to the same device that created a backup,
;; > but it isnt migrated when restoring another devices backup data.
;; > ...
;; > For extremely sensitive data
;; > THAT YOU NEVER WANT STORED IN iCloud,
;; > you might choose kSecAttrAccessibleWhenPasscodeSetThisDeviceOnly.
;; That is exactly what we use there.
;; Note that the password won't be stored if the device isn't locked by a passcode.
#js {:accessible (enum-val "ACCESSIBLE" "WHEN_PASSCODE_SET_THIS_DEVICE_ONLY")})
(def keychain-secure-hardware
;; (Android) Requires storing the encryption key for the entry in secure hardware
;; or StrongBox (see https://developer.android.com/training/articles/keystore#ExtractionPrevention)
"SECURE_HARDWARE")
;; Android only
(defn secure-hardware-available?
[callback]
(-> (.getSecurityLevel ^js react-native-keychain)
(.then (fn [level] (callback (= level keychain-secure-hardware))))))
;; iOS only
(defn device-encrypted?
[callback]
(-> (.canImplyAuthentication
^js react-native-keychain
(clj->js
{:authenticationType
(enum-val "ACCESS_CONTROL" "BIOMETRY_ANY_OR_DEVICE_PASSCODE")}))
(.then callback)))
(defn save-credentials
"Stores the credentials for the address to the Keychain"
[server username password callback]
(-> (.setInternetCredentials ^js react-native-keychain
(string/lower-case server)
username
password
keychain-secure-hardware
keychain-restricted-availability)
(.then callback)))
(defn get-credentials
"Gets the credentials for a specified server from the Keychain"
[server callback]
(-> (.getInternetCredentials ^js react-native-keychain (string/lower-case server))
(.then callback)))
(defn reset-credentials
[server]
(-> (.resetInternetCredentials ^js react-native-keychain (string/lower-case server))
(.then #(when-not % (log/error (str "Error while clearing saved password."))))))

View File

@ -0,0 +1,19 @@
(ns react-native.touch-id
(:require ["react-native-touch-id" :default touchid]))
;; currently, for android, react-native-touch-id
;; is not returning supported biometric type
;; defaulting to :fingerprint
(def android-default-support :fingerprint)
(defn get-supported-type
[callback]
(-> (.isSupported ^js touchid)
(.then #(callback (or (keyword %) android-default-support)))
(.catch #(callback nil))))
(defn authenticate
[{:keys [on-success on-fail reason options]}]
(-> (.authenticate ^js touchid reason (clj->js options))
(.then #(when on-success (on-success)))
(.catch #(when on-fail (on-fail (aget % "code"))))))

View File

@ -23,7 +23,6 @@
status-im.log-level.core status-im.log-level.core
status-im.mailserver.constants status-im.mailserver.constants
[status-im.mailserver.core :as mailserver] [status-im.mailserver.core :as mailserver]
[status-im.multiaccounts.biometric.core :as biometric]
status-im.multiaccounts.login.core status-im.multiaccounts.login.core
status-im.multiaccounts.logout.core status-im.multiaccounts.logout.core
[status-im.multiaccounts.model :as multiaccounts.model] [status-im.multiaccounts.model :as multiaccounts.model]
@ -63,7 +62,8 @@
[react-native.platform :as platform] [react-native.platform :as platform]
status-im2.contexts.chat.home.events status-im2.contexts.chat.home.events
status-im2.contexts.communities.home.events status-im2.contexts.communities.home.events
status-im.ui.components.invite.events)) status-im.ui.components.invite.events
[status-im2.common.biometric.events :as biometric]))
(re-frame/reg-fx (re-frame/reg-fx
:dismiss-keyboard :dismiss-keyboard
@ -124,33 +124,26 @@
[(get-in db [:profile/profile :appearance]) [(get-in db [:profile/profile :appearance])
(:view-id db) true]}))) (:view-id db) true]})))
(def authentication-options (defn- on-biometric-auth-fail
{:reason (i18n/label :t/biometric-auth-reason-login)}) [{:keys [code]}]
(if (= code "USER_FALLBACK")
(defn- on-biometric-auth-result
[{:keys [bioauth-success bioauth-code bioauth-message]}]
(when-not bioauth-success
(if (= bioauth-code "USER_FALLBACK")
(re-frame/dispatch [:multiaccounts.logout.ui/logout-confirmed]) (re-frame/dispatch [:multiaccounts.logout.ui/logout-confirmed])
(utils/show-confirmation (utils/show-confirmation
{:title (i18n/label :t/biometric-auth-confirm-title) {:title (i18n/label :t/biometric-auth-confirm-title)
:content (or bioauth-message (i18n/label :t/biometric-auth-confirm-message)) :content (i18n/label :t/biometric-auth-confirm-message)
:confirm-button-text (i18n/label :t/biometric-auth-confirm-try-again) :confirm-button-text (i18n/label :t/biometric-auth-confirm-try-again)
:cancel-button-text (i18n/label :t/biometric-auth-confirm-logout) :cancel-button-text (i18n/label :t/biometric-auth-confirm-logout)
:on-accept #(biometric/authenticate nil :on-accept #(biometric/authenticate nil {:on-fail on-biometric-auth-fail})
on-biometric-auth-result :on-cancel #(re-frame/dispatch [:multiaccounts.logout.ui/logout-confirmed])})))
authentication-options)
:on-cancel #(re-frame/dispatch [:multiaccounts.logout.ui/logout-confirmed])}))))
(rf/defn on-return-from-background (rf/defn on-return-from-background
[{:keys [db now] :as cofx}] [{:keys [db now] :as cofx}]
(let [new-account? (get db :onboarding-2/new-account?) (let [new-account? (get db :onboarding-2/new-account?)
app-in-background-since (get db :app-in-background-since) app-in-background-since (get db :app-in-background-since)
signed-up? (get-in db [:profile/profile :signed-up?]) signed-up? (get-in db [:profile/profile :signed-up?])
biometric-auth? (= (:auth-method db) "biometric")
requires-bio-auth (and requires-bio-auth (and
signed-up? signed-up?
biometric-auth? (= (:auth-method db) "biometric")
(some? app-in-background-since) (some? app-in-background-since)
(>= (- now app-in-background-since) (>= (- now app-in-background-since)
constants/ms-in-bg-for-require-bioauth))] constants/ms-in-bg-for-require-bioauth))]
@ -163,7 +156,7 @@
#(when-let [chat-id (:current-chat-id db)] #(when-let [chat-id (:current-chat-id db)]
{:dispatch [:chat/mark-all-as-read chat-id]}) {:dispatch [:chat/mark-all-as-read chat-id]})
#(when requires-bio-auth #(when requires-bio-auth
(biometric/authenticate % on-biometric-auth-result authentication-options))))) (biometric/authenticate % {:on-fail on-biometric-auth-fail})))))
(rf/defn on-going-in-background (rf/defn on-going-in-background
[{:keys [db now]}] [{:keys [db now]}]
@ -274,53 +267,3 @@
cofx cofx
(navigation/open-modal :buy-crypto nil) (navigation/open-modal :buy-crypto nil)
(wallet/keep-watching-history))) (wallet/keep-watching-history)))
;; Information Box
(def closable-information-boxes
"[{:id information box id
:global? true/false (close information box across all profiles)}]"
[])
(defn information-box-id-hash
[id public-key global?]
(if global?
(hash id)
(hash (str public-key id))))
(rf/defn close-information-box
{:events [:close-information-box]}
[{:keys [db]} id global?]
(let [public-key (get-in db [:profile/profile :public-key])
hash (information-box-id-hash id public-key global?)]
{::async-storage/set! {hash true}
:db (assoc-in db [:information-box-states id] true)}))
(rf/defn information-box-states-loaded
{:events [:information-box-states-loaded]}
[{:keys [db]} hashes states]
{:db (assoc db
:information-box-states
(reduce
(fn [acc [id hash]]
(assoc acc id (get states hash)))
{}
hashes))})
(rf/defn load-information-box-states
{:events [:load-information-box-states]}
[{:keys [db]}]
(let [public-key (get-in db [:profile/profile :public-key])
{:keys [keys hashes]} (reduce (fn [acc {:keys [id global?]}]
(let [hash (information-box-id-hash
id
public-key
global?)]
(-> acc
(assoc-in [:hashes id] hash)
(update :keys #(conj % hash)))))
{}
closable-information-boxes)]
{::async-storage/get {:keys keys
:cb #(re-frame/dispatch
[:information-box-states-loaded hashes %])}}))

View File

@ -26,13 +26,10 @@
[] []
(rf/dispatch [:app-started])) (rf/dispatch [:app-started]))
(defn generate-and-derive-addresses!
[]
(rf/dispatch [:generate-and-derive-addresses]))
(defn create-multiaccount! (defn create-multiaccount!
[] []
(rf/dispatch [:create-multiaccount password])) (rf/dispatch [:profile.create/create-and-login
{:display-name account-name :password password :color "blue"}]))
(defn assert-app-initialized (defn assert-app-initialized
[] []
@ -82,15 +79,12 @@
(initialize-app!) ; initialize app (initialize-app!) ; initialize app
(rf-test/wait-for (rf-test/wait-for
[:profile/get-profiles-overview-success] [:profile/get-profiles-overview-success]
(generate-and-derive-addresses!) ; generate 5 new keys
(rf-test/wait-for
[:multiaccount-generate-and-derive-addresses-success] ; wait for the keys
(create-multiaccount!) ; create a multiaccount (create-multiaccount!) ; create a multiaccount
(rf-test/wait-for ; wait for login (rf-test/wait-for ; wait for login
[::transport/messenger-started] [::transport/messenger-started]
(assert-messenger-started) (assert-messenger-started)
(logout!) (logout!)
(rf-test/wait-for [::logout/logout-method])))))) (rf-test/wait-for [::logout/logout-method])))))
(deftest create-community-test (deftest create-community-test
(log/info "====== create-community-test ==================") (log/info "====== create-community-test ==================")
@ -98,9 +92,6 @@
(initialize-app!) ; initialize app (initialize-app!) ; initialize app
(rf-test/wait-for (rf-test/wait-for
[:profile/get-profiles-overview-success] [:profile/get-profiles-overview-success]
(generate-and-derive-addresses!) ; generate 5 new keys
(rf-test/wait-for
[:multiaccount-generate-and-derive-addresses-success]
(create-multiaccount!) ; create a multiaccount (create-multiaccount!) ; create a multiaccount
(rf-test/wait-for ; wait for login (rf-test/wait-for ; wait for login
[::transport/messenger-started] [::transport/messenger-started]
@ -113,7 +104,7 @@
[:status-im.communities.core/community-created] [:status-im.communities.core/community-created]
(assert-community-created) (assert-community-created)
(logout!) (logout!)
(rf-test/wait-for [::logout/logout-method]))))))) (rf-test/wait-for [::logout/logout-method]))))))
(deftest create-wallet-account-test (deftest create-wallet-account-test
(log/info "====== create-wallet-account-test ==================") (log/info "====== create-wallet-account-test ==================")
@ -121,9 +112,6 @@
(initialize-app!) (initialize-app!)
(rf-test/wait-for (rf-test/wait-for
[:profile/get-profiles-overview-success] [:profile/get-profiles-overview-success]
(generate-and-derive-addresses!) ; generate 5 new keys
(rf-test/wait-for
[:multiaccount-generate-and-derive-addresses-success]
(create-multiaccount!) ; create a multiaccount (create-multiaccount!) ; create a multiaccount
(rf-test/wait-for ; wait for login (rf-test/wait-for ; wait for login
[::transport/messenger-started] [::transport/messenger-started]
@ -133,7 +121,7 @@
[:wallet.accounts/account-stored] [:wallet.accounts/account-stored]
(assert-new-account-created) ; assert account was created (assert-new-account-created) ; assert account was created
(logout!) (logout!)
(rf-test/wait-for [::logout/logout-method]))))))) (rf-test/wait-for [::logout/logout-method]))))))
(deftest back-up-seed-phrase-test (deftest back-up-seed-phrase-test
(log/info "========= back-up-seed-phrase-test ==================") (log/info "========= back-up-seed-phrase-test ==================")
@ -141,9 +129,6 @@
(initialize-app!) (initialize-app!)
(rf-test/wait-for (rf-test/wait-for
[:profile/get-profiles-overview-success] [:profile/get-profiles-overview-success]
(generate-and-derive-addresses!)
(rf-test/wait-for
[:multiaccount-generate-and-derive-addresses-success]
(create-multiaccount!) (create-multiaccount!)
(rf-test/wait-for (rf-test/wait-for
[::transport/messenger-started] [::transport/messenger-started]
@ -164,7 +149,7 @@
[:my-profile/finish-success] [:my-profile/finish-success]
(is (nil? @(rf/subscribe [:mnemonic]))) ; assert seed phrase has been removed (is (nil? @(rf/subscribe [:mnemonic]))) ; assert seed phrase has been removed
(logout!) (logout!)
(rf-test/wait-for [::logout/logout-method])))))))) (rf-test/wait-for [::logout/logout-method])))))))
(def multiaccount-name "Narrow Frail Lemming") (def multiaccount-name "Narrow Frail Lemming")
(def multiaccount-mnemonic (def multiaccount-mnemonic
@ -180,9 +165,6 @@
(initialize-app!) (initialize-app!)
(rf-test/wait-for (rf-test/wait-for
[:profile/get-profiles-overview-success] [:profile/get-profiles-overview-success]
(generate-and-derive-addresses!)
(rf-test/wait-for
[:multiaccount-generate-and-derive-addresses-success] ; wait for the keys
(create-multiaccount!) (create-multiaccount!)
(rf-test/wait-for (rf-test/wait-for
[::transport/messenger-started] [::transport/messenger-started]
@ -193,7 +175,7 @@
(rf/dispatch-sync [:chat/navigate-to-chat chat-id]) (rf/dispatch-sync [:chat/navigate-to-chat chat-id])
(is (= chat-id @(rf/subscribe [:chats/current-chat-id]))) (is (= chat-id @(rf/subscribe [:chats/current-chat-id])))
(logout!) (logout!)
(rf-test/wait-for [::logout/logout-method]))))))) (rf-test/wait-for [::logout/logout-method]))))))
(deftest delete-chat-test (deftest delete-chat-test
(log/info "========= delete-chat-test ==================") (log/info "========= delete-chat-test ==================")
@ -201,9 +183,6 @@
(initialize-app!) (initialize-app!)
(rf-test/wait-for (rf-test/wait-for
[:profile/get-profiles-overview-success] [:profile/get-profiles-overview-success]
(generate-and-derive-addresses!)
(rf-test/wait-for
[:multiaccount-generate-and-derive-addresses-success] ; wait for the keys
(create-multiaccount!) (create-multiaccount!)
(rf-test/wait-for (rf-test/wait-for
[::transport/messenger-started] [::transport/messenger-started]
@ -217,7 +196,7 @@
(rf/dispatch-sync [:chat.ui/show-remove-confirmation chat-id]) (rf/dispatch-sync [:chat.ui/show-remove-confirmation chat-id])
(rf/dispatch-sync [:chat.ui/remove-chat chat-id]) (rf/dispatch-sync [:chat.ui/remove-chat chat-id])
(logout!) (logout!)
(rf-test/wait-for [::logout/logout-method]))))))) (rf-test/wait-for [::logout/logout-method]))))))
(deftest mute-chat-test (deftest mute-chat-test
(log/info "========= mute-chat-test ==================") (log/info "========= mute-chat-test ==================")
@ -225,9 +204,6 @@
(initialize-app!) (initialize-app!)
(rf-test/wait-for (rf-test/wait-for
[:profile/get-profiles-overview-success] [:profile/get-profiles-overview-success]
(generate-and-derive-addresses!)
(rf-test/wait-for
[:multiaccount-generate-and-derive-addresses-success] ; wait for the keys
(create-multiaccount!) (create-multiaccount!)
(rf-test/wait-for (rf-test/wait-for
[::transport/messenger-started] [::transport/messenger-started]
@ -247,7 +223,7 @@
[:chat/mute-successfully] [:chat/mute-successfully]
(is (not @(rf/subscribe [:chats/muted chat-id]))) (is (not @(rf/subscribe [:chats/muted chat-id])))
(logout!) (logout!)
(rf-test/wait-for [::logout/logout-method]))))))))) (rf-test/wait-for [::logout/logout-method]))))))))
(deftest add-contact-test (deftest add-contact-test
(log/info "========= add-contact-test ==================") (log/info "========= add-contact-test ==================")
@ -260,9 +236,6 @@
(initialize-app!) (initialize-app!)
(rf-test/wait-for (rf-test/wait-for
[:profile/get-profiles-overview-success] [:profile/get-profiles-overview-success]
(generate-and-derive-addresses!)
(rf-test/wait-for
[:multiaccount-generate-and-derive-addresses-success]
(create-multiaccount!) (create-multiaccount!)
(rf-test/wait-for (rf-test/wait-for
[::transport/messenger-started] [::transport/messenger-started]
@ -283,4 +256,4 @@
(let [contact @(rf/subscribe [:contacts/current-contact])] (let [contact @(rf/subscribe [:contacts/current-contact])]
(is (= three-words-name (:primary-name contact)))) (is (= three-words-name (:primary-name contact))))
(logout!) (logout!)
(rf-test/wait-for [::logout/logout-method])))))))))) (rf-test/wait-for [::logout/logout-method])))))))))

View File

@ -14,7 +14,6 @@
[status-im.keycard.sign :as sign] [status-im.keycard.sign :as sign]
status-im.keycard.unpair status-im.keycard.unpair
[status-im.keycard.wallet :as wallet] [status-im.keycard.wallet :as wallet]
[status-im.multiaccounts.recover.core :as multiaccounts.recover]
[status-im.multiaccounts.update.core :as multiaccounts.update] [status-im.multiaccounts.update.core :as multiaccounts.update]
[utils.re-frame :as rf] [utils.re-frame :as rf]
[utils.datetime :as datetime] [utils.datetime :as datetime]
@ -589,7 +588,8 @@
(when (and (= card-state :profile/profile) (when (and (= card-state :profile/profile)
(= flow :import)) (= flow :import))
(if (common/find-multiaccount-by-key-uid db key-uid) (if (common/find-multiaccount-by-key-uid db key-uid)
(multiaccounts.recover/show-existing-multiaccount-alert key-uid) ;; reimplement
;;(multiaccounts.recover/show-existing-multiaccount-alert key-uid)
(if paired? (if paired?
(load-recovery-pin-screen) (load-recovery-pin-screen)
(recovery/load-pair-screen)))) (recovery/load-pair-screen))))
@ -681,3 +681,11 @@
{:events [:keycard.callback/stop-nfc-failure]} {:events [:keycard.callback/stop-nfc-failure]}
[{:keys [db]} _] [{:keys [db]} _]
(log/debug "[keycard] nfc failed stopping")) ;; leave current value on :nfc-running (log/debug "[keycard] nfc failed stopping")) ;; leave current value on :nfc-running
(rf/defn init
{:events [:keycard/init]}
[_]
{:keycard/register-card-events nil
:keycard/check-nfc-support nil
:keycard/check-nfc-enabled nil
:keycard/retrieve-pairings nil})

View File

@ -139,7 +139,6 @@
{:db (-> db {:db (-> db
(update :keycard dissoc :flow) (update :keycard dissoc :flow)
(dissoc :restored-account?))} (dissoc :restored-account?))}
(multiaccounts.create/prepare-intro-wizard)
(if (pos? (count accs)) (if (pos? (count accs))
(navigation/navigate-to :get-your-keys nil) (navigation/navigate-to :get-your-keys nil)
(navigation/set-stack-root :onboarding [:get-your-keys]))))) (navigation/set-stack-root :onboarding [:get-your-keys])))))

View File

@ -1,229 +0,0 @@
(ns status-im.multiaccounts.biometric.core
(:require ["react-native-touch-id" :default touchid]
[quo.design-system.colors :as colors]
[re-frame.core :as re-frame]
[utils.i18n :as i18n]
[native-module.core :as native-module]
[status-im.popover.core :as popover]
[utils.re-frame :as rf]
[status-im.utils.keychain.core :as keychain]
[status-im.utils.platform :as platform]
[taoensso.timbre :as log]))
;; currently, for android, react-native-touch-id
;; is not returning supported biometric type
;; defaulting to :fingerprint
(def android-default-support :fingerprint)
;;; android blacklist based on device info:
(def deviceinfo (native-module/get-device-model-info))
;; {:model ?
;; :brand "Xiaomi"
;; :build-id "13D15"
;; :device-id "goldfish"
;; more info on https://github.com/react-native-community/react-native-device-info
(def android-device-blacklisted?
(cond
(= (:brand deviceinfo) "bannedbrand") true
:else false))
;; biometric auth config
;; https://github.com/naoufal/react-native-touch-id#authenticatereason-config
(defn- authenticate-options
[ios-fallback-label]
(clj->js (merge
{:unifiedErrors true}
(when platform/ios?
{:passcodeFallback false
:fallbackLabel (or ios-fallback-label "")})
(when platform/android?
{:title (i18n/label :t/biometric-auth-android-title)
:imageColor colors/blue
:imageErrorColor colors/red
:sensorDescription (i18n/label :t/biometric-auth-android-sensor-desc)
:sensorErrorDescription (i18n/label :t/biometric-auth-android-sensor-error-desc)
:cancelText (i18n/label :cancel)}))))
(defn get-label
[supported-biometric-auth]
(case supported-biometric-auth
:fingerprint (i18n/label :t/biometric-fingerprint)
:FaceID (i18n/label :t/biometric-faceid)
(i18n/label :t/biometric-touchid)))
(defn- get-error-message
"must return an error message for the user"
[touchid-error-code]
(cond
;; no message if user canceled or falled back to password
(= touchid-error-code "USER_CANCELED") nil
(= touchid-error-code "USER_FALLBACK") nil
;; add here more specific errors if needed
;; https://github.com/naoufal/react-native-touch-id#unified-errors
:else (i18n/label :t/biometric-auth-error
{:code touchid-error-code})))
(def success-result
{:bioauth-success true})
(defn- generate-error-result
[touchid-error-obj]
(let [code (aget touchid-error-obj "code")]
{:bioauth-success false
:bioauth-code code
:bioauth-message (get-error-message code)}))
(defn- do-get-supported
[callback]
(-> (.isSupported touchid)
(.then #(callback (or (keyword %) android-default-support)))
(.catch #(callback nil))))
(defn get-supported
[callback]
(log/debug "[biometric] get-supported")
(cond platform/ios? (do-get-supported callback)
platform/android? (if android-device-blacklisted?
(callback nil)
(do-get-supported callback))
:else (callback nil)))
(defn authenticate-fx
([cb]
(authenticate-fx cb nil))
([cb {:keys [reason ios-fallback-label]}]
(log/debug "[biometric] authenticate-fx")
(-> (.authenticate touchid reason (authenticate-options ios-fallback-label))
(.then #(cb success-result))
(.catch #(cb (generate-error-result %))))))
(re-frame/reg-fx
:biometric/get-supported-biometric-auth
(fn []
(let [callback #(re-frame/dispatch [:init.callback/get-supported-biometric-auth-success %])]
;;NOTE: if we can't save user password, we can't support biometrics
(keychain/can-save-user-password?
(fn [can-save?]
(if can-save?
(get-supported callback)
(callback nil)))))))
(rf/defn set-supported-biometric-auth
{:events [:init.callback/get-supported-biometric-auth-success]}
[{:keys [db]} supported-biometric-auth]
{:db (assoc db :supported-biometric-auth supported-biometric-auth)})
(rf/defn authenticate
[_ cb options]
{:biometric-auth/authenticate [cb options]})
(re-frame/reg-fx
:biometric-auth/authenticate
(fn [[cb options]]
(authenticate-fx #(cb %) options)))
(re-frame/reg-fx
:biometric/enable-and-save-password
(fn [{:keys [key-uid
masked-password
on-success
on-error]}]
(-> (keychain/save-user-password!
key-uid
masked-password)
(.then
(fn [_]
(keychain/save-auth-method!
key-uid
keychain/auth-method-biometric)))
(.then
(fn [_]
(when on-success
(on-success))))
(.catch (fn [error]
(when on-error
(on-error error)))))))
(rf/defn update-biometric
[{db :db :as cofx} biometric-auth?]
(let [key-uid (or (get-in db [:profile/profile :key-uid])
(get-in db [:profile/login :key-uid]))]
(rf/merge cofx
(keychain/save-auth-method
key-uid
(if biometric-auth?
keychain/auth-method-biometric
keychain/auth-method-none))
#(when-not biometric-auth?
{:keychain/clear-user-password key-uid}))))
(rf/defn biometric-auth-switched
{:events [:multiaccounts.ui/biometric-auth-switched]}
[cofx biometric-auth?]
(if biometric-auth?
(authenticate
cofx
#(re-frame/dispatch [:biometric-init-done %])
{})
(update-biometric cofx false)))
(rf/defn show-message
[cofx bioauth-message bioauth-code]
(let [content (or (when (get #{"NOT_AVAILABLE" "NOT_ENROLLED"} bioauth-code)
(i18n/label :t/grant-face-id-permissions))
bioauth-message)]
(when content
{:utils/show-popup
{:title (i18n/label :t/biometric-auth-login-error-title)
:content content}})))
(rf/defn biometric-init-done
{:events [:biometric-init-done]}
[cofx {:keys [bioauth-success bioauth-message bioauth-code]}]
(if bioauth-success
(if (= keychain/auth-method-password
(get-in cofx [:db :auth-method]))
(update-biometric cofx true)
(popover/show-popover cofx {:view :enable-biometric}))
(show-message cofx bioauth-message bioauth-code)))
(rf/defn biometric-auth
{:events [:biometric-authenticate]}
[cofx]
(authenticate
cofx
#(re-frame/dispatch [:biometric-auth-done %])
{:reason (i18n/label :t/biometric-auth-reason-login)
:ios-fallback-label (i18n/label :t/biometric-auth-login-ios-fallback-label)}))
(rf/defn enable
{:events [:biometric/enable]}
[cofx]
(rf/merge
cofx
(popover/hide-popover)
(authenticate #(re-frame/dispatch [:biometric/setup-done %]) {})))
(rf/defn disable
{:events [:biometric/disable]}
[{:keys [db] :as cofx}]
(rf/merge
cofx
{:db (-> db
(assoc :auth-method keychain/auth-method-none)
(assoc-in [:profile/login :save-password?] false))}
(popover/hide-popover)))
(rf/defn setup-done
{:events [:biometric/setup-done]}
[{:keys [db] :as cofx} {:keys [bioauth-success bioauth-message bioauth-code]}]
(log/debug "[biometric] setup-done"
"bioauth-success" bioauth-success
"bioauth-message" bioauth-message
"bioauth-code" bioauth-code)
(if bioauth-success
{:db (assoc db :auth-method keychain/auth-method-biometric-prepare)}
(show-message cofx bioauth-message bioauth-code)))

View File

@ -41,43 +41,6 @@
(fn [cofx _] (fn [cofx _]
(assoc cofx :signing-phrase (signing-phrase/generate)))) (assoc cofx :signing-phrase (signing-phrase/generate))))
(re-frame/reg-fx
::store-multiaccount
(fn [[id key-uid hashed-password callback]]
(native-module/multiaccount-store-derived
id
key-uid
[constants/path-wallet-root
constants/path-eip1581
constants/path-whisper
constants/path-default-wallet]
hashed-password
callback)))
(rf/defn create-multiaccount
{:events [:create-multiaccount]}
[{:keys [db]} key-code]
(let [{:keys [selected-id]} (:intro-wizard db)]
{::store-multiaccount
[selected-id
(some
(fn [{:keys [id key-uid]}]
(when (= id selected-id)
key-uid))
(get-in db [:intro-wizard :multiaccounts]))
(ethereum/sha3 (security/safe-unmask-data key-code))
(fn [result]
(let [derived-data (normalize-derived-data-keys (types/json->clj result))
public-key (get-in derived-data [constants/path-whisper-keyword :public-key])]
(native-module/gfycat-identicon-async
public-key
(fn [name _]
(let [derived-whisper (derived-data constants/path-whisper-keyword)
derived-data-extended (assoc-in derived-data
[constants/path-whisper-keyword]
(assoc derived-whisper :name name))]
(re-frame/dispatch [::store-multiaccount-success key-code derived-data-extended]))))))]}))
(re-frame/reg-fx (re-frame/reg-fx
:multiaccount-generate-and-derive-addresses :multiaccount-generate-and-derive-addresses
(fn [] (fn []
@ -116,10 +79,6 @@
(dissoc :recovered-account?)) (dissoc :recovered-account?))
:multiaccount-generate-and-derive-addresses nil}) :multiaccount-generate-and-derive-addresses nil})
(rf/defn prepare-intro-wizard
[{:keys [db]}]
{:db (assoc db :intro-wizard {})})
(rf/defn save-multiaccount-and-login-with-keycard (rf/defn save-multiaccount-and-login-with-keycard
[_ args] [_ args]
{:keycard/save-multiaccount-and-login args}) {:keycard/save-multiaccount-and-login args})
@ -255,27 +214,3 @@
settings settings
(node/get-new-config db) (node/get-new-config db)
accounts-data))))) accounts-data)))))
(rf/defn store-multiaccount-success
{:events [::store-multiaccount-success]
:interceptors [(re-frame/inject-cofx :random-guid-generator)
(re-frame/inject-cofx ::get-signing-phrase)]}
[{:keys [db] :as cofx} password derived]
(rf/merge cofx
{:db (dissoc db :intro-wizard)}
(on-multiaccount-created (assoc (let [{:keys [selected-id multiaccounts]} (:intro-wizard db)]
(some #(when (= selected-id (:id %)) %) multiaccounts))
:derived derived
:recovered (get-in db [:intro-wizard :recovering?]))
password
{:save-mnemonic? true})))
(rf/defn on-key-selected
{:events [:intro-wizard/on-key-selected]}
[{:keys [db]} id]
{:db (assoc-in db [:intro-wizard :selected-id] id)})
(rf/defn on-key-storage-selected
{:events [:intro-wizard/on-key-storage-selected]}
[{:keys [db]} storage-type]
{:db (assoc-in db [:intro-wizard :selected-storage-type] storage-type)})

View File

@ -1,21 +0,0 @@
(ns status-im.multiaccounts.key-storage.core
(:require [clojure.string :as string]
[re-frame.core :as re-frame]
[status-im.ethereum.core :as ethereum]
[native-module.core :as native-module]
[status-im.utils.types :as types]
[utils.security.core :as security]))
(re-frame/reg-fx
:key-storage/delete-imported-key
(fn [{:keys [key-uid address password on-success on-error]}]
(let [hashed-pass (ethereum/sha3 (security/safe-unmask-data password))]
(native-module/delete-imported-key
key-uid
(string/lower-case (subs address 2))
hashed-pass
(fn [result]
(let [{:keys [error]} (types/json->clj result)]
(if-not (string/blank? error)
(on-error error)
(on-success))))))))

View File

@ -17,17 +17,14 @@
[status-im.fleet.core :as fleet] [status-im.fleet.core :as fleet]
[utils.i18n :as i18n] [utils.i18n :as i18n]
[status-im.mobile-sync-settings.core :as mobile-network] [status-im.mobile-sync-settings.core :as mobile-network]
[status-im.multiaccounts.biometric.core :as biometric]
[status-im.multiaccounts.core :as multiaccounts] [status-im.multiaccounts.core :as multiaccounts]
[native-module.core :as native-module] [native-module.core :as native-module]
[status-im.notifications.core :as notifications] [status-im.notifications.core :as notifications]
[status-im.popover.core :as popover]
[status-im.signing.eip1559 :as eip1559] [status-im.signing.eip1559 :as eip1559]
[status-im.transport.core :as transport] [status-im.transport.core :as transport]
[status-im.ui.components.react :as react] [status-im.ui.components.react :as react]
[status-im2.config :as config] [status-im2.config :as config]
[utils.re-frame :as rf] [utils.re-frame :as rf]
[status-im.utils.keychain.core :as keychain]
[status-im.utils.mobile-sync :as utils.mobile-sync] [status-im.utils.mobile-sync :as utils.mobile-sync]
[status-im.utils.platform :as platform] [status-im.utils.platform :as platform]
[status-im.utils.types :as types] [status-im.utils.types :as types]
@ -46,7 +43,7 @@
[taoensso.timbre :as log] [taoensso.timbre :as log]
[status-im2.contexts.shell.jump-to.utils :as shell.utils] [status-im2.contexts.shell.jump-to.utils :as shell.utils]
[utils.security.core :as security] [utils.security.core :as security]
[status-im.keycard.common :as keycard.common])) [status-im2.common.keychain.events :as keychain]))
(re-frame/reg-fx (re-frame/reg-fx
::initialize-transactions-management-enabled ::initialize-transactions-management-enabled
@ -56,22 +53,6 @@
:transactions-management-enabled? :transactions-management-enabled?
callback)))) callback))))
(re-frame/reg-fx
::login
(fn [[key-uid _ hashed-password]]
(native-module/login-account {:keyUid key-uid
:password hashed-password
:wakuV2Nameserver "1.1.1.1"
:openseaAPIKey config/opensea-api-key
:poktToken config/POKT_TOKEN
:infuraToken config/INFURA_TOKEN
:alchemyOptimismMainnetToken config/ALCHEMY_OPTIMISM_MAINNET_TOKEN
:alchemyOptimismGoerliToken config/ALCHEMY_OPTIMISM_GOERLI_TOKEN
:alchemyArbitrumMainnetToken config/ALCHEMY_ARBITRUM_MAINNET_TOKEN
:alchemyArbitrumGoerliToken config/ALCHEMY_ARBITRUM_GOERLI_TOKEN})))
(re-frame/reg-fx (re-frame/reg-fx
::export-db ::export-db
(fn [[key-uid account-data hashed-password callback]] (fn [[key-uid account-data hashed-password callback]]
@ -177,27 +158,8 @@
(wallet/request-current-block-update)) (wallet/request-current-block-update))
(prices/update-prices))) (prices/update-prices)))
(rf/defn login-local-paired-user
{:events [:multiaccounts.login/local-paired-user]}
[{:keys [db]}]
(let [{:keys [key-uid name password]} (get-in db [:syncing :profile/profile])]
{::login [key-uid
(types/clj->json {:name name
:key-uid key-uid})
password]}))
(rf/defn login
{:events [:multiaccounts.login.ui/password-input-submitted]}
[{:keys [db]}]
(let [{:keys [key-uid password name]} (:profile/login db)]
{:db (-> db
(assoc-in [:profile/login :processing] true)
(dissoc :intro-wizard :recovered-account?)
(update :keycard dissoc :flow))
::login [key-uid
(types/clj->json {:name name
:key-uid key-uid})
(ethereum/sha3 (security/safe-unmask-data password))]}))
(rf/defn export-db-submitted (rf/defn export-db-submitted
{:events [:multiaccounts.login.ui/export-db-submitted]} {:events [:multiaccounts.login.ui/export-db-submitted]}
@ -392,7 +354,7 @@
(rf/defn get-node-config-callback (rf/defn get-node-config-callback
{:events [::get-node-config-callback]} {:events [::get-node-config-callback]}
[{:keys [db] :as cofx} node-config-json] [{:keys [db]} node-config-json]
(let [node-config (types/json->clj node-config-json)] (let [node-config (types/json->clj node-config-json)]
{:db (assoc-in db {:db (assoc-in db
[:profile/profile :wakuv2-config] [:profile/profile :wakuv2-config]
@ -414,14 +376,11 @@
(get db :onboarding-2/new-account?) (get db :onboarding-2/new-account?)
{:dispatch [:onboarding-2/finalize-setup]} {:dispatch [:onboarding-2/finalize-setup]}
(get db :tos/accepted?) :else
(rf/merge (rf/merge
cofx cofx
(multiaccounts/switch-theme nil :shell-stack) (multiaccounts/switch-theme nil :shell-stack)
(navigation/init-root :shell-stack)) (navigation/init-root :shell-stack)))))
:else
{:dispatch [:init-root :tos]})))
(rf/defn get-settings-callback (rf/defn get-settings-callback
{:events [::get-settings-callback]} {:events [::get-settings-callback]}
@ -448,7 +407,6 @@
#(do (re-frame/dispatch [:chats-list/load-success %]) #(do (re-frame/dispatch [:chats-list/load-success %])
(rf/dispatch [:communities/get-user-requests-to-join]) (rf/dispatch [:communities/get-user-requests-to-join])
(re-frame/dispatch [::get-chats-callback]))}) (re-frame/dispatch [::get-chats-callback]))})
(initialize-wallet-connect)
(get-node-config) (get-node-config)
(communities/fetch) (communities/fetch)
(contract-communities/fetch-contract-communities) (contract-communities/fetch-contract-communities)
@ -530,11 +488,9 @@
(defn get-new-auth-method (defn get-new-auth-method
[auth-method save-password?] [auth-method save-password?]
(when save-password? (when save-password?
(when-not (or (= keychain/auth-method-biometric auth-method) (when-not (= keychain/auth-method-biometric auth-method)
(= keychain/auth-method-password auth-method)) (when (= auth-method keychain/auth-method-biometric-prepare)
(if (= auth-method keychain/auth-method-biometric-prepare) keychain/auth-method-biometric))))
keychain/auth-method-biometric
keychain/auth-method-password))))
(rf/defn login-only-events (rf/defn login-only-events
[{:keys [db] :as cofx} key-uid password save-password?] [{:keys [db] :as cofx} key-uid password save-password?]
@ -549,8 +505,6 @@
[{:method "settings_getSettings" [{:method "settings_getSettings"
:on-success #(re-frame/dispatch [::get-settings-callback %])}]} :on-success #(re-frame/dispatch [::get-settings-callback %])}]}
(notifications/load-notification-preferences) (notifications/load-notification-preferences)
(when save-password?
(keychain/save-user-password key-uid password))
(keychain/save-auth-method key-uid (keychain/save-auth-method key-uid
(or new-auth-method auth-method keychain/auth-method-none))))) (or new-auth-method auth-method keychain/auth-method-none)))))
@ -560,14 +514,13 @@
db db
{:keys [creating?]} (:profile/login db) {:keys [creating?]} (:profile/login db)
first-account? (and creating? (empty? profiles-overview)) first-account? (and creating? (empty? profiles-overview))
tos-accepted? (get db :tos/accepted?)
{:networks/keys [current-network networks]} db {:networks/keys [current-network networks]} db
network-id (str (get-in networks [current-network :config :NetworkId]))] network-id (str (get-in networks [current-network :config :NetworkId]))]
(shell.utils/change-selected-stack-id :communities-stack true nil) (shell.utils/change-selected-stack-id :communities-stack true nil)
(rf/merge cofx (rf/merge cofx
{:db (-> db {:db (-> db
(dissoc :profile/login) (dissoc :profile/login)
(assoc :tos/next-root :enable-notifications :chats/loading? false) (assoc :chats/loading? false)
(assoc-in [:profile/profile :multiaccounts/first-account] (assoc-in [:profile/profile :multiaccounts/first-account]
first-account?)) first-account?))
::get-tokens [network-id wallet-accounts recovered-account?]} ::get-tokens [network-id wallet-accounts recovered-account?]}
@ -586,7 +539,7 @@
(boolean (get-in cofx [:db :keycard :flow]))) (boolean (get-in cofx [:db :keycard :flow])))
(defn on-login-update-db (defn on-login-update-db
[db login-only? now] [db now]
(-> db (-> db
(dissoc :connectivity/ui-status-properties) (dissoc :connectivity/ui-status-properties)
(update :keycard dissoc :from-key-storage-and-migration?) (update :keycard dissoc :from-key-storage-and-migration?)
@ -595,10 +548,6 @@
:card-read-in-progress? :card-read-in-progress?
:pin :pin
:profile/profile) :profile/profile)
(assoc :tos-accept-next-root
(if login-only?
:shell-stack
:onboarding-notification))
(assoc :logged-in-since now) (assoc :logged-in-since now)
(assoc :view-id :home))) (assoc :view-id :home)))
@ -620,7 +569,7 @@
"login-only?" login-only? "login-only?" login-only?
"recovered-account?" recovered-account?) "recovered-account?" recovered-account?)
(rf/merge cofx (rf/merge cofx
{:db (on-login-update-db db login-only? now) {:db (on-login-update-db db now)
:json-rpc/call :json-rpc/call
[{:method "web3_clientVersion" [{:method "web3_clientVersion"
:on-success #(re-frame/dispatch [::initialize-web3-client-version %])}]} :on-success #(re-frame/dispatch [::initialize-web3-client-version %])}]}
@ -636,151 +585,3 @@
(if login-only? (if login-only?
(login-only-events key-uid password save-password?) (login-only-events key-uid password save-password?)
(create-only-events recovered-account?))))) (create-only-events recovered-account?)))))
(rf/defn open-login-callback
{:events [:multiaccounts.login.callback/get-user-password-success]}
[{:keys [db] :as cofx} password]
(let [key-uid (get-in db [:profile/login :key-uid])
keycard-account? (boolean (get-in db
[:profile/profiles-overview
key-uid
:keycard-pairing]))
goto-key-storage? (:goto-key-storage? db)]
(if password
(rf/merge
cofx
{:db (update-in db
[:profile/login]
assoc
:password password
:save-password? true)
:set-root :progress}
login)
(rf/merge
cofx
{:db (dissoc db :goto-key-storage?)}
(when keycard-account?
{:db (-> db
(assoc-in [:keycard :pin :status] nil)
(assoc-in [:keycard :pin :login] []))})
#(if keycard-account?
{:set-root :multiaccounts-keycard}
{:set-root :profiles})
#(when goto-key-storage?
(navigation/navigate-to % :actions-not-logged-in nil))))))
(rf/defn get-credentials
[{:keys [db] :as cofx} key-uid]
(let [keycard-multiaccount? (boolean (get-in db
[:profile/profiles-overview key-uid :keycard-pairing]))]
(log/debug "[login] get-credentials"
"keycard-multiacc?"
keycard-multiaccount?)
(if keycard-multiaccount?
(keychain/get-keycard-keys cofx key-uid)
(keychain/get-user-password cofx key-uid))))
(rf/defn get-auth-method-success
"Auth method: nil - not supported, \"none\" - not selected, \"password\", \"biometric\", \"biometric-prepare\""
{:events [:multiaccounts.login/get-auth-method-success]}
[{:keys [db] :as cofx} auth-method]
(let [key-uid (get-in db [:profile/login :key-uid])
keycard-profile? (boolean (get-in db [:profile/profiles-overview key-uid :keycard-pairing]))]
(rf/merge
cofx
{:db (assoc db :auth-method auth-method)}
#(cond
(= auth-method keychain/auth-method-biometric)
(biometric/biometric-auth %)
(= auth-method keychain/auth-method-password)
(get-credentials % key-uid)
(and keycard-profile? (get-in db [:keycard :card-connected?]))
(keycard.common/get-application-info % nil))
(open-login-callback nil))))
(rf/defn biometric-auth-done
{:events [:biometric-auth-done]}
[{:keys [db] :as cofx} {:keys [bioauth-success bioauth-message bioauth-code]}]
(let [key-uid (get-in db [:profile/login :key-uid])
auth-method (get db :auth-method)]
(log/debug "[biometric] biometric-auth-done"
"bioauth-success" bioauth-success
"bioauth-message" bioauth-message
"bioauth-code" bioauth-code)
(if bioauth-success
(get-credentials cofx key-uid)
(rf/merge cofx
{:db (assoc-in db
[:profile/login :save-password?]
(= auth-method keychain/auth-method-biometric))}
(when-not (= auth-method keychain/auth-method-biometric)
(keychain/save-auth-method key-uid keychain/auth-method-none))
(biometric/show-message bioauth-message bioauth-code)
(open-login-callback nil)))))
(rf/defn save-password
{:events [:multiaccounts/save-password]}
[{:keys [db] :as cofx} save-password?]
(let [bioauth-supported? (boolean (get db :supported-biometric-auth))
previous-auth-method (get db :auth-method)]
(log/debug "[login] save-password"
"save-password?" save-password?
"bioauth-supported?" bioauth-supported?
"previous-auth-method" previous-auth-method)
(rf/merge
cofx
{:db (cond-> db
(not= previous-auth-method
keychain/auth-method-biometric-prepare)
(assoc :auth-method keychain/auth-method-none)
(or save-password?
(not bioauth-supported?)
(and (not save-password?)
bioauth-supported?
(= previous-auth-method keychain/auth-method-none)))
(assoc-in [:profile/login :save-password?] save-password?))}
(when bioauth-supported?
(if save-password?
(popover/show-popover {:view :secure-with-biometric})
(when-not (= previous-auth-method keychain/auth-method-none)
(popover/show-popover {:view :disable-password-saving})))))))
(rf/defn welcome-lets-go
{:events [:welcome-lets-go]}
[_]
{:set-root :shell-stack})
(rf/defn hide-keycard-banner
{:events [:hide-keycard-banner]}
[{:keys [db]}]
{:db (assoc db :keycard/banner-hidden true)
::async-storage/set! {:keycard-banner-hidden true}})
(rf/defn get-keycard-banner-preference-cb
{:events [:get-keycard-banner-preference-cb]}
[{:keys [db]} {:keys [keycard-banner-hidden]}]
{:db (assoc db :keycard/banner-hidden keycard-banner-hidden)})
(rf/defn get-keycard-banner-preference
{:events [:get-keycard-banner-preference]}
[_]
{::async-storage/get {:keys [:keycard-banner-hidden]
:cb #(re-frame/dispatch [:get-keycard-banner-preference-cb %])}})
(rf/defn get-opted-in-to-new-terms-of-service-cb
{:events [:get-opted-in-to-new-terms-of-service-cb]}
[{:keys [db]} {:keys [new-terms-of-service-accepted]}]
{:db (assoc db :tos/accepted? new-terms-of-service-accepted)})
(rf/defn get-opted-in-to-new-terms-of-service
"New TOS sprint https://github.com/status-im/status-mobile/pull/12240"
{:events [:get-opted-in-to-new-terms-of-service]}
[{:keys [db]}]
{::async-storage/get {:keys [:new-terms-of-service-accepted]
:cb #(re-frame/dispatch [:get-opted-in-to-new-terms-of-service-cb %])}})
(rf/defn hide-terms-of-services-opt-in-screen
{:events [:hide-terms-of-services-opt-in-screen]}
[{:keys [db]}]
{::async-storage/set! {:new-terms-of-service-accepted true}
:db (assoc db :tos/accepted? true)})

View File

@ -1,62 +0,0 @@
(ns status-im.multiaccounts.login.core-test
(:require [cljs.test :as test]
[status-im.multiaccounts.biometric.core :as biometric]
[status-im.multiaccounts.login.core :as login]
[utils.re-frame :as rf]
[status-im.utils.keychain.core :as keychain]))
(test/deftest save-password-test
(test/testing "check save password, biometric unavailable"
(let [initial-cofx {:db {:auth-method keychain/auth-method-none
:supported-biometric-auth false}}
{:keys [db]} (login/save-password initial-cofx true)]
(test/is (= false (contains? db :popover/popover)))
(test/is (= true (get-in db [:profile/login :save-password?])))
(test/testing "uncheck save password"
(let [{:keys [db]} (login/save-password {:db db} false)]
(test/is (= false (contains? db :popover/popover)))
(test/is (= false (get-in db [:profile/login :save-password?])))))))
(test/testing "check save password, biometric available"
(let [initial-cofx {:db {:auth-method keychain/auth-method-none
:supported-biometric-auth true}}
{:keys [db]} (login/save-password initial-cofx true)]
(test/is (= true (get-in db [:profile/login :save-password?])))
(test/testing "enable biometric auth"
(let [{:keys [db] :as res} (biometric/enable {:db db})]
(test/is (contains? res :biometric-auth/authenticate))
(test/is (= false (contains? db :popover/popover)))
(test/testing "biometric auth successfully enabled"
(let [{:keys [db]} (biometric/setup-done
{:db db}
{:bioauth-success true
:bioauth-message nil
:bioauth-code nil})]
(test/is (= keychain/auth-method-biometric-prepare
(:auth-method db)))))
(test/testing "biometric auth canceled"
(let [{:keys [db]} (biometric/setup-done
{:db db}
{:bioauth-success false
:bioauth-message nil
:bioauth-code "USER_CANCELED"})]
(test/is (= nil db) "no db changes")))))))
(test/testing (str "check save password, enable biometric auth,"
"uncheck save password")
(let [initial-cofx {:db {:auth-method keychain/auth-method-none
:supported-biometric-auth true}}
{:keys [db]} (login/save-password (rf/merge
initial-cofx
(login/save-password true)
(biometric/enable)
(biometric/setup-done
{:bioauth-success true
:bioauth-message nil
:bioauth-code nil}))
false)]
(test/is (= true (get-in db [:profile/login :save-password?])))
;; case 2 from https://github.com/status-im/status-mobile/issues/9573
(test/is (= keychain/auth-method-biometric-prepare (:auth-method db)))
(test/testing "disable biometric"
(let [{:keys [db]} (biometric/disable {:db db})]
(test/is (= false (get-in db [:profile/login :save-password?])))
(test/is (= keychain/auth-method-none (:auth-method db))))))))

View File

@ -1,121 +0,0 @@
(ns status-im.multiaccounts.login.data-test)
(def all-contacts
[{:last-updated 1547185503000
:tags #{}
:address "2f88d65f3cb52605a54a833ae118fb1363acccd2"
:name "Darkviolet Lightgreen Halcyon"
:added? true
:last-online 0
:public-key
"0x04d6e56a475cd35f512d6ce0bf76c2c2af435c85ff48c2b9bdefd129f620e051a436f50961eae5717b2a750e59c3f5b60647d927da46d0b8b11621640b5678fc24"}
{:last-updated 1547271764000
:address "b267ff8336ac10b3a1986c04a70ff91fb03d0b78"
:name "rv"
:added? true
:last-online 0
:public-key
"0x043ae31038ff45a31b096a91d3f8290e079366fbbae76a00fbbd349cd0e5b8d7598965d206772ec4504f68908649a08383cdc51a52cdae5e9ccc744ace4d37020f"}])
(def chats
[{:updated-at nil
:tags #{}
:color "#51d0f0"
:contacts #{}
:last-clock-value 154990121501242
:admins #{}
:members-joined #{}
:name "status"
:membership-updates ()
:unviewed-messages-count 0
:last-message-content-type "text/plain"
:last-message-content {:chat-id "status" :text "darn typos...! "}
:debug? false
:group-chat true
:public? true
:chat-id "status"
:timestamp 1547361080397
:deleted-at-clock-value nil}
{:updated-at nil
:tags #{}
:color "#d37ef4"
:contacts
#{"0x043ae31038ff45a31b096a91d3f8290e079366fbbae76a00fbbd349cd0e5b8d7598965d206772ec4504f68908649a08383cdc51a52cdae5e9ccc744ace4d37020f"}
:last-clock-value 154727176928001
:admins #{}
:members-joined #{}
:name "rv"
:membership-updates ()
:unviewed-messages-count 0
:last-message-content-type "text/plain"
:last-message-content
{:chat-id
"0x04173f7cdea0076a7998abb674cc79fe61337c42db77043c01d5b0f3e3ac1e5a45bca0c93bb9f3c3d38b7cc9a7337cd64f9f9b2114fe4bbdfe1ae2633ba14d8c9c"
:text "Hey"}
:debug? false
:group-chat false
:public? false
:chat-id
"0x043ae31038ff45a31b096a91d3f8290e079366fbbae76a00fbbd349cd0e5b8d7598965d206772ec4504f68908649a08383cdc51a52cdae5e9ccc744ace4d37020f"
:timestamp 1547271770816
:deleted-at-clock-value nil}
{:updated-at nil
:tags #{}
:color "#7cda00"
:contacts
#{"0x04d6e56a475cd35f512d6ce0bf76c2c2af435c85ff48c2b9bdefd129f620e051a436f50961eae5717b2a750e59c3f5b60647d927da46d0b8b11621640b5678fc24"}
:last-clock-value 154718689430301
:admins #{}
:members-joined #{}
:name "Darkviolet Lightgreen Halcyon"
:membership-updates ()
:unviewed-messages-count 0
:last-message-content-type "text/plain"
:last-message-content
{:chat-id
"0x04173f7cdea0076a7998abb674cc79fe61337c42db77043c01d5b0f3e3ac1e5a45bca0c93bb9f3c3d38b7cc9a7337cd64f9f9b2114fe4bbdfe1ae2633ba14d8c9c"
:text "Djndjd"}
:debug? false
:group-chat false
:public? false
:chat-id
"0x04d6e56a475cd35f512d6ce0bf76c2c2af435c85ff48c2b9bdefd129f620e051a436f50961eae5717b2a750e59c3f5b60647d927da46d0b8b11621640b5678fc24"
:timestamp 1547186895328
:deleted-at-clock-value nil}])
(def multiaccount
{:last-updated 0
:address "7540c34d6c4082391f12468580a9a4e0724c6755"
:mnemonic "tumble gorilla neglect dumb budget involve tennis ocean diary eagle lady ring"
:custom-bootnodes {}
:signing-phrase "bull exam weed"
:signed-up? true
:name "name"
:last-request nil
:wallet/visible-tokens {:mainnet #{:SNT}
:goerli #{:STT}
:xdai #{}}
:preview-privacy? true
:fleet :eth.prod
:wallet-set-up-passed? false
:public-key
"0x04173f7cdea0076a7998abb674cc79fe61337c42db77043c01d5b0f3e3ac1e5a45bca0c93bb9f3c3d38b7cc9a7337cd64f9f9b2114fe4bbdfe1ae2633ba14d8c9c"
:keycard-key-uid nil
:installation-id "618ec020-13c8-5505-8aa6-9c5444317e7f"})
(def multiaccounts
{"address" multiaccount})
(defn get-chats [_ _] chats)
(def transport
{"0x04d6e56a475cd35f512d6ce0bf76c2c2af435c85ff48c2b9bdefd129f620e051a436f50961eae5717b2a750e59c3f5b60647d927da46d0b8b11621640b5678fc24"
{:resend? nil}
"0x043ae31038ff45a31b096a91d3f8290e079366fbbae76a00fbbd349cd0e5b8d7598965d206772ec4504f68908649a08383cdc51a52cdae5e9ccc744ace4d37020f"
{:resend? nil}
"status"
{:resend? nil}})
(def topics
{"0xf8946aac" {:chat-ids #{:discovery-topic}
:last-request 1547319670}})

View File

@ -1,59 +0,0 @@
(ns status-im.multiaccounts.login.flow-test
"The main purpose of these tests is to signal that some steps of the sign in
flow has been changed. Such changes should be reflected in both these tests
and documents which describe the whole \"sign in\" flow."
(:require [cljs.test :refer-macros [deftest is testing]]
[status-im.ethereum.core :as ethereum]
[status-im.multiaccounts.login.core :as login.core]
[status-im.multiaccounts.login.data-test :as data]))
(deftest on-password-input-submitted
(testing
"handling :multiaccounts.login.ui/password-input-submitted event"
(let [cofx {:db {:profile/login {:key-uid "key-uid"
:password "password"
:name "user"}}}
efx (login.core/login cofx)]
(testing "Change multiaccount."
(is (= (::login.core/login efx)
["key-uid" "{\"name\":\"user\",\"key-uid\":\"key-uid\"}"
(ethereum/sha3 "password")])))
(testing "start activity indicator"
(is (= (get-in efx [:db :profile/login :processing]) true))))))
(deftest login-success
(testing ":accounts.login.callback/login-success event received."
(let [db {:profile/login {:address "address"
:password "password"}
:profile/profile data/multiaccount}
cofx {:db db}
efx (login.core/multiaccount-login-success cofx)
json-rpc (into #{} (map :method (:json-rpc/call efx)))]
;; TODO: Account is now cleared only after all sign in fx are executed.
;; (testing ":accounts/login cleared."
;; (is (not (contains? new-db :profile/login))))
(testing "Check the rest of effects."
(is (json-rpc "web3_clientVersion"))))))
;;TODO re-enable when keycard is fixed
#_(deftest login
(testing "login with keycard"
(let
[wpk "c56c7ac797c27b3790ce02c2459e9957c5d20d7a2c55320535526ce9e4dcbbef"
epk
"04f43da85ff1c333f3e7277b9ac4df92c9120fbb251f1dede7d41286e8c055acfeb845f6d2654821afca25da119daff9043530b296ee0e28e202ba92ec5842d617"
db
{:keycard
{:profile/profile
{:encryption-public-key epk
:whisper-private-key wpk
:wallet-address "83278851e290d2488b6add2a257259f5741a3b7d"
:whisper-public-key
"0x04491c1272149d7fa668afa45968c9914c0661641ace7dbcbc585c15070257840a0b4b1f71ce66c2147e281e1a44d6231b4731a26f6cc0a49e9616bbc7fc2f1a93"
:whisper-address "b8bec30855ff20c2ddab32282e2b2c8c8baca70d"}}}
result (login.core/login {:db db})]
(is (= (-> result (get :keycard/login-with-keycard) keys count)
3))
(is (= (get-in result [:keycard/login-with-keycard :whisper-private-key wpk])))
(is (= (get-in result [:keycard/login-with-keycard :encryption-public-key epk])))
(is (fn? (get-in result [:keycard/login-with-keycard :on-result]))))))

View File

@ -5,9 +5,24 @@
[native-module.core :as native-module] [native-module.core :as native-module]
[status-im.notifications.core :as notifications] [status-im.notifications.core :as notifications]
[utils.re-frame :as rf] [utils.re-frame :as rf]
[status-im.utils.keychain.core :as keychain]
[status-im.wallet.core :as wallet] [status-im.wallet.core :as wallet]
[status-im2.events :as init])) [status-im2.common.keychain.events :as keychain]
[status-im2.db :as db]))
(re-frame/reg-fx
::logout
(fn []
(native-module/logout)))
(rf/defn initialize-app-db
[{{:keys [keycard]
:biometric/keys [supported-type]
:network/keys [type]}
:db}]
{:db (assoc db/app-db
:network/type type
:keycard (dissoc keycard :secrets :pin :application-info)
:biometric/supported-type supported-type)})
(rf/defn logout-method (rf/defn logout-method
{:events [::logout-method]} {:events [::logout-method]}
@ -21,12 +36,11 @@
::logout nil ::logout nil
::multiaccounts/webview-debug-changed false ::multiaccounts/webview-debug-changed false
:keychain/clear-user-password key-uid :keychain/clear-user-password key-uid
:profile/get-profiles-overview #(re-frame/dispatch :profile/get-profiles-overview #(rf/dispatch
[:profile/get-profiles-overview-success [:profile/get-profiles-overview-success %])}
%])}
(keychain/save-auth-method key-uid auth-method) (keychain/save-auth-method key-uid auth-method)
(wallet/clear-timeouts) (wallet/clear-timeouts)
(init/initialize-app-db)))) (initialize-app-db))))
(rf/defn logout (rf/defn logout
{:events [:logout :multiaccounts.logout.ui/logout-confirmed {:events [:logout :multiaccounts.logout.ui/logout-confirmed
@ -49,17 +63,3 @@
:confirm-button-text (i18n/label :t/logout) :confirm-button-text (i18n/label :t/logout)
:on-accept #(re-frame/dispatch [:multiaccounts.logout.ui/logout-confirmed]) :on-accept #(re-frame/dispatch [:multiaccounts.logout.ui/logout-confirmed])
:on-cancel nil}}) :on-cancel nil}})
(rf/defn biometric-logout
{:events [:biometric-logout]}
[cofx]
(rf/merge cofx
(logout-method {:auth-method keychain/auth-method-biometric-prepare
:logout? false})
(fn [{:keys [db]}]
{:db (assoc-in db [:profile/login :save-password?] true)})))
(re-frame/reg-fx
::logout
(fn []
(native-module/logout)))

View File

@ -1,86 +1,10 @@
(ns status-im.multiaccounts.recover.core (ns status-im.multiaccounts.recover.core
(:require [clojure.string :as string] (:require [re-frame.core :as re-frame]
[re-frame.core :as re-frame]
[status-im.bottom-sheet.events :as bottom-sheet]
[status-im2.constants :as constants] [status-im2.constants :as constants]
[status-im.ethereum.core :as ethereum]
[status-im.ethereum.mnemonic :as mnemonic]
[utils.i18n :as i18n]
[status-im.multiaccounts.core :as multiaccounts]
[status-im.multiaccounts.create.core :as multiaccounts.create] [status-im.multiaccounts.create.core :as multiaccounts.create]
[native-module.core :as native-module] [native-module.core :as native-module]
[status-im.popover.core :as popover]
[utils.re-frame :as rf]
[status-im.utils.types :as types] [status-im.utils.types :as types]
[status-im2.navigation.events :as navigation] [taoensso.timbre :as log]))
[taoensso.timbre :as log]
[utils.security.core :as security]))
(defn existing-account?
[multiaccounts key-uid]
{:pre [(not (nil? key-uid))]}
(contains? multiaccounts key-uid))
(defn check-phrase-warnings
[recovery-phrase]
(cond (string/blank? recovery-phrase) :t/required-field))
(rf/defn set-phrase
{:events [:multiaccounts.recover/passphrase-input-changed]}
[{:keys [db]} masked-recovery-phrase]
(let [recovery-phrase (security/safe-unmask-data masked-recovery-phrase)]
{:db (update db
:intro-wizard assoc
:passphrase (string/lower-case recovery-phrase)
:passphrase-error nil
:next-button-disabled? (or (empty? recovery-phrase)
(not (mnemonic/valid-length? recovery-phrase))))}))
(rf/defn validate-phrase-for-warnings
[{:keys [db]}]
(let [recovery-phrase (get-in db [:intro-wizard :passphrase])]
{:db (update db
:intro-wizard assoc
:passphrase-error (check-phrase-warnings recovery-phrase))}))
(rf/defn on-store-multiaccount-success
{:events [::store-multiaccount-success]
:interceptors [(re-frame/inject-cofx :random-guid-generator)
(re-frame/inject-cofx ::multiaccounts.create/get-signing-phrase)]}
[{:keys [db] :as cofx} result password]
(let [{:keys [error]} (types/json->clj result)]
(if error
{:utils/show-popup {:title (i18n/label :t/multiaccount-exists-title)
:content (i18n/label :t/multiaccount-exists-title)
:on-dismiss #(re-frame/dispatch [:pop-to-root :multiaccounts])}}
(let [{:keys [key-uid] :as multiaccount} (get-in db [:intro-wizard :root-key])
keycard-multiaccount? (boolean (get-in db
[:profile/profiles-overview key-uid
:keycard-pairing]))]
(if keycard-multiaccount?
;; trying to recover multiaccount created with keycard
{:db (-> db
(update :intro-wizard assoc
:processing? false
:passphrase-error :recover-keycard-multiaccount-not-supported)
(update :intro-wizard
dissoc
:passphrase-valid?))}
(let [multiaccount (assoc multiaccount :derived (get-in db [:intro-wizard :derived]))]
(multiaccounts.create/on-multiaccount-created cofx
multiaccount
password
{})))))))
(rf/defn store-multiaccount
{:events [::recover-multiaccount-confirmed]}
[{:keys [db]} password]
(let [{:keys [root-key]} (:intro-wizard db)
{:keys [id key-uid]} root-key
callback #(re-frame/dispatch [::store-multiaccount-success % password])
hashed-password (ethereum/sha3 (security/safe-unmask-data password))]
{:db (assoc-in db [:intro-wizard :processing?] true)
::multiaccounts.create/store-multiaccount [id key-uid hashed-password callback]}))
(re-frame/reg-fx (re-frame/reg-fx
::import-multiaccount ::import-multiaccount
@ -110,68 +34,3 @@
(update derived-data constants/path-whisper-keyword assoc :name name)] (update derived-data constants/path-whisper-keyword assoc :name name)]
(re-frame/dispatch [success-event root-data derived-data-extended])))))))))))) (re-frame/dispatch [success-event root-data derived-data-extended]))))))))))))
(rf/defn show-existing-multiaccount-alert
[_ key-uid]
{:utils/show-confirmation
{:title (i18n/label :t/multiaccount-exists-title)
:content (i18n/label :t/multiaccount-exists-content)
:confirm-button-text (i18n/label :t/unlock)
:on-accept #(do
(re-frame/dispatch [:pop-to-root :multiaccounts])
(re-frame/dispatch
[:profile/profile-selected key-uid]))
:on-cancel #(re-frame/dispatch [:pop-to-root :multiaccounts])}})
(rf/defn on-import-multiaccount-success
{:events [::import-multiaccount-success]}
[{:keys [db] :as cofx} {:keys [key-uid] :as root-data} derived-data]
(let [multiaccounts (:profile/profiles-overview db)]
(rf/merge
cofx
{:db (update db
:intro-wizard
assoc
:root-key root-data
:derived derived-data
:step :recovery-success)}
(when (existing-account? multiaccounts key-uid)
(show-existing-multiaccount-alert key-uid))
(navigation/navigate-to :recover-multiaccount-success nil))))
(rf/defn enter-phrase-pressed
{:events [::enter-phrase-pressed]}
[{:keys [db] :as cofx}]
(rf/merge
cofx
{:db (-> db
(assoc :intro-wizard
{:step :enter-phrase
:recovering? true
:next-button-disabled? true
:weak-password? true
:encrypt-with-password? true
:forward-action :multiaccounts.recover/enter-phrase-next-pressed}
:recovered-account? true)
(update :keycard dissoc :flow))}
(bottom-sheet/hide-bottom-sheet-old)
(navigation/navigate-to :recover-multiaccount-enter-phrase nil)))
(rf/defn proceed-to-import-mnemonic
{:events [:multiaccounts.recover/phrase-validated]}
[{:keys [db] :as cofx} phrase-warnings]
(let [{:keys [password passphrase]} (:intro-wizard db)]
(if-not (string/blank? (:error (types/json->clj phrase-warnings)))
(popover/show-popover cofx {:view :custom-seed-phrase})
(when (mnemonic/valid-length? passphrase)
{::import-multiaccount {:passphrase (mnemonic/sanitize-passphrase passphrase)
:password (when password
(security/safe-unmask-data password))
:success-event ::import-multiaccount-success}}))))
(rf/defn seed-phrase-next-pressed
{:events [:multiaccounts.recover/enter-phrase-next-pressed]}
[{:keys [db] :as cofx}]
(let [{:keys [passphrase]} (:intro-wizard db)]
{::multiaccounts/validate-mnemonic [passphrase
#(re-frame/dispatch [:multiaccounts.recover/phrase-validated
%])]}))

View File

@ -1,85 +0,0 @@
(ns status-im.multiaccounts.recover.core-test
(:require [cljs.test :refer-macros [deftest is testing]]
[status-im.multiaccounts.create.core :as multiaccounts.create]
[status-im.multiaccounts.recover.core :as models]
[utils.security.core :as security]))
;;;; helpers
(deftest check-phrase-warnings
(is (= :t/required-field (models/check-phrase-warnings ""))))
;;;; handlers
(deftest set-phrase
(is
(= {:db {:intro-wizard
{: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 {:intro-wizard
{: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 {:intro-wizard {: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 {:intro-wizard
{: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 {:intro-wizard {: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 store-multiaccount
(let [new-cofx (models/store-multiaccount
{:db {:intro-wizard
{:passphrase
"game buzz method pretty zeus fat quit display velvet unveil marine crater"}}}
(security/mask-data "thisisapaswoord"))]
(is (::multiaccounts.create/store-multiaccount new-cofx))))
(deftest on-import-multiaccount-success
(testing "importing a new multiaccount"
(let [res (models/on-import-multiaccount-success
{:db {:profile/profiles-overview {:acc1 {}}}}
{:key-uid :acc2}
nil)]
(is (nil? (:utils/show-confirmation res)))))
(testing "importing an existing multiaccount"
(let [res (models/on-import-multiaccount-success
{:db {:profile/profiles-overview {:acc1 {}}}}
{:key-uid :acc1}
nil)]
(is (contains? res :utils/show-confirmation)))))

View File

@ -5,7 +5,6 @@
[native-module.core :as native-module] [native-module.core :as native-module]
[status-im.popover.core :as popover] [status-im.popover.core :as popover]
[utils.re-frame :as rf] [utils.re-frame :as rf]
[status-im.utils.keychain.core :as keychain]
[status-im.utils.types :as types] [status-im.utils.types :as types]
[utils.security.core :as security])) [utils.security.core :as security]))
@ -35,19 +34,13 @@
(rf/defn password-reset-success (rf/defn password-reset-success
{:events [::password-reset-success]} {:events [::password-reset-success]}
[{:keys [db] :as cofx}] [{:keys [db] :as cofx}]
(let [{:keys [key-uid]} (:profile/profile db)
auth-method (get db :auth-method keychain/auth-method-none)
new-password (get-in db [:multiaccount/reset-password-form-vals :new-password])]
(rf/merge cofx (rf/merge cofx
{:db (dissoc {:db (dissoc
db db
:multiaccount/reset-password-form-vals :multiaccount/reset-password-form-vals
:multiaccount/reset-password-errors :multiaccount/reset-password-errors
:multiaccount/reset-password-next-enabled? :multiaccount/reset-password-next-enabled?
:multiaccount/resetting-password?)} :multiaccount/resetting-password?)}))
;; update password in keychain if biometrics are enabled
(when (= auth-method keychain/auth-method-biometric)
(keychain/save-user-password key-uid new-password)))))
(defn change-db-password-cb (defn change-db-password-cb
[res] [res]

View File

@ -88,7 +88,7 @@
(assoc-in [:syncing :pairing-status] :connected) (assoc-in [:syncing :pairing-status] :connected)
received-account? received-account?
(assoc-in [:syncing :profile/profile] multiaccount-data) (assoc-in [:syncing :profile] multiaccount-data)
error-on-pairing? error-on-pairing?
(assoc-in [:syncing :pairing-status] :error) (assoc-in [:syncing :pairing-status] :error)
@ -105,7 +105,7 @@
{:dispatch [:syncing/clear-states]} {:dispatch [:syncing/clear-states]}
(and completed-pairing? receiver?) (and completed-pairing? receiver?)
{:dispatch [:multiaccounts.login/local-paired-user]} {:dispatch [:profile.login/local-paired-user]}
(and error-on-pairing? (some? error)) (and error-on-pairing? (some? error))
{:dispatch [:toasts/upsert {:dispatch [:toasts/upsert

View File

@ -1,97 +0,0 @@
(ns status-im.ui.screens.biometric.views
(:require [quo.core :as quo]
[quo.design-system.colors :as colors]
[re-frame.core :as re-frame]
[utils.i18n :as i18n]
[status-im.multiaccounts.biometric.core :as biometric]
[status-im.ui.components.icons.icons :as icons]
[status-im.ui.components.react :as react]))
(defn get-supported-biometric-auth
[]
@(re-frame/subscribe [:supported-biometric-auth]))
(defn get-bio-type-label
[]
(biometric/get-label (get-supported-biometric-auth)))
(defn biometric-popover
[{:keys [title-label description-label description-text
ok-button-label cancel-button-label on-cancel on-confirm]}]
(let [supported-biometric-auth (get-supported-biometric-auth)
bio-type-label (get-bio-type-label)]
[react/view
{:margin-top 24
:align-items :center
:padding-horizontal 24}
[react/view
{:width 32
:height 32
:background-color colors/blue-light
:border-radius 16
:align-items :center
:justify-content :center}
[icons/icon
(if (= supported-biometric-auth :FaceID)
:main-icons/faceid
:main-icons/print)
{:color colors/blue}]]
[react/text
{:style {:typography :title-bold
:margin-top 16}}
(str (i18n/label title-label {:bio-type-label bio-type-label}))]
(vec
(concat
[react/nested-text
{:style {:margin-top 8
:color colors/gray
:text-align :center}}]
(if description-label
[(i18n/label description-label {:bio-type-label bio-type-label})]
description-text)))
[react/view {:padding-vertical 16}
[react/view {:padding-vertical 8}
[quo/button {:on-press #(re-frame/dispatch [on-confirm])}
(i18n/label ok-button-label
{:bio-type-label bio-type-label})]]
[quo/button
{:type :secondary
:on-press #(re-frame/dispatch [(or on-cancel :hide-popover)])}
(or cancel-button-label
(i18n/label :t/cancel))]]]))
(defn disable-password-saving-popover
[]
(let [bio-label-type (get-bio-type-label)]
[biometric-popover
{:title-label :t/biometric-disable-password-title
:ok-button-label :t/continue
:on-confirm :biometric/disable
:description-text
[[{:style {:color colors/gray}}
(i18n/label :t/biometric-disable-password-description)]
[{}
(i18n/label :t/biometric-disable-bioauth
{:bio-type-label bio-label-type})]]}]))
(defn enable-biometric-popover
[]
[biometric-popover
{:title-label :t/biometric-secure-with
:description-label :t/to-enable-biometric
:ok-button-label :t/biometric-enable-button
:on-confirm :biometric-logout}])
(defn secure-with-biometric-popover
[]
(let [keycard-account? @(re-frame/subscribe
[:multiaccounts.login/keycard-account?])]
[biometric-popover
{:title-label :t/biometric-secure-with
:ok-button-label :t/biometric-enable-button
:on-confirm :biometric/enable
:description-label (if keycard-account?
:t/biometric-enable-keycard
:t/biometric-enable)}]))

View File

@ -95,8 +95,9 @@
{:style {:flex-direction :row}} {:style {:flex-direction :row}}
[checkbox/checkbox [checkbox/checkbox
{:checked? save-password? {:checked? save-password?
:style {:margin-right 10} :style {:margin-right 10}}]
:on-value-change #(re-frame/dispatch [:multiaccounts/save-password %])}] ;; should be reimplemented
;;:on-value-change #(re-frame/dispatch [:multiaccounts/save-password %])}]
[react/text (i18n/label :t/keycard-dont-ask-card)]]))) [react/text (i18n/label :t/keycard-dont-ask-card)]])))
(defn bezier-easing (defn bezier-easing

View File

@ -9,7 +9,6 @@
[utils.i18n :as i18n] [utils.i18n :as i18n]
[status-im.keycard.login :as keycard.login] [status-im.keycard.login :as keycard.login]
[status-im.multiaccounts.core :as multiaccounts] [status-im.multiaccounts.core :as multiaccounts]
[status-im.multiaccounts.create.core :as multiaccounts.create]
[status-im.react-native.resources :as resources] [status-im.react-native.resources :as resources]
[status-im.ui.components.icons.icons :as icons] [status-im.ui.components.icons.icons :as icons]
[status-im.ui.components.react :as react] [status-im.ui.components.react :as react]
@ -444,7 +443,6 @@
{:events [:multiaccounts.create.ui/get-new-key]} {:events [:multiaccounts.create.ui/get-new-key]}
[{:keys [db] :as cofx}] [{:keys [db] :as cofx}]
(rf/merge cofx (rf/merge cofx
(multiaccounts.create/prepare-intro-wizard)
(bottom-sheet/hide-bottom-sheet-old) (bottom-sheet/hide-bottom-sheet-old)
(navigation/navigate-to :get-your-keys nil))) (navigation/navigate-to :get-your-keys nil)))

View File

@ -6,7 +6,6 @@
[reagent.core :as reagent] [reagent.core :as reagent]
[status-im.ui.components.animation :as anim] [status-im.ui.components.animation :as anim]
[status-im.ui.components.react :as react] [status-im.ui.components.react :as react]
[status-im.ui.screens.biometric.views :as biometric]
[status-im.ui.screens.keycard.frozen-card.view :as frozen-card] [status-im.ui.screens.keycard.frozen-card.view :as frozen-card]
[status-im.ui.screens.keycard.views :as keycard.views] [status-im.ui.screens.keycard.views :as keycard.views]
[status-im.ui.screens.profile.user.views :as profile.user] [status-im.ui.screens.profile.user.views :as profile.user]
@ -142,15 +141,6 @@
(= :share-chat-key view) (= :share-chat-key view)
[profile.user/share-chat-key] [profile.user/share-chat-key]
(= :enable-biometric view)
[biometric/enable-biometric-popover]
(= :secure-with-biometric view)
[biometric/secure-with-biometric-popover]
(= :disable-password-saving view)
[biometric/disable-password-saving-popover]
(= :transaction-data view) (= :transaction-data view)
[signing/transaction-data] [signing/transaction-data]

View File

@ -3,7 +3,6 @@
[re-frame.core :as re-frame] [re-frame.core :as re-frame]
[status-im2.constants :as constants] [status-im2.constants :as constants]
[utils.i18n :as i18n] [utils.i18n :as i18n]
[status-im.multiaccounts.biometric.core :as biometric]
[status-im.multiaccounts.reset-password.core :as reset-password] [status-im.multiaccounts.reset-password.core :as reset-password]
[status-im.multiaccounts.update.core :as multiaccounts.update] [status-im.multiaccounts.update.core :as multiaccounts.update]
[status-im.ui.components.common.common :as components.common] [status-im.ui.components.common.common :as components.common]
@ -34,9 +33,7 @@
profile-pictures-visibility]} profile-pictures-visibility]}
[:profile/profile] [:profile/profile]
has-picture [:profile/has-picture] has-picture [:profile/has-picture]
supported-biometric-auth [:supported-biometric-auth]
keycard? [:keycard-multiaccount?] keycard? [:keycard-multiaccount?]
auth-method [:auth-method]
profile-pictures-show-to [:multiaccount/profile-pictures-show-to]] profile-pictures-show-to [:multiaccount/profile-pictures-show-to]]
[react/scroll-view {:padding-vertical 8} [react/scroll-view {:padding-vertical 8}
[quo/list-header (i18n/label :t/security)] [quo/list-header (i18n/label :t/security)]
@ -48,18 +45,6 @@
:chevron (boolean mnemonic) :chevron (boolean mnemonic)
:accessory (when mnemonic [components.common/counter {:size 22} 1]) :accessory (when mnemonic [components.common/counter {:size 22} 1])
:on-press #(re-frame/dispatch [:navigate-to :backup-seed])}] :on-press #(re-frame/dispatch [:navigate-to :backup-seed])}]
(when supported-biometric-auth
[quo/list-item
{:size :small
:title (str (i18n/label :t/lock-app-with)
" "
(biometric/get-label supported-biometric-auth))
:active (= auth-method "biometric")
:accessibility-label :biometric-auth-settings-switch
:accessory :switch
:on-press #(re-frame/dispatch [:multiaccounts.ui/biometric-auth-switched
((complement boolean)
(= auth-method "biometric"))])}])
[separator] [separator]
[quo/list-header (i18n/label :t/privacy)] [quo/list-header (i18n/label :t/privacy)]
[quo/list-item [quo/list-item

View File

@ -58,7 +58,6 @@
[status-im.ui.screens.rpc-usage-info :as rpc-usage-info] [status-im.ui.screens.rpc-usage-info :as rpc-usage-info]
[status-im.ui.screens.stickers.views :as stickers] [status-im.ui.screens.stickers.views :as stickers]
[status-im.ui.screens.sync-settings.views :as sync-settings] [status-im.ui.screens.sync-settings.views :as sync-settings]
[status-im.ui.screens.terms-of-service.views :as terms-of-service]
[status-im.ui.screens.wakuv2-settings.edit-node.views :as edit-wakuv2-node] [status-im.ui.screens.wakuv2-settings.edit-node.views :as edit-wakuv2-node]
[status-im.ui.screens.wakuv2-settings.views :as wakuv2-settings] [status-im.ui.screens.wakuv2-settings.views :as wakuv2-settings]
[status-im.ui.screens.wallet.account-settings.views :as account-settings] [status-im.ui.screens.wallet.account-settings.views :as account-settings]
@ -349,9 +348,6 @@
:options {:topBar {:title {:text (i18n/label :t/principles)}} :options {:topBar {:title {:text (i18n/label :t/principles)}}
:insets {:top? true}} :insets {:top? true}}
:component about-app/principles} :component about-app/principles}
{:name :force-accept-tos
:options {:insets {:top? true}}
:component terms-of-service/force-accept-tos}
{:name :manage-dapps-permissions {:name :manage-dapps-permissions
;;TODO dynamic title ;;TODO dynamic title
:options {:insets {:top? true}} :options {:insets {:top? true}}

View File

@ -1,112 +0,0 @@
(ns status-im.ui.screens.terms-of-service.views
(:require [quo.core :as quo]
[quo.design-system.colors :as colors]
[quo.design-system.spacing :as spacing]
[quo.design-system.typography :as typography]
[re-frame.core :as re-frame]
[status-im2.constants :refer [docs-link]]
[utils.i18n :as i18n]
[status-im.react-native.resources :as resources]
[status-im.ui.components.icons.icons :as icons]
[status-im.ui.components.react :as react]
[status-im.ui.components.toolbar :as toolbar])
(:require-macros [status-im.utils.views :refer [defview letsubs]]))
(defn principles-item
[]
[react/nested-text {}
(i18n/label :t/wc-new-tos-based-on-principles-prefix)
[{:style (merge {:color colors/blue}
typography/font-medium)
:on-press #(re-frame/dispatch [:open-modal :principles])}
" "
(i18n/label :t/principles)]])
(def changes
[[principles-item]
:wc-how-to-use-status-app
:wc-brand-guide
:wc-disclaimer
:wc-dispute])
(defn change-list-item
[label]
[react/view
{:flex-direction :row
:align-items :center
:margin-horizontal (:base spacing/spacing)
:margin-vertical (:tiny spacing/spacing)}
[icons/icon :main-icons/checkmark-circle
{:color colors/blue
:container-style {:margin-top 1.2
:margin-right (:tiny spacing/spacing)}}]
[react/view {:style {:padding-right (:xx-large spacing/spacing)}}
(if (keyword? label)
[react/text (i18n/label label)]
label)]])
(defview force-accept-tos
[]
(letsubs [next-root [:tos-accept-next-root]]
[react/scroll-view
[react/view
{:style (merge {:align-items :center}
(:x-large spacing/padding-horizontal))}
[react/image
{:source (resources/get-image :status-logo)
:style {:margin-vertical (:base spacing/spacing)
:width 32
:height 32}}]
[quo/text
{:size :x-large
:align :center
:weight :bold
:style {:margin-bottom (:base spacing/spacing)}}
(i18n/label :t/updates-to-tos)]
[quo/text
{:color :secondary
:align :center}
(i18n/label :t/updates-to-tos-desc)]]
[quo/separator {:style {:margin-top (:base spacing/spacing)}}]
[quo/list-item
{:title [quo/text
{:color :link
:weight :medium}
(i18n/label :t/terms-of-service)]
:accessibility-label :tos
:chevron true
:on-press #(re-frame/dispatch [:open-modal :terms-of-service])}]
[quo/separator]
[quo/list-header (i18n/label :t/what-changed)]
(for [c changes]
^{:key c}
[change-list-item c])
[quo/separator {:style {:margin-vertical (:base spacing/spacing)}}]
[react/view {:style (:base spacing/padding-horizontal)}
[quo/text {:weight :medium} (i18n/label :t/status-is-open-source)]
[quo/text {:color :secondary} (i18n/label :t/build-yourself)]
[quo/text
{:color :link
:weight :medium
:on-press #(.openURL ^js react/linking docs-link)}
docs-link]]
[quo/separator {:style {:margin-vertical (:base spacing/spacing)}}]
[toolbar/toolbar
{:size :large
:center
[react/view {:padding-horizontal 8}
[quo/button
{:type :primary
:on-press #(do
(re-frame/dispatch [:hide-terms-of-services-opt-in-screen])
(re-frame/dispatch [:init-root next-root]))}
(i18n/label :t/accept-and-continue)]]}]]))
(comment
(re-frame/dispatch [:navigate-to :force-accept-tos]))

View File

@ -1,213 +1,45 @@
(ns status-im.utils.keychain.core (ns status-im.utils.keychain.core
(:require ["react-native-keychain" :as react-native-keychain] (:require [react-native.keychain :as keychain]
[clojure.string :as string]
[re-frame.core :as re-frame] [re-frame.core :as re-frame]
[native-module.core :as native-module]
[utils.re-frame :as rf] [utils.re-frame :as rf]
[status-im.utils.platform :as platform]
[taoensso.timbre :as log] [taoensso.timbre :as log]
[utils.security.core :as security])) [oops.core :as oops]))
(defn- check-conditions (def auth-method-biometric "biometric")
[callback & checks] (def auth-method-biometric-prepare "biometric-prepare")
(if (= (count checks) 0) (def auth-method-none "none")
(callback true)
(let [current-check-fn (first checks)
process-check-result (fn [callback-success callback-fail]
(fn [current-check-passed?]
(if current-check-passed?
(callback-success)
(callback-fail))))]
(current-check-fn (process-check-result
#(apply (partial check-conditions callback) (rest checks))
#(callback false))))))
;; ********************************************************************************
;; Storing / Retrieving a user password to/from Keychain
;; ********************************************************************************
;;
;; We are using set/get/reset internet credentials there because they are bound
;; to an address (`server`) property.
(defn enum-val
[enum-name value-name]
(get-in (js->clj react-native-keychain) [enum-name value-name]))
;; We need a more strict access mode for keychain entries that save user password.
;; iOS
;; see this article for more details:
;; https://developer.apple.com/documentation/security/keychain_services/keychain_items/restricting_keychain_item_accessibility?language=objc
(def keychain-restricted-availability
;; From Apple's documentation:
;; > The kSecAttrAccessible attribute enables you to control item availability
;; > relative to the lock state of the device.
;; > It also lets you specify eligibility for restoration to a new device.
;; > If the attribute ends with the string ThisDeviceOnly,
;; > the item can be restored to the same device that created a backup,
;; > but it isnt migrated when restoring another devices backup data.
;; > ...
;; > For extremely sensitive data
;; > THAT YOU NEVER WANT STORED IN iCloud,
;; > you might choose kSecAttrAccessibleWhenPasscodeSetThisDeviceOnly.
;; That is exactly what we use there.
;; Note that the password won't be stored if the device isn't locked by a passcode.
#js {:accessible (enum-val "ACCESSIBLE" "WHEN_PASSCODE_SET_THIS_DEVICE_ONLY")})
(def keychain-secure-hardware
;; (Android) Requires storing the encryption key for the entry in secure hardware
;; or StrongBox (see https://developer.android.com/training/articles/keystore#ExtractionPrevention)
"SECURE_HARDWARE")
;; These helpers check if the device is okay to use for password storage
;; They resolve callback with `true` if the check is passed, with `false` otherwise.
;; Android only
(defn- device-not-rooted?
[callback]
(native-module/rooted-device? (fn [rooted?] (callback (not rooted?)))))
;; Android only
(defn- secure-hardware-available?
[callback]
(-> (.getSecurityLevel react-native-keychain)
(.then (fn [level] (callback (= level keychain-secure-hardware))))))
;; iOS only
(defn- device-encrypted?
[callback]
(-> (.canImplyAuthentication
react-native-keychain
(clj->js
{:authenticationType
(enum-val "ACCESS_CONTROL" "BIOMETRY_ANY_OR_DEVICE_PASSCODE")}))
(.then callback)))
(defn- whisper-key-name (defn- whisper-key-name
[address] [address]
(str address "-whisper")) (str address "-whisper"))
(defn can-save-user-password?
[callback]
(log/debug "[keychain] can-save-user-password?")
(cond
platform/ios?
(check-conditions callback device-encrypted?)
platform/android?
(check-conditions callback secure-hardware-available? device-not-rooted?)
:else
(callback false)))
(defn save-credentials
"Stores the credentials for the address to the Keychain"
[server username password callback]
(log/debug "[keychain] save-credentials")
(-> (.setInternetCredentials react-native-keychain
(string/lower-case server)
username
password
keychain-secure-hardware
keychain-restricted-availability)
(.then callback)))
(defn get-credentials
"Gets the credentials for a specified server from the Keychain"
[server callback]
(log/debug "[keychain] get-credentials")
(-> (.getInternetCredentials react-native-keychain (string/lower-case server))
(.then callback)))
(def auth-method-password "password")
(def auth-method-biometric "biometric")
(def auth-method-biometric-prepare "biometric-prepare")
(def auth-method-none "none")
(re-frame/reg-fx
:keychain/get-auth-method
(fn [[key-uid callback]]
(can-save-user-password?
(fn [can-save?]
(if can-save?
(get-credentials (str key-uid "-auth")
#(callback (if %
(.-password ^js %)
auth-method-none)))
(callback nil))))))
(re-frame/reg-fx
:keychain/get-user-password
(fn [[key-uid callback]]
(get-credentials key-uid #(if % (callback (security/mask-data (.-password ^js %))) (callback nil)))))
(re-frame/reg-fx (re-frame/reg-fx
:keychain/get-keycard-keys :keychain/get-keycard-keys
(fn [[key-uid callback]] (fn [[key-uid callback]]
(get-credentials (keychain/get-credentials
key-uid key-uid
(fn [^js encryption-key-data] (fn [encryption-key-data]
(if encryption-key-data (if encryption-key-data
(get-credentials (keychain/get-credentials
(whisper-key-name key-uid) (whisper-key-name key-uid)
(fn [^js whisper-key-data] (fn [whisper-key-data]
(if whisper-key-data (if whisper-key-data
(callback [(.-password encryption-key-data) (callback [(oops/oget encryption-key-data "password")
(.-password whisper-key-data)]) (oops/oget whisper-key-data "password")])
(callback nil)))) (callback nil))))
(callback nil)))))) (callback nil))))))
(defn save-user-password!
[key-uid password]
(save-credentials
key-uid
key-uid
(security/safe-unmask-data password)
#(when-not %
(log/error
(str "Error while saving password."
" "
"The app will continue to work normally, "
"but you will have to login again next time you launch it.")))))
(defn save-auth-method!
[key-uid method]
(save-credentials
(str key-uid "-auth")
key-uid
method
#(when-not %
(log/error
(str "Error while saving auth method."
" "
"The app will continue to work normally, "
"but you will have to login again next time you launch it.")))))
(re-frame/reg-fx
:keychain/save-user-password
(fn [[key-uid masked-password]]
(save-user-password! key-uid masked-password)))
(re-frame/reg-fx
:keychain/save-auth-method
(fn [[key-uid method]]
(log/debug "[keychain] :keychain/save-auth-method"
"key-uid"
key-uid
"method"
method)
(when-not (empty? key-uid) ; key-uid may be nil after restore from local pairing
(save-auth-method! key-uid method))))
(re-frame/reg-fx (re-frame/reg-fx
:keychain/save-keycard-keys :keychain/save-keycard-keys
(fn [[key-uid encryption-public-key whisper-private-key]] (fn [[key-uid encryption-public-key whisper-private-key]]
(save-credentials (keychain/save-credentials
key-uid key-uid
key-uid key-uid
encryption-public-key encryption-public-key
#(when-not % #(when-not %
(log/error (log/error
(str "Error while saving encryption-public-key")))) (str "Error while saving encryption-public-key"))))
(save-credentials (keychain/save-credentials
(whisper-key-name key-uid) (whisper-key-name key-uid)
key-uid key-uid
whisper-private-key whisper-private-key
@ -215,19 +47,6 @@
(log/error (log/error
(str "Error while saving whisper-private-key")))))) (str "Error while saving whisper-private-key"))))))
(re-frame/reg-fx
:keychain/clear-user-password
(fn [key-uid]
(-> (.resetInternetCredentials react-native-keychain (string/lower-case key-uid))
(.then #(when-not % (log/error (str "Error while clearing saved password.")))))))
(rf/defn get-user-password
[_ key-uid]
{:keychain/get-user-password
[key-uid
#(re-frame/dispatch
[:multiaccounts.login.callback/get-user-password-success % key-uid])]})
(rf/defn get-keycard-keys (rf/defn get-keycard-keys
[_ key-uid] [_ key-uid]
{:keychain/get-keycard-keys {:keychain/get-keycard-keys
@ -235,16 +54,8 @@
#(re-frame/dispatch #(re-frame/dispatch
[:multiaccounts.login.callback/get-keycard-keys-success key-uid %])]}) [:multiaccounts.login.callback/get-keycard-keys-success key-uid %])]})
(rf/defn save-user-password
[_ key-uid password]
{:keychain/save-user-password [key-uid password]})
(rf/defn save-keycard-keys (rf/defn save-keycard-keys
[_ key-uid encryption-public-key whisper-private-key] [_ key-uid encryption-public-key whisper-private-key]
{:keychain/save-keycard-keys [key-uid {:keychain/save-keycard-keys [key-uid
encryption-public-key encryption-public-key
whisper-private-key]}) whisper-private-key]})
(rf/defn save-auth-method
[{:keys [db]} key-uid method]
{:db (assoc db :auth-method method)
:keychain/save-auth-method [key-uid method]})

View File

@ -36,16 +36,21 @@
(fn [json callback] (fn [json callback]
(callback (.multiAccountStoreDerivedAccounts native-status json))) (callback (.multiAccountStoreDerivedAccounts native-status json)))
:getNodeConfig (fn [] (types/clj->json {:WakuV2Config ""}))
:backupDisabledDataDir (fn [] "./smth")
:keystoreDir (fn [] "")
:logFileDirectory (fn [] "./smth")
:clearCookies identity :clearCookies identity
:clearStorageAPIs identity :clearStorageAPIs identity
:setBlankPreviewFlag identity :setBlankPreviewFlag identity
:callPrivateRPC :callPrivateRPC
(fn [payload callback] (fn [payload callback]
(callback (.callPrivateRPC native-status payload))) (callback (.callPrivateRPC native-status payload)))
:createAccountAndLogin (fn [request] (.createAccountAndLogin native-status request))
:saveAccountAndLogin :saveAccountAndLogin
(fn [multiaccount-data password settings config accounts-data] (fn [multiaccount-data password settings config accounts-data]
(.saveAccountAndLogin native-status multiaccount-data password settings config accounts-data)) (.saveAccountAndLogin native-status multiaccount-data password settings config accounts-data))

View File

@ -333,6 +333,20 @@
(update-in [:wallet :accounts] dissoc deleted-address))} (update-in [:wallet :accounts] dissoc deleted-address))}
(navigation/pop-to-root :shell-stack)))) (navigation/pop-to-root :shell-stack))))
(re-frame/reg-fx
:key-storage/delete-imported-key
(fn [{:keys [key-uid address password on-success on-error]}]
(let [hashed-pass (ethereum/sha3 (security/safe-unmask-data password))]
(native-module/delete-imported-key
key-uid
(string/lower-case (subs address 2))
hashed-pass
(fn [result]
(let [{:keys [error]} (types/json->clj result)]
(if-not (string/blank? error)
(on-error error)
(on-success))))))))
(rf/defn delete-account-key (rf/defn delete-account-key
{:events [:wallet.accounts/delete-key]} {:events [:wallet.accounts/delete-key]}
[{:keys [db] :as cofx} account password on-error] [{:keys [db] :as cofx} account password on-error]

View File

@ -0,0 +1,76 @@
(ns status-im2.common.biometric.events
(:require [react-native.platform :as platform]
[react-native.touch-id :as touch-id]
[native-module.core :as native-module]
[status-im2.common.keychain.events :as keychain]
[re-frame.core :as re-frame]
[utils.re-frame :as rf]
[utils.i18n :as i18n]))
(def android-device-blacklisted?
(= (:brand (native-module/get-device-model-info)) "bannedbrand"))
(defn get-supported-type
[callback]
(cond platform/ios? (touch-id/get-supported-type callback)
platform/android? (if android-device-blacklisted?
(callback nil)
(touch-id/get-supported-type callback))
:else (callback nil)))
(defn get-label-by-type
[biometric-type]
(case biometric-type
:fingerprint (i18n/label :t/biometric-fingerprint)
:FaceID (i18n/label :t/biometric-faceid)
(i18n/label :t/biometric-touchid)))
(re-frame/reg-fx
:biometric/get-supported-biometric-type
(fn []
;;NOTE: if we can't save user password, we can't use biometric
(keychain/can-save-user-password?
(fn [can-save?]
(when can-save?
(get-supported-type #(rf/dispatch [:biometric/get-supported-biometric-type-success %])))))))
(rf/defn get-supported-biometric-auth-success
{:events [:biometric/get-supported-biometric-type-success]}
[{:keys [db]} supported-type]
{:db (assoc db :biometric/supported-type supported-type)})
(rf/defn show-message
[_ code]
(let [content (if (#{"NOT_AVAILABLE" "NOT_ENROLLED"} code)
(i18n/label :t/grant-face-id-permissions)
(when-not (or (= code "USER_CANCELED") (= code "USER_FALLBACK"))
(i18n/label :t/biometric-auth-error {:code code})))]
(when content
{:utils/show-popup
{:title (i18n/label :t/biometric-auth-login-error-title)
:content content}})))
(re-frame/reg-fx
:biometric/authenticate
(fn [options]
(touch-id/authenticate
(merge
{:reason (i18n/label :t/biometric-auth-reason-login)
:options (merge
{:unifiedErrors true}
(when platform/ios?
{:passcodeFallback false
:fallbackLabel (i18n/label :t/biometric-auth-login-ios-fallback-label)})
(when platform/android?
{:title (i18n/label :t/biometric-auth-android-title)
:imageColor :blue
:imageErrorColor :red
:sensorDescription (i18n/label :t/biometric-auth-android-sensor-desc)
:sensorErrorDescription (i18n/label :t/biometric-auth-android-sensor-error-desc)
:cancelText (i18n/label :cancel)}))}
options))))
(rf/defn authenticate
{:events [:biometric/authenticate]}
[_ opts]
{:biometric/authenticate opts})

View File

@ -0,0 +1,110 @@
(ns status-im2.common.keychain.events
(:require [taoensso.timbre :as log]
[react-native.platform :as platform]
[react-native.keychain :as keychain]
[re-frame.core :as re-frame]
[utils.re-frame :as rf]
[oops.core :as oops]
[native-module.core :as native-module]
[utils.security.core :as security]))
(defn- check-conditions
[callback & checks]
(if (= (count checks) 0)
(callback true)
(let [current-check-fn (first checks)
process-check-result (fn [callback-success callback-fail]
(fn [current-check-passed?]
(if current-check-passed?
(callback-success)
(callback-fail))))]
(current-check-fn (process-check-result
#(apply (partial check-conditions callback) (rest checks))
#(callback false))))))
;; These helpers check if the device is okay to use for password storage
;; They resolve callback with `true` if the check is passed, with `false` otherwise.
;; Android only
(defn- device-not-rooted?
[callback]
(native-module/rooted-device? (fn [rooted?] (callback (not rooted?)))))
(defn can-save-user-password?
[callback]
(log/debug "[keychain] can-save-user-password?")
(cond
platform/ios?
(check-conditions callback keychain/device-encrypted?)
platform/android?
(check-conditions callback keychain/secure-hardware-available? device-not-rooted?)
:else
(callback false)))
(def auth-method-biometric "biometric")
(def auth-method-biometric-prepare "biometric-prepare")
(def auth-method-none "none")
(defn save-auth-method!
[key-uid method]
(keychain/save-credentials
(str key-uid "-auth")
key-uid
method
#(when-not %
(log/error
(str "Error while saving auth method."
" "
"The app will continue to work normally, "
"but you will have to login again next time you launch it.")))))
(re-frame/reg-fx
:keychain/save-auth-method
(fn [[key-uid method]]
(when-not (empty? key-uid) ; key-uid may be nil after restore from local pairing
(save-auth-method! key-uid method))))
(rf/defn save-auth-method
[{:keys [db]} key-uid method]
{:db (assoc db :auth-method method)
:keychain/save-auth-method [key-uid method]})
(re-frame/reg-fx
:keychain/get-auth-method
(fn [[key-uid callback]]
(can-save-user-password?
(fn [can-save?]
(if can-save?
(keychain/get-credentials
(str key-uid "-auth")
#(callback (if % (oops/oget % "password") auth-method-none)))
(callback nil))))))
(defn save-user-password!
[key-uid password]
(keychain/save-credentials key-uid key-uid (security/safe-unmask-data password) #()))
(re-frame/reg-fx
:keychain/get-user-password
(fn [[key-uid callback]]
(keychain/get-credentials
key-uid
#(if % (callback (security/mask-data (oops/oget % "password"))) (callback nil)))))
(rf/defn get-user-password
[_ key-uid callback]
{:keychain/get-user-password [key-uid callback]})
(re-frame/reg-fx
:keychain/clear-user-password
(fn [key-uid]
(keychain/reset-credentials key-uid)))
(re-frame/reg-fx
:keychain/save-password-and-auth-method
(fn [{:keys [key-uid masked-password on-success on-error]}]
(-> (save-user-password! key-uid masked-password)
(.then #(save-auth-method! key-uid auth-method-biometric))
(.then #(when on-success (on-success)))
(.catch #(when on-error (on-error %))))))

View File

@ -5,13 +5,13 @@
[react-native.safe-area :as safe-area] [react-native.safe-area :as safe-area]
[status-im2.contexts.onboarding.enable-biometrics.style :as style] [status-im2.contexts.onboarding.enable-biometrics.style :as style]
[status-im2.contexts.onboarding.common.navigation-bar.view :as navigation-bar] [status-im2.contexts.onboarding.common.navigation-bar.view :as navigation-bar]
[status-im.multiaccounts.biometric.core :as biometric]
[utils.i18n :as i18n] [utils.i18n :as i18n]
[utils.re-frame :as rf] [utils.re-frame :as rf]
[status-im2.common.resources :as resources] [status-im2.common.resources :as resources]
[status-im2.common.parallax.view :as parallax] [status-im2.common.parallax.view :as parallax]
[status-im2.contexts.onboarding.common.background.view :as background] [status-im2.contexts.onboarding.common.background.view :as background]
[status-im2.common.parallax.whitelist :as whitelist])) [status-im2.common.parallax.whitelist :as whitelist]
[status-im2.common.biometric.events :as biometric]))
(defn page-title (defn page-title
[] []
@ -24,8 +24,8 @@
(defn enable-biometrics-buttons (defn enable-biometrics-buttons
[insets] [insets]
(let [supported-biometric (rf/sub [:supported-biometric-auth]) (let [supported-biometric-type (rf/sub [:biometric/supported-type])
bio-type-label (biometric/get-label supported-biometric) bio-type-label (biometric/get-label-by-type supported-biometric-type)
profile-color (:color (rf/sub [:onboarding-2/profile]))] profile-color (:color (rf/sub [:onboarding-2/profile]))]
[rn/view {:style (style/buttons insets)} [rn/view {:style (style/buttons insets)}
[quo/button [quo/button

View File

@ -1,21 +1,15 @@
(ns status-im2.contexts.onboarding.events (ns status-im2.contexts.onboarding.events
(:require (:require [native-module.core :as native-module]
[clojure.string :as string]
[native-module.core :as native-module]
[re-frame.core :as re-frame] [re-frame.core :as re-frame]
[status-im.ethereum.core :as ethereum]
[status-im.utils.types :as types] [status-im.utils.types :as types]
[status-im2.config :as config]
[status-im2.constants :as constants] [status-im2.constants :as constants]
[taoensso.timbre :as log] [taoensso.timbre :as log]
[utils.i18n :as i18n] [utils.i18n :as i18n]
[utils.re-frame :as rf] [utils.re-frame :as rf]
[utils.security.core :as security])) [utils.security.core :as security]
[status-im2.contexts.profile.create.events :as profile.create]
(re-frame/reg-fx [status-im2.contexts.profile.recover.events :as profile.recover]
:multiaccount/create-account-and-login [status-im2.common.biometric.events :as biometric]))
(fn [request]
(native-module/create-account-and-login request)))
(re-frame/reg-fx (re-frame/reg-fx
:multiaccount/validate-mnemonic :multiaccount/validate-mnemonic
@ -28,11 +22,6 @@
(when on-error (on-error error)) (when on-error (on-error error))
(on-success mnemonic keyUID))))))) (on-success mnemonic keyUID)))))))
(re-frame/reg-fx
:multiaccount/restore-account-and-login
(fn [request]
(native-module/restore-account-and-login request)))
(rf/defn profile-data-set (rf/defn profile-data-set
{:events [:onboarding-2/profile-data-set]} {:events [:onboarding-2/profile-data-set]}
[{:keys [db]} onboarding-data] [{:keys [db]} onboarding-data]
@ -42,82 +31,34 @@
(rf/defn enable-biometrics (rf/defn enable-biometrics
{:events [:onboarding-2/enable-biometrics]} {:events [:onboarding-2/enable-biometrics]}
[_] [_]
{:biometric-auth/authenticate [#(rf/dispatch [:onboarding-2/biometrics-done %]) {}]}) {:biometric/authenticate {:on-success #(rf/dispatch [:onboarding-2/biometrics-done])
:on-fail #(rf/dispatch [:onboarding-2/biometrics-fail %])}})
(rf/defn show-biometrics-message
[cofx bioauth-message bioauth-code]
(let [content (or (when (get #{"NOT_AVAILABLE" "NOT_ENROLLED"} bioauth-code)
(i18n/label :t/grant-face-id-permissions))
bioauth-message)]
(when content
{:utils/show-popup
{:title (i18n/label :t/biometric-auth-login-error-title)
:content content}})))
(rf/defn biometrics-done (rf/defn biometrics-done
{:events [:onboarding-2/biometrics-done]} {:events [:onboarding-2/biometrics-done]}
[{:keys [db] :as cofx} {:keys [bioauth-success bioauth-message bioauth-code]}] [{:keys [db]}]
(if bioauth-success
{:db (assoc-in db [:onboarding-2/profile :auth-method] constants/auth-method-biometric) {:db (assoc-in db [:onboarding-2/profile :auth-method] constants/auth-method-biometric)
:dispatch [:onboarding-2/create-account-and-login]} :dispatch [:onboarding-2/create-account-and-login]})
(show-biometrics-message cofx bioauth-message bioauth-code)))
(defn strip-file-prefix (rf/defn biometrics-fail
[path] {:events [:onboarding-2/biometrics-fail]}
(when path [cofx code]
(string/replace-first path "file://" ""))) (biometric/show-message cofx code))
(rf/defn create-account-and-login (rf/defn create-account-and-login
{:events [:onboarding-2/create-account-and-login]} {:events [:onboarding-2/create-account-and-login]}
[{:keys [db]}] [{:keys [db] :as cofx}]
(let [{:keys [display-name (let [{:keys [display-name seed-phrase password image-path color] :as profile}
seed-phrase (:onboarding-2/profile db)]
password (rf/merge cofx
image-path {:dispatch [:navigate-to :generating-keys]
color]} (:onboarding-2/profile db)
log-enabled? (boolean (not-empty config/log-level))
effect (if seed-phrase
:multiaccount/restore-account-and-login
:multiaccount/create-account-and-login)
request {:displayName display-name
:deviceName (native-module/get-installation-name)
:password (ethereum/sha3 (security/safe-unmask-data
password))
:mnemonic (when seed-phrase
(security/safe-unmask-data seed-phrase))
:imagePath (strip-file-prefix image-path)
:customizationColor color
:backupDisabledDataDir (native-module/backup-disabled-data-dir)
:rootKeystoreDir (native-module/keystore-dir)
;; Temporary fix until https://github.com/status-im/status-go/issues/3024 is
;; resolved
:wakuV2Nameserver "1.1.1.1"
:logLevel (when log-enabled? config/log-level)
:logEnabled log-enabled?
:logFilePath (native-module/log-file-directory)
:openseaAPIKey config/opensea-api-key
:verifyTransactionURL config/verify-transaction-url
:verifyENSURL config/verify-ens-url
:verifyENSContractAddress config/verify-ens-contract-address
:verifyTransactionChainID config/verify-transaction-chain-id
:upstreamConfig config/default-network-rpc-url
:networkId config/default-network-id
:poktToken config/POKT_TOKEN
:infuraToken config/INFURA_TOKEN
:alchemyOptimismMainnetToken config/ALCHEMY_OPTIMISM_MAINNET_TOKEN
:alchemyOptimismGoerliToken config/ALCHEMY_OPTIMISM_GOERLI_TOKEN
:alchemyArbitrumMainnetToken config/ALCHEMY_ARBITRUM_MAINNET_TOKEN
:alchemyArbitrumGoerliToken config/ALCHEMY_ARBITRUM_GOERLI_TOKEN
:currentNetwork config/default-network
:previewPrivacy config/blank-preview?}]
{effect request
:dispatch [:navigate-to :generating-keys]
:db (-> db :db (-> db
(dissoc :profile/login) (dissoc :profile/login)
(dissoc :auth-method) (dissoc :auth-method)
(assoc :onboarding-2/new-account? true))})) (assoc :onboarding-2/new-account? true))}
(if seed-phrase
(profile.recover/recover-profile-and-login profile)
(profile.create/create-profile-and-login profile)))))
(rf/defn on-delete-profile-success (rf/defn on-delete-profile-success
{:events [:onboarding-2/on-delete-profile-success]} {:events [:onboarding-2/on-delete-profile-success]}
@ -173,13 +114,11 @@
[{:keys [db]}] [{:keys [db]}]
(let [masked-password (get-in db [:onboarding-2/profile :password]) (let [masked-password (get-in db [:onboarding-2/profile :password])
key-uid (get-in db [:profile/profile :key-uid]) key-uid (get-in db [:profile/profile :key-uid])
biometric-enabled? (= biometric-enabled? (= (get-in db [:onboarding-2/profile :auth-method])
constants/auth-method-biometric constants/auth-method-biometric)]
(get-in db [:onboarding-2/profile :auth-method]))]
(cond-> {:dispatch [:navigate-to :identifiers]} (cond-> {:dispatch [:navigate-to :identifiers]}
biometric-enabled? biometric-enabled?
(assoc :biometric/enable-and-save-password (assoc :keychain/save-password-and-auth-method
{:key-uid key-uid {:key-uid key-uid
:masked-password masked-password :masked-password masked-password
:on-success #(log/debug "successfully saved biometric") :on-success #(log/debug "successfully saved biometric")

View File

@ -18,18 +18,13 @@
(reset! scan-sync-code/dismiss-animations reset-top-animation-fn)) (reset! scan-sync-code/dismiss-animations reset-top-animation-fn))
:animations-duration constants/onboarding-modal-animation-duration :animations-duration constants/onboarding-modal-animation-duration
:animations-delay constants/onboarding-modal-animation-delay :animations-delay constants/onboarding-modal-animation-delay
:top-card {:on-press (fn [] :top-card {:on-press #(debounce/dispatch-and-chill [:open-modal
(debounce/dispatch-and-chill [:open-modal
:sign-in-intro] :sign-in-intro]
2000) 2000)
(rf/dispatch [:hide-terms-of-services-opt-in-screen]))
:heading (i18n/label :t/sign-in) :heading (i18n/label :t/sign-in)
:animated-heading (i18n/label :t/sign-in-by-syncing) :animated-heading (i18n/label :t/sign-in-by-syncing)
:accessibility-label :already-use-status-button} :accessibility-label :already-use-status-button}
:bottom-card {:on-press (fn [] :bottom-card {:on-press #(rf/dispatch [:navigate-to :new-to-status])
(rf/dispatch [:navigate-to :new-to-status])
(rf/dispatch
[:hide-terms-of-services-opt-in-screen]))
:heading (i18n/label :t/new-to-status) :heading (i18n/label :t/new-to-status)
:accessibility-label :new-to-status-button}} :accessibility-label :new-to-status-button}}
[quo/text [quo/text

View File

@ -0,0 +1,42 @@
(ns status-im2.contexts.profile.config
(:require [status-im2.config :as config]
[native-module.core :as native-module]
[clojure.string :as string]))
(defn login
[]
{;; Temporary fix until https://github.com/status-im/status-go/issues/3024 is
;; resolved
:wakuV2Nameserver "1.1.1.1"
:openseaAPIKey config/opensea-api-key
:poktToken config/POKT_TOKEN
:infuraToken config/INFURA_TOKEN
:alchemyOptimismMainnetToken config/ALCHEMY_OPTIMISM_MAINNET_TOKEN
:alchemyOptimismGoerliToken config/ALCHEMY_OPTIMISM_GOERLI_TOKEN
:alchemyArbitrumMainnetToken config/ALCHEMY_ARBITRUM_MAINNET_TOKEN
:alchemyArbitrumGoerliToken config/ALCHEMY_ARBITRUM_GOERLI_TOKEN})
(defn create
[]
(let [log-enabled? (boolean (not-empty config/log-level))]
(merge (login)
{:deviceName (native-module/get-installation-name)
:backupDisabledDataDir (native-module/backup-disabled-data-dir)
:rootKeystoreDir (native-module/keystore-dir)
:logLevel (when log-enabled? config/log-level)
:logEnabled log-enabled?
:logFilePath (native-module/log-file-directory)
:verifyTransactionURL config/verify-transaction-url
:verifyENSURL config/verify-ens-url
:verifyENSContractAddress config/verify-ens-contract-address
:verifyTransactionChainID config/verify-transaction-chain-id
:upstreamConfig config/default-network-rpc-url
:networkId config/default-network-id
:currentNetwork config/default-network
:previewPrivacy config/blank-preview?})))
(defn strip-file-prefix
[path]
(when path
(string/replace-first path "file://" "")))

View File

@ -0,0 +1,23 @@
(ns status-im2.contexts.profile.create.events
(:require [utils.re-frame :as rf]
[native-module.core :as native-module]
[status-im2.contexts.profile.config :as profile.config]
[status-im.ethereum.core :as ethereum]
[utils.security.core :as security]
[re-frame.core :as re-frame]))
(re-frame/reg-fx
::create-profile-and-login
(fn [request]
;;"node.login" signal will be triggered as a callback
(native-module/create-account-and-login request)))
(rf/defn create-profile-and-login
{:events [:profile.create/create-and-login]}
[_ {:keys [display-name password image-path color]}]
{::create-profile-and-login
(merge (profile.config/create)
{:displayName display-name
:password (ethereum/sha3 (security/safe-unmask-data password))
:imagePath (profile.config/strip-file-prefix image-path)
:customizationColor color})})

View File

@ -1,6 +1,15 @@
(ns status-im2.contexts.profile.events (ns status-im2.contexts.profile.events
(:require [utils.re-frame :as rf] (:require [utils.re-frame :as rf]
[clojure.string :as string])) [clojure.string :as string]
[re-frame.core :as re-frame]
[native-module.core :as native-module]
[status-im2.navigation.events :as navigation]
[status-im2.contexts.profile.login.events :as login]))
(re-frame/reg-fx
:profile/get-profiles-overview
(fn [callback]
(native-module/open-accounts callback)))
(rf/defn profile-selected (rf/defn profile-selected
{:events [:profile/profile-selected]} {:events [:profile/profile-selected]}
@ -11,6 +20,12 @@
(assoc :key-uid key-uid) (assoc :key-uid key-uid)
(dissoc :error :password)))}) (dissoc :error :password)))})
(rf/defn init-profiles-overview
[{:keys [db] :as cofx} profiles key-uid]
(rf/merge cofx
{:db (assoc db :profile/profiles-overview profiles)}
(profile-selected key-uid)))
(defn rpc->profiles-overview (defn rpc->profiles-overview
[{:keys [customizationColor keycard-pairing] :as profile}] [{:keys [customizationColor keycard-pairing] :as profile}]
(-> profile (-> profile
@ -18,18 +33,24 @@
(assoc :customization-color (keyword customizationColor)) (assoc :customization-color (keyword customizationColor))
(assoc :keycard-pairing (when-not (string/blank? keycard-pairing) keycard-pairing)))) (assoc :keycard-pairing (when-not (string/blank? keycard-pairing) keycard-pairing))))
(rf/defn init-profiles-overview (defn reduce-profiles
[{:keys [db] :as cofx} profiles] [profiles]
(let [profiles (reduce (reduce
(fn [acc {:keys [key-uid] :as profile}] (fn [acc {:keys [key-uid] :as profile}]
(assoc acc key-uid (rpc->profiles-overview profile))) (assoc acc key-uid (rpc->profiles-overview profile)))
{} {}
profiles) profiles))
(rf/defn get-profiles-overview-success
{:events [:profile/get-profiles-overview-success]}
[cofx profiles-overview]
(if (seq profiles-overview)
(let [profiles (reduce-profiles profiles-overview)
{:keys [key-uid]} (first (sort-by :timestamp > (vals profiles)))] {:keys [key-uid]} (first (sort-by :timestamp > (vals profiles)))]
(rf/merge cofx (rf/merge cofx
{:db (assoc db :profile/profiles-overview profiles) (navigation/init-root :profiles)
:keychain/get-auth-method (init-profiles-overview profiles key-uid)
[key-uid #(rf/dispatch [:multiaccounts.login/get-auth-method-success % key-uid])] ;;we check if biometric is available, and try to login with it,
:dispatch-n [[:get-opted-in-to-new-terms-of-service] ;;if succeed "node.login" signal will be triggered
[:load-information-box-states]]} (login/login-with-biometric-if-available key-uid)))
(profile-selected key-uid)))) (navigation/init-root cofx :intro)))

View File

@ -1,3 +1,71 @@
(ns status-im2.contexts.profile.login.events) (ns status-im2.contexts.profile.login.events
(:require [utils.re-frame :as rf]
[status-im.ethereum.core :as ethereum]
[utils.security.core :as security]
[re-frame.core :as re-frame]
[native-module.core :as native-module]
[status-im2.navigation.events :as navigation]
[status-im2.common.keychain.events :as keychain]
[status-im2.common.biometric.events :as biometric]
[status-im2.contexts.profile.config :as profile.config]))
(re-frame/reg-fx
::login
(fn [[key-uid hashed-password]]
;;"node.login" signal will be triggered as a callback
(native-module/login-account
(assoc (profile.config/login) :keyUid key-uid :password hashed-password))))
(rf/defn login
{:events [:profile.login/login]}
[{:keys [db]}]
(let [{:keys [key-uid password]} (:profile/login db)]
{:db (assoc-in db [:profile/login :processing] true)
::login [key-uid (ethereum/sha3 (security/safe-unmask-data password))]}))
(rf/defn login-local-paired-user
{:events [:profile.login/local-paired-user]}
[{:keys [db]}]
(let [{:keys [key-uid password]} (get-in db [:syncing :profile])]
{::login [key-uid password]}))
(rf/defn login-with-biometric-if-available
{:events [:profile.login/login-with-biometric-if-available]}
[_ key-uid]
{:keychain/get-auth-method [key-uid
#(rf/dispatch [:profile.login/get-auth-method-success % key-uid])]})
(rf/defn get-auth-method-success
{:events [:profile.login/get-auth-method-success]}
[{:keys [db]} auth-method]
(merge {:db (assoc db :auth-method auth-method)}
(when (= auth-method keychain/auth-method-biometric)
{:biometric/authenticate
{:on-success #(rf/dispatch [:profile.login/biometric-success])
:on-faile #(rf/dispatch [:profile.login/biometric-auth-fail])}})))
(rf/defn biometric-auth-success
{:events [:profile.login/biometric-success]}
[{:keys [db] :as cofx}]
(let [key-uid (get-in db [:profile/login :key-uid])]
(keychain/get-user-password cofx
key-uid
#(rf/dispatch [:profile.login/get-user-password-success %]))))
;; result of :keychain/get-auth-method above
(rf/defn get-user-password-success
{:events [:profile.login/get-user-password-success]}
[{:keys [db] :as cofx} password]
(when password
(rf/merge
cofx
{:db (assoc-in db [:profile/login :password] password)}
(navigation/init-root :progress)
(login))))
(rf/defn biometric-auth-fail
{:events [:profile.login/biometric-auth-fail]}
[{:keys [db] :as cofx} code]
(rf/merge cofx
(navigation/init-root :profiles)
(biometric/show-message code)))

View File

@ -1,4 +1,4 @@
(ns status-im2.contexts.onboarding.profiles.style (ns status-im2.contexts.profile.profiles.style
(:require [quo2.foundations.colors :as colors])) (:require [quo2.foundations.colors :as colors]))
;; Profiles Section ;; Profiles Section

View File

@ -1,11 +1,11 @@
(ns status-im2.contexts.onboarding.profiles.view (ns status-im2.contexts.profile.profiles.view
(:require [native-module.core :as native-module] (:require [native-module.core :as native-module]
[quo2.core :as quo] [quo2.core :as quo]
[react-native.core :as rn] [react-native.core :as rn]
[reagent.core :as reagent] [reagent.core :as reagent]
[status-im2.common.confirmation-drawer.view :as confirmation-drawer] [status-im2.common.confirmation-drawer.view :as confirmation-drawer]
[status-im2.contexts.onboarding.common.background.view :as background] [status-im2.contexts.onboarding.common.background.view :as background]
[status-im2.contexts.onboarding.profiles.style :as style] [status-im2.contexts.profile.profiles.style :as style]
[taoensso.timbre :as log] [taoensso.timbre :as log]
[utils.i18n :as i18n] [utils.i18n :as i18n]
[utils.re-frame :as rf] [utils.re-frame :as rf]
@ -15,10 +15,6 @@
[react-native.safe-area :as safe-area] [react-native.safe-area :as safe-area]
[clojure.string :as string])) [clojure.string :as string]))
(defn login-multiaccount
[]
(rf/dispatch [:multiaccounts.login.ui/password-input-submitted]))
(defn new-account-options (defn new-account-options
[] []
[quo/action-drawer [quo/action-drawer
@ -76,8 +72,10 @@
:shell? true}])) :shell? true}]))
(defn profile-card (defn profile-card
[{:keys [name key-uid customization-color keycard-pairing last-index set-hide-profiles]} [{:keys [name key-uid customization-color keycard-pairing]}
index] index
_
{:keys [last-index set-hide-profiles]}]
(let [last-item? (= last-index index) (let [last-item? (= last-index index)
profile-picture (rf/sub [:profile/login-profiles-picture key-uid])] profile-picture (rf/sub [:profile/login-profiles-picture key-uid])]
[quo/profile-card [quo/profile-card
@ -101,11 +99,7 @@
(defn profiles-section (defn profiles-section
[{:keys [set-hide-profiles]}] [{:keys [set-hide-profiles]}]
(let [multiaccounts (vals (rf/sub [:profile/profiles-overview])) (let [profiles (vals (rf/sub [:profile/profiles-overview]))]
profiles-data (map #(assoc %
:last-index (- (count multiaccounts) 1)
:set-hide-profiles set-hide-profiles)
multiaccounts)]
[rn/view [rn/view
{:style style/profiles-container} {:style style/profiles-container}
[rn/view [rn/view
@ -123,9 +117,11 @@
:accessibility-label :show-new-account-options} :accessibility-label :show-new-account-options}
:main-icons/add]] :main-icons/add]]
[rn/flat-list [rn/flat-list
{:data (sort-by :timestamp > profiles-data) {:data (sort-by :timestamp > profiles)
:key-fn :key-uid :key-fn :key-uid
:content-container-style {:padding-bottom 20} :content-container-style {:padding-bottom 20}
:render-data {:last-index (- (count profiles) 1)
:set-hide-profiles set-hide-profiles}
:render-fn profile-card}]])) :render-fn profile-card}]]))
(defn forget-password-doc (defn forget-password-doc
@ -175,19 +171,23 @@
[quo/text {:size :paragraph-2} [quo/text {:size :paragraph-2}
(i18n/label :t/forgot-your-password-info-create-new-password-description)]]]]]) (i18n/label :t/forgot-your-password-info-create-new-password-description)]]]]])
(defn- get-error-message
[error]
(if (and (some? error)
(or (= error "file is not a database")
(string/starts-with? error "failed to set ")
(string/starts-with? error "Failed")))
(i18n/label :t/oops-wrong-password)
error))
(defn login-section (defn login-section
[{:keys [set-show-profiles]}] [{:keys [set-show-profiles]}]
(let [{:keys [error processing password]} (rf/sub [:profile/login]) (let [{:keys [error processing password]} (rf/sub [:profile/login])
{:keys [key-uid name customization-color]} (rf/sub [:profile/login-profile]) {:keys [key-uid name customization-color]} (rf/sub [:profile/login-profile])
sign-in-enabled? (rf/sub [:sign-in-enabled?]) sign-in-enabled? (rf/sub [:sign-in-enabled?])
profile-picture (rf/sub [:profile/login-profiles-picture key-uid]) profile-picture (rf/sub [:profile/login-profiles-picture key-uid])
error (if (and (some? error) error (get-error-message error)
(or (= error "file is not a database") login-multiaccount #(rf/dispatch [:profile.login/login])]
(string/starts-with? error
"failed to set ")
(string/starts-with? error "Failed")))
(i18n/label :t/oops-wrong-password)
error)]
[rn/keyboard-avoiding-view [rn/keyboard-avoiding-view
{:style style/login-container {:style style/login-container
:keyboardVerticalOffset (- (safe-area/get-bottom))} :keyboardVerticalOffset (- (safe-area/get-bottom))}
@ -257,7 +257,7 @@
:style {:margin-bottom (+ (safe-area/get-bottom) 12)}} :style {:margin-bottom (+ (safe-area/get-bottom) 12)}}
(i18n/label :t/log-in)]])) (i18n/label :t/log-in)]]))
(defn views (defn view
[] []
(let [show-profiles? (reagent/atom false) (let [show-profiles? (reagent/atom false)
set-show-profiles #(reset! show-profiles? true) set-show-profiles #(reset! show-profiles? true)

View File

@ -0,0 +1,24 @@
(ns status-im2.contexts.profile.recover.events
(:require [utils.security.core :as security]
[status-im.ethereum.core :as ethereum]
[status-im2.contexts.profile.config :as profile.config]
[utils.re-frame :as rf]
[re-frame.core :as re-frame]
[native-module.core :as native-module]))
(re-frame/reg-fx
::restore-profile-and-login
(fn [request]
;;"node.login" signal will be triggered as a callback
(native-module/restore-account-and-login request)))
(rf/defn recover-profile-and-login
{:events [:profile.recover/recover-and-login]}
[_ {:keys [display-name password image-path color seed-phrase]}]
{::restore-profile-and-login
(merge (profile.config/create)
{:displayName display-name
:mnemonic (security/safe-unmask-data seed-phrase)
:password (ethereum/sha3 (security/safe-unmask-data password))
:imagePath (profile.config/strip-file-prefix image-path)
:customizationColor color})})

View File

@ -1,62 +1,30 @@
(ns status-im2.events (ns status-im2.events
(:require [re-frame.core :as re-frame] (:require [status-im2.common.json-rpc.events]
[status-im2.common.json-rpc.events]
[status-im2.common.toasts.events] [status-im2.common.toasts.events]
[status-im2.contexts.add-new-contact.events] [status-im2.contexts.add-new-contact.events]
status-im2.contexts.onboarding.events status-im2.contexts.onboarding.events
[status-im.bottom-sheet.events] [status-im.bottom-sheet.events]
[status-im2.db :as db] [status-im2.db :as db]
[utils.re-frame :as rf] [utils.re-frame :as rf]
[utils.datetime :as datetime]
status-im2.contexts.shell.share.events status-im2.contexts.shell.share.events
status-im2.contexts.syncing.events status-im2.contexts.syncing.events
status-im2.contexts.chat.events status-im2.contexts.chat.events
status-im2.common.password-authentication.events status-im2.common.password-authentication.events
status-im2.contexts.communities.overview.events status-im2.contexts.communities.overview.events
status-im2.contexts.chat.photo-selector.events status-im2.contexts.chat.photo-selector.events
[status-im2.contexts.profile.events :as profile.events]
status-im2.common.theme.events status-im2.common.theme.events
[status-im2.navigation.events :as navigation] status-im2.contexts.profile.events
[native-module.core :as native-module])) [status-im.keycard.core :as keycard]))
(re-frame/reg-cofx :now (fn [coeffects _] (assoc coeffects :now (datetime/timestamp))))
(rf/defn initialize-app-db
"Initialize db to initial state"
[{{:keys [keycard supported-biometric-auth goto-key-storage?]
:network/keys [type]
:keycard/keys [banner-hidden]}
:db}]
{:db (assoc db/app-db
:network/type type
:keycard/banner-hidden banner-hidden
:keycard (dissoc keycard :secrets :pin :application-info)
:supported-biometric-auth supported-biometric-auth
:goto-key-storage? goto-key-storage?)})
(re-frame/reg-fx
:profile/get-profiles-overview
(fn [callback]
(native-module/open-accounts callback)))
(rf/defn get-profiles-overview-success
{:events [:profile/get-profiles-overview-success]}
[cofx profiles-overview]
(if (seq profiles-overview)
(profile.events/init-profiles-overview cofx profiles-overview)
(navigation/init-root cofx :intro)))
(rf/defn start-app (rf/defn start-app
{:events [:app-started]} {:events [:app-started]}
[cofx] [cofx]
(rf/merge cofx (rf/merge cofx
{:theme/init-theme nil {:db db/app-db
:biometric/get-supported-biometric-auth nil :theme/init-theme nil
:network/listen-to-network-info nil :network/listen-to-network-info nil
:keycard/register-card-events nil :biometric/get-supported-biometric-type nil
:keycard/check-nfc-support nil ;;app starting flow continues in get-profiles-overview
:keycard/check-nfc-enabled nil :profile/get-profiles-overview #(rf/dispatch
:keycard/retrieve-pairings nil
:profile/get-profiles-overview #(re-frame/dispatch
[:profile/get-profiles-overview-success %])} [:profile/get-profiles-overview-success %])}
(initialize-app-db))) (keycard/init)))

View File

@ -64,15 +64,6 @@
{:root {:stack {:children [{:component {:name :onboarding-notification {:root {:stack {:children [{:component {:name :onboarding-notification
:id :onboarding-notification :id :onboarding-notification
:options (options/statusbar-and-navbar-root)}}] :options (options/statusbar-and-navbar-root)}}]
:options (merge (options/default-root)
(options/statusbar-and-navbar-root)
{:topBar (assoc (options/topbar-options) :visible false)})}}}
;; TERMS OF SERVICE
:tos
{:root {:stack {:children [{:component {:name :force-accept-tos
:id :force-accept-tos
:options (get-screen-options :force-accept-tos)}}]
:options (merge (options/default-root) :options (merge (options/default-root)
(options/statusbar-and-navbar-root) (options/statusbar-and-navbar-root)
{:topBar (assoc (options/topbar-options) :visible false)})}}}}) {:topBar (assoc (options/topbar-options) :visible false)})}}}})

View File

@ -19,7 +19,7 @@
[status-im2.contexts.onboarding.sign-in.view :as sign-in] [status-im2.contexts.onboarding.sign-in.view :as sign-in]
[status-im2.contexts.onboarding.generating-keys.view :as generating-keys] [status-im2.contexts.onboarding.generating-keys.view :as generating-keys]
[status-im2.contexts.onboarding.enter-seed-phrase.view :as enter-seed-phrase] [status-im2.contexts.onboarding.enter-seed-phrase.view :as enter-seed-phrase]
[status-im2.contexts.onboarding.profiles.view :as profiles] [status-im2.contexts.profile.profiles.view :as profiles]
[status-im2.contexts.quo-preview.main :as quo.preview] [status-im2.contexts.quo-preview.main :as quo.preview]
[status-im2.contexts.shell.jump-to.view :as shell] [status-im2.contexts.shell.jump-to.view :as shell]
[status-im2.contexts.syncing.scan-sync-code-page.view :as scan-sync-code-page] [status-im2.contexts.syncing.scan-sync-code-page.view :as scan-sync-code-page]
@ -143,7 +143,7 @@
{:name :profiles {:name :profiles
:options {:theme :dark :options {:theme :dark
:layout options/onboarding-layout} :layout options/onboarding-layout}
:component profiles/views} :component profiles/view}
{:name :new-to-status {:name :new-to-status
:options {:theme :dark :options {:theme :dark

View File

@ -228,12 +228,6 @@
(fn [network] (fn [network]
(tokens/native-currency network))) (tokens/native-currency network)))
(re-frame/reg-sub
:information-box-closed?
:<- [:information-box-states]
(fn [states [_ id]]
(get states id)))
(re-frame/reg-sub (re-frame/reg-sub
:connectivity/state :connectivity/state
:<- [:network-status] :<- [:network-status]

View File

@ -1,80 +0,0 @@
(ns status-im2.subs.onboarding
(:require [quo2.theme :as theme]
[re-frame.core :as re-frame]
[status-im.multiaccounts.recover.core :as recover]
[status-im2.constants :as constants]
[utils.image-server :as image-server]))
(re-frame/reg-sub
:intro-wizard
:<- [:intro-wizard-state]
:<- [:dimensions/window]
(fn [[wizard-state {:keys [width height]}]]
(assoc wizard-state :view-height height :view-width width)))
(re-frame/reg-sub
:intro-wizard/choose-key
:<- [:intro-wizard]
(fn [wizard-state]
(select-keys wizard-state [:multiaccounts :selected-id :view-height])))
(re-frame/reg-sub
:intro-wizard/select-key-storage
:<- [:intro-wizard]
(fn [wizard-state]
(select-keys wizard-state [:selected-storage-type :recovering?])))
(re-frame/reg-sub
:intro-wizard/enter-phrase
:<- [:intro-wizard]
(fn [wizard-state]
(select-keys wizard-state
[:processing?
:passphrase-word-count
:next-button-disabled?
:passphrase-error])))
(re-frame/reg-sub
:intro-wizard/recovery-success
:<- [:intro-wizard]
(fn [wizard-state]
{:pubkey (get-in wizard-state [:derived constants/path-whisper-keyword :public-key])
:compressed-key (get-in wizard-state [:derived constants/path-whisper-keyword :compressed-key])
:name (get-in wizard-state [:derived constants/path-whisper-keyword :name])
:processing? (:processing? wizard-state)}))
(re-frame/reg-sub
:intro-wizard/recover-existing-account?
:<- [:intro-wizard]
:<- [:profile/profiles-overview]
(fn [[intro-wizard multiaccounts]]
(recover/existing-account? (:root-key intro-wizard) multiaccounts)))
(re-frame/reg-sub
:profile/login-profiles-picture
:<- [:profile/profiles-overview]
:<- [:mediaserver/port]
(fn [[multiaccounts port] [_ target-key-uid]]
(let [image-name (-> multiaccounts
(get-in [target-key-uid :images])
first
:type)]
(when image-name
(image-server/get-account-image-uri {:port port
:image-name image-name
:key-uid target-key-uid
:theme (theme/get-theme)
:ring? true})))))
(defn login-profile-keycard-pairing
"Compute the keycard-pairing value of the multiaccount selected for login"
[db _]
(when-let [acc-to-login (-> db :profile/login)]
(-> db
:profile/profiles-overview
(get (:key-uid acc-to-login))
:keycard-pairing)))
(re-frame/reg-sub
:intro-wizard/acc-to-login-keycard-pairing
login-profile-keycard-pairing)

View File

@ -1,38 +0,0 @@
(ns status-im2.subs.onboarding-test
(:require [cljs.test :as t]
[quo2.theme :as theme]
[re-frame.db :as rf-db]
status-im2.subs.onboarding
[test-helpers.unit :as h]
[utils.image-server :as image-server]
[utils.re-frame :as rf]))
(def key-uid "0x1")
(def port "mediaserver-port")
(def cur-theme :current-theme)
(h/deftest-sub :profile/login-profiles-picture
[sub-name]
(with-redefs [image-server/get-account-image-uri identity
theme/get-theme (constantly cur-theme)]
(t/testing "nil when no images"
(swap! rf-db/app-db assoc :profile/profiles-overview {key-uid {}})
(t/is (nil? (rf/sub [sub-name key-uid]))))
(t/testing "nil when no key-uid"
(swap! rf-db/app-db assoc :profile/profiles-overview {key-uid {}})
(t/is (nil? (rf/sub [sub-name "0x2"]))))
(t/testing "result from image-server/get-account-image-uri"
(swap!
rf-db/app-db
assoc
:profile/profiles-overview {key-uid {:images [{:type "large"}
{:type "thumbnail"}]}}
:mediaserver/port port)
(t/is (= (rf/sub [sub-name key-uid])
{:port port
:image-name "large"
:key-uid key-uid
:theme cur-theme
:ring? true})))))

View File

@ -23,6 +23,22 @@
(fn [{:keys [customization-color]}] (fn [{:keys [customization-color]}]
(or customization-color constants/profile-default-color))) (or customization-color constants/profile-default-color)))
(re-frame/reg-sub
:profile/login-profiles-picture
:<- [:profile/profiles-overview]
:<- [:mediaserver/port]
(fn [[multiaccounts port] [_ target-key-uid]]
(let [image-name (-> multiaccounts
(get-in [target-key-uid :images])
first
:type)]
(when image-name
(image-server/get-account-image-uri {:port port
:image-name image-name
:key-uid target-key-uid
:theme (theme/get-theme)
:ring? true})))))
(re-frame/reg-sub (re-frame/reg-sub
:multiaccount/public-key :multiaccount/public-key
:<- [:profile/profile] :<- [:profile/profile]

View File

@ -15,7 +15,6 @@
status-im2.subs.mailservers status-im2.subs.mailservers
status-im2.subs.profile status-im2.subs.profile
status-im2.subs.networks status-im2.subs.networks
status-im2.subs.onboarding
status-im2.subs.pairing status-im2.subs.pairing
status-im2.subs.search status-im2.subs.search
status-im2.subs.shell status-im2.subs.shell
@ -66,7 +65,7 @@
(reg-root-key-sub :networks/manage :networks/manage) (reg-root-key-sub :networks/manage :networks/manage)
(reg-root-key-sub :get-pairing-installations :pairing/installations) (reg-root-key-sub :get-pairing-installations :pairing/installations)
(reg-root-key-sub :tooltips :tooltips) (reg-root-key-sub :tooltips :tooltips)
(reg-root-key-sub :supported-biometric-auth :supported-biometric-auth) (reg-root-key-sub :biometric/supported-type :biometric/supported-type)
(reg-root-key-sub :connectivity/ui-status-properties :connectivity/ui-status-properties) (reg-root-key-sub :connectivity/ui-status-properties :connectivity/ui-status-properties)
(reg-root-key-sub :logged-in-since :logged-in-since) (reg-root-key-sub :logged-in-since :logged-in-since)
(reg-root-key-sub :app-state :app-state) (reg-root-key-sub :app-state :app-state)
@ -231,8 +230,6 @@
(reg-root-key-sub :auth-method :auth-method) (reg-root-key-sub :auth-method :auth-method)
(reg-root-key-sub :tos-accept-next-root :tos-accept-next-root)
;; keycard ;; keycard
(reg-root-key-sub :keycard/banner-hidden :keycard/banner-hidden) (reg-root-key-sub :keycard/banner-hidden :keycard/banner-hidden)
@ -290,7 +287,5 @@
(reg-root-key-sub :messenger/started? :messenger/started?) (reg-root-key-sub :messenger/started? :messenger/started?)
(reg-root-key-sub :information-box-states :information-box-states)
; Messages home view -> tabs ; Messages home view -> tabs
(reg-root-key-sub :messages-home/selected-tab :messages-home/selected-tab) (reg-root-key-sub :messages-home/selected-tab :messages-home/selected-tab)

View File

@ -1,6 +1,5 @@
(ns status-im2.subs.subs-test (ns status-im2.subs.subs-test
(:require [cljs.test :refer [deftest is testing]] (:require [cljs.test :refer [deftest is testing]]
[status-im2.subs.onboarding :as onboarding]
[status-im2.subs.wallet.transactions :as wallet.transactions])) [status-im2.subs.wallet.transactions :as wallet.transactions]))
(def transactions (def transactions
@ -23,20 +22,3 @@
(testing "Check if transactions are sorted by date" (testing "Check if transactions are sorted by date"
(is (= (wallet.transactions/group-transactions-by-date transactions) (is (= (wallet.transactions/group-transactions-by-date transactions)
grouped-transactions)))) grouped-transactions))))
(deftest login-profile-keycard-pairing
(testing "returns nil when no :profile/login"
(let [res (onboarding/login-profile-keycard-pairing
{:profile/login nil
:profile/profiles-overview
{"0x1" {:keycard-pairing "keycard-pairing-code"}}}
{})]
(is (nil? res))))
(testing "returns :keycard-pairing when :profile/login is present"
(let [res (onboarding/login-profile-keycard-pairing
{:profile/login {:key-uid "0x1"}
:profile/profiles-overview
{"0x1" {:keycard-pairing "keycard-pairing-code"}}}
{})]
(is (= res "keycard-pairing-code")))))

View File

@ -2,11 +2,14 @@
(:require-macros utils.re-frame) (:require-macros utils.re-frame)
(:require [re-frame.core :as re-frame] (:require [re-frame.core :as re-frame]
[re-frame.interceptor :as interceptor] [re-frame.interceptor :as interceptor]
[taoensso.timbre :as log]) [taoensso.timbre :as log]
[utils.datetime :as datetime])
(:refer-clojure :exclude [merge reduce])) (:refer-clojure :exclude [merge reduce]))
(def handler-nesting-level (atom 0)) (def handler-nesting-level (atom 0))
(re-frame/reg-cofx :now (fn [coeffects _] (assoc coeffects :now (datetime/timestamp))))
(def debug-handlers-names (def debug-handlers-names
"Interceptor which logs debug information to js/console for each event." "Interceptor which logs debug information to js/console for each event."
(interceptor/->interceptor (interceptor/->interceptor