diff --git a/android/app/src/main/AndroidManifest.xml b/android/app/src/main/AndroidManifest.xml
index c884a92f7..8deddd2c4 100644
--- a/android/app/src/main/AndroidManifest.xml
+++ b/android/app/src/main/AndroidManifest.xml
@@ -10,6 +10,8 @@
+
+
diff --git a/android/build.gradle b/android/build.gradle
index 66a28dcc2..18115dae5 100644
--- a/android/build.gradle
+++ b/android/build.gradle
@@ -64,6 +64,7 @@ allprojects {
includeGroup("com.henninghall.android")
includeGroup("org.matomo.sdk")
includeModule("com.yqritc", "android-scalablevideoview")
+ includeModule("com.wei.android.lib", "fingerprintidentify")
}
}
}
diff --git a/ios/Ecency/Info.plist b/ios/Ecency/Info.plist
index 7bff4366f..7f8ff0c70 100644
--- a/ios/Ecency/Info.plist
+++ b/ios/Ecency/Info.plist
@@ -74,6 +74,8 @@
Photo Library Access for allowing user to download and upload photos
NSPhotoLibraryUsageDescription
Photo Usage Access for allowing user to upload photos
+ NSFaceIDUsageDescription
+ Ecency requires FaceID access to allow you quick and secure access.
UIAppFonts
Entypo.ttf
diff --git a/ios/Podfile.lock b/ios/Podfile.lock
index 7811fec77..7608fde70 100644
--- a/ios/Podfile.lock
+++ b/ios/Podfile.lock
@@ -346,6 +346,8 @@ PODS:
- React-Core
- react-native-date-picker (4.2.0):
- React-Core
+ - react-native-fingerprint-scanner (6.0.0):
+ - React
- react-native-matomo-sdk (0.4.1):
- MatomoTracker (~> 7)
- React (~> 0.60)
@@ -530,6 +532,7 @@ DEPENDENCIES:
- "react-native-cameraroll (from `../node_modules/@react-native-community/cameraroll`)"
- react-native-config (from `../node_modules/react-native-config`)
- react-native-date-picker (from `../node_modules/react-native-date-picker`)
+ - react-native-fingerprint-scanner (from `../node_modules/react-native-fingerprint-scanner`)
- react-native-matomo-sdk (from `../node_modules/react-native-matomo-sdk`)
- "react-native-netinfo (from `../node_modules/@react-native-community/netinfo`)"
- react-native-orientation-locker (from `../node_modules/react-native-orientation-locker`)
@@ -652,6 +655,8 @@ EXTERNAL SOURCES:
:path: "../node_modules/react-native-config"
react-native-date-picker:
:path: "../node_modules/react-native-date-picker"
+ react-native-fingerprint-scanner:
+ :path: "../node_modules/react-native-fingerprint-scanner"
react-native-matomo-sdk:
:path: "../node_modules/react-native-matomo-sdk"
react-native-netinfo:
@@ -784,6 +789,7 @@ SPEC CHECKSUMS:
react-native-cameraroll: e2917a5e62da9f10c3d525e157e25e694d2d6dfa
react-native-config: c98128a72bc2c3a1ca72caec0b021f0fa944aa29
react-native-date-picker: d83ab9cccbc497642a93fdca783ae76ecd6b17b6
+ react-native-fingerprint-scanner: ac6656f18c8e45a7459302b84da41a44ad96dbbe
react-native-matomo-sdk: 025c54f92e1e26a4d0acee7c3f28cb0fc7e4729c
react-native-netinfo: 30fb89fa913c342be82a887b56e96be6d71201dd
react-native-orientation-locker: 2da91e5391971dace445495821c899c111dcad7a
@@ -833,4 +839,4 @@ SPEC CHECKSUMS:
PODFILE CHECKSUM: 0282022703ad578ab2d9afbf3147ba3b373b4311
-COCOAPODS: 1.11.2
+COCOAPODS: 1.11.3
diff --git a/package.json b/package.json
index 55eb2ea38..bf166d13c 100644
--- a/package.json
+++ b/package.json
@@ -93,6 +93,7 @@
"react-native-dynamic": "^1.0.0",
"react-native-extended-stylesheet": "^0.10.0",
"react-native-fast-image": "^8.3.2",
+ "react-native-fingerprint-scanner": "hieuvp/react-native-fingerprint-scanner",
"react-native-gesture-handler": "^1.9.0",
"react-native-highlight-words": "^1.0.1",
"react-native-iap": "^7.5.6",
diff --git a/src/config/ecencyApi.ts b/src/config/ecencyApi.ts
index 47f92fe24..c58b89376 100644
--- a/src/config/ecencyApi.ts
+++ b/src/config/ecencyApi.ts
@@ -17,39 +17,42 @@ const api = axios.create({
api.interceptors.request.use((request) => {
console.log('Starting ecency Request', request);
-
+
//skip code addition is register and token refresh endpoint is triggered
- if(request.url === '/private-api/account-create'
- || request.url === '/auth-api/hs-token-refresh'
+ if (request.url === '/private-api/account-create'
+ || request.url === '/auth-api/hs-token-refresh'
|| request.url === '/private-api/promoted-entries'
|| request.url.startsWith('private-api/leaderboard')
|| request.url.startsWith('/private-api/received-vesting/')
|| request.url.startsWith('/private-api/referrals/')
|| request.url.startsWith('/private-api/market-data')
|| request.url.startsWith('/private-api/comment-history')
- ){
+ ) {
return request
}
- //decrypt access token
- const state = store.getState();
- const token = get(state, 'account.currentAccount.local.accessToken');
- const pin = get(state, 'application.pin');
- const digitPinCode = getDigitPinCode(pin);
- const accessToken = decryptKey(token, digitPinCode);
+ if (!request.data?.code) {
+ //if access code not already set, decrypt access token
+ const state = store.getState();
+ const token = get(state, 'account.currentAccount.local.accessToken');
+ const pin = get(state, 'application.pin');
+ const digitPinCode = getDigitPinCode(pin);
+ const accessToken = decryptKey(token, digitPinCode);
- if (accessToken && !request.data?.code ) {
- if (!request.data){
- request.data = {};
+ if (accessToken) {
+ if (!request.data) {
+ request.data = {};
+ }
+ request.data.code = accessToken;
+ console.log('Added access token:', accessToken);
+ } else {
+ const isLoggedIn = state.application.isLoggedIn;
+ console.warn("Failed to inject accessToken", `isLoggedIn:${isLoggedIn}`)
+ bugsnagInstance.notify(new Error(`Failed to inject accessToken in ${request.url} call. isLoggedIn:${isLoggedIn}, local.acccessToken:${token}, pin:${pin}`))
}
- request.data.code = accessToken;
- console.log('Added access token:', accessToken);
- } else {
- const isLoggedIn = state.application.isLoggedIn;
- console.warn("Failed to inject accessToken", `isLoggedIn:${isLoggedIn}`)
- bugsnagInstance.notify(new Error(`Failed to inject accessToken in ${request.url} call. isLoggedIn:${isLoggedIn}`))
}
+
return request;
});
diff --git a/src/config/locales/en-US.json b/src/config/locales/en-US.json
index 4292e1593..82271b924 100644
--- a/src/config/locales/en-US.json
+++ b/src/config/locales/en-US.json
@@ -229,6 +229,7 @@
"delegations": "Delegations"
},
"pincode": "PIN code",
+ "biometric": "Finger Print / Face Unlock",
"reset_pin": "Reset Pin Code",
"reset": "Reset",
"nsfw_content": "NSFW",
@@ -419,7 +420,8 @@
"forgot_text": "Oh, I forgot it...",
"pin_not_matched":"PIN do not match, Please try again.",
"attempts_postfix":"failed attempt(s)",
- "message_reset_warning":"User data will be wiped on next failed attempt"
+ "message_reset_warning":"User data will be wiped on next failed attempt",
+ "biometric_desc":"Scan your fingerprint on the device scanner to continue"
},
"alert": {
"success": "Success!",
diff --git a/src/redux/actions/applicationActions.ts b/src/redux/actions/applicationActions.ts
index f13fb24ac..3e409f127 100644
--- a/src/redux/actions/applicationActions.ts
+++ b/src/redux/actions/applicationActions.ts
@@ -32,6 +32,7 @@ import {
SET_SETTINGS_MIGRATED,
HIDE_POSTS_THUMBNAILS,
SET_TERMS_ACCEPTED,
+ SET_IS_BIOMETRIC_ENABLED,
SET_ENC_UNLOCK_PIN
} from '../constants/constants';
@@ -210,6 +211,11 @@ export const setIsTermsAccepted = (isTermsAccepted:boolean) => ({
type: SET_TERMS_ACCEPTED
})
+export const setIsBiometricEnabled = (enabled:boolean) => ({
+ payload:enabled,
+ type: SET_IS_BIOMETRIC_ENABLED
+})
+
export const setEncryptedUnlockPin = (encryptedUnlockPin:string) => ({
payload:encryptedUnlockPin,
type: SET_ENC_UNLOCK_PIN
diff --git a/src/redux/constants/constants.js b/src/redux/constants/constants.js
index 5424ebee0..9e6263831 100644
--- a/src/redux/constants/constants.js
+++ b/src/redux/constants/constants.js
@@ -36,6 +36,7 @@ export const SET_LAST_APP_VERSION = 'SET_LAST_APP_VERSION';
export const SET_COLOR_THEME = 'SET_COLOR_THEME';
export const SET_SETTINGS_MIGRATED = 'SET_SETTINGS_MIGRATED';
export const SET_TERMS_ACCEPTED = 'SET_TERMS_ACCEPTED';
+export const SET_IS_BIOMETRIC_ENABLED = 'SET_IS_BIOMETRIC_ENABLED';
export const SET_ENC_UNLOCK_PIN = 'SET_ENC_UNLOCK_PIN';
// Accounts
diff --git a/src/redux/reducers/applicationReducer.ts b/src/redux/reducers/applicationReducer.ts
index 03dda02d5..6cd6c460a 100644
--- a/src/redux/reducers/applicationReducer.ts
+++ b/src/redux/reducers/applicationReducer.ts
@@ -30,6 +30,7 @@ import {
SET_SETTINGS_MIGRATED,
HIDE_POSTS_THUMBNAILS,
SET_TERMS_ACCEPTED,
+ SET_IS_BIOMETRIC_ENABLED,
SET_ENC_UNLOCK_PIN
} from '../constants/constants';
@@ -71,6 +72,7 @@ interface State {
settingsMigratedV2: boolean;
hidePostsThumbnails: boolean;
isTermsAccepted: boolean;
+ isBiometricEnabled: boolean;
}
const initialState:State = {
@@ -111,6 +113,7 @@ const initialState:State = {
settingsMigratedV2: false,
hidePostsThumbnails: false,
isTermsAccepted: false,
+ isBiometricEnabled: false
};
export default function (state = initialState, action):State {
@@ -285,6 +288,12 @@ export default function (state = initialState, action):State {
...state,
isTermsAccepted:action.payload
}
+
+ case SET_IS_BIOMETRIC_ENABLED:
+ return {
+ ...state,
+ isBiometricEnabled:action.payload
+ }
case SET_ENC_UNLOCK_PIN:
return {
diff --git a/src/screens/pinCode/container/pinCodeContainer.tsx b/src/screens/pinCode/container/pinCodeContainer.tsx
index 46b46e018..076dabe9c 100644
--- a/src/screens/pinCode/container/pinCodeContainer.tsx
+++ b/src/screens/pinCode/container/pinCodeContainer.tsx
@@ -4,6 +4,7 @@ import { connect } from 'react-redux';
import { injectIntl } from 'react-intl';
import Config from 'react-native-config';
import get from 'lodash/get';
+import FingerprintScanner from 'react-native-fingerprint-scanner';
// Actions & Services
@@ -37,6 +38,8 @@ import PinCodeScreen from '../screen/pinCodeScreen';
class PinCodeContainer extends Component {
+ screenRef = null;
+
constructor(props) {
super(props);
this.state = {
@@ -74,9 +77,43 @@ class PinCodeContainer extends Component {
}
+ _processBiometric = async () => {
+ try {
+ const {
+ intl,
+ pinCodeParams: { isReset },
+ applicationPinCode,
+ isBiometricEnabled,
+ } = this.props;
+
+ if (isReset || !isBiometricEnabled) {
+ return;
+ }
+
+ const biometryType = await FingerprintScanner.isSensorAvailable();
+ console.log('biometryType is => ', biometryType);
+
+ await FingerprintScanner.authenticate({
+ description: intl.formatMessage({ id: 'pincode.biometric_desc' }),
+ });
+ console.log('successfully passed biometric auth');
+
+ //code gets here means biometeric succeeded
+ if (this.screenRef) {
+ const verifiedPin = decryptKey(applicationPinCode, Config.PIN_KEY, this._onDecryptFail);
+ this.screenRef.setPinThroughBiometric(verifiedPin);
+ }
+ } catch (err) {
+ console.warn('Failed to process biometric', err);
+ }
+
+ FingerprintScanner.release();
+ };
+
//this function updates realm with appropriate master key required for encyrption
//this function is important: must run while chaning pin
//and even logging in with existing pin code
+
_updatePinCodeRealm = async (pinData) => {
try {
const { currentAccount, dispatch } = this.props;
@@ -388,12 +425,14 @@ class PinCodeContainer extends Component {
return (
(this.screenRef = ref)}
informationText={informationText}
setPinCode={(pin) => this._setPinCode(pin, isReset)}
showForgotButton={!isOldPinVerified}
username={currentAccount.name}
intl={intl}
handleForgotButton={() => this._handleForgotButton()}
+ isReset={isReset}
{...this.props}
/>
);
@@ -406,6 +445,7 @@ const mapStateToProps = (state) => ({
encUnlockPin: state.application.encUnlockPin,
otherAccounts: state.account.otherAccounts,
pinCodeParams: state.application.pinCodeNavigation,
+ isBiometricEnabled: state.application.isBiometricEnabled,
});
export default injectIntl(connect(mapStateToProps)(PinCodeContainer));
diff --git a/src/screens/pinCode/screen/pinCodeScreen.js b/src/screens/pinCode/screen/pinCodeScreen.tsx
similarity index 87%
rename from src/screens/pinCode/screen/pinCodeScreen.js
rename to src/screens/pinCode/screen/pinCodeScreen.tsx
index db18685a4..318bfc73c 100644
--- a/src/screens/pinCode/screen/pinCodeScreen.js
+++ b/src/screens/pinCode/screen/pinCodeScreen.tsx
@@ -1,27 +1,39 @@
-import React, { useState, useEffect } from 'react';
+import React, { useState, useEffect, forwardRef, useImperativeHandle } from 'react';
import { useIntl } from 'react-intl';
import { Text, TouchableOpacity, View } from 'react-native';
-
import { NumericKeyboard, PinAnimatedInput, UserAvatar } from '../../../components';
import styles from './pinCodeStyles';
-const PinCodeScreen = ({
+const PinCodeScreen = forwardRef(({
informationText,
showForgotButton,
username,
handleForgotButton,
setPinCode,
-}) => {
+}, ref) => {
const [pin, setPin] = useState('');
const [loading, setLoading] = useState(false);
const intl = useIntl();
+ useImperativeHandle(ref, () => ({
+ setPinThroughBiometric(bioPin){
+ if(bioPin && bioPin.length === 4){
+ setLoading(true);
+ setPin(bioPin)
+ }
+ }
+ }));
+
+
+
useEffect(() => {
_handlePinComplete();
}, [pin]);
+
+
const _handlePinComplete = async () => {
if (pin.length === 4) {
setLoading(true);
@@ -31,6 +43,7 @@ const PinCodeScreen = ({
}
};
+
const _handleKeyboardOnPress = async (value) => {
try {
if (loading) {
@@ -82,6 +95,6 @@ const PinCodeScreen = ({
)}
);
-};
+})
export default PinCodeScreen;
diff --git a/src/screens/settings/container/settingsContainer.tsx b/src/screens/settings/container/settingsContainer.tsx
index 0a47ce948..c0c3b433c 100644
--- a/src/screens/settings/container/settingsContainer.tsx
+++ b/src/screens/settings/container/settingsContainer.tsx
@@ -43,6 +43,7 @@ import {
logoutDone,
closePinCodeModal,
setColorTheme,
+ setIsBiometricEnabled,
setEncryptedUnlockPin,
} from '../../../redux/actions/applicationActions';
import { toastNotification } from '../../../redux/actions/uiAction';
@@ -240,6 +241,14 @@ class SettingsContainer extends Component {
);
}
break;
+
+ case 'biometric':
+ dispatch(
+ openPinCodeModal({
+ callback: () => dispatch(setIsBiometricEnabled(action)),
+ }),
+ );
+ break;
default:
break;
}
@@ -460,6 +469,7 @@ const mapStateToProps = (state) => ({
colorTheme: state.application.colorTheme,
isPinCodeOpen: state.application.isPinCodeOpen,
encUnlockPin: state.application.encUnlockPin,
+ isBiometricEnabled: state.application.isBiometricEnabled,
isDefaultFooter: state.application.isDefaultFooter,
isLoggedIn: state.application.isLoggedIn,
isNotificationSettingsOpen: state.application.isNotificationOpen,
diff --git a/src/screens/settings/screen/settingsScreen.js b/src/screens/settings/screen/settingsScreen.js
index 5aa362e50..95ff12486 100644
--- a/src/screens/settings/screen/settingsScreen.js
+++ b/src/screens/settings/screen/settingsScreen.js
@@ -24,6 +24,7 @@ const SettingsScreen = ({
isDarkTheme,
colorThemeIndex,
isPinCodeOpen,
+ isBiometricEnabled,
isLoggedIn,
isNotificationSettingsOpen,
nsfw,
@@ -147,6 +148,16 @@ const SettingsScreen = ({
{!!isLoggedIn && !!isPinCodeOpen && (
+
+
- //
)}
{!!isLoggedIn && (
diff --git a/yarn.lock b/yarn.lock
index 5d346ba07..1b30c18a4 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -8782,6 +8782,10 @@ react-native-fast-image@^8.3.2:
resolved "https://registry.yarnpkg.com/react-native-fast-image/-/react-native-fast-image-8.3.4.tgz#79edca177e30311b19d59ff335625bcbe22650d7"
integrity sha512-LpzAdjUphihUpVEBn5fEv5AILe55rHav0YiZroPZ1rumKDhAl4u2cG01ku2Pb7l8sayjTsNu7FuURAlXUUDsow==
+react-native-fingerprint-scanner@hieuvp/react-native-fingerprint-scanner:
+ version "6.0.0"
+ resolved "https://codeload.github.com/hieuvp/react-native-fingerprint-scanner/tar.gz/9cecc0db326471c571553ea85f7c016fee2f803d"
+
react-native-flipper@^0.34.0:
version "0.34.0"
resolved "https://registry.yarnpkg.com/react-native-flipper/-/react-native-flipper-0.34.0.tgz#7df1f38ba5d97a9321125fe0fccbe47d99e6fa1d"