[#21316] Keycard - Allow user to migrate existing Profile to the new … (#21467)

This commit is contained in:
flexsurfer 2024-11-01 12:48:07 +01:00 committed by GitHub
parent 9862abb7eb
commit f0079961f4
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
49 changed files with 783 additions and 415 deletions

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 51 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 45 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 40 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 69 KiB

View File

@ -3,8 +3,7 @@
["react-native" :as rn]
["react-native-status-keycard" :default status-keycard]
[react-native.platform :as platform]
[taoensso.timbre :as log]
[utils.address :as address]))
[taoensso.timbre :as log]))
(defonce event-emitter
(if platform/ios?
@ -94,22 +93,14 @@
[{:keys [on-success on-failure]}]
(.. status-keycard
(getApplicationInfo)
(then (fn [response]
(let [info (-> response
(js->clj :keywordize-keys true)
(update :key-uid address/normalized-hex))]
(on-success info))))
(then on-success)
(catch on-failure)))
(defn factory-reset
[{:keys [on-success on-failure]}]
(.. status-keycard
(factoryReset)
(then (fn [response]
(let [info (-> response
(js->clj :keywordize-keys true)
(update :key-uid address/normalized-hex))]
(on-success info))))
(then on-success)
(catch on-failure)))
(defn install-applet
@ -155,6 +146,13 @@
(then on-success)
(catch on-failure)))
(defn save-mnemonic
[{:keys [mnemonic pin on-success on-failure]}]
(.. status-keycard
(saveMnemonic mnemonic pin)
(then on-success)
(catch on-failure)))
(defn unblock-pin
[{:keys [puk new-pin on-success on-failure]}]
(when (and new-pin puk)

View File

@ -508,17 +508,16 @@
new-password-hashed
callback))))
(defn convert-to-keycard-account
[{:keys [key-uid] :as multiaccount-data} settings current-password# new-password callback]
(log/debug "[native-module] convert-to-keycard-account")
(defn convert-to-keycard-profile
[{:keys [key-uid] :as profile} settings password new-password callback]
(.convertToKeycardAccount ^js (encryption)
key-uid
(types/clj->json multiaccount-data)
(types/clj->json profile)
(types/clj->json settings)
""
current-password#
(:keycard-instance-uid settings)
password
new-password
callback))
#(when callback (callback (types/json->clj %)))))
(defn backup-disabled-data-dir
[]

View File

@ -16,7 +16,7 @@
"
[{:keys [holder-name locked?]}]
(let [theme (quo.theme/use-theme)
label (if (boolean holder-name)
label (if holder-name
(i18n/label :t/user-keycard {:name holder-name})
(i18n/label :t/empty-keycard))]
[rn/view {:style (style/card-container locked? theme)}

View File

@ -3,7 +3,9 @@
(defn container
[container-style]
(merge container-style
{:flex-direction :row
{:padding-vertical 7
:padding-horizontal 20
:flex-direction :row
:align-items :flex-start}))
(def index

View File

@ -45,7 +45,7 @@
:or {type :bullet}}]
(let [theme (quo.theme/use-theme)]
[rn/view {:style (style/container container-style)}
[rn/view {:style style/index}
[rn/view
(case type
:step
[step/view

View File

@ -8,8 +8,7 @@
(def header
{:flex-direction :row
:justify-content :space-between
:height 32})
:justify-content :space-between})
(def header-title
{:flex 1

View File

@ -59,7 +59,6 @@
[rn/view {:style (merge style/container container-style)}
[text/text
{:size :heading-1
:number-of-lines 1
:weight :semi-bold
:style style/text
:accessibility-label accessibility-label}

View File

@ -7,8 +7,7 @@
:margin-bottom 8})
(def item-text
{:margin-top 10
:margin-left -4})
{:margin-left -24})
(def info-text
{:margin-top -5})

View File

@ -22,6 +22,11 @@
:qr-code (js/require "../resources/images/ui2/qr-code.png")
:keycard-logo (js/require "../resources/images/ui2/keycard-logo.png")
:keycard-watermark (js/require "../resources/images/ui2/keycard-watermark.png")
:keycard-buy (js/require "../resources/images/ui2/keycard-buy.png")
:keycard-migration (js/require "../resources/images/ui2/keycard-migration.png")
:keycard-migration-failed (js/require "../resources/images/ui2/keycard-migration-failed.png")
:keycard-migration-succeeded (js/require "../resources/images/ui2/keycard-migration-succeeded.png")
:not-keycard (js/require "../resources/images/ui2/not-keycard.png")
:discover (js/require "../resources/images/ui2/discover.png")
:invite-friends (js/require "../resources/images/ui2/invite-friends.png")
:transaction-progress (js/require "../resources/images/ui2/transaction-progress.png")

View File

@ -1,9 +1,10 @@
(ns status-im.common.standard-authentication.enter-password.view
(:require
[clojure.string :as string]
[quo.core :as quo]
[react-native.core :as rn]
[status-im.common.standard-authentication.core :as standard-authentication]
[status-im.common.standard-authentication.enter-password.style :as style]
[status-im.common.standard-authentication.password-input.view :as password-input]
[status-im.contexts.profile.utils :as profile.utils]
[utils.i18n :as i18n]
[utils.re-frame :as rf]))
@ -31,11 +32,12 @@
:full-name (profile.utils/displayed-name profile)
:customization-color customization-color
:size 24}]]
[password-input/view
[standard-authentication/password-input
{:on-press-biometrics on-press-biometrics
:blur? true
:processing processing
:error error
:error {:error? (not (string/blank? error))
:error-message error}
:default-password password
:sign-in-enabled? sign-in-enabled?}]
[quo/button

View File

@ -1,6 +1,5 @@
(ns status-im.common.standard-authentication.password-input.view
(:require
[clojure.string :as string]
[quo.core :as quo]
[quo.foundations.colors :as colors]
[quo.theme :as quo.theme]
@ -12,14 +11,6 @@
[utils.re-frame :as rf]
[utils.security.core :as security]))
(defn- get-error-message
[error]
(if (and (some? error)
(or (= error "file is not a database")
(string/starts-with? (string/lower-case error) "failed")))
(i18n/label :t/oops-wrong-password)
error))
(defn- error-info
[error-message processing shell?]
(let [theme (quo.theme/use-theme)
@ -49,17 +40,17 @@
(i18n/label :t/forgot-password)]]]))
(defn view
[{:keys [shell? on-press-biometrics blur?]}]
(let [{:keys [error processing]} (rf/sub [:profile/login])
error-message (rn/use-memo #(get-error-message error) [error])
error? (boolean (seq error-message))
[{:keys [shell? on-press-biometrics blur? processing error]}]
(let [{:keys [error?
error-message]} error
default-value (rn/use-ref-atom "") ;;bug on Android
;;https://github.com/status-im/status-mobile/issues/19004
theme (quo.theme/use-theme)
on-change-password (rn/use-callback
(fn [entered-password]
(reset! default-value entered-password)
(debounce/debounce-and-dispatch [:profile/on-password-input-changed
(debounce/debounce-and-dispatch
[:profile/on-password-input-changed
{:password (security/mask-data
entered-password)
:error ""}]

View File

@ -580,13 +580,14 @@
(def ^:const sheet-screen-handle-height 20)
(def ^:const status-hostname "status.app")
(def ^:const get-keycard-url "https://get.keycard.tech/")
(def ^:const community-joined-notification-type "communityJoined")
(def ^:const default-telemetry-server-url "https://telemetry.status.im")
(def ^:const contact-item-height 56)
(def ^:const page-nav-height 56)
(def ^:const currency-item-height 64)
(def ^:const slippages [0.1 0.5 1])

View File

@ -4,19 +4,17 @@
[status-im.common.events-helper :as events-helper]
[status-im.common.standard-authentication.core :as standard-auth]
[utils.i18n :as i18n]
[utils.re-frame :as rf]
[utils.security.core :as security]))
[utils.re-frame :as rf]))
(defn view
[]
(let [profile-name (rf/sub [:profile/name])
profile-picture (rf/sub [:profile/image])
customization-color (rf/sub [:profile/customization-color])]
customization-color (rf/sub [:profile/customization-color])
{:keys [on-success]} (rf/sub [:get-screen-params])]
[:<>
[quo/page-nav
{:key :header
:background :blur
:icon-name :i/close
{:icon-name :i/close
:on-press events-helper/navigate-back}]
[quo/page-top
{:title (i18n/label :t/authorise-with-password)
@ -31,5 +29,5 @@
:container-style {}
:customization-color customization-color
:track-text (i18n/label :t/slide-to-authorise)
:on-auth-success #(println "TBD" (security/safe-unmask-data %))
:on-auth-success #(when on-success (on-success %))
:auth-button-label (i18n/label :t/confirm)}]]]))

View File

@ -10,9 +10,7 @@
[]
[:<>
[quo/page-nav
{:key :header
:background :blur
:icon-name :i/arrow-left
{:icon-name :i/arrow-left
:on-press events-helper/navigate-back}]
[quo/page-top
{:title (i18n/label :t/check-keycard)
@ -23,14 +21,9 @@
{:resize-mode :contain
:source (resources/get-image :check-your-keycard)}]]
[quo/divider-label (i18n/label :t/tips-scan-keycard)]
[rn/view {:style {:padding-horizontal 10}}
[quo/markdown-list
{:container-style {:padding-vertical 10}
:description (i18n/label :t/remove-phone-case)}]
[quo/markdown-list
{:container-style {:padding-bottom 25}
:description (i18n/label :t/keep-card-steady)}]]
[quo/button
{:on-press #(rf/dispatch [:keycard/connect])
:container-style {:margin-horizontal 20}}
(i18n/label :t/ready-to-scan)]])
[quo/markdown-list {:description (i18n/label :t/remove-phone-case)}]
[quo/markdown-list {:description (i18n/label :t/keep-card-steady)}]
[quo/bottom-actions
{:actions :one-action
:button-one-label (i18n/label :t/ready-to-scan)
:button-one-props {:on-press #(rf/dispatch [:keycard/migration.check-empty-card])}}]])

View File

@ -3,10 +3,10 @@
[native-module.core :as native-module]
[react-native.async-storage :as async-storage]
[react-native.platform :as platform]
status-im.contexts.keycard.nfc.effects
[status-im.contexts.keycard.utils :as keycard.utils]
[status-im.contexts.profile.config :as profile.config]
[taoensso.timbre :as log]
[utils.re-frame :as rf]
[utils.transforms :as transforms]))
[utils.re-frame :as rf]))
(defonce ^:private active-listeners (atom []))
@ -32,106 +32,45 @@
(rf/reg-fx :effects.keycard/register-card-events register-card-events)
(rf/reg-fx :effects.keycard/unregister-card-events unregister-card-events)
(defn check-nfc-enabled
[]
(log/debug "[keycard] check-nfc-enabled")
(keycard/check-nfc-enabled
{:on-success
(fn [response]
(log/debug "[keycard response] check-nfc-enabled")
(rf/dispatch [:keycard/on-check-nfc-enabled-success response]))}))
(rf/reg-fx :effects.keycard/check-nfc-enabled check-nfc-enabled)
(rf/reg-fx
:effects.keycard.ios/start-nfc
(rf/reg-fx :effects.keycard/get-application-info
(fn [args]
(log/debug "fx start-nfc")
(keycard/start-nfc args)))
(keycard/get-application-info (keycard.utils/wrap-handlers args))))
(rf/reg-fx
:effects.keycard.ios/stop-nfc
(rf/reg-fx :effects.keycard/get-keys
(fn [args]
(log/debug "fx stop-nfc")
(keycard/stop-nfc args)))
(keycard/get-keys (keycard.utils/wrap-handlers args))))
(defn- error-object->map
[^js object]
{:code (.-code object)
:error (.-message object)})
(rf/reg-fx :effects.keycard/sign
(fn [args]
(keycard/sign (keycard.utils/wrap-handlers args))))
(defn get-application-info
[{:keys [on-success on-failure] :as args}]
(log/debug "[keycard] get-application-info")
(keycard/get-application-info
(assoc
args
:on-success
(fn [response]
(log/debug "[keycard response succ] get-application-info")
(when on-success
(on-success response)))
:on-failure
(fn [response]
(log/error "[keycard response fail] get-application-info")
(when on-failure
(on-failure (error-object->map response)))))))
(rf/reg-fx :effects.keycard/get-application-info get-application-info)
(rf/reg-fx :keycard/init-card
(fn [args]
(keycard/init-card (keycard.utils/wrap-handlers args))))
(defn get-keys
[{:keys [on-success on-failure] :as args}]
(log/debug "[keycard] get-keys")
(keycard/get-keys
(assoc
args
:on-success
(fn [response]
(log/debug "[keycard response succ] get-keys")
(when on-success
(on-success (transforms/js->clj response))))
:on-failure
(fn [response]
(log/warn "[keycard response fail] get-keys"
(error-object->map response))
(when on-failure
(on-failure (error-object->map response)))))))
(rf/reg-fx :effects.keycard/get-keys get-keys)
(rf/reg-fx :effects.keycard/generate-and-load-key
(fn [args]
(keycard/generate-and-load-key (keycard.utils/wrap-handlers args))))
(defn login
[{:keys [key-uid password whisper-private-key]}]
(rf/reg-fx :effects.keycard/login-with-keycard
(fn [{:keys [key-uid password whisper-private-key]}]
(native-module/login-account
(assoc (profile.config/login)
:keyUid key-uid
:password password
:keycardWhisperPrivateKey whisper-private-key)))
(rf/reg-fx :effects.keycard/login-with-keycard login)
:keycardWhisperPrivateKey whisper-private-key))))
(defn retrieve-pairings
[]
(rf/reg-fx :effects.keycard/set-pairing-to-keycard
(fn [pairings]
(keycard/set-pairings pairings)))
(rf/reg-fx
:keycard/persist-pairings
(fn [pairings]
(async-storage/set-item! "status-keycard-pairings" pairings)))
(rf/reg-fx :effects.keycard/retrieve-pairings
(fn []
(async-storage/get-item
"status-keycard-pairings"
#(rf/dispatch [:keycard/on-retrieve-pairings-success %])))
(rf/reg-fx :effects.keycard/retrieve-pairings retrieve-pairings)
(defn set-pairing-to-keycard
[pairings]
(keycard/set-pairings pairings))
(rf/reg-fx :effects.keycard/set-pairing-to-keycard set-pairing-to-keycard)
(defn sign
[{:keys [on-success on-failure] :as args}]
(log/debug "[keycard] sign")
(keycard/sign
(assoc
args
:on-success
(fn [response]
(log/debug "[keycard response succ] sign")
(when on-success
(on-success (transforms/js->clj response))))
:on-failure
(fn [response]
(log/warn "[keycard response fail] sign"
(error-object->map response))
(when on-failure
(on-failure (error-object->map response)))))))
(rf/reg-fx :effects.keycard/sign sign)
#(rf/dispatch [:keycard/on-retrieve-pairings-success %]))))

View File

@ -3,7 +3,7 @@
[react-native.core :as rn]
[status-im.common.events-helper :as events-helper]
[status-im.common.resources :as resources]
[status-im.contexts.keycard.sheets.migrate.view :as sheets.migrate]
[status-im.contexts.keycard.migrate.sheets.view :as sheets.migrate]
[utils.i18n :as i18n]
[utils.re-frame :as rf]))
@ -11,9 +11,7 @@
[]
[:<>
[quo/page-nav
{:key :header
:background :blur
:icon-name :i/arrow-left
{:icon-name :i/close
:on-press events-helper/navigate-back}]
[quo/page-top
{:title (i18n/label :t/keycard-empty)
@ -26,10 +24,15 @@
:subtitle (i18n/label :t/use-keycard-login-sign)
:button-label (i18n/label :t/import-profile-key-pair)
:accessibility-label :get-keycard
:image (resources/get-image :generate-keys)
:on-press #(rf/dispatch [:show-bottom-sheet
:image (resources/get-image :keycard-buy)
:on-press (fn []
(rf/dispatch
[:show-bottom-sheet
{:theme :dark
:content (fn [] [sheets.migrate/view])}])}]]
:content (fn []
[sheets.migrate/view
{:on-continue #(rf/dispatch
[:keycard/migration.get-phrase])}])}]))}]]
[quo/information-box
{:type :default
:style {:margin-top 32 :margin-horizontal 28}}

View File

@ -1,42 +1,37 @@
(ns status-im.contexts.keycard.error.view
(:require [quo.core :as quo]
[react-native.core :as rn]
[react-native.safe-area :as safe-area]
[status-im.common.events-helper :as events-helper]
[utils.i18n :as i18n]
[utils.re-frame :as rf]))
(def titles
{:keycard/error.keycard-wrong {:title "Keycard is not empty"
:description "You cant use it to store new keys right now"}
:keycard/error.keycard-unpaired {:title "Keycard is full"
:description "All pairing slots are occupied"}
:keycard/error.keycard-frozen {:title "Keycard is locked"
:description "You cant use it right now"}
:keycard/error.keycard-locked {:title "Keycard is locked"
:description "You cant use it right now"}})
{:keycard/error.keycard-blank {:title (i18n/label :t/keycard-empty)
:description (i18n/label :t/no-key-pair-keycard)}
:keycard/error.keycard-wrong-profile {:title (i18n/label :t/keycard-not-empty)
:description (i18n/label :t/cant-store-new-keys)}
:keycard/error.keycard-unpaired {:title (i18n/label :t/keycard-full)
:description (i18n/label :t/pairing-slots-occupied)}
:keycard/error.keycard-frozen {:title (i18n/label :t/keycard-locked)
:description (i18n/label :t/cant-use-right-now)}
:keycard/error.keycard-locked {:title (i18n/label :t/keycard-locked)
:description (i18n/label :t/cant-use-right-now)}})
(defn view
[]
(let [{:keys [top bottom]} (safe-area/get-insets)
error (rf/sub [:keycard/application-info-error])
(let [error (rf/sub [:keycard/application-info-error])
{:keys [title description]} (get titles error)]
[quo/overlay
{:type :shell
:container-style {:padding-top top
:padding-bottom bottom}}
[:<>
[quo/page-nav
{:key :header
:background :blur
:icon-name :i/arrow-left
{:icon-name :i/close
:on-press events-helper/navigate-back}]
[quo/page-top
{:title title
:description :text
:description-text description}]
[rn/view {:height 226}]
[rn/view {:padding-horizontal 20}
[quo/info-message
{:container-style {:padding-top 15}
:icon :i/info
:size :default}
"To unlock or factory reset the Keycard, please use the Status desktop app. If you'd like this features on mobile, feel free to upvote them and discuss in the Status community."]]]))
[rn/view {:style {:margin-horizontal 20}}
[quo/keycard {:holder-name ""}]
[quo/information-box
{:type :default
:style {:margin-top 20}}
(i18n/label :t/unlock-reset-instructions)]]]))

View File

@ -1,116 +1,117 @@
(ns status-im.contexts.keycard.events
(:require [re-frame.core :as rf]
status-im.contexts.keycard.login.events
status-im.contexts.keycard.nfc-sheet.events
status-im.contexts.keycard.migrate.events
status-im.contexts.keycard.migrate.re-encrypting.events
status-im.contexts.keycard.nfc.events
status-im.contexts.keycard.nfc.sheets.events
status-im.contexts.keycard.pin.events
status-im.contexts.keycard.sign.events
[status-im.contexts.keycard.utils :as keycard.utils]
[taoensso.timbre :as log]))
(rf/reg-event-fx :keycard/on-check-nfc-enabled-success
(fn [{:keys [db]} [nfc-enabled?]]
{:db (assoc-in db [:keycard :nfc-enabled?] nfc-enabled?)}))
(rf/reg-event-fx :keycard.ios/on-nfc-user-cancelled
(fn [{:keys [db]}]
(log/debug "[keycard] nfc user cancelled")
{:db (-> db
(assoc-in [:keycard :pin :status] nil)
(assoc-in [:keycard :on-nfc-cancelled-event-vector] nil))
:fx [(when-let [on-nfc-cancelled-event-vector (get-in db [:keycard :on-nfc-cancelled-event-vector])]
[:dispatch on-nfc-cancelled-event-vector])]}))
utils.datetime))
(rf/reg-event-fx :keycard/on-card-connected
(fn [{:keys [db]} _]
(log/debug "[keycard] card globally connected")
{:db (assoc-in db [:keycard :card-connected?] true)
:fx [(when-let [event (get-in db [:keycard :on-card-connected-event-vector])]
[:dispatch event])]}))
(rf/reg-event-fx :keycard/on-card-disconnected
(fn [{:keys [db]} _]
(log/debug "[keycard] card disconnected")
{:db (assoc-in db [:keycard :card-connected?] false)
:fx [(when-let [event (get-in db [:keycard :on-card-disconnected-event-vector])]
[:dispatch event])]}))
(rf/reg-event-fx :keycard.ios/start-nfc
(fn [_]
{:effects.keycard.ios/start-nfc nil}))
(rf/reg-event-fx :keycard.ios/on-nfc-timeout
(fn [{:keys [db]} _]
(log/debug "[keycard] nfc timeout")
{:db (assoc-in db [:keycard :card-connected?] false)
:fx [[:dispatch-later [{:ms 500 :dispatch [:keycard.ios/start-nfc]}]]]}))
(rf/reg-event-fx :keycard/on-retrieve-pairings-success
(fn [{:keys [db]} [pairings]]
{:db (assoc-in db [:keycard :pairings] pairings)
:fx [[:effects.keycard/set-pairing-to-keycard pairings]]}))
(rf/reg-event-fx :keycard.ios/on-start-nfc-success
(fn [{:keys [db]} [{:keys [on-cancel-event-vector]}]]
(log/debug "[keycard] nfc started success")
{:db (assoc-in db [:keycard :on-nfc-cancelled-event-vector] on-cancel-event-vector)}))
(rf/reg-event-fx :keycard/update-pairings
(fn [{:keys [db]} [instance-uid pairing]]
(let [pairings (get-in db [:keycard :pairings])
new-pairings (assoc pairings
instance-uid
{:pairing pairing
:paired-on (utils.datetime/timestamp)})]
{:db (assoc-in db [:keycard :pairings] new-pairings)
:keycard/persist-pairings new-pairings})))
(rf/reg-event-fx :keycard/on-action-with-pin-error
(fn [{:keys [db]} [error]]
(log/debug "[keycard] on-action-with-pin-error: " error)
(let [tag-was-lost? (keycard.utils/tag-lost? (:error error))
pin-retries-count (keycard.utils/pin-retries (:error error))]
(if tag-was-lost?
(if (or tag-was-lost? (nil? pin-retries-count))
{:db (assoc-in db [:keycard :pin :status] nil)}
(if (nil? pin-retries-count)
{:effects.utils/show-popup {:title "wrong-keycard"}}
{:db (-> db
(assoc-in [:keycard :application-info :pin-retry-counter] pin-retries-count)
(assoc-in [:keycard :pin :status] :error))
:fx [[:dispatch [:keycard/disconnect]]
(when (zero? pin-retries-count)
[:effects.utils/show-popup {:title "frozen-keycard"}])]})))))
[:dispatch
[:keycard/on-application-info-error
:keycard/error.keycard-locked]])]}))))
(rf/reg-event-fx :keycard/on-get-application-info-success
(fn [{:keys [db]} [application-info {:keys [key-uid on-success-fx]}]]
(if-let [error (keycard.utils/validate-application-info key-uid application-info)]
(case error
:keycard/error.not-keycard
{:fx [[:dispatch [:keycard/disconnect]]
[:dispatch [:open-modal :screen/keycard.not-keycard]]]}
:keycard/error.keycard-blank
{:fx [[:dispatch [:keycard/disconnect]]
[:dispatch [:open-modal :screen/keycard.empty]]]}
{:db (assoc-in db [:keycard :application-info-error] error)
:fx [[:dispatch [:keycard/disconnect]]
[:dispatch [:open-modal :screen/keycard.error]]]})
{:db (-> db
(assoc-in [:keycard :application-info] application-info)
(assoc-in [:keycard :pin :status] :verifying))
:fx on-success-fx})))
(rf/reg-event-fx :keycard/get-application-info
(fn [_ [{:keys [on-success on-failure]}]]
{:effects.keycard/get-application-info {:on-success on-success :on-failure on-failure}}))
(rf/reg-event-fx :keycard/get-keys
(fn [_ [data]]
{:effects.keycard/get-keys data}))
(rf/reg-event-fx :keycard/cancel-connection
(fn [{:keys [db]}]
{:db (-> db
(assoc-in [:keycard :on-card-connected-event-vector] nil)
(assoc-in [:keycard :on-nfc-cancelled-event-vector] nil))}))
{:db (update db :keycard dissoc :on-card-connected-event-vector :on-nfc-cancelled-event-vector)}))
(rf/reg-event-fx :keycard/disconnect
(fn [_ _]
{:fx [[:dispatch [:keycard/cancel-connection]]
[:dispatch [:keycard/hide-connection-sheet]]]}))
(rf/reg-event-fx :keycard/on-application-info-error
(fn [{:keys [db]} [error]]
{:db (assoc-in db [:keycard :application-info-error] error)
:fx [[:dispatch [:keycard/disconnect]]
[:dispatch
[:open-modal
(if (= :keycard/error.not-keycard error)
:screen/keycard.not-keycard
:screen/keycard.error)]]]}))
(rf/reg-event-fx :keycard/update-application-info
(fn [{:keys [db]} [app-info]]
{:db (update db
:keycard
#(-> %
(assoc :application-info app-info)
(dissoc :application-info-error)))}))
(rf/reg-event-fx :keycard/get-application-info
(fn [_ [{:keys [key-uid on-success on-error]}]]
{:effects.keycard/get-application-info
{:on-success (fn [{:keys [instance-uid new-pairing] :as app-info}]
(rf/dispatch [:keycard/update-application-info app-info])
(when (and instance-uid new-pairing)
(rf/dispatch [:keycard/update-pairings instance-uid new-pairing]))
(if-let [error (keycard.utils/validate-application-info key-uid app-info)]
(if on-error
(on-error error)
(rf/dispatch [:keycard/on-application-info-error error]))
(when on-success (on-success app-info))))
:on-error (fn []
(if on-error
(on-error :keycard/error.not-keycard)
(rf/dispatch [:keycard/on-application-info-error
:keycard/error.not-keycard])))}}))
(rf/reg-event-fx :keycard/connect
(fn [{:keys [db]} [args]]
(let [connected? (get-in db [:keycard :card-connected?])
event-vector [:keycard/get-application-info
{:on-success #(rf/dispatch [:keycard/on-get-application-info-success % args])}]]
(fn [{:keys [db]} [{:keys [key-uid on-success on-error on-connect-event-vector]}]]
(let [event-vector
(or on-connect-event-vector
[:keycard/get-application-info
{:key-uid key-uid
:on-success on-success
:on-error on-error}])]
{:db (assoc-in db [:keycard :on-card-connected-event-vector] event-vector)
:fx [[:dispatch
[:keycard/show-connection-sheet
{:on-cancel-event-vector [:keycard/cancel-connection]}]]
(when connected?
(when (get-in db [:keycard :card-connected?])
[:dispatch event-vector])]})))

View File

@ -5,7 +5,6 @@
(fn [{:keys [db]} [data]]
(let [{:keys [key-uid encryption-public-key
whisper-private-key]} data
key-uid (str "0x" key-uid)
profile (get-in db [:profile/profiles-overview key-uid])]
{:db
(-> db

View File

@ -0,0 +1,132 @@
(ns status-im.contexts.keycard.migrate.events
(:require [clojure.string :as string]
[status-im.contexts.keycard.pin.view :as keycard.pin]
[utils.re-frame :as rf]
[utils.security.core :as security]))
(rf/reg-event-fx :keycard/migration.check-empty-card
(fn [{:keys [db]}]
{:fx [[:dispatch
[:keycard/connect
{:key-uid (get-in db [:profile/profile :key-uid])
:on-success
(fn []
;;TODO keys already on the keycard, new flow needs to be implemented
;; https://github.com/status-im/status-mobile/issues/21446
(rf/dispatch [:keycard/disconnect])
(rf/dispatch [:open-modal :screen/keycard.authorise
{:on-success
#(rf/dispatch [:keycard/migration.authorisation-success %])}]))
:on-error
(fn [error]
(if (= error :keycard/error.keycard-blank)
(do
(rf/dispatch [:keycard/disconnect])
(rf/dispatch [:open-modal :screen/keycard.empty]))
(rf/dispatch [:keycard/on-application-info-error error])))}]]]}))
(defn get-application-info-and-continue
[key-uid]
(rf/dispatch [:keycard/get-application-info
{:key-uid key-uid
:on-success #(rf/dispatch [:keycard/migration.continue])
:on-error
(fn [error]
(if (= error :keycard/error.keycard-blank)
(rf/dispatch [:keycard/migration.continue])
(rf/dispatch [:keycard/on-application-info-error error])))}]))
(rf/reg-event-fx :keycard/migration.continue
(fn [{:keys [db]}]
(let [key-uid (get-in db [:profile/profile :key-uid])
{:keys [initialized? has-master-key?]} (get-in db [:keycard :application-info])
{:keys [masked-phrase pin]} (get-in db [:keycard :migration])
on-failure (fn []
(rf/dispatch [:keycard/disconnect])
(rf/dispatch [:navigate-to
:screen/keycard.migrate.fail]))]
(cond
(not initialized?)
{:fx [[:keycard/init-card
{:pin pin
:on-success #(get-application-info-and-continue key-uid)
:on-failure on-failure}]]}
(not has-master-key?)
{:fx [[:effects.keycard/generate-and-load-key
{:mnemonic (security/safe-unmask-data masked-phrase)
:pin pin
:on-success #(get-application-info-and-continue key-uid)
:on-failure on-failure}]]}
:else
{:fx [[:effects.keycard/get-keys
{:pin pin
:on-success #(rf/dispatch [:keycard/migration.convert-to-keycard-profile %])
:on-failure on-failure}]]}))))
(rf/reg-event-fx :keycard/migration.start
(fn [{:keys [db]}]
{:fx [[:dispatch
[:keycard/connect
{:key-uid (get-in db [:profile/profile :key-uid])
:on-success #(rf/dispatch [:keycard/migration.continue])
:on-error
(fn [error]
(if (= error :keycard/error.keycard-blank)
(rf/dispatch [:keycard/migration.continue])
(rf/dispatch [:keycard/on-application-info-error error])))}]]]}))
(rf/reg-event-fx :keycard/migration.get-phrase
(fn [{:keys [db]}]
{:db (assoc-in db [:keycard :migration] nil)
:fx [[:dispatch [:navigate-back]]
(if (string/blank? (get-in db [:profile/profile :mnemonic]))
[:dispatch
[:open-modal :screen/use-recovery-phrase
{:on-success #(rf/dispatch [:keycard/migration.phrase-entered %])}]]
[:dispatch
[:open-modal :screen/backup-recovery-phrase
{:on-success #(rf/dispatch [:keycard/migration.phrase-backed-up %])}]])]}))
(rf/reg-event-fx :keycard/migration.phrase-entered
(fn [{:keys [db]} [{:keys [phrase]}]]
{:db (assoc-in db [:keycard :migration :masked-phrase] (security/mask-data phrase))
:fx [[:dispatch [:navigate-back]]
[:dispatch
[:open-modal :screen/keycard.authorise
{:on-success #(rf/dispatch [:keycard/migration.authorisation-success %])}]]]}))
(rf/reg-event-fx :keycard/migration.phrase-backed-up
(fn [{:keys [db]}]
{:db (assoc-in db
[:keycard :migration :masked-phrase]
(security/mask-data (get-in db [:profile/profile :mnemonic])))
:fx [[:dispatch [:profile.settings/profile-update :mnemonic nil]]
[:dispatch [:navigate-back]]
[:dispatch
[:open-modal :screen/keycard.authorise
{:on-success #(rf/dispatch [:keycard/migration.authorisation-success %])}]]]}))
(rf/reg-event-fx :keycard/migration.authorisation-success
(fn [{:keys [db]} [masked-password]]
(let [{:keys [initialized?]} (get-in db [:keycard :application-info])]
{:db (assoc-in db [:keycard :migration :masked-password] masked-password)
:fx [[:dispatch [:navigate-back]]
(if initialized?
[:dispatch
[:show-bottom-sheet
{:content (fn []
[keycard.pin/auth
{:on-complete #(rf/dispatch [:keycard/migration.pin-created %])}])}]]
[:dispatch
[:open-modal :screen/keycard.pin.create
{:on-complete (fn [new-pin]
(rf/dispatch [:navigate-back])
(rf/dispatch [:keycard/migration.pin-created new-pin]))}]])]})))
(rf/reg-event-fx :keycard/migration.pin-created
(fn [{:keys [db]} [pin]]
{:db (assoc-in db [:keycard :migration :pin] pin)
:fx [[:dispatch [:open-modal :screen/keycard.migrate]]]}))

View File

@ -0,0 +1,32 @@
(ns status-im.contexts.keycard.migrate.fail.view
(:require [quo.core :as quo]
[react-native.core :as rn]
[status-im.common.resources :as resources]
[status-im.constants :as constants]
[utils.i18n :as i18n]
[utils.re-frame :as rf]))
(defn view
[]
(let [profile-name (rf/sub [:profile/name])
profile-picture (rf/sub [:profile/image])
customization-color (rf/sub [:profile/customization-color])]
[:<>
[quo/page-top
{:title (i18n/label :t/failed-to-migrate-key-pair)
:description :context-tag
:context-tag {:full-name profile-name
:profile-picture profile-picture
:customization-color customization-color}
:container-style {:margin-top constants/page-nav-height}}]
[rn/view {:style {:flex 1 :align-items :center :justify-content :center}}
[rn/image
{:resize-mode :contain
:source (resources/get-image :keycard-migration-failed)}]]
[quo/divider-label (i18n/label :t/what-you-can-do)]
[quo/markdown-list {:description (i18n/label :t/log-out-remove-profile)}]
[quo/markdown-list {:description (i18n/label :t/recover-status-profile)}]
[quo/bottom-actions
{:actions :one-action
:button-one-label (i18n/label :t/log-out-remove)
:button-one-props {:on-press #(rf/dispatch [:logout])}}]]))

View File

@ -0,0 +1,31 @@
(ns status-im.contexts.keycard.migrate.re-encrypting.events
(:require [clojure.string :as string]
[utils.re-frame :as rf]
[utils.security.core :as security]))
(rf/reg-event-fx :keycard/migration.convert-to-keycard-profile
(fn [{:keys [db]} [{:keys [key-uid instance-uid encryption-public-key]}]]
(let [{:keys [masked-password]} (get-in db [:keycard :migration])
{:keys [pairing paired-on]} (get-in db [:keycard :pairings instance-uid])
{:keys [kdfIterations]} (:profile/profile db)]
{:fx [[:dispatch [:keycard/disconnect]]
[:dispatch [:navigate-back]]
[:dispatch [:open-modal :screen/keycard.re-encrypting]]
[:effects.profile/convert-to-keycard-profile
{:profile
{:key-uid key-uid
:keycard-pairing pairing
:kdfIterations kdfIterations}
:settings
{:keycard-instance-uid instance-uid
:keycard-paired-on paired-on
:keycard-pairing pairing}
:password
(security/safe-unmask-data masked-password)
:new-password
encryption-public-key
:callback
(fn [{:keys [error]}]
(if (string/blank? error)
(rf/dispatch [:navigate-to :screen/keycard.migrate.success])
(rf/dispatch [:navigate-to :screen/keycard.migrate.fail])))}]]})))

View File

@ -0,0 +1,27 @@
(ns status-im.contexts.keycard.migrate.re-encrypting.view
(:require [quo.core :as quo]
[react-native.core :as rn]
[status-im.common.resources :as resources]
[status-im.constants :as constants]
[utils.i18n :as i18n]
[utils.re-frame :as rf]))
(defn view
[]
(let [profile-name (rf/sub [:profile/name])
profile-picture (rf/sub [:profile/image])
customization-color (rf/sub [:profile/customization-color])]
[:<>
[quo/page-top
{:title (i18n/label :t/re-encrypting-data)
:description :context-tag
:context-tag {:full-name profile-name
:profile-picture profile-picture
:customization-color customization-color}
:container-style {:margin-top constants/page-nav-height}}]
[quo/text {:style {:padding-horizontal 20}}
(i18n/label :t/do-not-quit)]
[rn/view {:style {:flex 1 :align-items :center :justify-content :center}}
[rn/image
{:resize-mode :contain
:source (resources/get-image :keycard-migration)}]]]))

View File

@ -1,15 +1,14 @@
(ns status-im.contexts.keycard.sheets.migrate.view
(ns status-im.contexts.keycard.migrate.sheets.view
(:require [quo.core :as quo]
[react-native.core :as rn]
[utils.i18n :as i18n]
[utils.re-frame :as rf]))
(defn view
[]
[{:keys [on-continue]}]
(let [profile-name (rf/sub [:profile/name])
profile-picture (rf/sub [:profile/image])
customization-color (rf/sub [:profile/customization-color])
recovery-phrase-backed-up? (rf/sub [:profile/recovery-phrase-backed-up?])]
customization-color (rf/sub [:profile/customization-color])]
[:<>
[quo/drawer-top
{:type :context-tag
@ -28,11 +27,7 @@
[quo/bottom-actions
{:actions :two-actions
:button-one-label (i18n/label :t/continue)
:button-one-props {:on-press #(if recovery-phrase-backed-up?
(rf/dispatch [:open-modal :screen/use-recovery-phrase
{:on-success (fn [])}])
(rf/dispatch [:open-modal :screen/backup-recovery-phrase
{:on-success (fn [])}]))}
:button-one-props {:on-press on-continue}
:button-two-label (i18n/label :t/cancel)
:button-two-props {:type :grey
:on-press #(rf/dispatch [:hide-bottom-sheet])}}]]))

View File

@ -0,0 +1,31 @@
(ns status-im.contexts.keycard.migrate.success.view
(:require [quo.core :as quo]
[react-native.core :as rn]
[status-im.common.resources :as resources]
[status-im.constants :as constants]
[utils.i18n :as i18n]
[utils.re-frame :as rf]))
(defn view
[]
(let [profile-name (rf/sub [:profile/name])
profile-picture (rf/sub [:profile/image])
customization-color (rf/sub [:profile/customization-color])]
[:<>
[quo/page-top
{:title (i18n/label :t/key-pair-migrated-successfully)
:description :context-tag
:context-tag {:full-name profile-name
:profile-picture profile-picture
:customization-color customization-color}
:container-style {:margin-top constants/page-nav-height}}]
[quo/text {:style {:padding-horizontal 20}}
(i18n/label :t/use-keycard-for-status)]
[rn/view {:style {:flex 1 :align-items :center :justify-content :center}}
[rn/image
{:resize-mode :contain
:source (resources/get-image :keycard-migration-succeeded)}]]
[quo/button
{:on-press #(rf/dispatch [:logout])
:container-style {:margin-horizontal 20}}
(i18n/label :t/logout)]]))

View File

@ -0,0 +1,34 @@
(ns status-im.contexts.keycard.migrate.view
(:require [quo.core :as quo]
[react-native.core :as rn]
[status-im.common.events-helper :as events-helper]
[status-im.common.resources :as resources]
[utils.i18n :as i18n]
[utils.re-frame :as rf]))
(defn view
[]
(let [profile-name (rf/sub [:profile/name])
profile-picture (rf/sub [:profile/image])
customization-color (rf/sub [:profile/customization-color])]
[:<>
[quo/page-nav
{:icon-name :i/close
:on-press events-helper/navigate-back}]
[quo/page-top
{:title (i18n/label :t/ready-to-migrate-key-pair)
:description :context-tag
:context-tag {:full-name profile-name
:profile-picture profile-picture
:customization-color customization-color}}]
[rn/view {:style {:flex 1 :align-items :center :justify-content :center}}
[rn/image
{:resize-mode :contain
:source (resources/get-image :keycard-migration)}]]
[quo/divider-label (i18n/label :t/tips-scan-keycard)]
[quo/markdown-list {:description (i18n/label :t/remove-phone-case)}]
[quo/markdown-list {:description (i18n/label :t/keep-card-steady)}]
[quo/bottom-actions
{:actions :one-action
:button-one-label (i18n/label :t/scan-keycard)
:button-one-props {:on-press #(rf/dispatch [:keycard/migration.start])}}]]))

View File

@ -0,0 +1,11 @@
(ns status-im.contexts.keycard.nfc.effects
(:require [keycard.keycard :as keycard]
[utils.re-frame :as rf]))
(rf/reg-fx :effects.keycard/check-nfc-enabled
(fn []
(keycard/check-nfc-enabled
{:on-success #(rf/dispatch [:keycard/on-check-nfc-enabled-success %])})))
(rf/reg-fx :effects.keycard.ios/start-nfc keycard/start-nfc)
(rf/reg-fx :effects.keycard.ios/stop-nfc keycard/stop-nfc)

View File

@ -0,0 +1,27 @@
(ns status-im.contexts.keycard.nfc.events
(:require [utils.re-frame :as rf]))
(rf/reg-event-fx :keycard.ios/start-nfc
(fn [_]
{:fx [[:effects.keycard.ios/start-nfc]]}))
(rf/reg-event-fx :keycard.ios/on-start-nfc-success
(fn [{:keys [db]} [{:keys [on-cancel-event-vector]}]]
{:db (assoc-in db [:keycard :on-nfc-cancelled-event-vector] on-cancel-event-vector)}))
(rf/reg-event-fx :keycard.ios/on-nfc-timeout
(fn [{:keys [db]} _]
{:db (assoc-in db [:keycard :card-connected?] false)
:fx [[:dispatch-later [{:ms 500 :dispatch [:keycard.ios/start-nfc]}]]]}))
(rf/reg-event-fx :keycard/on-check-nfc-enabled-success
(fn [{:keys [db]} [nfc-enabled?]]
{:db (assoc-in db [:keycard :nfc-enabled?] nfc-enabled?)}))
(rf/reg-event-fx :keycard.ios/on-nfc-user-cancelled
(fn [{:keys [db]}]
{:db (-> db
(assoc-in [:keycard :pin :status] nil)
(assoc-in [:keycard :on-nfc-cancelled-event-vector] nil))
:fx [(when-let [on-nfc-cancelled-event-vector (get-in db [:keycard :on-nfc-cancelled-event-vector])]
[:dispatch on-nfc-cancelled-event-vector])]}))

View File

@ -1,4 +1,4 @@
(ns status-im.contexts.keycard.nfc-sheet.events
(ns status-im.contexts.keycard.nfc.sheets.events
(:require [re-frame.core :as rf]
[react-native.platform :as platform]
[taoensso.timbre :as log]))

View File

@ -1,4 +1,4 @@
(ns status-im.contexts.keycard.nfc-sheet.view
(ns status-im.contexts.keycard.nfc.sheets.view
(:require [quo.foundations.colors :as colors]
quo.theme
[react-native.core :as rn]

View File

@ -1,28 +1,24 @@
(ns status-im.contexts.keycard.not-keycard.view
(:require [quo.core :as quo]
[react-native.core :as rn]
[react-native.safe-area :as safe-area]
[status-im.common.events-helper :as events-helper]
[utils.i18n :as i18n]
[utils.re-frame :as rf]))
[status-im.common.resources :as resources]
[utils.i18n :as i18n]))
(defn view
[]
(let [{:keys [top bottom]} (safe-area/get-insets)]
[quo/overlay
{:type :shell
:container-style {:padding-top top
:padding-bottom bottom}}
[:<>
[quo/page-nav
{:key :header
:background :blur
:icon-name :i/arrow-left
{:icon-name :i/close
:on-press events-helper/navigate-back}]
[quo/page-top
{:title (i18n/label :t/oops-not-keycard)
:description :text
:description-text (i18n/label :t/make-sure-keycard)}]
[rn/view {:flex 1}]
[rn/view {:style {:flex 1 :align-items :center :justify-content :center}}
[rn/image
{:resize-mode :contain
:source (resources/get-image :not-keycard)}]]
[rn/view {:padding-horizontal 20}
[quo/button {:on-press #(rf/dispatch [:keycard/connect])}
(i18n/label :t/try-again)]]]))
[quo/button {:on-press events-helper/navigate-back}
(i18n/label :t/try-again)]]])

View File

@ -2,12 +2,15 @@
(:require [clojure.string :as string]
[quo.core :as quo]
[react-native.core :as rn]
[status-im.common.events-helper :as events-helper]
[status-im.constants :as constants]
[utils.i18n :as i18n]))
[utils.i18n :as i18n]
[utils.re-frame :as rf]))
(defn view
[{:keys [on-complete]}]
(let [[pin set-pin] (rn/use-state "")
[]
(let [{:keys [on-complete]} (rf/sub [:get-screen-params])
[pin set-pin] (rn/use-state "")
[first-pin set-first-pin] (rn/use-state "")
[error? set-error] (rn/use-state false)
[stage set-stage] (rn/use-state :create)
@ -38,10 +41,15 @@
(set-stage :repeat)))))))
[pin stage first-pin])]
[rn/view {:style {:padding-bottom 12 :flex 1}}
[quo/drawer-top
[quo/page-nav
{:icon-name :i/close
:on-press events-helper/navigate-back}]
[quo/page-top
{:title (if (= :create stage)
(i18n/label :t/create-keycard-pin)
(i18n/label :t/repeat-keycard-pin))}]
(i18n/label :t/repeat-keycard-pin))
:description :text
:description-text "Youll need this PIN to login and sign transactions"}]
[rn/view {:style {:flex 1 :justify-content :center :align-items :center :padding-vertical 34}}
[quo/pin-input
{:blur? false

View File

@ -23,3 +23,7 @@
(assoc-in [:keycard :pin :status] nil))
:fx [(when (and on-complete (= (dec max-numbers) (count pin)))
[:effects.keycard.pin/dispatch-on-complete [on-complete new-pin]])]}))))
(rf/reg-event-fx :keycard.pin/clear
(fn [{:keys [db]}]
{:db (assoc-in db [:keycard :pin] nil)}))

View File

@ -6,10 +6,12 @@
[utils.re-frame :as rf]))
(defn auth
[{:keys [on-complete]}]
(let [{:keys [text status]} (rf/sub [:keycard/pin])
[{:keys [on-complete error]}]
(let [{:keys [error? error-message]} error
{:keys [text status]} (rf/sub [:keycard/pin])
pin-retry-counter (rf/sub [:keycard/pin-retry-counter])
error? (= status :error)]
error? (or error? (= status :error))]
(rn/use-unmount #(rf/dispatch [:keycard.pin/clear]))
[rn/view {:padding-bottom 12 :flex 1}
[rn/view {:flex 1 :justify-content :center :align-items :center :padding 34}
[quo/pin-input
@ -18,7 +20,9 @@
:number-of-filled-pins (count text)
:error? error?
:info (when error?
(i18n/label :t/pin-retries-left {:number pin-retry-counter}))}]]
(if error-message
error-message
(i18n/label :t/pin-retries-left {:number pin-retry-counter})))}]]
[quo/numbered-keyboard
{:delete-key? true
:on-delete #(rf/dispatch [:keycard.pin/delete-pressed])

View File

@ -8,6 +8,10 @@
:s (subs signature 64 128)
:v (str (js/parseInt (subs signature 128 130) 16))}})
(rf/reg-event-fx :keycard/sign
(fn [_ [data]]
{:effects.keycard/sign data}))
(rf/reg-event-fx :keycard/sign-hash
(fn [{:keys [db]} [pin-text tx-hash]]
(let [current-address (get-in db [:wallet :current-viewing-account-address])
@ -16,15 +20,15 @@
{:fx [[:dispatch
[:keycard/connect
{:key-uid key-uid
:on-success-fx [[:effects.keycard/sign
:on-success
(fn []
(rf/dispatch
[:keycard/sign
{:pin pin-text
:path path
:hash (utils.address/naked-address tx-hash)
:on-success
#(do
:on-success (fn [signature]
(rf/dispatch [:keycard/disconnect])
(rf/dispatch
[:wallet/proceed-with-transactions-signatures
(get-signature-map tx-hash %)]))
:on-failure #(rf/dispatch [:keycard/on-action-with-pin-error
%])}]]}]]]})))
(rf/dispatch [:wallet/proceed-with-transactions-signatures
(get-signature-map tx-hash signature)]))
:on-failure #(rf/dispatch [:keycard/on-action-with-pin-error %])}]))}]]]})))

View File

@ -1,5 +1,7 @@
(ns status-im.contexts.keycard.utils
(:require [taoensso.timbre :as log]))
(:require [clojure.string :as string]
[utils.address :as address]
[utils.transforms :as transforms]))
(def pin-mismatch-error #"Unexpected error SW, 0x63C(\d+)|wrongPIN\(retryCounter: (\d+)\)")
@ -16,22 +18,18 @@
(re-matches #".*NFCError:100.*" error)))
(defn validate-application-info
[profile-key-uid {:keys [key-uid paired? pin-retry-counter puk-retry-counter] :as application-info}]
(let [profile-mismatch? (or (nil? profile-key-uid) (not= profile-key-uid key-uid))]
(log/debug "[keycard]" "login-with-keycard"
"empty application info" (empty? application-info)
"no key-uid" (empty? key-uid)
"profile-mismatch?" profile-mismatch?
"no pairing" paired?)
[profile-key-uid
{:keys [key-uid has-master-key? paired? pin-retry-counter puk-retry-counter] :as application-info}]
(cond
(empty? application-info)
:keycard/error.not-keycard
(empty? (:key-uid application-info))
(not has-master-key?)
:keycard/error.keycard-blank
profile-mismatch?
:keycard/error.keycard-wrong
(not= profile-key-uid key-uid)
:keycard/error.keycard-wrong-profile
(not paired?)
:keycard/error.keycard-unpaired
@ -45,4 +43,30 @@
:keycard/error.keycard-locked
:else
nil)))
nil))
(defn- error-object->map
[^js object]
{:code (.-code object)
:error (.-message object)})
(defn normalize-key-uid
[{:keys [key-uid] :as data}]
(if (string/blank? key-uid)
data
(update data :key-uid address/normalized-hex)))
(defn get-on-success
[{:keys [on-success]}]
#(when on-success (on-success (normalize-key-uid (transforms/js->clj %)))))
(defn get-on-failure
[{:keys [on-failure]}]
#(when on-failure (on-failure (error-object->map %))))
(defn wrap-handlers
[args]
(assoc
args
:on-success (get-on-success args)
:on-failure (get-on-failure args)))

View File

@ -13,3 +13,7 @@
(rf/reg-fx :effects.profile/enable-local-notifications
(fn []
(native-module/start-local-notifications)))
(rf/reg-fx :effects.profile/convert-to-keycard-profile
(fn [{:keys [profile settings password new-password callback]}]
(native-module/convert-to-keycard-profile profile settings password new-password callback)))

View File

@ -1,5 +1,6 @@
(ns status-im.contexts.profile.profiles.view
(:require
[clojure.string :as string]
[native-module.core :as native-module]
[quo.core :as quo]
[react-native.core :as rn]
@ -182,7 +183,7 @@
:render-fn profile-card}]]))
(defn password-input
[]
[processing error]
(let [auth-method (rf/sub [:auth-method])
on-press-biometrics (fn []
(rf/dispatch [:biometric/authenticate
@ -193,18 +194,31 @@
%])}]))]
[standard-authentication/password-input
{:shell? true
{:processing processing
:error error
:shell? true
:blur? true
:on-press-biometrics (when (= auth-method constants/auth-method-biometric) on-press-biometrics)}]))
(defn- get-error-message
[error]
(if (and (some? error)
(or (= error "file is not a database")
(string/starts-with? (string/lower-case error) "failed")))
(i18n/label :t/oops-wrong-password)
error))
(defn login-section
[{:keys [show-profiles]}]
(let [processing (rf/sub [:profile/login-processing])
{:keys [key-uid name keycard-pairing
(let [{:keys [key-uid name keycard-pairing
customization-color]} (rf/sub [:profile/login-profile])
sign-in-enabled? (rf/sub [:sign-in-enabled?])
profile-picture (rf/sub [:profile/login-profiles-picture key-uid])
login-multiaccount (rn/use-callback #(rf/dispatch [:profile.login/login]))]
{:keys [error processing]} (rf/sub [:profile/login])
error-message (rn/use-memo #(get-error-message error) [error])
error {:error? (boolean (seq error-message))
:error-message error-message}
login-profile (rn/use-callback #(rf/dispatch [:profile.login/login]))]
[rn/keyboard-avoiding-view
{:style style/login-container
:keyboard-vertical-offset (- (safe-area/get-bottom))}
@ -239,17 +253,20 @@
:card-style style/login-profile-card}]
(if keycard-pairing
[keycard.pin/auth
{:on-complete
(fn [pin-text]
{:error error
:on-complete
(fn [pin]
(rf/dispatch
[:keycard/connect
{:key-uid key-uid
:on-success-fx [[:effects.keycard/get-keys
{:pin pin-text
:on-success
(fn []
(rf/dispatch
[:keycard/get-keys
{:pin pin
:on-success #(rf/dispatch [:keycard.login/on-get-keys-success %])
:on-failure #(rf/dispatch [:keycard/on-action-with-pin-error
%])}]]}]))}]
[password-input])]
:on-failure #(rf/dispatch [:keycard/on-action-with-pin-error %])}]))}]))}]
[password-input processing error])]
(when-not keycard-pairing
[quo/button
{:size 40
@ -258,7 +275,7 @@
:accessibility-label :login-button
:icon-left :i/unlocked
:disabled? (or (not sign-in-enabled?) processing)
:on-press login-multiaccount
:on-press login-profile
:container-style {:margin-bottom (+ (safe-area/get-bottom) 12)}}
(i18n/label :t/log-in)])]))

View File

@ -4,6 +4,7 @@
[react-native.core :as rn]
[status-im.common.events-helper :as events-helper]
[status-im.common.resources :as resources]
[status-im.constants :as constants]
[utils.i18n :as i18n]
[utils.re-frame :as rf]))
@ -27,11 +28,11 @@
:subtitle (i18n/label :t/secure-wallet-card)
:button-label (i18n/label :t/buy-keycard)
:accessibility-label :get-keycard
:image (resources/get-image :generate-keys)
:on-press #()}]
:image (resources/get-image :keycard-buy)
:on-press #(rf/dispatch [:browser.ui/open-url constants/get-keycard-url])}]
[rn/view {:style {:margin-top 24}}
[quo/text
{:style {:margin-bottom 12
{:style {:margin-bottom 1
:color colors/white-opa-70}
:size :paragraph-2
:weight :medium}

View File

@ -43,8 +43,7 @@
[quo/markdown-list
{:description (i18n/label label)
:blur? true
:type (when lock? :lock)
:container-style {:padding-top 8}}])]})
:type (when lock? :lock)}])]})
(defn- on-privacy-policy-press
[]

View File

@ -69,12 +69,6 @@
transitions/new-to-status-modal-animations
transitions/push-animations-for-transparent-background)}))
(def keycard-modal-screen-options
(assoc transparent-modal-screen-options
:layout nil
:theme :dark
:insets {:top? true :bottom? true}))
(def sheet-options
{:layout {:componentBackgroundColor :transparent
:orientation ["portrait"]

View File

@ -32,7 +32,12 @@
[status-im.contexts.keycard.check.view :as keycard.check]
[status-im.contexts.keycard.empty.view :as keycard.empty]
[status-im.contexts.keycard.error.view :as keycard.error]
[status-im.contexts.keycard.migrate.fail.view :as keycard.migrate.fail]
[status-im.contexts.keycard.migrate.re-encrypting.view :as keycard.re-encrypting]
[status-im.contexts.keycard.migrate.success.view :as keycard.migrate.success]
[status-im.contexts.keycard.migrate.view :as keycard.migrate]
[status-im.contexts.keycard.not-keycard.view :as keycard.not-keycard]
[status-im.contexts.keycard.pin.create.view :as pin.create]
[status-im.contexts.onboarding.create-or-sync-profile.view :as create-or-sync-profile]
[status-im.contexts.onboarding.create-password.view :as create-password]
[status-im.contexts.onboarding.create-profile.view :as create-profile]
@ -310,8 +315,9 @@
:component settings/view}
{:name :screen/settings.keycard
:metrics {:track? :true}
:options options/keycard-modal-screen-options
:metrics {:track? :true
:alias-id :settings.keycard}
:options {:insets {:top? true :bottom? true}}
:component settings.keycard/view}
{:name :edit-profile
@ -877,29 +883,73 @@
(def keycard-screens
[{:name :screen/keycard.check
:metrics {:track? true}
:options options/keycard-modal-screen-options
:metrics {:track? :true
:alias-id :keycard.check}
:options {:insets {:top? true :bottom? true}}
:component keycard.check/view}
{:name :screen/keycard.empty
:metrics {:track? true}
:options options/keycard-modal-screen-options
:metrics {:track? :true
:alias-id :keycard.empty}
:options {:insets {:top? true :bottom? true}}
:component keycard.empty/view}
{:name :screen/keycard.error
:metrics {:track? true}
:options options/keycard-modal-screen-options
:metrics {:track? :true
:alias-id :keycard.error}
:options {:insets {:top? true :bottom? true}}
:component keycard.error/view}
{:name :screen/keycard.not-keycard
:metrics {:track? true}
:options options/keycard-modal-screen-options
:metrics {:track? :true
:alias-id :keycard.not-keycard}
:options {:insets {:top? true :bottom? true}}
:component keycard.not-keycard/view}
{:name :screen/keycard.authorise
:metrics {:track? true}
:options options/keycard-modal-screen-options
:component keycard.authorise/view}])
:metrics {:track? :true
:alias-id :keycard.authorise}
:options {:insets {:top? true :bottom? true}}
:component keycard.authorise/view}
{:name :screen/keycard.migrate
:metrics {:track? :true
:alias-id :keycard.migrate}
:options {:insets {:top? true :bottom? true}}
:component keycard.migrate/view}
{:name :screen/keycard.re-encrypting
:metrics {:track? :true
:alias-id :keycard.re-encrypting}
:options {:insets {:top? true :bottom? true}
:popGesture false
:hardwareBackButton {:dismissModalOnPress false
:popStackOnPress false}}
:component keycard.re-encrypting/view}
{:name :screen/keycard.migrate.success
:metrics {:track? :true
:alias-id :keycard.migrate.success}
:options {:insets {:top? true :bottom? true}
:popGesture false
:hardwareBackButton {:dismissModalOnPress false
:popStackOnPress false}}
:component keycard.migrate.success/view}
{:name :screen/keycard.migrate.fail
:metrics {:track? :true
:alias-id :keycard.migrate.fail}
:options {:insets {:top? true :bottom? true}
:popGesture false
:hardwareBackButton {:dismissModalOnPress false
:popStackOnPress false}}
:component keycard.migrate.fail/view}
{:name :screen/keycard.pin.create
:metrics {:track? :true
:alias-id :keycard.pin.create}
:options {:insets {:top? true :bottom? true}}
:component pin.create/view}])
(defn screens
[]

View File

@ -11,7 +11,7 @@
[status-im.common.bottom-sheet-screen.view :as bottom-sheet-screen]
[status-im.common.bottom-sheet.view :as bottom-sheet]
[status-im.common.toasts.view :as toasts]
[status-im.contexts.keycard.nfc-sheet.view :as keycard.sheet]
[status-im.contexts.keycard.nfc.sheets.view :as keycard.sheet]
[status-im.navigation.screens :as screens]
[status-im.setup.hot-reload :as reloader]
[utils.re-frame :as rf]))

View File

@ -293,6 +293,8 @@
"cant-fetch-info": "Can't fetch info",
"cant-open-public-chat": "Can't open public chat",
"cant-report-bug": "Can't report a bug",
"cant-store-new-keys": "You cant use it to store new keys right now",
"cant-use-right-now": "You cant use it right now",
"card-is-blank": "This card is blank",
"card-reseted": "Card has been reseted",
"card-unpaired": "Card has been unpaired from current device",
@ -782,6 +784,8 @@
"display-collectibles": "Display collectibles",
"do-not-cheat": "Don't try to cheat",
"do-not-cheat-description": "These 12 words give access to all of your funds so it is important that you write them in the correct order, take it seriously.",
"do-not-quit": "Do not quit the application or turn off your device. Doing so will lead to data corruption, loss of your Status profile and the inability to use Status.",
"do-not-share": "Do not share",
"done": "Done",
"dont-ask": "Don't ask me again",
@ -1010,6 +1014,7 @@
"failed": "Failed",
"failed-on": "Failed on",
"failed-to-fetch-community": "Failed to fetch community",
"failed-to-migrate-key-pair": "Failed to migrate key pair",
"faq": "Frequently asked questions",
"fast": "Fast",
"favorite-communities": "Your favourite communities",
@ -1317,6 +1322,7 @@
"key-name-error-too-short": "Key pair name must be at least {{count}} characters",
"key-on-device": "Private key is saved on this device",
"key-pair-imported-successfully": "{{name}} key pair imported successfully",
"key-pair-migrated-successfully": "Profile key pair successfully migrated",
"key-pair-name-updated": "Key pair name updated",
"key-pair-removed": "Key pair and derived accounts has been removed",
"key-pairs-successfully-imported": "{{count}} key pairs successfully imported",
@ -1344,6 +1350,7 @@
"keycard-factory-reset-text": "Performing this will delete any mnemonic phrase stored on the card. Make sure you have a backup of the mnemonic phrase you've been using with this Keycard.",
"keycard-factory-reset-title": "Are you sure you want to perform a factory reset?",
"keycard-free-pairing-slots": "Keycard has {{n}} free pairing slots",
"keycard-full": "Keycard is full",
"keycard-has-multiaccount-on-it": "This card is full. Each card can hold one main key pair",
"keycard-init-description": "Put the card to the back of your phone to continue",
"keycard-init-title": "Looking for cards...",
@ -1354,6 +1361,8 @@
"keycard-is-frozen-factory-reset": "Reset with mnemonic",
"keycard-is-frozen-reset": "Reset with PUK",
"keycard-is-frozen-title": "Keycard is frozen",
"keycard-locked": "Keycard is locked",
"keycard-not-empty": "Keycard is not empty",
"keycard-onboarding-finishing-header": "Finishing up",
"keycard-onboarding-intro-header": "Store your keys on Keycard",
"keycard-onboarding-intro-text": "Get ready, this might take a few minutes, but it's important to secure your account",
@ -1450,6 +1459,8 @@
"log-in": "Log in",
"log-level": "Log level",
"log-level-settings": "Log level settings",
"log-out-remove": "Log out & Remove profile",
"log-out-remove-profile": "Log out and remove profile from this device",
"logged-in": "Logged in",
"logging": "Logging",
"logging-enabled": "Logging enabled?",
@ -1718,6 +1729,7 @@
"no-fees": "No fees",
"no-group-chats": "No group chats",
"no-group-chats-description": "Much fun. Have friends. Wow!",
"no-key-pair-keycard": "There is no key pair on this Keycard",
"no-keycard-applet-on-card": "No Keycard applet on card",
"no-messages": "No messages",
"no-messages-description": "Heres a cat in a box instead",
@ -1876,6 +1888,7 @@
"pairing-new-installation-detected-title": "New device detected",
"pairing-no-info": "No info",
"pairing-please-set-a-name": "Please set a name for your device.",
"pairing-slots-occupied": "All pairing slots are occupied",
"paraswap-error": "Paraswap error: {{paraswap-error}}",
"participate-in-the-metaverse": "Participate in the truly free metaverse",
"passphrase": "Passphrase",
@ -2024,9 +2037,11 @@
"re-encrypt": "Re-encrypt",
"re-encrypt-data": "Re-encrypt your data",
"re-encrypt-key": "Re-encrypt your keys",
"re-encrypting-data": "Re-encrypting data",
"read": "Read",
"read-more": "Read more",
"ready-keycard": "Get your Keycard ready",
"ready-to-migrate-key-pair": "Ready to migrate profile key pair to the Keycard",
"ready-to-scan": "Ready to Scan",
"rearrange-categories": "Rearrange Categories",
"receive": "Receive",
@ -2045,6 +2060,7 @@
"recover": "Recover",
"recover-key": "Access existing keys",
"recover-keycard-multiaccount-not-supported": "Keys for this account already exist and can't be added again. If you've lost your password, passcode or Keycard, uninstall the app, reinstall and access your keys by entering your seed phrase",
"recover-status-profile": "Recover your Status profile with recovery phrase",
"recover-with-keycard": "Recover with Keycard",
"recover-with-seed-phrase": "Recover with seed phrase",
"recovering-key": "Accessing keys...",
@ -2170,6 +2186,7 @@
"scan-an-account-qr-code": "Scan an account QR code",
"scan-an-address-qr-code": "Scan an address QR code",
"scan-key-pairs-qr-code": "Scan key pairs QR code",
"scan-keycard": "Scan Keycard",
"scan-or-enter-a-sync-code": "Scan or enter a sync code",
"scan-or-enter-sync-code": "Scan or enter sync code",
"scan-or-enter-sync-code-seen-on-this-device": "Scan or enter sync code seen on this device",
@ -2622,6 +2639,7 @@
"unknown-status-go-error": "Unknown status-go error",
"unlimited": "Unlimited",
"unlock": "Unlock",
"unlock-reset-instructions": "To unlock or factory reset the Keycard, please use the Status desktop app. If you'd like this feature on mobile, feel free to upvote them and discuss in the Status community.",
"unmute": "Unmute",
"unmute-channel": "Unmute channel",
"unmute-chat": "Unmute chat",
@ -2660,6 +2678,7 @@
"use-as-profile-picture": "Use as profile picture",
"use-biometrics": "Use biometrics to fill in your password",
"use-keycard": "Use Keycard",
"use-keycard-for-status": "From now on, use this Keycard to login to Status and transact with derived accounts on all synced devices. To start using it, logout and log back in with this Keycard.",
"use-keycard-login-sign": "Use this Keycard to login and sign transactions",
"use-keycard-subtitle": "Keys will be stored on your Keycard",
"use-photo": "Use Photo",
@ -2809,6 +2828,7 @@
"what-to-do": "What you would like to do?",
"what-we-will-receive": "What we will receive:",
"what-we-wont-receive": "What we won't receive:",
"what-you-can-do": "What you can do:",
"whats-on-your-mind": "Whats on your mind…",
"which-connection-to-use": "Which connection to use for syncing?",
"who-are-you-looking-for": "Who are you looking for ?",