diff --git a/app/constants/localAuthentication.ts b/app/constants/localAuthentication.ts index 1312e57aa5..bd74997a3c 100644 --- a/app/constants/localAuthentication.ts +++ b/app/constants/localAuthentication.ts @@ -1,6 +1,7 @@ export const PASSCODE_KEY = 'kPasscode'; export const LOCKED_OUT_TIMER_KEY = 'kLockedOutTimer'; export const ATTEMPTS_KEY = 'kAttempts'; +export const BIOMETRY_ENABLED_KEY = 'kBiometryEnabled'; export const LOCAL_AUTHENTICATE_EMITTER = 'LOCAL_AUTHENTICATE'; export const CHANGE_PASSCODE_EMITTER = 'CHANGE_PASSCODE'; diff --git a/app/lib/database/schema/servers.js b/app/lib/database/schema/servers.js index 1d849b8745..68eddb036f 100644 --- a/app/lib/database/schema/servers.js +++ b/app/lib/database/schema/servers.js @@ -33,7 +33,7 @@ export default appSchema({ { name: 'last_local_authenticated_session', type: 'number', isOptional: true }, { name: 'auto_lock', type: 'boolean', isOptional: true }, { name: 'auto_lock_time', type: 'number', isOptional: true }, - { name: 'biometry', type: 'boolean', isOptional: true }, + { name: 'biometry', type: 'boolean', isOptional: true }, // deprecated { name: 'unique_id', type: 'string', isOptional: true }, { name: 'enterprise_modules', type: 'string', isOptional: true }, { name: 'e2e_enable', type: 'boolean', isOptional: true } diff --git a/app/sagas/init.js b/app/sagas/init.js index 3d7fbd9dc9..3671e83f41 100644 --- a/app/sagas/init.js +++ b/app/sagas/init.js @@ -1,6 +1,7 @@ import { put, takeLatest } from 'redux-saga/effects'; import RNBootSplash from 'react-native-bootsplash'; +import { BIOMETRY_ENABLED_KEY } from '../constants/localAuthentication'; import UserPreferences from '../lib/userPreferences'; import { selectServerRequest } from '../actions/server'; import { setAllPreferences } from '../actions/sortPreferences'; @@ -17,11 +18,25 @@ export const initLocalSettings = function* initLocalSettings() { yield put(setAllPreferences(sortPreferences)); }; +const BIOMETRY_MIGRATION_KEY = 'kBiometryMigration'; + const restore = function* restore() { try { const server = yield UserPreferences.getStringAsync(RocketChat.CURRENT_SERVER); let userId = yield UserPreferences.getStringAsync(`${RocketChat.TOKEN_KEY}-${server}`); + // Migration biometry setting from WatermelonDB to MMKV + // TODO: remove it after a few versions + const hasMigratedBiometry = yield UserPreferences.getBoolAsync(BIOMETRY_MIGRATION_KEY); + if (!hasMigratedBiometry) { + const serversDB = database.servers; + const serversCollection = serversDB.get('servers'); + const servers = yield serversCollection.query().fetch(); + const isBiometryEnabled = servers.some(server => !!server.biometry); + yield UserPreferences.setBoolAsync(BIOMETRY_ENABLED_KEY, isBiometryEnabled); + yield UserPreferences.setBoolAsync(BIOMETRY_MIGRATION_KEY, true); + } + if (!server) { yield put(appStart({ root: RootEnum.ROOT_OUTSIDE })); } else if (!userId) { diff --git a/app/utils/localAuthentication.ts b/app/utils/localAuthentication.ts index f435996333..0e72d9c5e3 100644 --- a/app/utils/localAuthentication.ts +++ b/app/utils/localAuthentication.ts @@ -9,6 +9,7 @@ import store from '../lib/createStore'; import database from '../lib/database'; import { ATTEMPTS_KEY, + BIOMETRY_ENABLED_KEY, CHANGE_PASSCODE_EMITTER, LOCAL_AUTHENTICATE_EMITTER, LOCKED_OUT_TIMER_KEY, @@ -72,32 +73,18 @@ export const biometryAuth = (force?: boolean): Promise { - const serversDB = database.servers; - +const checkBiometry = async () => { const result = await biometryAuth(true); - await serversDB.write(async () => { - try { - await serverRecord.update(record => { - record.biometry = !!result?.success; - }); - } catch { - // Do nothing - } - }); + const isBiometryEnabled = !!result?.success; + await UserPreferences.setBoolAsync(BIOMETRY_ENABLED_KEY, isBiometryEnabled); + return isBiometryEnabled; }; -export const checkHasPasscode = async ({ - force = true, - serverRecord -}: { - force?: boolean; - serverRecord: TServerModel; -}): Promise<{ newPasscode?: boolean } | void> => { +export const checkHasPasscode = async ({ force = true }: { force?: boolean }): Promise<{ newPasscode?: boolean } | void> => { const storedPasscode = await UserPreferences.getStringAsync(PASSCODE_KEY); if (!storedPasscode) { await changePasscode({ force }); - await checkBiometry(serverRecord); + await checkBiometry(); return Promise.resolve({ newPasscode: true }); } return Promise.resolve(); @@ -124,7 +111,7 @@ export const localAuthenticate = async (server: string): Promise => { } // Check if the app has passcode - const result = await checkHasPasscode({ serverRecord }); + const result = await checkHasPasscode({}); // `checkHasPasscode` results newPasscode = true if a passcode has been set if (!result?.newPasscode) { @@ -136,10 +123,11 @@ export const localAuthenticate = async (server: string): Promise => { // set isLocalAuthenticated to false store.dispatch(setLocalAuthenticated(false)); - let hasBiometry = false; + // let hasBiometry = false; + let hasBiometry = (await UserPreferences.getBoolAsync(BIOMETRY_ENABLED_KEY)) ?? false; // if biometry is enabled on the app - if (serverRecord.biometry) { + if (hasBiometry) { const isEnrolled = await LocalAuthentication.isEnrolledAsync(); hasBiometry = isEnrolled; } diff --git a/app/views/ScreenLockConfigView.tsx b/app/views/ScreenLockConfigView.tsx index 626f88caba..57060bfe98 100644 --- a/app/views/ScreenLockConfigView.tsx +++ b/app/views/ScreenLockConfigView.tsx @@ -11,10 +11,11 @@ import StatusBar from '../containers/StatusBar'; import * as List from '../containers/List'; import database from '../lib/database'; import { changePasscode, checkHasPasscode, supportedBiometryLabel } from '../utils/localAuthentication'; -import { DEFAULT_AUTO_LOCK } from '../constants/localAuthentication'; +import { BIOMETRY_ENABLED_KEY, DEFAULT_AUTO_LOCK } from '../constants/localAuthentication'; import SafeAreaView from '../containers/SafeAreaView'; import { events, logEvent } from '../utils/log'; import { TServerModel } from '../definitions/IServer'; +import userPreferences from '../lib/userPreferences'; const DEFAULT_BIOMETRY = false; @@ -34,7 +35,7 @@ interface IScreenLockConfigViewProps { interface IScreenLockConfigViewState { autoLock: boolean; autoLockTime?: number | null; - biometry?: boolean; + biometry: boolean; biometryLabel: string | null; } @@ -91,12 +92,13 @@ class ScreenLockConfigView extends React.Component { - this.observable = this.serverRecord?.observe()?.subscribe(({ biometry }) => { - this.setState({ biometry: !!biometry }); - }); }; save = async () => { logEvent(events.SLC_SAVE_SCREEN_LOCK); - const { autoLock, autoLockTime, biometry } = this.state; + const { autoLock, autoLockTime } = this.state; const serversDB = database.servers; await serversDB.write(async () => { await this.serverRecord?.update(record => { record.autoLock = autoLock; record.autoLockTime = autoLockTime === null ? DEFAULT_AUTO_LOCK : autoLockTime; - record.biometry = biometry === null ? DEFAULT_BIOMETRY : biometry; }); }); }; @@ -145,7 +133,7 @@ class ScreenLockConfigView extends React.Component ({ biometry: !biometry }), - () => this.save() + async () => { + const { biometry } = this.state; + await userPreferences.setBoolAsync(BIOMETRY_ENABLED_KEY, biometry); + } ); };