Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[IMPROVE] Keep biometry option from last session #3668

Merged
merged 12 commits into from
Feb 8, 2022
1 change: 1 addition & 0 deletions app/constants/localAuthentication.ts
Original file line number Diff line number Diff line change
@@ -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';
Expand Down
2 changes: 1 addition & 1 deletion app/lib/database/schema/servers.js
Original file line number Diff line number Diff line change
Expand Up @@ -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 }
Expand Down
15 changes: 15 additions & 0 deletions app/sagas/init.js
Original file line number Diff line number Diff line change
@@ -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';
Expand All @@ -16,11 +17,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
reinaldonetof marked this conversation as resolved.
Show resolved Hide resolved
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: ROOT_OUTSIDE }));
} else if (!userId) {
Expand Down
34 changes: 11 additions & 23 deletions app/utils/localAuthentication.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -72,32 +73,18 @@ export const biometryAuth = (force?: boolean): Promise<LocalAuthentication.Local
* It'll help us to get the permission to use FaceID
* and enable/disable the biometry when user put their first passcode
*/
const checkBiometry = async (serverRecord: TServerModel) => {
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();
Expand All @@ -124,7 +111,7 @@ export const localAuthenticate = async (server: string): Promise<void> => {
}

// 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) {
Expand All @@ -136,10 +123,11 @@ export const localAuthenticate = async (server: string): Promise<void> => {
// 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;
}
Expand Down
31 changes: 11 additions & 20 deletions app/views/ScreenLockConfigView.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand All @@ -34,7 +35,7 @@ interface IScreenLockConfigViewProps {
interface IScreenLockConfigViewState {
autoLock: boolean;
autoLockTime?: number | null;
biometry?: boolean;
biometry: boolean;
biometryLabel: string | null;
}

Expand Down Expand Up @@ -91,43 +92,30 @@ class ScreenLockConfigView extends React.Component<IScreenLockConfigViewProps, I
const { server } = this.props;
const serversDB = database.servers;
const serversCollection = serversDB.get('servers');
const hasBiometry = (await userPreferences.getBoolAsync(BIOMETRY_ENABLED_KEY)) ?? DEFAULT_BIOMETRY;
try {
this.serverRecord = await serversCollection.find(server);
this.setState({
autoLock: this.serverRecord?.autoLock,
autoLockTime: this.serverRecord?.autoLockTime === null ? DEFAULT_AUTO_LOCK : this.serverRecord?.autoLockTime,
biometry: this.serverRecord.biometry === null ? DEFAULT_BIOMETRY : this.serverRecord.biometry
biometry: hasBiometry
});
} catch (error) {
// Do nothing
}

const biometryLabel = await supportedBiometryLabel();
this.setState({ biometryLabel });

this.observe();
};

/*
* We should observe biometry value
* because it can be changed by PasscodeChange
* when the user set his first passcode
*/
observe = () => {
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;
});
});
};
Expand All @@ -145,7 +133,7 @@ class ScreenLockConfigView extends React.Component<IScreenLockConfigViewProps, I
const { autoLock } = this.state;
if (autoLock) {
try {
await checkHasPasscode({ force: false, serverRecord: this.serverRecord! });
await checkHasPasscode({ force: false });
} catch {
this.toggleAutoLock();
}
Expand All @@ -159,7 +147,10 @@ class ScreenLockConfigView extends React.Component<IScreenLockConfigViewProps, I
logEvent(events.SLC_TOGGLE_BIOMETRY);
this.setState(
({ biometry }) => ({ biometry: !biometry }),
() => this.save()
async () => {
const { biometry } = this.state;
await userPreferences.setBoolAsync(BIOMETRY_ENABLED_KEY, biometry);
}
);
};

Expand Down