From 20f322a98d6417bf8b275ae9c696a7bd3eade4ab Mon Sep 17 00:00:00 2001 From: Ammar Ahmed Date: Thu, 15 Feb 2024 16:07:33 +0500 Subject: [PATCH] mobile: improve verification flow before changing app lock --- .../app/components/dialog-provider/index.js | 2 +- .../dialogs/applock-password/index.tsx | 2 +- apps/mobile/app/screens/settings/functions.js | 59 ++++++- .../app/screens/settings/picker/index.tsx | 5 +- .../app/screens/settings/picker/pickers.jsx | 5 +- .../app/screens/settings/section-item.tsx | 19 ++- .../app/screens/settings/settings-data.tsx | 155 +++++------------- apps/mobile/app/screens/settings/types.ts | 1 + apps/mobile/app/services/backup.js | 2 +- 9 files changed, 127 insertions(+), 123 deletions(-) diff --git a/apps/mobile/app/components/dialog-provider/index.js b/apps/mobile/app/components/dialog-provider/index.js index a3dee1020..6ccb27594 100644 --- a/apps/mobile/app/components/dialog-provider/index.js +++ b/apps/mobile/app/components/dialog-provider/index.js @@ -43,6 +43,7 @@ const DialogProvider = () => { return ( <> + @@ -61,7 +62,6 @@ const DialogProvider = () => { - ); }; diff --git a/apps/mobile/app/components/dialogs/applock-password/index.tsx b/apps/mobile/app/components/dialogs/applock-password/index.tsx index 3237e9e37..9c22d5c90 100644 --- a/apps/mobile/app/components/dialogs/applock-password/index.tsx +++ b/apps/mobile/app/components/dialogs/applock-password/index.tsx @@ -91,7 +91,7 @@ export const AppLockPassword = () => { setVisible(false); }; - return ( + return !visible ? null : ( { await sleep(100); diff --git a/apps/mobile/app/screens/settings/functions.js b/apps/mobile/app/screens/settings/functions.js index e42c3cc90..36c59db0a 100644 --- a/apps/mobile/app/screens/settings/functions.js +++ b/apps/mobile/app/screens/settings/functions.js @@ -17,9 +17,13 @@ You should have received a copy of the GNU General Public License along with this program. If not, see . */ -import { presentDialog } from "../../components/dialog/functions"; -import { ToastManager } from "../../services/event-manager"; import { db } from "../../common/database"; +import { validateAppLockPassword } from "../../common/database/encryption"; +import { presentDialog } from "../../components/dialog/functions"; +import BiometicService from "../../services/biometrics"; +import { ToastManager } from "../../services/event-manager"; +import SettingsService from "../../services/settings"; +import { useUserStore } from "../../stores/use-user-store"; import { sleep } from "../../utils/time"; export async function verifyUser( @@ -69,3 +73,54 @@ export async function verifyUser( } }); } + +export async function verifyUserWithApplock() { + const keyboardType = SettingsService.getProperty("applockKeyboardType"); + return new Promise((resolve) => { + if (SettingsService.getProperty("appLockHasPasswordSecurity")) { + presentDialog({ + title: "Verify it's you", + input: true, + inputPlaceholder: `Enter app lock ${ + keyboardType === "numeric" ? "pin" : "password" + }`, + paragraph: `Please enter your app lock ${ + keyboardType === "numeric" ? "pin" : "password" + }`, + positiveText: "Disable", + secureTextEntry: true, + negativeText: "Cancel", + positivePress: async (value) => { + try { + const verified = await validateAppLockPassword(value); + resolve(verified); + } catch (e) { + resolve(false); + } + } + }); + } else { + BiometicService.isBiometryAvailable().then((available) => { + if (available) { + BiometicService.validateUser("Verify it's you").then((verified) => { + resolve(verified); + }); + } else if (useUserStore.getState().user) { + let verified = false; + verifyUser( + null, + () => { + resolve(true); + }, + false, + () => { + resolve(verified); + } + ); + } else { + resolve(true); + } + }); + } + }); +} diff --git a/apps/mobile/app/screens/settings/picker/index.tsx b/apps/mobile/app/screens/settings/picker/index.tsx index 4f83b387c..ac18da566 100644 --- a/apps/mobile/app/screens/settings/picker/index.tsx +++ b/apps/mobile/app/screens/settings/picker/index.tsx @@ -40,6 +40,7 @@ interface PickerOptions { premium?: boolean; onCheckOptionIsPremium?: (item: T) => boolean; requiresVerification?: () => boolean; + onVerify?: () => Promise; } export function SettingsPicker({ @@ -51,7 +52,8 @@ export function SettingsPicker({ getItemKey, premium, onCheckOptionIsPremium = () => true, - requiresVerification = () => false + requiresVerification = () => false, + onVerify }: PickerOptions) { const { colors } = useThemeColors("contextMenu"); const menuRef = useRef(); @@ -105,6 +107,7 @@ export function SettingsPicker({ anchor={ { + if (onVerify && !(await onVerify())) return; menuRef.current?.show(); }} type="grayBg" diff --git a/apps/mobile/app/screens/settings/picker/pickers.jsx b/apps/mobile/app/screens/settings/picker/pickers.jsx index 2e117dc0b..17b689580 100644 --- a/apps/mobile/app/screens/settings/picker/pickers.jsx +++ b/apps/mobile/app/screens/settings/picker/pickers.jsx @@ -27,6 +27,7 @@ import { getFontById, getFonts } from "@notesnook/editor/dist/utils/font"; import { DATE_FORMATS, TIME_FORMATS } from "@notesnook/core/dist/common"; import dayjs from "dayjs"; import { useUserStore } from "../../../stores/use-user-store"; +import { verifyUserWithApplock } from "../functions"; export const FontPicker = createSettingsPicker({ getValue: () => useSettingStore.getState().settings.defaultFontFamily, @@ -156,5 +157,7 @@ export const ApplockTimerPicker = createSettingsPicker({ getItemKey: (item) => item.toString(), options: [-1, 0, 1, 5, 15, 30], compareValue: (current, item) => current === item, - premium: true + onVerify: () => { + return verifyUserWithApplock(); + } }); diff --git a/apps/mobile/app/screens/settings/section-item.tsx b/apps/mobile/app/screens/settings/section-item.tsx index d8c900ae8..297ef2ada 100644 --- a/apps/mobile/app/screens/settings/section-item.tsx +++ b/apps/mobile/app/screens/settings/section-item.tsx @@ -104,17 +104,26 @@ const _SectionItem = ({ item }: { item: SettingSection }) => { borderRadius: 0, ...styles }} - onPress={() => { + onPress={async () => { switch (item.type) { case "screen": - navigation.dispatch(StackActions.push("SettingsGroup", item)); - useNavigationStore.getState().update("Settings"); + { + if (item.onVerify && !(await item.onVerify())) return; + navigation.dispatch(StackActions.push("SettingsGroup", item)); + useNavigationStore.getState().update("Settings"); + } break; case "switch": - onChangeSettings(); + { + if (item.onVerify && !(await item.onVerify())) return; + onChangeSettings(); + } break; default: - item.modifer && item.modifer(current); + { + if (item.onVerify && !(await item.onVerify())) return; + item.modifer && item.modifer(current); + } break; } }} diff --git a/apps/mobile/app/screens/settings/settings-data.tsx b/apps/mobile/app/screens/settings/settings-data.tsx index c518da972..116599a30 100644 --- a/apps/mobile/app/screens/settings/settings-data.tsx +++ b/apps/mobile/app/screens/settings/settings-data.tsx @@ -25,7 +25,6 @@ import { getVersion } from "react-native-device-info"; import * as RNIap from "react-native-iap"; import { enabled } from "react-native-privacy-snapshot"; import { db } from "../../common/database"; -import { validateAppLockPassword } from "../../common/database/encryption"; import { MMKV } from "../../common/database/mmkv"; import { AttachmentDialog } from "../../components/attachments"; import { ChangePassword } from "../../components/auth/change-password"; @@ -53,6 +52,7 @@ import PremiumService from "../../services/premium"; import SettingsService from "../../services/settings"; import Sync from "../../services/sync"; import { clearAllStores } from "../../stores"; +import { refreshAllStores } from "../../stores/create-db-collection-store"; import { useThemeStore } from "../../stores/use-theme-store"; import { useUserStore } from "../../stores/use-user-store"; import { SUBSCRIPTION_STATUS } from "../../utils/constants"; @@ -67,10 +67,9 @@ import { NotesnookModule } from "../../utils/notesnook-module"; import { sleep } from "../../utils/time"; import { MFARecoveryCodes, MFASheet } from "./2fa"; import { useDragState } from "./editor/state"; -import { verifyUser } from "./functions"; +import { verifyUser, verifyUserWithApplock } from "./functions"; import { SettingSection } from "./types"; import { getTimeLeft } from "./user-section"; -import { refreshAllStores } from "../../stores/create-db-collection-store"; type User = any; export const settingsGroups: SettingSection[] = [ @@ -790,94 +789,33 @@ export const settingsGroups: SettingSection[] = [ icon: "lock", type: "switch", property: "appLockEnabled", - onChange: async () => { + onVerify: async () => { + const verified = await verifyUserWithApplock(); + if (!verified) return false; + if (!SettingsService.getProperty("appLockEnabled")) { - const keyboardType = SettingsService.getProperty( - "applockKeyboardType" - ); - if (SettingsService.getProperty("appLockHasPasswordSecurity")) { - presentDialog({ - title: "Verify it's you", - input: true, - inputPlaceholder: `Enter app lock ${ - keyboardType === "numeric" ? "pin" : "password" - }`, - paragraph: `Please enter your app lock ${ - keyboardType === "numeric" ? "pin" : "password" - } to disable app lock`, - positiveText: "Disable", - secureTextEntry: true, - negativeText: "Cancel", - positivePress: async (value) => { - try { - const verified = await validateAppLockPassword(value); - if (!verified) { - SettingsService.setProperty("appLockEnabled", true); - return false; - } else { - SettingsService.setProperty("appLockEnabled", false); - } - } catch (e) { - SettingsService.setProperty("appLockEnabled", true); - return false; - } - } - }); - } else { - if (await BiometicService.isBiometryAvailable()) { - const verified = await BiometicService.validateUser( - "Verify it's you" - ); - if (!verified) { - SettingsService.setProperty("appLockEnabled", true); - return; - } else { - SettingsService.setProperty("appLockEnabled", false); - } - } else if (useUserStore.getState().user) { - let verified = false; - verifyUser( - null, - () => { - SettingsService.setProperty("appLockEnabled", false); - verified = true; - }, - false, - () => { - if (!verified) - SettingsService.setProperty("appLockEnabled", true); - } - ); - } - } - return; - } - - if ( - !(await BiometicService.isBiometryAvailable()) && - !SettingsService.getProperty("appLockHasPasswordSecurity") - ) { - ToastManager.show({ - heading: "Biometrics not enrolled", - type: "error", - message: - "To use app lock, you must enable biometrics such as Fingerprint lock or Face ID on your phone." - }); - SettingsService.setProperty("appLockEnabled", false); - return; - } - - if (!SettingsService.getProperty("appLockHasPasswordSecurity")) { - const verified = await BiometicService.validateUser( - "Verify it's you" - ); - if (verified) { + if ( + !SettingsService.getProperty("appLockHasPasswordSecurity") && + (await BiometicService.isBiometryAvailable()) + ) { SettingsService.setProperty("biometricsAuthEnabled", true); - } else { - SettingsService.setProperty("appLockEnabled", false); - return; + } + + if ( + !(await BiometicService.isBiometryAvailable()) && + !SettingsService.getProperty("appLockHasPasswordSecurity") + ) { + ToastManager.show({ + heading: "Biometrics not enrolled", + type: "error", + message: + "To use app lock, you must enable biometrics such as Fingerprint lock or Face ID on your phone." + }); + return false; } } + + return verified; } }, { @@ -907,8 +845,12 @@ export const settingsGroups: SettingSection[] = [ "appLockHasPasswordSecurity" ); }, + onVerify: () => { + return verifyUserWithApplock(); + }, property: "appLockHasPasswordSecurity", modifer: () => { + console.log("called modifier.."); AppLockPassword.present("create"); } }, @@ -962,32 +904,23 @@ export const settingsGroups: SettingSection[] = [ description: "Allow biometric authentication to unlock the app", type: "switch", property: "biometricsAuthEnabled", - onChange: async () => { - if (await BiometicService.isBiometryAvailable()) { - const verified = await BiometicService.validateUser( - "Verify it's you" - ); - if (!verified) { - SettingsService.setProperty("biometricsAuthEnabled", false); + onVerify: async () => { + const verified = await verifyUserWithApplock(); + if (!verified) return false; + + if (SettingsService.getProperty("biometricsAuthEnabled")) { + if ( + !SettingsService.getProperty("appLockHasPasswordSecurity") + ) { + SettingsService.setProperty("appLockEnabled", false); + ToastManager.show({ + heading: "App lock disabled", + type: "success" + }); } - } else { - ToastManager.error( - new Error( - "Biometric authentication is unavailable on this device." - ) - ); - SettingsService.setProperty("biometricsAuthEnabled", false); - } - if ( - !SettingsService.getProperty("biometricsAuthEnabled") && - !SettingsService.getProperty("appLockHasPasswordSecurity") - ) { - SettingsService.setProperty("appLockEnabled", false); - ToastManager.show({ - heading: "App lock disabled", - type: "success" - }); } + + return verified; }, icon: "fingerprint" } diff --git a/apps/mobile/app/screens/settings/types.ts b/apps/mobile/app/screens/settings/types.ts index 43718942a..4876e219c 100644 --- a/apps/mobile/app/screens/settings/types.ts +++ b/apps/mobile/app/screens/settings/types.ts @@ -45,6 +45,7 @@ export type SettingSection = { options?: any[]; minInputValue?: number; maxInputValue?: number; + onVerify?: () => Promise; }; export type SettingsGroup = { diff --git a/apps/mobile/app/services/backup.js b/apps/mobile/app/services/backup.js index 8e60716bf..77f5cdac9 100644 --- a/apps/mobile/app/services/backup.js +++ b/apps/mobile/app/services/backup.js @@ -154,7 +154,7 @@ async function updateNextBackupTime() { * @param {string} context * @returns {Promise<{path?: string, error?: Error}}> */ -async function run(progress, context) { +async function run(progress, context = "global") { let androidBackupDirectory = await checkBackupDirExists(false, context); if (!androidBackupDirectory) return {