implement account conversion
Signed-off-by: Michele Balistreri <michele@bitgamma.com>
This commit is contained in:
parent
baa96ed22e
commit
e8f7ae8f27
|
@ -528,6 +528,22 @@ class StatusModule extends ReactContextBaseJavaModule implements LifecycleEventL
|
|||
StatusThreadPoolExecutor.getInstance().execute(r);
|
||||
}
|
||||
|
||||
@ReactMethod
|
||||
public void verifyDatabasePassword(final String keyUID, final String password, final Callback callback) {
|
||||
Log.d(TAG, "verifyDatabasePassword");
|
||||
|
||||
Runnable r = new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
String result = Statusgo.verifyDatabasePassword(keyUID, password);
|
||||
|
||||
callback.invoke(result);
|
||||
}
|
||||
};
|
||||
|
||||
StatusThreadPoolExecutor.getInstance().execute(r);
|
||||
}
|
||||
|
||||
public String getKeyStorePath(String keyUID) {
|
||||
final String commonKeydir = pathCombine(this.getNoBackupDirectory(), "/keystore");
|
||||
final String keydir = pathCombine(commonKeydir, keyUID);
|
||||
|
@ -1478,6 +1494,20 @@ class StatusModule extends ReactContextBaseJavaModule implements LifecycleEventL
|
|||
StatusThreadPoolExecutor.getInstance().execute(r);
|
||||
}
|
||||
|
||||
@ReactMethod
|
||||
public void convertToKeycardAccount(final String keyUID, final String accountData, final String options, final String password, final String newPassword, final Callback callback) {
|
||||
Log.d(TAG, "convertToKeycardAccount");
|
||||
|
||||
final String keyStoreDir = this.getKeyStorePath(keyUID);
|
||||
Runnable r = new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
String result = Statusgo.convertToKeycardAccount(keyStoreDir, accountData, options, password, newPassword);
|
||||
callback.invoke(result);
|
||||
}
|
||||
};
|
||||
|
||||
StatusThreadPoolExecutor.getInstance().execute(r);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -570,6 +570,17 @@ RCT_EXPORT_METHOD(verify:(NSString *)address
|
|||
callback(@[result]);
|
||||
}
|
||||
|
||||
//////////////////////////////////////////////////////////////////// verifyDatabasePassword
|
||||
RCT_EXPORT_METHOD(verifyDatabasePassword:(NSString *)keyUID
|
||||
password:(NSString *)password
|
||||
callback:(RCTResponseSenderBlock)callback) {
|
||||
#if DEBUG
|
||||
NSLog(@"VerifyDatabasePassword() method called");
|
||||
#endif
|
||||
NSString *result = StatusgoVerifyDatabasePassword(keyUID, password);
|
||||
callback(@[result]);
|
||||
}
|
||||
|
||||
//////////////////////////////////////////////////////////////////// changeDatabasePassword
|
||||
RCT_EXPORT_METHOD(reEncryptDbAndKeystore:(NSString *)keyUID
|
||||
currentPassword:(NSString *)currentPassword
|
||||
|
@ -583,6 +594,21 @@ RCT_EXPORT_METHOD(reEncryptDbAndKeystore:(NSString *)keyUID
|
|||
callback(@[result]);
|
||||
}
|
||||
|
||||
//////////////////////////////////////////////////////////////////// convertToKeycardAccount
|
||||
RCT_EXPORT_METHOD(convertToKeycardAccount:(NSString *)keyUID
|
||||
accountData:(NSString *)accountData
|
||||
settings:(NSString *)settings
|
||||
currentPassword:(NSString *)currentPassword
|
||||
newPassword:(NSString *)newPassword
|
||||
callback:(RCTResponseSenderBlock)callback) {
|
||||
#if DEBUG
|
||||
NSLog(@"convertToKeycardAccount() method called");
|
||||
#endif
|
||||
NSURL *multiaccountKeystoreDir = [self getKeyStoreDir:keyUID];
|
||||
NSString *result = StatusgoConvertToKeycardAccount(multiaccountKeystoreDir.path, accountData, settings, currentPassword, newPassword);
|
||||
callback(@[result]);
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////
|
||||
#pragma mark - SendTransaction
|
||||
//////////////////////////////////////////////////////////////////// sendTransaction
|
||||
|
|
|
@ -5,6 +5,7 @@
|
|||
[status-im.multiaccounts.model :as multiaccounts.model]
|
||||
[status-im.utils.fx :as fx]
|
||||
[re-frame.core :as re-frame]
|
||||
[clojure.string :as string]
|
||||
[status-im.i18n.i18n :as i18n]
|
||||
[taoensso.timbre :as log]
|
||||
[status-im.keycard.common :as common]
|
||||
|
@ -13,6 +14,10 @@
|
|||
[status-im.ethereum.eip55 :as eip55]
|
||||
[status-im.ethereum.core :as ethereum]
|
||||
[status-im.bottom-sheet.core :as bottom-sheet]
|
||||
[status-im.native-module.core :as status]
|
||||
[status-im.utils.types :as types]
|
||||
[status-im.utils.security :as security]
|
||||
[status-im.utils.keychain.core :as keychain]
|
||||
[status-im.utils.platform :as platform]))
|
||||
|
||||
(fx/defn pair* [_ password]
|
||||
|
@ -217,13 +222,54 @@
|
|||
(navigation/set-stack-root :profile-stack [:my-profile :keycard-settings])
|
||||
(return-to-keycard-login))))
|
||||
|
||||
(re-frame/reg-fx
|
||||
::finish-migration
|
||||
(fn [[account settings password encryption-pass login-params]]
|
||||
(status/convert-to-keycard-account
|
||||
account
|
||||
settings
|
||||
password
|
||||
encryption-pass
|
||||
#(let [{:keys [error]} (types/json->clj %)]
|
||||
(if (string/blank? error)
|
||||
(status/login-with-keycard login-params)
|
||||
(throw (js/Error. "Please shake the phone to report this error and restart the app. Migration failed unexpectedly.")))))))
|
||||
|
||||
(fx/defn migrate-account
|
||||
[{:keys [db] :as cofx}]
|
||||
(let [pairing (get-in db [:keycard :secrets :pairing])
|
||||
paired-on (get-in db [:keycard :secrets :paired-on])
|
||||
instance-uid (get-in db [:keycard :multiaccount :instance-uid])
|
||||
account (-> db
|
||||
:multiaccounts/login
|
||||
(assoc :keycard-pairing pairing)
|
||||
(assoc :save-password? false))
|
||||
key-uid (-> account :key-uid)
|
||||
settings {:keycard-instance-uid instance-uid
|
||||
:keycard-paired-on paired-on
|
||||
:keycard-pairing pairing}
|
||||
password (ethereum/sha3 (security/safe-unmask-data (get-in db [:keycard :migration-password])))
|
||||
encryption-pass (get-in db [:keycard :multiaccount :encryption-public-key])
|
||||
login-params {:key-uid key-uid
|
||||
:multiaccount-data (types/clj->json account)
|
||||
:password encryption-pass
|
||||
:chat-key (get-in db [:keycard :multiaccount :whisper-private-key])}]
|
||||
{:db (-> db
|
||||
(assoc-in [:multiaccounts/multiaccounts key-uid :keycard-pairing] pairing)
|
||||
(assoc :multiaccounts/login account)
|
||||
(assoc :auth-method keychain/auth-method-none)
|
||||
(update :keycard dissoc :flow :migration-password)
|
||||
(dissoc :recovered-account?))
|
||||
::finish-migration [account settings password encryption-pass login-params]}))
|
||||
|
||||
(fx/defn on-generate-and-load-key-success
|
||||
{:events [:keycard.callback/on-generate-and-load-key-success]
|
||||
:interceptors [(re-frame/inject-cofx :random-guid-generator)
|
||||
(re-frame/inject-cofx ::multiaccounts.create/get-signing-phrase)]}
|
||||
[{:keys [db random-guid-generator] :as cofx} data]
|
||||
(let [account-data (js->clj data :keywordize-keys true)
|
||||
backup? (get-in db [:keycard :creating-backup?])]
|
||||
backup? (get-in db [:keycard :creating-backup?])
|
||||
migration? (get-in db [:keycard :converting-account?])]
|
||||
(fx/merge cofx
|
||||
{:db (-> db
|
||||
(assoc-in [:keycard :multiaccount]
|
||||
|
@ -242,14 +288,14 @@
|
|||
(assoc-in [:keycard :pin :status] nil)
|
||||
(assoc-in [:keycard :application-info :key-uid]
|
||||
(ethereum/normalized-hex (:key-uid account-data)))
|
||||
(update :keycard dissoc :recovery-phrase)
|
||||
(update :keycard dissoc :creating-backup?)
|
||||
(update-in [:keycard :secrets] dissoc :pin :puk :password)
|
||||
(assoc :multiaccounts/new-installation-id (random-guid-generator))
|
||||
(update-in [:keycard :secrets] dissoc :mnemonic))}
|
||||
(update :keycard dissoc :recovery-phrase :creating-backup? :converting-account?)
|
||||
(update-in [:keycard :secrets] dissoc :pin :puk :password :mnemonic)
|
||||
(assoc :multiaccounts/new-installation-id (random-guid-generator)))}
|
||||
(common/remove-listener-to-hardware-back-button)
|
||||
(common/hide-connection-sheet)
|
||||
(if backup? (on-backup-success backup?) (create-keycard-multiaccount)))))
|
||||
(cond backup? (on-backup-success backup?)
|
||||
migration? (migrate-account)
|
||||
:else (create-keycard-multiaccount)))))
|
||||
|
||||
(fx/defn on-generate-and-load-key-error
|
||||
{:events [:keycard.callback/on-generate-and-load-key-error]}
|
||||
|
|
|
@ -10,8 +10,11 @@
|
|||
[status-im.popover.core :as popover]
|
||||
[status-im.utils.fx :as fx]
|
||||
[status-im.utils.security :as security]
|
||||
[status-im.ethereum.core :as ethereum]
|
||||
[status-im.i18n.i18n :as i18n]
|
||||
[status-im.utils.types :as types]
|
||||
[status-im.keycard.backup-key :as keycard.backup]))
|
||||
[status-im.keycard.backup-key :as keycard.backup]
|
||||
[status-im.bottom-sheet.core :as bottom-sheet]))
|
||||
|
||||
(fx/defn key-and-storage-management-pressed
|
||||
"This event can be dispatched before login and from profile and needs to redirect accordingly"
|
||||
|
@ -29,6 +32,11 @@
|
|||
[{:keys [db] :as cofx} checked?]
|
||||
{:db (assoc-in db [:multiaccounts/key-storage :move-keystore-checked?] checked?)})
|
||||
|
||||
(fx/defn reset-db-checked
|
||||
{:events [::reset-db-checked]}
|
||||
[{:keys [db] :as cofx} checked?]
|
||||
{:db (assoc-in db [:multiaccounts/key-storage :reset-db-checked?] checked?)})
|
||||
|
||||
(fx/defn navigate-back
|
||||
{:events [::navigate-back]}
|
||||
[{:keys [db] :as cofx}]
|
||||
|
@ -120,11 +128,6 @@
|
|||
[{:keys [db]} selected?]
|
||||
{:db (assoc-in db [:multiaccounts/key-storage :keycard-storage-selected?] selected?)})
|
||||
|
||||
(fx/defn warning-popup
|
||||
{:events [::show-transfer-warning-popup]}
|
||||
[cofx]
|
||||
(popover/show-popover cofx {:view :transfer-multiaccount-to-keycard-warning}))
|
||||
|
||||
(re-frame/reg-fx
|
||||
::delete-multiaccount
|
||||
(fn [{:keys [key-uid on-success on-error]}]
|
||||
|
@ -158,13 +161,54 @@ The exact events dispatched for this flow if consumed from the UI are:
|
|||
|
||||
We don't need to take the exact steps, just set the required state and redirect to correct screen
|
||||
"
|
||||
(fx/defn handle-delete-multiaccount-success
|
||||
(fx/defn import-multiaccount
|
||||
{:events [::delete-multiaccount-success]}
|
||||
[{:keys [db] :as cofx} _]
|
||||
{::multiaccounts.recover/import-multiaccount {:passphrase (get-in db [:multiaccounts/key-storage :seed-phrase])
|
||||
[{:keys [db] :as cofx}]
|
||||
{:dispatch [:bottom-sheet/hide]
|
||||
::multiaccounts.recover/import-multiaccount {:passphrase (get-in db [:multiaccounts/key-storage :seed-phrase])
|
||||
:password nil
|
||||
:success-event ::import-multiaccount-success}})
|
||||
|
||||
(fx/defn storage-selected
|
||||
{:events [::storage-selected]}
|
||||
[{:keys [db] :as cofx}]
|
||||
(if (get-in db [:multiaccounts/key-storage :reset-db-checked?])
|
||||
(popover/show-popover cofx {:view :transfer-multiaccount-to-keycard-warning})
|
||||
(bottom-sheet/show-bottom-sheet cofx {:view :migrate-account-password})))
|
||||
|
||||
(fx/defn skip-password-pressed
|
||||
{:events [::skip-password-pressed]}
|
||||
[cofx]
|
||||
(popover/show-popover cofx {:view :transfer-multiaccount-to-keycard-warning}))
|
||||
|
||||
(fx/defn password-changed
|
||||
{:events [::password-changed]}
|
||||
[{db :db} password]
|
||||
(let [unmasked-pass (security/safe-unmask-data password)]
|
||||
{:db (update db :keycard assoc
|
||||
:migration-password password
|
||||
:migration-password-error nil
|
||||
:migration-password-valid? (and unmasked-pass (> (count unmasked-pass) 5)))}))
|
||||
|
||||
(fx/defn verify-password-result
|
||||
{:events [::verify-password-result]}
|
||||
[{:keys [db] :as cofx} result]
|
||||
(let [{:keys [error]} (types/json->clj result)]
|
||||
(if (string/blank? error)
|
||||
(fx/merge
|
||||
cofx
|
||||
{:db (update db :keycard dissoc :migration-password-error :migration-password-valid?)}
|
||||
(import-multiaccount))
|
||||
{:db (assoc-in db [:keycard :migration-password-error] (i18n/label :t/wrong-password))})))
|
||||
|
||||
(fx/defn verify-password
|
||||
{:events [::verify-password]}
|
||||
[{:keys [db] :as cofx}]
|
||||
(native-module/verify-database-password
|
||||
(get-in db [:multiaccounts/login :key-uid])
|
||||
(ethereum/sha3 (security/safe-unmask-data (get-in db [:keycard :migration-password])))
|
||||
#(re-frame/dispatch [::verify-password-result %])))
|
||||
|
||||
(fx/defn handle-multiaccount-import
|
||||
{:events [::import-multiaccount-success]}
|
||||
[{:keys [db] :as cofx} root-data derived-data]
|
||||
|
@ -178,6 +222,7 @@ We don't need to take the exact steps, just set the required state and redirect
|
|||
:selected-storage-type :advanced)
|
||||
(assoc-in [:keycard :flow] :recovery)
|
||||
(assoc-in [:keycard :from-key-storage-and-migration?] true)
|
||||
(assoc-in [:keycard :converting-account?] (not (get-in db [:multiaccounts/key-storage :reset-db-checked?])))
|
||||
(dissoc :multiaccounts/key-storage))}
|
||||
(popover/hide-popover)
|
||||
(navigation/navigate-to-cofx :keycard-onboarding-intro nil)))
|
||||
|
|
|
@ -337,12 +337,15 @@
|
|||
(fx/defn login-only-events
|
||||
[{:keys [db] :as cofx} key-uid password save-password?]
|
||||
(let [auth-method (:auth-method db)
|
||||
new-auth-method (get-new-auth-method auth-method save-password?)]
|
||||
new-auth-method (get-new-auth-method auth-method save-password?)
|
||||
from-migration? (get-in db [:keycard :from-key-storage-and-migration?])]
|
||||
(log/debug "[login] login-only-events"
|
||||
"auth-method" auth-method
|
||||
"new-auth-method" new-auth-method)
|
||||
(fx/merge cofx
|
||||
{:db (assoc db :chats/loading? true)
|
||||
{:db (-> db
|
||||
(assoc :chats/loading? true)
|
||||
(update :keycard dissoc :from-key-storage-and-migration?))
|
||||
::json-rpc/call
|
||||
[{:method "browsers_getBrowsers"
|
||||
:on-success #(re-frame/dispatch [::initialize-browsers %])}
|
||||
|
@ -354,6 +357,8 @@
|
|||
:on-success #(do (re-frame/dispatch [::get-settings-callback %])
|
||||
(redirect-to-root db))}]}
|
||||
(notifications/load-notification-preferences)
|
||||
(when from-migration?
|
||||
(utils/show-popup (i18n/label :t/migration-successful) (i18n/label :t/migration-successful-text)))
|
||||
(when save-password?
|
||||
(keychain/save-user-password key-uid password))
|
||||
(keychain/save-auth-method key-uid (or new-auth-method auth-method keychain/auth-method-none)))))
|
||||
|
|
|
@ -192,6 +192,12 @@
|
|||
(log/debug "[native-module] verify")
|
||||
(.verify ^js (status) address hashed-password callback))
|
||||
|
||||
(defn verify-database-password
|
||||
"NOTE: beware, the password has to be sha3 hashed"
|
||||
[key-uid hashed-password callback]
|
||||
(log/debug "[native-module] verify-database-password")
|
||||
(.verifyDatabasePassword ^js (status) key-uid hashed-password callback))
|
||||
|
||||
(defn login-with-keycard
|
||||
[{:keys [key-uid multiaccount-data password chat-key]}]
|
||||
(log/debug "[native-module] login-with-keycard")
|
||||
|
@ -414,3 +420,14 @@
|
|||
(init-keystore
|
||||
key-uid
|
||||
#(.reEncryptDbAndKeystore ^js (status) key-uid current-password# new-password# 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")
|
||||
(.convertToKeycardAccount ^js (status)
|
||||
key-uid
|
||||
(types/clj->json multiaccount-data)
|
||||
(types/clj->json settings)
|
||||
current-password#
|
||||
new-password
|
||||
callback))
|
||||
|
|
|
@ -3,6 +3,7 @@
|
|||
[re-frame.core :as re-frame]
|
||||
[status-im.ui.screens.home.sheet.views :as home.sheet]
|
||||
[status-im.ui.screens.keycard.views :as keycard]
|
||||
[status-im.ui.screens.multiaccounts.key-storage.views :as key-storage]
|
||||
[status-im.ui.screens.about-app.views :as about-app]
|
||||
[status-im.ui.screens.multiaccounts.recover.views :as recover.views]
|
||||
[quo.core :as quo]))
|
||||
|
@ -33,7 +34,10 @@
|
|||
(merge about-app/learn-more)
|
||||
|
||||
(= view :recover-sheet)
|
||||
(merge recover.views/bottom-sheet))]
|
||||
(merge recover.views/bottom-sheet)
|
||||
|
||||
(= view :migrate-account-password)
|
||||
(merge key-storage/migrate-account-password))]
|
||||
[quo/bottom-sheet opts
|
||||
(when content
|
||||
[content])]))
|
||||
|
|
|
@ -24,3 +24,11 @@
|
|||
{:color colors/gray
|
||||
:text-align :center
|
||||
:line-height 22})
|
||||
|
||||
(def header
|
||||
{:flex-direction :row
|
||||
:align-items :center
|
||||
:justify-content :space-between
|
||||
:padding-top 16
|
||||
:padding-left 16
|
||||
:margin-bottom 11})
|
|
@ -55,7 +55,7 @@
|
|||
;; Component to render Key and Storage management screen
|
||||
(defview actions-base [{:keys [next-title next-event]}]
|
||||
(letsubs [{:keys [name] :as multiaccount} [:multiaccounts/login]
|
||||
{:keys [move-keystore-checked?]} [:multiaccounts/key-storage]]
|
||||
{:keys [move-keystore-checked? reset-db-checked?]} [:multiaccounts/key-storage]]
|
||||
[react/view {:flex 1}
|
||||
[local-topbar (i18n/label :t/choose-actions)]
|
||||
[accordion/section {:title name
|
||||
|
@ -77,8 +77,8 @@
|
|||
[quo/list-item {:title (i18n/label :t/reset-database)
|
||||
:subtitle (i18n/label :t/reset-database-warning)
|
||||
:subtitle-max-lines 4
|
||||
:disabled true
|
||||
:active move-keystore-checked?
|
||||
:active reset-db-checked?
|
||||
:on-press #(re-frame/dispatch [::multiaccounts.key-storage/reset-db-checked (not reset-db-checked?)])
|
||||
:accessory :checkbox}]]
|
||||
(when (and next-title next-event)
|
||||
[toolbar/toolbar {:show-border? true
|
||||
|
@ -195,9 +195,37 @@
|
|||
:right [quo/button
|
||||
{:type :secondary
|
||||
:disabled (not keycard-storage-selected?)
|
||||
:on-press #(re-frame/dispatch [::multiaccounts.key-storage/show-transfer-warning-popup])}
|
||||
:on-press #(re-frame/dispatch [::multiaccounts.key-storage/storage-selected])}
|
||||
(i18n/label :t/confirm)]}]]]))
|
||||
|
||||
(defview migrate-account-password-view []
|
||||
(letsubs [{:keys [migration-password-error migration-password-valid?]} [:keycard]]
|
||||
[react/view {:flex 1}
|
||||
[react/view styles/header
|
||||
[react/text {:style {:typography :title-bold}} (i18n/label :t/enter-password)]
|
||||
[react/view {:padding-horizontal 24}
|
||||
[quo/button {:type :secondary
|
||||
:on-press #(re-frame/dispatch [::multiaccounts.key-storage/skip-password-pressed])}
|
||||
(i18n/label :t/skip)]]]
|
||||
[quo/separator]
|
||||
[react/view {:padding-horizontal 16 :padding-vertical 12}
|
||||
[react/text {:style {:margin-top 6 :margin-bottom 12 :color colors/gray}} (i18n/label :t/enter-password-migration-prompt)]
|
||||
[quo/text-input
|
||||
{:secure-text-entry true
|
||||
:placeholder (i18n/label :t/current-password)
|
||||
:on-change-text #(re-frame/dispatch [::multiaccounts.key-storage/password-changed (status-im.utils.security/mask-data %)])
|
||||
:accessibility-label :enter-password-input
|
||||
:auto-capitalize :none
|
||||
:error migration-password-error
|
||||
:show-cancel false}]]
|
||||
[react/view {:padding-horizontal 16 :padding-vertical 12}
|
||||
[quo/button {:on-press #(re-frame/dispatch [::multiaccounts.key-storage/verify-password])
|
||||
:disabled (not migration-password-valid?)}
|
||||
(i18n/label :t/confirm)]]]))
|
||||
|
||||
(def migrate-account-password
|
||||
{:content migrate-account-password-view})
|
||||
|
||||
(defview seed-key-uid-mismatch-popover []
|
||||
(letsubs [{:keys [name]} [:multiaccounts/login]
|
||||
{logged-in-name :name} [:multiaccount]]
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
"_comment": "DO NOT EDIT THIS FILE BY HAND. USE 'scripts/update-status-go.sh <tag>' instead",
|
||||
"owner": "status-im",
|
||||
"repo": "status-go",
|
||||
"version": "v0.82.0",
|
||||
"commit-sha1": "2f6b32b1f54de5802882c0177bb0b16924d04271",
|
||||
"src-sha256": "1nlslsbibwnb7s6mbxqsijxrsw3lrs1dqvqy6bhfxsvwi5x97a28"
|
||||
"version": "v0.83.0",
|
||||
"commit-sha1": "6c2e9652d0e77a732af8649a1a75961ac9ac820f",
|
||||
"src-sha256": "14fhj54d46xv3vh5qkbryxq7hdjrp13pabz8012qa592i0lf72fb"
|
||||
}
|
||||
|
|
|
@ -521,6 +521,10 @@
|
|||
"pair-code-placeholder": "Pair code...",
|
||||
"enter-pair-code-description": "Pairing code can be set from an already paired Status client",
|
||||
"enter-password": "Enter password",
|
||||
"enter-password-migration-prompt": "Enter your password to move contacts, chats and settings along with your keys",
|
||||
"migration-successful": "Migration successful",
|
||||
"migration-successful-text": "Account succesfully migrated to Keycard",
|
||||
"skip": "Skip",
|
||||
"password-placeholder":"Password...",
|
||||
"confirm-password-placeholder": "Confirm your password...",
|
||||
"enter-pin": "Enter 6-digit passcode",
|
||||
|
|
Loading…
Reference in New Issue