[FEAT]: Request current passcode before enter "Screen lock" screen (#4052)

* move auth to handleLocalAuthentication function

* add support to close ScreenLockedView

* create useServer hook

* add check to verify if you have a password before entering the screen

* fix import
This commit is contained in:
Gleidson Daniel Silva 2022-05-03 17:29:00 -03:00 committed by GitHub
parent 2077671761
commit 305f360b40
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 108 additions and 35 deletions

View File

@ -0,0 +1,29 @@
import { useEffect, useState } from 'react';
import { useSelector } from 'react-redux';
import { IApplicationState, TServerModel } from '../../definitions';
import database from '../database';
export default function useServer() {
const [server, setServer] = useState<TServerModel | null>(null);
const shareServer = useSelector((state: IApplicationState) => state.share.server.server);
const appServer = useSelector((state: IApplicationState) => state.server.server);
useEffect(() => {
async function init() {
const serversDB = database.servers;
const serversCollection = serversDB.get('servers');
let serverInfo = null;
try {
serverInfo = await serversCollection.find(shareServer || appServer);
setServer(serverInfo);
} catch {
setServer(serverInfo);
}
}
init();
}, []);
return [server];
}

View File

@ -50,11 +50,13 @@ export const saveLastLocalAuthenticationSession = async (
export const resetAttempts = (): Promise<void> => AsyncStorage.multiRemove([LOCKED_OUT_TIMER_KEY, ATTEMPTS_KEY]); export const resetAttempts = (): Promise<void> => AsyncStorage.multiRemove([LOCKED_OUT_TIMER_KEY, ATTEMPTS_KEY]);
const openModal = (hasBiometry: boolean) => const openModal = (hasBiometry: boolean, force?: boolean) =>
new Promise<void>(resolve => { new Promise<void>((resolve, reject) => {
EventEmitter.emit(LOCAL_AUTHENTICATE_EMITTER, { EventEmitter.emit(LOCAL_AUTHENTICATE_EMITTER, {
submit: () => resolve(), submit: () => resolve(),
hasBiometry hasBiometry,
force,
cancel: () => reject()
}); });
}); });
@ -100,6 +102,20 @@ export const checkHasPasscode = async ({ force = true }: { force?: boolean }): P
return Promise.resolve(); return Promise.resolve();
}; };
export const handleLocalAuthentication = async (canCloseModal = false) => {
// let hasBiometry = false;
let hasBiometry = UserPreferences.getBool(BIOMETRY_ENABLED_KEY) ?? false;
// if biometry is enabled on the app
if (hasBiometry) {
const isEnrolled = await LocalAuthentication.isEnrolledAsync();
hasBiometry = isEnrolled;
}
// Authenticate
await openModal(hasBiometry, canCloseModal);
};
export const localAuthenticate = async (server: string): Promise<void> => { export const localAuthenticate = async (server: string): Promise<void> => {
const serversDB = database.servers; const serversDB = database.servers;
const serversCollection = serversDB.get('servers'); const serversCollection = serversDB.get('servers');
@ -136,17 +152,7 @@ export const localAuthenticate = async (server: string): Promise<void> => {
// set isLocalAuthenticated to false // set isLocalAuthenticated to false
store.dispatch(setLocalAuthenticated(false)); store.dispatch(setLocalAuthenticated(false));
// let hasBiometry = false; await handleLocalAuthentication();
let hasBiometry = UserPreferences.getBool(BIOMETRY_ENABLED_KEY) ?? false;
// if biometry is enabled on the app
if (hasBiometry) {
const isEnrolled = await LocalAuthentication.isEnrolledAsync();
hasBiometry = isEnrolled;
}
// Authenticate
await openModal(hasBiometry);
// set isLocalAuthenticated to true // set isLocalAuthenticated to true
store.dispatch(setLocalAuthenticated(true)); store.dispatch(setLocalAuthenticated(true));

View File

@ -1,22 +1,37 @@
import React, { useEffect, useState } from 'react';
import Modal from 'react-native-modal';
import useDeepCompareEffect from 'use-deep-compare-effect';
import isEmpty from 'lodash/isEmpty'; import isEmpty from 'lodash/isEmpty';
import React, { useEffect, useState } from 'react';
import { StyleSheet } from 'react-native';
import Modal from 'react-native-modal';
import Orientation from 'react-native-orientation-locker'; import Orientation from 'react-native-orientation-locker';
import Touchable from 'react-native-platform-touchable';
import useDeepCompareEffect from 'use-deep-compare-effect';
import EventEmitter from '../utils/events';
import { LOCAL_AUTHENTICATE_EMITTER } from '../lib/constants';
import { isTablet } from '../utils/deviceInfo';
import { PasscodeEnter } from '../containers/Passcode'; import { PasscodeEnter } from '../containers/Passcode';
import { LOCAL_AUTHENTICATE_EMITTER } from '../lib/constants';
import { CustomIcon } from '../containers/CustomIcon';
import { useTheme } from '../theme';
import { hasNotch, isTablet } from '../utils/deviceInfo';
import EventEmitter from '../utils/events';
interface IData { interface IData {
submit?: () => void; submit?: () => void;
cancel?: () => void;
hasBiometry?: boolean; hasBiometry?: boolean;
force?: boolean;
} }
const styles = StyleSheet.create({
close: {
position: 'absolute',
top: hasNotch ? 50 : 30,
left: 15
}
});
const ScreenLockedView = (): JSX.Element => { const ScreenLockedView = (): JSX.Element => {
const [visible, setVisible] = useState(false); const [visible, setVisible] = useState(false);
const [data, setData] = useState<IData>({}); const [data, setData] = useState<IData>({});
const { colors } = useTheme();
useDeepCompareEffect(() => { useDeepCompareEffect(() => {
if (!isEmpty(data)) { if (!isEmpty(data)) {
@ -51,6 +66,14 @@ const ScreenLockedView = (): JSX.Element => {
setData({}); setData({});
}; };
const onCancel = () => {
const { cancel } = data;
if (cancel) {
cancel();
}
setData({});
};
return ( return (
<Modal <Modal
useNativeDriver useNativeDriver
@ -60,6 +83,11 @@ const ScreenLockedView = (): JSX.Element => {
animationIn='fadeIn' animationIn='fadeIn'
animationOut='fadeOut'> animationOut='fadeOut'>
<PasscodeEnter hasBiometry={!!data?.hasBiometry} finishProcess={onSubmit} /> <PasscodeEnter hasBiometry={!!data?.hasBiometry} finishProcess={onSubmit} />
{data?.force ? (
<Touchable onPress={onCancel} style={styles.close}>
<CustomIcon name='close' color={colors.passcodePrimary} size={30} />
</Touchable>
) : null}
</Modal> </Modal>
); );
}; };

View File

@ -1,24 +1,26 @@
import AsyncStorage from '@react-native-community/async-storage';
import { StackNavigationProp } from '@react-navigation/stack';
import React, { useEffect, useState } from 'react'; import React, { useEffect, useState } from 'react';
import { Switch } from 'react-native'; import { Switch } from 'react-native';
import { StackNavigationProp } from '@react-navigation/stack';
import AsyncStorage from '@react-native-community/async-storage';
import { useSelector } from 'react-redux'; import { useSelector } from 'react-redux';
import StatusBar from '../containers/StatusBar';
import * as List from '../containers/List'; import * as List from '../containers/List';
import I18n from '../i18n';
import {
logEvent,
events,
toggleCrashErrorsReport,
toggleAnalyticsEventsReport,
getReportCrashErrorsValue,
getReportAnalyticsEventsValue
} from '../utils/log';
import SafeAreaView from '../containers/SafeAreaView'; import SafeAreaView from '../containers/SafeAreaView';
import { ANALYTICS_EVENTS_KEY, CRASH_REPORT_KEY, isFDroidBuild, SWITCH_TRACK_COLOR } from '../lib/constants'; import StatusBar from '../containers/StatusBar';
import { SettingsStackParamList } from '../stacks/types';
import { IApplicationState } from '../definitions'; import { IApplicationState } from '../definitions';
import I18n from '../i18n';
import { ANALYTICS_EVENTS_KEY, CRASH_REPORT_KEY, isFDroidBuild, SWITCH_TRACK_COLOR } from '../lib/constants';
import useServer from '../lib/methods/useServer';
import { SettingsStackParamList } from '../stacks/types';
import { handleLocalAuthentication } from '../utils/localAuthentication';
import {
events,
getReportAnalyticsEventsValue,
getReportCrashErrorsValue,
logEvent,
toggleAnalyticsEventsReport,
toggleCrashErrorsReport
} from '../utils/log';
interface ISecurityPrivacyViewProps { interface ISecurityPrivacyViewProps {
navigation: StackNavigationProp<SettingsStackParamList, 'SecurityPrivacyView'>; navigation: StackNavigationProp<SettingsStackParamList, 'SecurityPrivacyView'>;
@ -27,6 +29,7 @@ interface ISecurityPrivacyViewProps {
const SecurityPrivacyView = ({ navigation }: ISecurityPrivacyViewProps): JSX.Element => { const SecurityPrivacyView = ({ navigation }: ISecurityPrivacyViewProps): JSX.Element => {
const [crashReportState, setCrashReportState] = useState(getReportCrashErrorsValue()); const [crashReportState, setCrashReportState] = useState(getReportCrashErrorsValue());
const [analyticsEventsState, setAnalyticsEventsState] = useState(getReportAnalyticsEventsValue()); const [analyticsEventsState, setAnalyticsEventsState] = useState(getReportAnalyticsEventsValue());
const [server] = useServer();
const e2eEnabled = useSelector((state: IApplicationState) => state.settings.E2E_Enable); const e2eEnabled = useSelector((state: IApplicationState) => state.settings.E2E_Enable);
@ -56,6 +59,13 @@ const SecurityPrivacyView = ({ navigation }: ISecurityPrivacyViewProps): JSX.Ele
navigation.navigate(screen); navigation.navigate(screen);
}; };
const navigateToScreenLockConfigView = async () => {
if (server?.autoLock) {
await handleLocalAuthentication(true);
}
navigateToScreen('ScreenLockConfigView');
};
return ( return (
<SafeAreaView testID='security-privacy-view'> <SafeAreaView testID='security-privacy-view'>
<StatusBar /> <StatusBar />
@ -76,7 +86,7 @@ const SecurityPrivacyView = ({ navigation }: ISecurityPrivacyViewProps): JSX.Ele
<List.Item <List.Item
title='Screen_lock' title='Screen_lock'
showActionIndicator showActionIndicator
onPress={() => navigateToScreen('ScreenLockConfigView')} onPress={navigateToScreenLockConfigView}
testID='security-privacy-view-screen-lock' testID='security-privacy-view-screen-lock'
/> />
<List.Separator /> <List.Separator />