enable saving password on android
Signed-off-by: Igor Mandrigin <i@mandrigin.ru>
This commit is contained in:
parent
744abb3984
commit
2777809db9
|
@ -52,7 +52,7 @@ public class MainApplication extends MultiDexApplication implements ReactApplica
|
|||
webViewDebugEnabled = true;
|
||||
}
|
||||
|
||||
StatusPackage statusPackage = new StatusPackage(BuildConfig.DEBUG, devCluster);
|
||||
StatusPackage statusPackage = new StatusPackage(BuildConfig.DEBUG, devCluster, RootUtil.isDeviceRooted());
|
||||
Function<String, String> callRPC = statusPackage.getCallRPC();
|
||||
List<ReactPackage> packages = new ArrayList<ReactPackage>(Arrays.asList(
|
||||
new MainReactPackage(),
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
(ns env.config)
|
||||
|
||||
(def figwheel-urls {:android "ws://192.168.10.203:3449/figwheel-ws",
|
||||
:ios "ws://localhost:3449/figwheel-ws",
|
||||
:desktop "ws://localhost:3449/figwheel-ws"})
|
||||
(def figwheel-urls {:android "ws://localhost:3449/figwheel-ws",
|
||||
:ios "ws://192.168.0.9:3449/figwheel-ws",
|
||||
:desktop "ws://localhost:3449/figwheel-ws"}
|
||||
)
|
|
@ -68,7 +68,7 @@ PODS:
|
|||
- React
|
||||
- React/Core (0.56.0):
|
||||
- yoga (= 0.56.0.React)
|
||||
- RNKeychain (3.0.0):
|
||||
- RNKeychain (3.0.0-rc.3):
|
||||
- React
|
||||
- yoga (0.56.0.React)
|
||||
|
||||
|
@ -123,4 +123,4 @@ SPEC CHECKSUMS:
|
|||
|
||||
PODFILE CHECKSUM: 7636f960a0dbec2dd55b8b20e244befa3fdb4438
|
||||
|
||||
COCOAPODS: 1.5.2
|
||||
COCOAPODS: 1.5.3
|
||||
|
|
|
@ -47,7 +47,7 @@
|
|||
"react-native-image-crop-picker": "0.18.1",
|
||||
"react-native-image-resizer": "https://github.com/status-im/react-native-image-resizer.git#1.0.0-1",
|
||||
"react-native-invertible-scroll-view": "1.1.0",
|
||||
"react-native-keychain": "3.0.0",
|
||||
"react-native-keychain": "https://github.com/status-im/react-native-keychain#v.3.0.0-status",
|
||||
"react-native-level-fs": "3.0.1",
|
||||
"react-native-os": "https://github.com/status-im/react-native-os.git#1.1.0-1",
|
||||
"react-native-qrcode": "0.2.7",
|
||||
|
|
|
@ -5936,10 +5936,9 @@ react-native-invertible-scroll-view@1.1.0:
|
|||
react-clone-referenced-element "^1.0.1"
|
||||
react-native-scrollable-mixin "^1.0.1"
|
||||
|
||||
react-native-keychain@3.0.0:
|
||||
version "3.0.0"
|
||||
resolved "https://registry.yarnpkg.com/react-native-keychain/-/react-native-keychain-3.0.0.tgz#29da1dfa43c2581f76bf9420914fd38a1558cf18"
|
||||
integrity sha512-0incABt1+aXsZvG34mDV57KKanSB+iMHmxvWv+N6lgpNLaSoqrCxazjbZdeqD4qJ7Z+Etp5CLf/4v1aI+sNLBw==
|
||||
"react-native-keychain@https://github.com/status-im/react-native-keychain#v.3.0.0-status":
|
||||
version "3.0.0-rc.3"
|
||||
resolved "https://github.com/status-im/react-native-keychain#43e5512cabb8ee064fd9e503be943dcf2c7d7669"
|
||||
|
||||
react-native-level-fs@3.0.1:
|
||||
version "3.0.1"
|
||||
|
|
|
@ -61,8 +61,9 @@ class StatusModule extends ReactContextBaseJavaModule implements LifecycleEventL
|
|||
private boolean debug;
|
||||
private boolean devCluster;
|
||||
private ReactApplicationContext reactContext;
|
||||
private boolean rootedDevice;
|
||||
|
||||
StatusModule(ReactApplicationContext reactContext, boolean debug, boolean devCluster) {
|
||||
StatusModule(ReactApplicationContext reactContext, boolean debug, boolean devCluster, boolean rootedDevice) {
|
||||
super(reactContext);
|
||||
if (executor == null) {
|
||||
executor = Executors.newCachedThreadPool();
|
||||
|
@ -70,6 +71,7 @@ class StatusModule extends ReactContextBaseJavaModule implements LifecycleEventL
|
|||
this.debug = debug;
|
||||
this.devCluster = devCluster;
|
||||
this.reactContext = reactContext;
|
||||
this.rootedDevice = rootedDevice;
|
||||
reactContext.addLifecycleEventListener(this);
|
||||
}
|
||||
|
||||
|
@ -857,7 +859,7 @@ class StatusModule extends ReactContextBaseJavaModule implements LifecycleEventL
|
|||
Log.d(TAG, "AppStateChange: " + type);
|
||||
Statusgo.AppStateChange(type);
|
||||
}
|
||||
|
||||
|
||||
private static String uniqueID = null;
|
||||
private static final String PREF_UNIQUE_ID = "PREF_UNIQUE_ID";
|
||||
|
||||
|
@ -989,4 +991,9 @@ class StatusModule extends ReactContextBaseJavaModule implements LifecycleEventL
|
|||
constants.put("is24Hour", this.is24Hour());
|
||||
return constants;
|
||||
}
|
||||
|
||||
@ReactMethod
|
||||
public void isDeviceRooted(final Callback callback) {
|
||||
callback.invoke(rootedDevice);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -16,10 +16,12 @@ public class StatusPackage implements ReactPackage {
|
|||
|
||||
private boolean debug;
|
||||
private boolean devCluster;
|
||||
private boolean rootedDevice;
|
||||
|
||||
public StatusPackage (boolean debug, boolean devCluster) {
|
||||
public StatusPackage (boolean debug, boolean devCluster, boolean rootedDevice) {
|
||||
this.debug = debug;
|
||||
this.devCluster = devCluster;
|
||||
this.rootedDevice = rootedDevice;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -27,7 +29,7 @@ public class StatusPackage implements ReactPackage {
|
|||
List<NativeModule> modules = new ArrayList<>();
|
||||
System.loadLibrary("statusgoraw");
|
||||
System.loadLibrary("statusgo");
|
||||
modules.add(new StatusModule(reactContext, this.debug, this.devCluster));
|
||||
modules.add(new StatusModule(reactContext, this.debug, this.devCluster, this.rootedDevice ));
|
||||
|
||||
return modules;
|
||||
}
|
||||
|
|
|
@ -84,3 +84,5 @@
|
|||
(def disable-installation native-module/disable-installation)
|
||||
|
||||
(def update-mailservers native-module/update-mailservers)
|
||||
|
||||
(def rooted-device? native-module/rooted-device?)
|
||||
|
|
|
@ -8,7 +8,8 @@
|
|||
[status-im.utils.platform :as p]
|
||||
[status-im.utils.async :as async-util]
|
||||
[status-im.react-native.js-dependencies :as rn-dependencies]
|
||||
[clojure.string :as string]))
|
||||
[clojure.string :as string]
|
||||
[status-im.utils.platform :as platform]))
|
||||
|
||||
;; if StatusModule is not initialized better to store
|
||||
;; calls and make them only when StatusModule is ready
|
||||
|
@ -171,3 +172,24 @@
|
|||
(defn update-mailservers [enodes on-result]
|
||||
(when status
|
||||
(call-module #(.updateMailservers status enodes on-result))))
|
||||
|
||||
(defn rooted-device? [callback]
|
||||
(cond
|
||||
;; we assume that iOS is safe by default
|
||||
platform/ios?
|
||||
(callback false)
|
||||
|
||||
;; we assume that Desktop is unsafe by default
|
||||
;; (theoretically, Desktop is always "rooted", by design
|
||||
platform/desktop?
|
||||
(callback true)
|
||||
|
||||
;; we check root on android
|
||||
platform/android?
|
||||
(if status
|
||||
(call-module #(.isDeviceRooted status callback))
|
||||
;; if module isn't initialized we return true to avoid degrading security
|
||||
(callback true))
|
||||
|
||||
;; in unknown scenarios we also consider the device rooted to avoid degrading security
|
||||
:else (callback true)))
|
||||
|
|
|
@ -52,3 +52,11 @@
|
|||
:text-align :center
|
||||
:flex-direction :row
|
||||
:align-items :center})
|
||||
|
||||
(def save-password-unavailable-android
|
||||
{:margin-top 8
|
||||
:width "100%"
|
||||
:color colors/text-gray
|
||||
:text-align :center
|
||||
:flex-direction :row
|
||||
:align-items :center})
|
||||
|
|
|
@ -18,7 +18,8 @@
|
|||
[cljs.spec.alpha :as spec]
|
||||
[status-im.utils.platform :as platform]
|
||||
[status-im.accounts.db :as db]
|
||||
[status-im.utils.security :as security]))
|
||||
[status-im.utils.security :as security]
|
||||
[status-im.utils.keychain.core :as keychain]))
|
||||
|
||||
(defn login-toolbar [can-navigate-back?]
|
||||
[toolbar/toolbar
|
||||
|
@ -81,15 +82,22 @@
|
|||
(re-frame/dispatch [:set-in [:accounts/login :error] ""]))
|
||||
:secure-text-entry true
|
||||
:error (when (not-empty error) (i18n/label (error-key error)))}]]
|
||||
(when platform/ios?
|
||||
|
||||
(when-not platform/desktop?
|
||||
;; saving passwords is unavailable on Desktop
|
||||
[react/view {:style styles/save-password-checkbox-container}
|
||||
[profile.components/settings-switch-item
|
||||
{:label-kw (if can-save-password?
|
||||
:t/save-password
|
||||
:t/save-password-unavailable)
|
||||
:active? can-save-password?
|
||||
:value save-password?
|
||||
:action-fn #(re-frame/dispatch [:set-in [:accounts/login :save-password?] %])}]])]]
|
||||
(if (and platform/android? (not can-save-password?))
|
||||
;; on Android, there is much more reasons for the password save to be unavailable,
|
||||
;; so we don't show the checkbox whatsoever but put a label explaining why it happenned.
|
||||
[react/i18n-text {:style styles/save-password-unavailable-android
|
||||
:key :save-password-unavailable-android}]
|
||||
[profile.components/settings-switch-item
|
||||
{:label-kw (if can-save-password?
|
||||
:t/save-password
|
||||
:t/save-password-unavailable)
|
||||
:active? can-save-password?
|
||||
:value save-password?
|
||||
:action-fn #(re-frame/dispatch [:set-in [:accounts/login :save-password?] %])}])])]]
|
||||
(when processing
|
||||
[react/view styles/processing-view
|
||||
[components/activity-indicator {:animating true}]
|
||||
|
|
|
@ -3,10 +3,12 @@
|
|||
[taoensso.timbre :as log]
|
||||
[status-im.react-native.js-dependencies :as rn]
|
||||
[status-im.utils.platform :as platform]
|
||||
[status-im.utils.security :as security]))
|
||||
[status-im.utils.security :as security]
|
||||
[status-im.native-module.core :as status]))
|
||||
|
||||
(def key-bytes 64)
|
||||
(def username "status-im.encryptionkey")
|
||||
(def android-keystore-min-version 23)
|
||||
|
||||
(defn- bytes->js-array [b]
|
||||
(.from js/Array b))
|
||||
|
@ -14,6 +16,19 @@
|
|||
(defn- string->js-array [s]
|
||||
(.parse js/JSON (.-password s)))
|
||||
|
||||
(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))))))
|
||||
|
||||
;; ********************************************************************************
|
||||
;; Storing / Retrieving a user password to/from Keychain
|
||||
;; ********************************************************************************
|
||||
|
@ -21,7 +36,6 @@
|
|||
;; 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 rn/keychain) [enum-name value-name]))
|
||||
|
||||
|
@ -43,15 +57,37 @@
|
|||
;; > 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.
|
||||
{:accessible (enum-val "ACCESSIBLE" "WHEN_PASSCODE_SET_THIS_DEVICE_ONLY")})
|
||||
{: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]
|
||||
(status/rooted-device? (fn [rooted?] (callback (not rooted?)))))
|
||||
|
||||
;; Android only
|
||||
(defn- secure-hardware-available? [callback]
|
||||
(-> (.getSecurityLevel rn/keychain)
|
||||
(.then (fn [level] (callback (= level keychain-secure-hardware))))))
|
||||
|
||||
;; iOS only
|
||||
(defn- device-encrypted? [callback]
|
||||
(-> (.canImplyAuthentication
|
||||
rn/keychain
|
||||
(clj->js
|
||||
{:authenticationType
|
||||
(enum-val "ACCESS_CONTROL" "BIOMETRY_ANY_OR_DEVICE_PASSCODE")}))
|
||||
(.then callback)))
|
||||
|
||||
;; Stores the password for the address to the Keychain
|
||||
(defn save-user-password [address password callback]
|
||||
(if-not platform/ios?
|
||||
(callback true) ;; no-op on Androids (for now)
|
||||
(-> (.setInternetCredentials rn/keychain address address password
|
||||
(clj->js keychain-restricted-availability))
|
||||
(.then callback))))
|
||||
(-> (.setInternetCredentials rn/keychain address address password keychain-secure-hardware (clj->js keychain-restricted-availability))
|
||||
(.then callback)))
|
||||
|
||||
(defn handle-callback [callback result]
|
||||
(if result
|
||||
|
@ -60,29 +96,30 @@
|
|||
|
||||
;; Gets the password for a specified address from the Keychain
|
||||
(defn get-user-password [address callback]
|
||||
(if-not platform/ios?
|
||||
(callback) ;; no-op on Androids (for now)
|
||||
(if (or platform/ios? platform/android?)
|
||||
(-> (.getInternetCredentials rn/keychain address)
|
||||
(.then (partial handle-callback callback)))))
|
||||
(.then (partial handle-callback callback)))
|
||||
(callback))) ;; no-op for Desktop
|
||||
|
||||
;; Clears the password for a specified address from the Keychain
|
||||
;; (example of usage is logout or signing in w/o "save-password")
|
||||
(defn clear-user-password [address callback]
|
||||
(if-not platform/ios?
|
||||
(callback true)
|
||||
(if (or platform/ios? platform/android?)
|
||||
(-> (.resetInternetCredentials rn/keychain address)
|
||||
(.then callback))))
|
||||
(.then callback))
|
||||
(callback true))) ;; no-op for Desktop
|
||||
|
||||
;; Resolves to `false` if the device doesn't have neither a passcode nor a biometry auth.
|
||||
(defn can-save-user-password? [callback]
|
||||
(if-not platform/ios?
|
||||
(callback false)
|
||||
(-> (.canImplyAuthentication
|
||||
rn/keychain
|
||||
(clj->js
|
||||
{:authenticationType
|
||||
(enum-val "ACCESS_CONTROL" "BIOMETRY_ANY_OR_DEVICE_PASSCODE")}))
|
||||
(.then callback))))
|
||||
(cond
|
||||
platform/ios? (device-encrypted? callback)
|
||||
|
||||
platform/android? (check-conditions
|
||||
callback
|
||||
secure-hardware-available?
|
||||
device-not-rooted?)
|
||||
|
||||
:else (callback false)))
|
||||
|
||||
;; ********************************************************************************
|
||||
;; Storing / Retrieving the realm encryption key to/from the Keychain
|
||||
|
|
|
@ -36,3 +36,6 @@
|
|||
android? (str (.-DocumentDirectoryPath rn-dependencies/fs)
|
||||
"/../no_backup")
|
||||
ios? (.-LibraryDirectoryPath rn-dependencies/fs)))
|
||||
|
||||
(defn android-version>= [v]
|
||||
(and android? (>= version v)))
|
||||
|
|
|
@ -136,6 +136,7 @@
|
|||
"twelve-words-in-correct-order": "12 words in correct order",
|
||||
"permissions": "Permissions",
|
||||
"save-password-unavailable": "Set device passcode to save password",
|
||||
"save-password-unavailable-android": "Save password is unavailable: your device may be rooted or lacks necessary security features.",
|
||||
"currency-display-name-inr": "India Rupee",
|
||||
"transaction-moved-title": "Transaction moved",
|
||||
"counter-9-plus": "9+",
|
||||
|
|
Loading…
Reference in New Issue