[slow sign in] Better handling of migration failures and db encryption problems.

Migration failures are handled separately from other errors which might appear
during opening account's realm DB. In case if user chooses to erase
the account's database, only this database will be removed and other accounts
will not be touched.
This commit is contained in:
Roman Volosovskyi 2018-11-23 22:10:01 +02:00
parent 970c0ca7cd
commit c506521778
No known key found for this signature in database
GPG Key ID: 0238A4B5ECEE70DE
6 changed files with 142 additions and 55 deletions

View File

@ -33,7 +33,7 @@
(catch (fn [error]
(log/warn "Could not change account" error)
;; If all else fails we fallback to showing initial error
(re-frame/dispatch [:init.callback/account-change-error (str error)])))))
(re-frame/dispatch [:init.callback/account-change-error error])))))
;;;; Handlers
(fx/defn login [cofx]

View File

@ -27,7 +27,19 @@
(defn change-account [address password encryption-key]
(log/debug "changing account to: " address)
(data-source/change-account address password encryption-key))
(..
(js/Promise.
(fn [on-success on-error]
(try
(data-source/close-account-realm)
(on-success)
(catch :default e
(on-error {:message (str e)
:error :closing-account-failed})))))
(then
#(data-source/check-db-encryption address password encryption-key))
(then
#(data-source/open-account address password encryption-key))))
(defn- perform-transactions [raw-transactions realm]
(let [success-events (keep :success-event raw-transactions)

View File

@ -30,6 +30,17 @@
(.schemaVersion rn-dependencies/realm file-name (to-buffer encryption-key))
(.schemaVersion rn-dependencies/realm file-name)))
(defn encrypted-realm-version-promise
[file-name encryption-key]
(js/Promise.
(fn [on-success on-error]
(try
(encrypted-realm-version file-name encryption-key)
(on-success)
(catch :default e
(on-error {:message (str e)
:error :decryption-failed}))))))
(defn open-realm
[options file-name encryption-key]
(log/debug "Opening realm at " file-name "...")
@ -73,6 +84,15 @@
(log/warn "realm: deleting all realms")
(fs/unlink realm-dir))
(defn delete-account-realm
[address]
(log/warn "realm: deleting account db " (utils.ethereum/sha3 address))
(let [file (str accounts-realm-dir (utils.ethereum/sha3 address))]
(.. (fs/unlink file)
(then #(fs/unlink (str file ".lock")))
(then #(fs/unlink (str file ".management")))
(then #(fs/unlink (str file ".note"))))))
(defn ensure-directories []
(..
(fs/mkdir realm-dir)
@ -104,11 +124,12 @@
(defn- migrate-schemas
"Apply migrations in sequence and open database with the last schema"
[file-name schemas encryption-key current-version]
(log/info "migrate schemas")
(doseq [schema schemas
:when (> (:schemaVersion schema) current-version)
:let [migrated-realm (open-realm schema file-name encryption-key)]]
(close migrated-realm))
(log/info "migrate schemas" current-version)
(when (pos? current-version)
(doseq [schema schemas
:when (> (:schemaVersion schema) current-version)
:let [migrated-realm (open-realm schema file-name encryption-key)]]
(close migrated-realm)))
(open-realm (last schemas) file-name encryption-key))
(defn keccak512-array [key]
@ -136,10 +157,6 @@
file-name
encryption-key)))
(defn open-migrated-realm
[file-name schemas encryption-key]
(migrate-realm file-name schemas encryption-key))
(defn- index-entity-schemas [all-schemas]
(into {} (map (juxt :name identity)) (-> all-schemas last :schema)))
@ -152,6 +169,7 @@
(def realm-queue (utils.async/task-queue 2000))
(defn close-account-realm []
(log/debug "closing account realm")
(close @account-realm)
(reset! account-realm nil))
@ -159,7 +177,7 @@
(log/debug "Opening base realm... (first run)")
(when @base-realm
(close @base-realm))
(reset! base-realm (open-migrated-realm base-realm-path base/schemas encryption-key))
(reset! base-realm (migrate-realm base-realm-path base/schemas encryption-key))
(log/debug "Created @base-realm"))
(defn re-encrypt-realm
@ -172,11 +190,12 @@
(catch (fn [e]
(let [message (str "can't move old database " (str e) " " file-name)]
(log/debug message)
(on-error {:error message}))))
(on-error {:message message
:error :removing-old-db-failed}))))
(then (fn []
(let [old-account-db (open-migrated-realm old-file-name
account/schemas
old-key)]
(let [old-account-db (migrate-realm old-file-name
account/schemas
old-key)]
(log/info "copy old database")
(.writeCopyTo old-account-db file-name (to-buffer new-key))
(log/info "old database copied")
@ -190,38 +209,48 @@
(catch :default _))
(let [message (str "something went wrong " (str e) " " file-name)]
(log/info message)
(on-error {:error message})))))))
(on-error {:error :write-copy-to-failed
:message message})))))))
(defn get-account-db-path
[address]
(str accounts-realm-dir (utils.ethereum/sha3 address)))
(defn check-db-encryption
[file-name old-key new-key]
(js/Promise.
(fn [on-success on-error]
(try
(do
(log/info "try to encrypt with password")
(encrypted-realm-version file-name new-key)
(log/info "try to encrypt with password success")
(on-success))
(catch :default e
[address password old-key]
(let [file-name (get-account-db-path address)
new-key (db-encryption-key password old-key)]
(js/Promise.
(fn [on-success on-error]
(try
(do
(log/warn "failed checking db encryption with" e)
(log/info "try to encrypt with old key")
(encrypted-realm-version file-name old-key)
(log/info "try to encrypt with old key success")
(re-encrypt-realm file-name old-key new-key on-success on-error)))))))
(log/info "try to encrypt with password")
(encrypted-realm-version file-name new-key)
(log/info "try to encrypt with password success")
(on-success))
(catch :default e
(do
(log/warn "failed checking db encryption with" e)
(log/info "try to encrypt with old key")
(.. (encrypted-realm-version-promise file-name old-key)
(then
#(re-encrypt-realm file-name old-key new-key on-success on-error))
(catch on-error)))))))))
(defn change-account [address password encryption-key]
(let [path (str accounts-realm-dir (utils.ethereum/sha3 address))
(defn open-account [address password encryption-key]
(let [path (get-account-db-path address)
account-db-key (db-encryption-key password encryption-key)]
(close-account-realm)
(..
(check-db-encryption path encryption-key account-db-key)
(then
(fn []
(log/info "change-account done" (nil? @account-realm))
(reset! account-realm
(open-migrated-realm path account/schemas account-db-key))
(log/info "account-realm " (nil? @account-realm)))))))
(js/Promise.
(fn [on-success on-error]
(try
(log/info "open-account")
(reset! account-realm
(migrate-realm path account/schemas account-db-key))
(log/info "account-realm " (nil? @account-realm))
(on-success)
(catch :default e
(on-error {:message (str e)
:error :migrations-failed})))))))
(declare realm-obj->clj)

View File

@ -54,6 +54,11 @@
(fn [cofx _]
{:init/reset-data nil}))
(handlers/register-handler-fx
:init.ui/account-data-reset-accepted
(fn [_ [_ address]]
{:init/reset-account-data address}))
(handlers/register-handler-fx
:init.ui/data-reset-cancelled
(fn [cofx [_ encryption-key]]
@ -128,6 +133,11 @@
(fn [cofx _]
(init/initialize-keychain cofx)))
(handlers/register-handler-fx
:init.callback/account-db-removed
(fn [{:keys [db]} _]
{:db (assoc-in db [:accounts/login :processing] false)}))
;; home screen
(handlers/register-handler-fx

View File

@ -29,7 +29,8 @@
[status-im.utils.utils :as utils]
[taoensso.timbre :as log]
[status-im.utils.fx :as fx]
[status-im.chat.models :as chat-model]))
[status-im.chat.models :as chat-model]
[status-im.accounts.db :as accounts.db]))
(defn init-store!
"Try to decrypt the database, move on if successful otherwise go back to
@ -52,6 +53,12 @@
(then reset-keychain!)
(catch reset-keychain!)))
(defn reset-account-data! [address]
(let [callback #(re-frame/dispatch [:init.callback/account-db-removed])]
(.. (realm/delete-account-realm address)
(then callback)
(catch callback))))
(fx/defn initialize-keychain
"Entrypoint, fetches the key from the keychain and initialize the app"
[cofx]
@ -98,15 +105,37 @@
{:db (assoc db :device-UUID device-uuid)})
(fx/defn handle-change-account-error
[cofx error]
{:ui/show-confirmation
{:title (i18n/label :invalid-key-title)
:content (str error "\n" (i18n/label :invalid-key-content))
:confirm-button-text (i18n/label :invalid-key-confirm)
;; On cancel we initialize the app with the invalid key, to allow the user
;; to recover the seed phrase
:on-cancel #(re-frame/dispatch [:init.ui/data-reset-cancelled ""])
:on-accept #(re-frame/dispatch [:init.ui/data-reset-accepted])}})
[{:keys [db]} error]
(let [{:keys [error message]}
(if (map? error)
error
{:message (str error)})
address (get-in db [:accounts/login :address])
erase-button-text (i18n/label :migrations-erase-accounts-data-button)]
(case error
:migrations-failed
{:ui/show-confirmation
{:title (i18n/label :migrations-failed-title)
:content (i18n/label
:migrations-failed-content
{:message message
:erase-accounts-data-button-text erase-button-text})
:confirm-button-text erase-button-text
:on-cancel #(re-frame/dispatch [:init.ui/data-reset-cancelled ""])
:on-accept #(re-frame/dispatch [:init.ui/account-data-reset-accepted address])}}
;; TODO(rasom): handle different errors as separate cases
;; Right now it might be corrupted db file, wrong password,
;; problem with permissions, etc.
{:ui/show-confirmation
{:title (i18n/label :invalid-key-title)
:content (i18n/label
:invalid-key-content
{:message message
:erase-accounts-data-button-text erase-button-text})
:confirm-button-text (i18n/label :invalid-key-confirm)
:on-cancel #(re-frame/dispatch [:init.ui/data-reset-cancelled ""])
:on-accept #(re-frame/dispatch [:init.ui/account-data-reset-accepted address])}})))
(fx/defn handle-init-store-error
[encryption-key cofx]
@ -238,3 +267,7 @@
(re-frame/reg-fx
:init/reset-data
reset-data!)
(re-frame/reg-fx
:init/reset-account-data
reset-account-data!)

View File

@ -380,7 +380,7 @@
"recover-access": "Recover access",
"currency-display-name-ron": "Romania Leu",
"log-level-settings": "Log level settings",
"invalid-key-content": "To protect yourself, you need to create new account and erase your old data by tapping “Apply”. If you have an existing account and would like to save your recovery phrase then choose “Cancel”, back it up, and restart the app. We strongly recommend creating new account because the old one is stored unencrypted.",
"invalid-key-content": "{{message}}\n\nAccount's database can't be encrypted because file is corrupted. There is no way to restore it. If you press \"Cancel\" button, nothing will happen. If you press \"{{erase-accounts-data-button-text}}\" button, account's db will be removed and you will be able to unlock account. All account's data will be lost.",
"advanced-settings": "Advanced settings",
"group-info": "Group info",
"currency-display-name-nio": "Nicaragua Cordoba",
@ -782,5 +782,8 @@
"network-invalid-status-code": "Invalid status code: {{code}}",
"extension-is-already-added": "The extension is already added",
"extension-uninstalled": "The extension was uninstalled",
"extension-hooks-cannot-be-added": "The following hooks from this extension cannot be added: {{hooks}}"
"extension-hooks-cannot-be-added": "The following hooks from this extension cannot be added: {{hooks}}",
"migrations-failed-title": "Migration failed",
"migrations-failed-content": "{{message}}\n\nPlease let us know about this problem at #status public chat. If you press \"Cancel\" button, nothing will happen. If you press \"{{erase-accounts-data-button-text}}\" button, account's db will be removed and you will be able to unlock account. All account's data will be lost.",
"migrations-erase-accounts-data-button": "Erase account's db"
}