diff --git a/src/providers/hive/auth.js b/src/providers/hive/auth.js index e78b3cd20..4ee1e4e2b 100644 --- a/src/providers/hive/auth.js +++ b/src/providers/hive/auth.js @@ -25,7 +25,7 @@ import { getSCAccessToken, getUnreadNotificationCount } from '../ecency/ecency'; import AUTH_TYPE from '../../constants/authType'; import { makeHsCode } from '../../utils/hive-signer-helper'; -export const login = async (username, password, isPinCodeOpen) => { +export const login = async (username, password) => { let loginFlag = false; let avatar = ''; let authType = ''; @@ -98,19 +98,15 @@ export const login = async (username, password, isPinCodeOpen) => { accessToken: '', }; - if (isPinCodeOpen) { - account.local = userData; - } else { - const resData = { - pinCode: Config.DEFAULT_PIN, - password, - accessToken: get(scTokens, 'access_token', ''), - }; - const updatedUserData = await getUpdatedUserData(userData, resData); + const resData = { + pinCode: Config.DEFAULT_PIN, + password, + accessToken: get(scTokens, 'access_token', ''), + }; + const updatedUserData = await getUpdatedUserData(userData, resData); - account.local = updatedUserData; - account.local.avatar = avatar; - } + account.local = updatedUserData; + account.local.avatar = avatar; const authData = { isLoggedIn: true, @@ -130,7 +126,7 @@ export const login = async (username, password, isPinCodeOpen) => { return Promise.reject(new Error('auth.invalid_credentials')); }; -export const loginWithSC2 = async (code, isPinCodeOpen) => { +export const loginWithSC2 = async (code) => { const scTokens = await getSCAccessToken(code); await hsApi.setAccessToken(get(scTokens, 'access_token', '')); const scAccount = await hsApi.me(); @@ -168,18 +164,14 @@ export const loginWithSC2 = async (code, isPinCodeOpen) => { }; const isUserLoggedIn = await isLoggedInUser(account.name); - if (isPinCodeOpen) { - account.local = userData; - } else { - const resData = { - pinCode: Config.DEFAULT_PIN, - accessToken: get(scTokens, 'access_token', ''), - }; - const updatedUserData = await getUpdatedUserData(userData, resData); + const resData = { + pinCode: Config.DEFAULT_PIN, + accessToken: get(scTokens, 'access_token', ''), + }; + const updatedUserData = await getUpdatedUserData(userData, resData); - account.local = updatedUserData; - account.local.avatar = avatar; - } + account.local = updatedUserData; + account.local.avatar = avatar; if (isUserLoggedIn) { reject(new Error('auth.already_logged')); @@ -296,37 +288,38 @@ export const updatePinCode = (data) => } }); -export const verifyPinCode = async (data) => { - try { - const pinHash = await getPinCode(); +// export const verifyPinCode = async (data) => { +// try { +// const pinHash = await getPinCode(); - const result = await getUserDataWithUsername(data.username); - const userData = result[0]; +// const result = await getUserDataWithUsername(data.username); +// const userData = result[0]; - // This is migration for new pin structure, it will remove v2.2 - if (!pinHash) { - try { - if (get(userData, 'authType', '') === AUTH_TYPE.STEEM_CONNECT) { - decryptKey(get(userData, 'accessToken'), get(data, 'pinCode')); - } else { - decryptKey(userData.masterKey, get(data, 'pinCode')); - } - await setPinCode(get(data, 'pinCode')); - } catch (error) { - return Promise.reject(new Error('Invalid pin code, please check and try again')); - } - } +// // This is migration for new pin structure, it will remove v2.2 +// if (!pinHash) { +// try { +// //if decrypt fails, means key is invalid +// if (userData.accessToken === AUTH_TYPE.STEEM_CONNECT) { +// decryptKey(userData.accessToken, data.pinCode); +// } else { +// decryptKey(userData.masterKey, data.pinCode); +// } +// await setPinCode(data.pinCode); +// } catch (error) { +// return Promise.reject(new Error('Invalid pin code, please check and try again')); +// } +// } - if (sha256(get(data, 'pinCode')).toString() !== pinHash) { - return Promise.reject(new Error('auth.invalid_pin')); - } +// if (sha256(get(data, 'pinCode')).toString() !== pinHash) { +// return Promise.reject(new Error('auth.invalid_pin')); +// } - return true; - } catch (err) { - console.warn('Failed to verify pin in auth: ', data, err); - return Promise.reject(err); - } -}; +// return true; +// } catch (err) { +// console.warn('Failed to verify pin in auth: ', data, err); +// return Promise.reject(err); +// } +// }; export const refreshSCToken = async (userData, pinCode) => { const scAccount = await getSCAccount(userData.username); diff --git a/src/redux/actions/applicationActions.ts b/src/redux/actions/applicationActions.ts index 4d36b1e7b..3e409f127 100644 --- a/src/redux/actions/applicationActions.ts +++ b/src/redux/actions/applicationActions.ts @@ -32,7 +32,8 @@ import { SET_SETTINGS_MIGRATED, HIDE_POSTS_THUMBNAILS, SET_TERMS_ACCEPTED, - SET_IS_BIOMETRIC_ENABLED + SET_IS_BIOMETRIC_ENABLED, + SET_ENC_UNLOCK_PIN } from '../constants/constants'; export const login = (payload) => ({ @@ -215,4 +216,9 @@ export const setIsBiometricEnabled = (enabled:boolean) => ({ 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 e2b15891b..9e6263831 100644 --- a/src/redux/constants/constants.js +++ b/src/redux/constants/constants.js @@ -37,6 +37,7 @@ 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 export const ADD_OTHER_ACCOUNT = 'ADD_OTHER_ACCOUNT'; diff --git a/src/redux/reducers/applicationReducer.ts b/src/redux/reducers/applicationReducer.ts index bdd013a74..6cd6c460a 100644 --- a/src/redux/reducers/applicationReducer.ts +++ b/src/redux/reducers/applicationReducer.ts @@ -30,7 +30,8 @@ import { SET_SETTINGS_MIGRATED, HIDE_POSTS_THUMBNAILS, SET_TERMS_ACCEPTED, - SET_IS_BIOMETRIC_ENABLED + SET_IS_BIOMETRIC_ENABLED, + SET_ENC_UNLOCK_PIN } from '../constants/constants'; interface State { @@ -63,9 +64,10 @@ interface State { }, upvotePercent: number; nsfw: string; - pin: string|null; + pin: string|null; //encrypted pin used for encrypting sensitive user data isPinCodeOpen: boolean; isRenderRequired: boolean; + encUnlockPin: string; //ecryped pin used for user defined lock screen pass code lastAppVersion:string; settingsMigratedV2: boolean; hidePostsThumbnails: boolean; @@ -106,6 +108,7 @@ const initialState:State = { pin: null, isPinCodeOpen: false, isRenderRequired: false, + encUnlockPin: '', lastAppVersion:'', settingsMigratedV2: false, hidePostsThumbnails: false, @@ -113,7 +116,7 @@ const initialState:State = { isBiometricEnabled: false }; -export default function (state = initialState, action) { +export default function (state = initialState, action):State { switch (action.type) { case LOGIN: return { @@ -292,6 +295,12 @@ export default function (state = initialState, action) { isBiometricEnabled:action.payload } + case SET_ENC_UNLOCK_PIN: + return { + ...state, + encUnlockPin:action.payload + } + default: return state; } diff --git a/src/screens/application/children/migrationHelpers.ts b/src/screens/application/children/migrationHelpers.ts deleted file mode 100644 index 1f3c5774f..000000000 --- a/src/screens/application/children/migrationHelpers.ts +++ /dev/null @@ -1,83 +0,0 @@ -import { Appearance } from 'react-native'; - -// Constants -import THEME_OPTIONS from '../../../constants/options/theme'; - -// Services -import { - getSettings, -} from '../../../realm/realm'; - -import { - isDarkTheme, - changeNotificationSettings, - changeAllNotificationSettings, - setApi, - setCurrency, - setLanguage, - setUpvotePercent, - setNsfw, - isDefaultFooter, - isPinCodeOpen, - setColorTheme, - setSettingsMigrated, -} from '../../../redux/actions/applicationActions'; -import { - hideActionModal, - hideProfileModal, - setRcOffer, - toastNotification, -} from '../../../redux/actions/uiAction'; - - -//migrates settings from realm to redux once and do no user realm for settings again; -export const migrateSettings = async (dispatch: any, settingsMigratedV2: boolean) => { - - if (settingsMigratedV2) { - return; - } - - //reset certain properties - dispatch(hideActionModal()); - dispatch(hideProfileModal()); - dispatch(toastNotification('')); - dispatch(setRcOffer(false)); - - - const settings = await getSettings(); - - if (settings) { - const isDarkMode = Appearance.getColorScheme() === 'dark'; - dispatch(isDarkTheme(settings.isDarkTheme !== null ? settings.isDarkTheme : isDarkMode)); - dispatch(setColorTheme(THEME_OPTIONS.findIndex(item => item.value === settings.isDarkTheme))); - await dispatch(isPinCodeOpen(!!settings.isPinCodeOpen)); - if (settings.language !== '') dispatch(setLanguage(settings.language)); - if (settings.server !== '') dispatch(setApi(settings.server)); - if (settings.upvotePercent !== '') { - dispatch(setUpvotePercent(Number(settings.upvotePercent))); - } - if (settings.isDefaultFooter !== '') dispatch(isDefaultFooter(settings.isDefaultFooter)); //TODO: remove as not being used - - - if (settings.nsfw !== '') dispatch(setNsfw(settings.nsfw)); - - dispatch(setCurrency(settings.currency !== '' ? settings.currency : 'usd')); - - if (settings.notification !== '') { - dispatch( - changeNotificationSettings({ - type: 'notification', - action: settings.notification, - }), - ); - - dispatch(changeAllNotificationSettings(settings)); - } - - await dispatch(setSettingsMigrated(true)) - } -} - -export default { - migrateSettings -} \ No newline at end of file diff --git a/src/screens/application/container/applicationContainer.tsx b/src/screens/application/container/applicationContainer.tsx index 8e191b00e..4622ce651 100644 --- a/src/screens/application/container/applicationContainer.tsx +++ b/src/screens/application/container/applicationContainer.tsx @@ -69,6 +69,8 @@ import { setPinCode as savePinCode, isRenderRequired, logout, + isPinCodeOpen, + setEncryptedUnlockPin, } from '../../../redux/actions/applicationActions'; import { setAvatarCacheStamp, @@ -89,7 +91,7 @@ import { setMomentLocale } from '../../../utils/time'; import parseAuthUrl from '../../../utils/parseAuthUrl'; import { purgeExpiredCache } from '../../../redux/actions/cacheActions'; import { fetchSubscribedCommunities } from '../../../redux/actions/communitiesAction'; -import MigrationHelpers from '../children/migrationHelpers'; +import MigrationHelpers from '../../../utils/migrationHelpers'; // Workaround let previousAppState = 'background'; @@ -666,9 +668,9 @@ class ApplicationContainer extends Component { }; _refreshAccessToken = async (currentAccount) => { - const { pinCode, isPinCodeOpen, dispatch, intl } = this.props; + const { pinCode, isPinCodeOpen, encUnlockPin, dispatch, intl } = this.props; - if (isPinCodeOpen) { + if (isPinCodeOpen && !encUnlockPin) { return currentAccount; } @@ -703,14 +705,14 @@ class ApplicationContainer extends Component { }; _fetchUserDataFromDsteem = async (realmObject) => { - const { dispatch, intl, pinCode, isPinCodeOpen } = this.props; + const { dispatch, intl, pinCode, isPinCodeOpen, encUnlockPin } = this.props; try { let accountData = await getUser(realmObject.username); accountData.local = realmObject; //cannot migrate or refresh token since pin would null while pin code modal is open - if (!isPinCodeOpen) { + if (!isPinCodeOpen || encUnlockPin) { //migration script for previously mast key based logged in user not having access token if (realmObject.authType !== AUTH_TYPE.STEEM_CONNECT && realmObject.accessToken === '') { accountData = await migrateToMasterKeyWithAccessToken(accountData, realmObject, pinCode); @@ -792,6 +794,7 @@ class ApplicationContainer extends Component { currentAccount: { name, local }, dispatch, intl, + } = this.props; removeUserData(name) @@ -812,6 +815,8 @@ class ApplicationContainer extends Component { isLoggedIn: false, }); setExistUser(false); + dispatch(isPinCodeOpen(false)); + dispatch(setEncryptedUnlockPin(encryptKey(Config.DEFAULT_KEU, Config.PIN_KEY))) if (local.authType === AUTH_TYPE.STEEM_CONNECT) { removeSCAccount(name); } @@ -984,6 +989,7 @@ export default connect( isDarkTheme: state.application.isDarkTheme, selectedLanguage: state.application.language, isPinCodeOpen: state.application.isPinCodeOpen, + encUnlockPin: state.application.encUnlockPin, isLogingOut: state.application.isLogingOut, isLoggedIn: state.application.isLoggedIn, //TODO: remove as is not being used in this class isConnected: state.application.isConnected, diff --git a/src/screens/login/container/loginContainer.js b/src/screens/login/container/loginContainer.js index 800b88d82..fc94cdff2 100644 --- a/src/screens/login/container/loginContainer.js +++ b/src/screens/login/container/loginContainer.js @@ -60,7 +60,7 @@ class LoginContainer extends PureComponent { this.setState({ isLoading: true }); - login(username, password, isPinCodeOpen) + login(username, password) .then((result) => { if (result) { const persistAccountData = persistAccountGenerator(result); @@ -75,11 +75,12 @@ class LoginContainer extends PureComponent { userActivity(20); setExistUser(true); this._setPushToken(result.name); + const encryptedPin = encryptKey(Config.DEFAULT_PIN, Config.PIN_KEY); + dispatch(setPinCode(encryptedPin)); + if (isPinCodeOpen) { dispatch(openPinCodeModal({ navigateTo: ROUTES.DRAWER.MAIN })); } else { - const encryptedPin = encryptKey(Config.DEFAULT_PIN, Config.PIN_KEY); - dispatch(setPinCode(encryptedPin)); navigation.navigate({ routeName: ROUTES.DRAWER.MAIN, }); diff --git a/src/screens/pinCode/container/pinCodeContainer.js b/src/screens/pinCode/container/pinCodeContainer.tsx similarity index 51% rename from src/screens/pinCode/container/pinCodeContainer.js rename to src/screens/pinCode/container/pinCodeContainer.tsx index a0f1b4884..8e6a15681 100644 --- a/src/screens/pinCode/container/pinCodeContainer.js +++ b/src/screens/pinCode/container/pinCodeContainer.tsx @@ -6,17 +6,11 @@ import Config from 'react-native-config'; import get from 'lodash/get'; import FingerprintScanner from 'react-native-fingerprint-scanner'; -//Contstants -import AUTH_TYPE from '../../../constants/authType'; // Actions & Services import { navigate } from '../../../navigation/service'; import { - setUserDataWithPinCode, - verifyPinCode, updatePinCode, - migrateToMasterKeyWithAccessToken, - refreshSCToken, } from '../../../providers/hive/auth'; import { closePinCodeModal, @@ -24,28 +18,24 @@ import { login, logout, logoutDone, - setPinCode as savePinCode, + setEncryptedUnlockPin, } from '../../../redux/actions/applicationActions'; import { getExistUser, setExistUser, - getUserDataWithUsername, removeAllUserData, removePinCode, setAuthStatus, - setPinCodeOpen, } from '../../../realm/realm'; import { updateCurrentAccount, removeOtherAccount } from '../../../redux/actions/accountAction'; -import { getDigitPinCode, getMutes, getUser } from '../../../providers/hive/dhive'; -import { getPointsSummary } from '../../../providers/ecency/ePoint'; // Utils import { encryptKey, decryptKey } from '../../../utils/crypto'; +import MigrationHelpers from '../../../utils/migrationHelpers'; // Component import PinCodeScreen from '../screen/pinCodeScreen'; -import { getUnreadNotificationCount } from '../../../providers/ecency/ecency'; -import { fetchSubscribedCommunities } from '../../../redux/actions/communitiesAction'; + class PinCodeContainer extends Component { screenRef = null; @@ -62,49 +52,37 @@ class PinCodeContainer extends Component { }; } - // TODO: if check for decide to set to pin or verify to pin page - // TODO: these text should move to view! - componentDidMount() { - this._getDataFromStorage().then(() => { - const { intl } = this.props; - const { isOldPinVerified } = this.state; - if (!isOldPinVerified) { - this.setState({ - informationText: intl.formatMessage({ - id: 'pincode.enter_text', - }), - }); - } else { - this.setState({ - informationText: intl.formatMessage({ - id: 'pincode.set_new', - }), - }); - } - }); + //sets initial pin code screen label based on oldPinVerified param/state + componentDidMount() { + + const { intl } = this.props; + const { isOldPinVerified } = this.state; + + if (!isOldPinVerified) { + this.setState({ + informationText: intl.formatMessage({ + id: 'pincode.enter_text', + }), + }); + } else { + this.setState({ + informationText: intl.formatMessage({ + id: 'pincode.set_new', + }), + }); + } this._processBiometric(); } - _getDataFromStorage = () => - new Promise((resolve) => { - getExistUser().then((isExistUser) => { - this.setState( - { - isExistUser, - }, - resolve, - ); - }); - }); - _processBiometric = async () => { try { const { intl, pinCodeParams: { isReset }, applicationPinCode, + encUnlockPin, isBiometricEnabled, } = this.props; @@ -122,7 +100,8 @@ class PinCodeContainer extends Component { //code gets here means biometeric succeeded if (this.screenRef) { - const verifiedPin = decryptKey(applicationPinCode, Config.PIN_KEY, this._onDecryptFail); + const encPin = encUnlockPin || applicationPinCode; + const verifiedPin = decryptKey(encPin, Config.PIN_KEY, this._onDecryptFail); this.screenRef.setPinThroughBiometric(verifiedPin); } } catch (err) { @@ -153,46 +132,38 @@ class PinCodeContainer extends Component { } }; + + //routine for checking and setting new pin code, same routine is used for + //setting pin for the first time _resetPinCode = (pin) => new Promise((resolve, reject) => { const { - currentAccount, dispatch, - pinCodeParams: { navigateTo, navigateParams, accessToken, callback }, + pinCodeParams: { navigateTo, navigateParams, callback }, + encUnlockPin, intl, } = this.props; const { isOldPinVerified, oldPinCode, newPinCode } = this.state; - const pinData = { - pinCode: pin, - password: currentAccount ? currentAccount.password : '', - username: currentAccount ? currentAccount.name : '', - accessToken, - oldPinCode, - }; //if old pin already verified, check new pin setup conditions. if (isOldPinVerified) { //if newPin already exist and pin is a valid pin, compare and set new pin if (pin !== undefined && pin === newPinCode) { - this._updatePinCodeRealm(pinData).then((status) => { - if (!status) { - resolve(); - return; - } - this._savePinCode(pin); - if (callback) { - callback(pin, oldPinCode); - } - dispatch(closePinCodeModal()); - if (navigateTo) { - navigate({ - routeName: navigateTo, - params: navigateParams, - }); - } - resolve(); - }); + + this._savePinCode(pin); + if (callback) { + callback(pin, oldPinCode); + } + dispatch(closePinCodeModal()); + + if (navigateTo) { + navigate({ + routeName: navigateTo, + params: navigateParams, + }); + } + resolve(); } // if newPin code exists and above case failed, that means pins did not match @@ -223,73 +194,31 @@ class PinCodeContainer extends Component { // if old pin code is not yet verified attempt to verify code else { - verifyPinCode(pinData) - .then(() => { - this.setState({ isOldPinVerified: true }); - this.setState({ - informationText: intl.formatMessage({ - id: 'pincode.set_new', - }), - newPinCode: null, - oldPinCode: pin, - }); - resolve(); - }) - .catch((err) => { - console.warn('Failed to verify pin code', err); - Alert.alert( - intl.formatMessage({ - id: 'alert.warning', - }), - intl.formatMessage({ - id: err.message, - }), - ); - reject(err); - }); + + let unlockPin = decryptKey(encUnlockPin, Config.PIN_KEY) + + //check if pins match + if (unlockPin !== pin) { + const err = new Error('alert.invalid_pincode'); + reject(err); + return; + } + + + this.setState({ isOldPinVerified: true }); + this.setState({ + informationText: intl.formatMessage({ + id: 'pincode.set_new', + }), + newPinCode: null, + oldPinCode: pin, + }); + resolve(); + } }); - _setFirstPinCode = (pin) => - new Promise((resolve) => { - const { - currentAccount, - dispatch, - pinCodeParams: { navigateTo, navigateParams, accessToken, callback }, - } = this.props; - const { oldPinCode } = this.state; - const pinData = { - pinCode: pin, - password: currentAccount ? currentAccount.password : '', - username: currentAccount ? currentAccount.name : '', - accessToken, - }; - - setUserDataWithPinCode(pinData).then((response) => { - getUser(currentAccount.name).then((user) => { - const _currentAccount = user; - _currentAccount.local = response; - - dispatch(updateCurrentAccount({ ..._currentAccount })); - - setExistUser(true).then(() => { - this._savePinCode(pin); - if (callback) { - callback(pin, oldPinCode); - } - dispatch(closePinCodeModal()); - if (navigateTo) { - navigate({ - routeName: navigateTo, - params: navigateParams, - }); - } - resolve(); - }); - }); - }); - }); _onRefreshTokenFailed = (error) => { setTimeout(() => { @@ -308,98 +237,68 @@ class PinCodeContainer extends Component { }, 300); }; - _verifyPinCode = (pin, { shouldUpdateRealm } = {}) => - new Promise((resolve, reject) => { + + + //verifies is the pin entered is right or wrong, also migrates to newer locking method + _verifyPinCode = async (pin) => { + try { const { + intl, currentAccount, dispatch, - pinCodeParams: { navigateTo, navigateParams, accessToken, callback }, + encUnlockPin, + applicationPinCode, + pinCodeParams: { navigateTo, navigateParams, callback }, } = this.props; const { oldPinCode } = this.state; - // If the user is exist, we are just checking to pin and navigating to feed screen - const pinData = { - pinCode: pin, - password: currentAccount ? currentAccount.password : '', - username: currentAccount ? currentAccount.name : '', - accessToken, - }; - verifyPinCode(pinData) - .then(() => { - this._savePinCode(pin); - getUserDataWithUsername(currentAccount.name).then(async (realmData) => { - if (shouldUpdateRealm) { - this._updatePinCodeRealm(pinData).then(() => { - dispatch(closePinCodeModal()); - }); - } else { - let _currentAccount = currentAccount; - _currentAccount.username = _currentAccount.name; - [_currentAccount.local] = realmData; + let unlockPin = encUnlockPin ? + decryptKey(encUnlockPin, Config.PIN_KEY) : decryptKey(applicationPinCode, Config.PIN_KEY); - try { - const pinHash = encryptKey(pin, Config.PIN_KEY); - //migration script for previously mast key based logged in user not having access token - if ( - realmData[0].authType !== AUTH_TYPE.STEEM_CONNECT && - realmData[0].accessToken === '' - ) { - _currentAccount = await migrateToMasterKeyWithAccessToken( - _currentAccount, - realmData[0], - pinHash, - ); - } - //refresh access token - const encryptedAccessToken = await refreshSCToken(_currentAccount.local, pin); - _currentAccount.local.accessToken = encryptedAccessToken; - } catch (error) { - this._onRefreshTokenFailed(error); - } + //check if pins match + if (unlockPin !== pin) { + throw new Error(intl.formatMessage({ + id: 'alert.invalid_pincode', + })); + } - //get unread notifications - try { - _currentAccount.unread_activity_count = await getUnreadNotificationCount(); - _currentAccount.pointsSummary = await getPointsSummary(_currentAccount.username); - _currentAccount.mutes = await getMutes(_currentAccount.username); - } catch (err) { - console.warn( - 'Optional user data fetch failed, account can still function without them', - err, - ); - } + //migrate data to default pin if encUnlockPin is not set. + if (!encUnlockPin) { + await MigrationHelpers.migrateUserEncryption(dispatch, currentAccount, applicationPinCode, this._onRefreshTokenFailed); + } - dispatch(updateCurrentAccount({ ..._currentAccount })); - dispatch(fetchSubscribedCommunities(_currentAccount.username)); - dispatch(closePinCodeModal()); - } - //on successful code verification run requested operation passed as props - if (callback) { - callback(pin, oldPinCode); - } - if (navigateTo) { - navigate({ - routeName: navigateTo, - params: navigateParams, - }); - } - resolve(); - }); - }) - .catch((err) => { - console.warn('code verification for login failed: ', err); - reject(err); + //on successful code verification run requested operation passed as props + if (callback) { + callback(pin, oldPinCode); + } + + if (navigateTo) { + navigate({ + routeName: navigateTo, + params: navigateParams, }); - }); + } + dispatch(closePinCodeModal()); + + return true; + } catch (err) { + throw err + } + } + + + //encryptes and saved unlockPin _savePinCode = (pin) => { const { dispatch } = this.props; const encryptedPin = encryptKey(pin, Config.PIN_KEY); - dispatch(savePinCode(encryptedPin)); + dispatch(setEncryptedUnlockPin(encryptedPin)); }; + + _forgotPinCode = async () => { const { otherAccounts, dispatch } = this.props; @@ -416,7 +315,6 @@ class PinCodeContainer extends Component { dispatch(logoutDone()); dispatch(closePinCodeModal()); dispatch(isPinCodeOpen(false)); - setPinCodeOpen(false); }) .catch((err) => { console.warn('Failed to remove user data', err); @@ -478,51 +376,16 @@ class PinCodeContainer extends Component { }; _setPinCode = async (pin, isReset) => { - const { intl, currentAccount, applicationPinCode } = this.props; - const { isExistUser } = this.state; - try { - const realmData = await getUserDataWithUsername(currentAccount.name); - const userData = realmData[0]; - // check if reset routine is triggered by user, reroute code to reset hanlder if (isReset) { await this._resetPinCode(pin); - return true; + } else { + await this._verifyPinCode(pin); } - //user is logged in and is not reset routine... - if (isExistUser) { - if (!userData.accessToken && !userData.masterKey && applicationPinCode) { - const verifiedPin = decryptKey(applicationPinCode, Config.PIN_KEY, this._onDecryptFail); - if (verifiedPin === undefined) { - return true; - } - if (verifiedPin === pin) { - await this._setFirstPinCode(pin); - } else { - Alert.alert( - intl.formatMessage({ - id: 'alert.warning', - }), - intl.formatMessage({ - id: 'alert.invalid_pincode', - }), - ); - } - } else { - await this._verifyPinCode(pin); - } - return true; - } + return true - //means this is not reset routine and user do not exist - //only possible option left is user logging int, - //verifyPinCode then and update realm as well. - else { - await this._verifyPinCode(pin, { shouldUpdateRealm: true }); - return true; - } } catch (error) { return this._handleFailedAttempt(error); } @@ -551,7 +414,7 @@ class PinCodeContainer extends Component { intl, pinCodeParams: { isReset }, } = this.props; - const { informationText, isOldPinVerified, isExistUser } = this.state; + const { informationText, isOldPinVerified } = this.state; return ( ({ currentAccount: state.account.currentAccount, applicationPinCode: state.application.pin, + encUnlockPin: state.application.encUnlockPin, otherAccounts: state.account.otherAccounts, pinCodeParams: state.application.pinCodeNavigation, isBiometricEnabled: state.application.isBiometricEnabled, diff --git a/src/screens/settings/container/settingsContainer.js b/src/screens/settings/container/settingsContainer.tsx similarity index 90% rename from src/screens/settings/container/settingsContainer.js rename to src/screens/settings/container/settingsContainer.tsx index dc371303f..213b3254f 100644 --- a/src/screens/settings/container/settingsContainer.js +++ b/src/screens/settings/container/settingsContainer.tsx @@ -18,13 +18,10 @@ import { setLanguage as setLanguage2DB, setNsfw as setNsfw2DB, setTheme, - setPinCodeOpen, - removeUserData, removePinCode, setAuthStatus, setExistUser, removeAllUserData, - getTheme, } from '../../../realm/realm'; // Services and Actions @@ -38,17 +35,15 @@ import { openPinCodeModal, setNsfw, isPinCodeOpen, - setPinCode as savePinCode, login, logoutDone, - closePinCodeModal, setColorTheme, setIsBiometricEnabled, + setEncryptedUnlockPin, } from '../../../redux/actions/applicationActions'; import { toastNotification } from '../../../redux/actions/uiAction'; import { setPushToken, getNodes } from '../../../providers/ecency/ecency'; import { checkClient } from '../../../providers/hive/dhive'; -import { updatePinCode } from '../../../providers/hive/auth'; import { removeOtherAccount, updateCurrentAccount } from '../../../redux/actions/accountAction'; // Middleware @@ -226,7 +221,7 @@ class SettingsContainer extends Component { if (action) { dispatch( openPinCodeModal({ - callback: () => this._setDefaultPinCode(action), + callback: () => this._enableDefaultUnlockPin(action), isReset: true, isOldPinVerified: true, oldPinCode: Config.DEFAULT_PIN, @@ -235,7 +230,7 @@ class SettingsContainer extends Component { } else { dispatch( openPinCodeModal({ - callback: () => this._setDefaultPinCode(action), + callback: () => this._enableDefaultUnlockPin(action), }), ); } @@ -403,7 +398,6 @@ class SettingsContainer extends Component { } dispatch(logoutDone()); dispatch(isPinCodeOpen(false)); - setPinCodeOpen(false); }) .catch((err) => { console.warn('Failed to remove user data', err); @@ -428,45 +422,21 @@ class SettingsContainer extends Component { }, 500); }; - _setDefaultPinCode = (action) => { - const { dispatch, username, currentAccount, pinCode } = this.props; - if (!action) { - const oldPinCode = decryptKey(pinCode, Config.PIN_KEY, this._onDecryptFail); + _enableDefaultUnlockPin = (isEnabled) => { + const { dispatch, encUnlockPin } = this.props; + + dispatch(isPinCodeOpen(isEnabled)); + + if (!isEnabled) { + const oldPinCode = decryptKey(encUnlockPin, Config.PIN_KEY, this._onDecryptFail); if (oldPinCode === undefined) { return; } - const pinData = { - pinCode: Config.DEFAULT_PIN, - username, - oldPinCode, - }; - updatePinCode(pinData) - .then((response) => { - const _currentAccount = currentAccount; - _currentAccount.local = response; - - dispatch( - updateCurrentAccount({ - ..._currentAccount, - }), - ); - - const encryptedPin = encryptKey(Config.DEFAULT_PIN, Config.PIN_KEY); - dispatch(savePinCode(encryptedPin)); - - setPinCodeOpen(action); - dispatch(isPinCodeOpen(action)); - }) - .catch((err) => { - console.warn('pin update failure: ', err); - this._onDecryptFail(); - }); - } else { - setPinCodeOpen(action); - dispatch(isPinCodeOpen(action)); + const encryptedPin = encryptKey(Config.DEFAULT_PIN, Config.PIN_KEY); + dispatch(setEncryptedUnlockPin(encryptedPin)); } }; @@ -492,8 +462,8 @@ const mapStateToProps = (state) => ({ isDarkTheme: state.application.isDarkTheme, colorTheme: state.application.colorTheme, isPinCodeOpen: state.application.isPinCodeOpen, + encUnlockPin: state.application.encUnlockPin, isBiometricEnabled: state.application.isBiometricEnabled, - pinCode: state.application.pin, isDefaultFooter: state.application.isDefaultFooter, isLoggedIn: state.application.isLoggedIn, isNotificationSettingsOpen: state.application.isNotificationOpen, diff --git a/src/screens/steem-connect/hiveSigner.js b/src/screens/steem-connect/hiveSigner.js index dafa35304..f2eb90793 100644 --- a/src/screens/steem-connect/hiveSigner.js +++ b/src/screens/steem-connect/hiveSigner.js @@ -40,7 +40,7 @@ class HiveSigner extends PureComponent { if (!isLoading) { this.setState({ isLoading: true }); handleOnModalClose(); - loginWithSC2(code[1], isPinCodeOpen) + loginWithSC2(code[1]) .then((result) => { if (result) { const persistAccountData = persistAccountGenerator(result); diff --git a/src/utils/migrationHelpers.ts b/src/utils/migrationHelpers.ts new file mode 100644 index 000000000..aaacab29e --- /dev/null +++ b/src/utils/migrationHelpers.ts @@ -0,0 +1,179 @@ +import { Appearance } from 'react-native'; +import Config from 'react-native-config'; + +// Constants +import THEME_OPTIONS from '../constants/options/theme'; +import { getUnreadNotificationCount } from '../providers/ecency/ecency'; +import { getPointsSummary } from '../providers/ecency/ePoint'; +import { migrateToMasterKeyWithAccessToken, refreshSCToken, updatePinCode } from '../providers/hive/auth'; +import { getMutes } from '../providers/hive/dhive'; +import AUTH_TYPE from '../constants/authType'; + +// Services +import { + getSettings, getUserDataWithUsername, +} from '../realm/realm'; +import { updateCurrentAccount } from '../redux/actions/accountAction'; + +import { + isDarkTheme, + changeNotificationSettings, + changeAllNotificationSettings, + setApi, + setCurrency, + setLanguage, + setUpvotePercent, + setNsfw, + isDefaultFooter, + isPinCodeOpen, + setColorTheme, + setSettingsMigrated, + setPinCode, + setEncryptedUnlockPin, +} from '../redux/actions/applicationActions'; +import { fetchSubscribedCommunities } from '../redux/actions/communitiesAction'; +import { + hideActionModal, + hideProfileModal, + setRcOffer, + toastNotification, +} from '../redux/actions/uiAction'; +import { decryptKey, encryptKey } from './crypto'; + + +//migrates settings from realm to redux once and do no user realm for settings again; +export const migrateSettings = async (dispatch: any, settingsMigratedV2: boolean) => { + + if (settingsMigratedV2) { + return; + } + + //reset certain properties + dispatch(hideActionModal()); + dispatch(hideProfileModal()); + dispatch(toastNotification('')); + dispatch(setRcOffer(false)); + + + const settings = await getSettings(); + + if (settings) { + const isDarkMode = Appearance.getColorScheme() === 'dark'; + dispatch(isDarkTheme(settings.isDarkTheme !== null ? settings.isDarkTheme : isDarkMode)); + dispatch(setColorTheme(THEME_OPTIONS.findIndex(item => item.value === settings.isDarkTheme))); + await dispatch(isPinCodeOpen(!!settings.isPinCodeOpen)); + if (settings.language !== '') dispatch(setLanguage(settings.language)); + if (settings.server !== '') dispatch(setApi(settings.server)); + if (settings.upvotePercent !== '') { + dispatch(setUpvotePercent(Number(settings.upvotePercent))); + } + if (settings.isDefaultFooter !== '') dispatch(isDefaultFooter(settings.isDefaultFooter)); //TODO: remove as not being used + + + if (settings.nsfw !== '') dispatch(setNsfw(settings.nsfw)); + + dispatch(setCurrency(settings.currency !== '' ? settings.currency : 'usd')); + + if (settings.notification !== '') { + dispatch( + changeNotificationSettings({ + type: 'notification', + action: settings.notification, + }), + ); + + dispatch(changeAllNotificationSettings(settings)); + } + + await dispatch(setSettingsMigrated(true)) + } +} + + + +//migrates local user data to use default pin encruption instead of user pin encryption +export const migrateUserEncryption = async (dispatch, currentAccount, encUserPin, onFailure) => { + + const oldPinCode = decryptKey(encUserPin, Config.PIN_KEY); + + if (oldPinCode === undefined || oldPinCode === Config.DEFAULT_PIN) { + return; + } + + + try{ + const pinData = { + pinCode: Config.DEFAULT_PIN, + username: currentAccount.username, + oldPinCode, + }; + + const response = updatePinCode(pinData) + + const _currentAccount = currentAccount; + _currentAccount.local = response; + + dispatch( + updateCurrentAccount({ + ..._currentAccount, + }), + ); + + const encryptedPin = encryptKey(Config.DEFAULT_PIN, Config.PIN_KEY); + dispatch(setPinCode(encryptedPin)); + + } catch(err){ + console.warn('pin update failure: ', err); + } + + + dispatch(setEncryptedUnlockPin(encUserPin)) + + const realmData = await getUserDataWithUsername(currentAccount.name) + + let _currentAccount = currentAccount; + _currentAccount.username = _currentAccount.name; + _currentAccount.local = realmData[0]; + + try { + const pinHash = encryptKey(Config.DEFAULT_PIN, Config.PIN_KEY); + //migration script for previously mast key based logged in user not having access token + if ( + realmData[0].authType !== AUTH_TYPE.STEEM_CONNECT && + realmData[0].accessToken === '' + ) { + _currentAccount = await migrateToMasterKeyWithAccessToken( + _currentAccount, + realmData[0], + pinHash, + ); + } + + //refresh access token + const encryptedAccessToken = await refreshSCToken(_currentAccount.local, Config.DEFAULT_PIN); + _currentAccount.local.accessToken = encryptedAccessToken; + } catch (error) { + onFailure(error) + } + + //get unread notifications + try { + _currentAccount.unread_activity_count = await getUnreadNotificationCount(); + _currentAccount.pointsSummary = await getPointsSummary(_currentAccount.username); + _currentAccount.mutes = await getMutes(_currentAccount.username); + } catch (err) { + console.warn( + 'Optional user data fetch failed, account can still function without them', + err, + ); + } + + dispatch(updateCurrentAccount({ ..._currentAccount })); + dispatch(fetchSubscribedCommunities(_currentAccount.username)); + +} + +export default { + migrateSettings, + migrateUserEncryption +}