From 1a2dde2e0ea406b9818571ab71d38442f9c9fb50 Mon Sep 17 00:00:00 2001 From: Mo Date: Thu, 13 Oct 2022 15:48:36 -0500 Subject: [PATCH] feat: soft biometrics lock (#1793) --- .../Application/ApplicationInterface.ts | 1 + .../src/Domain/Event/ApplicationEvent.ts | 2 ++ packages/snjs/lib/Application/Application.ts | 19 ++++++++++++++ .../javascripts/Application/Application.ts | 6 +++-- .../ApplicationView/ApplicationView.tsx | 6 ++++- .../ResponsivePane/ResponsivePaneProvider.tsx | 25 +++++++++--------- .../javascripts/Controllers/PaneController.ts | 26 +++++++++++++++++++ .../Controllers/ViewControllerManager.ts | 5 ++++ .../MobileWebReceiver.ts | 0 9 files changed, 75 insertions(+), 15 deletions(-) create mode 100644 packages/web/src/javascripts/Controllers/PaneController.ts rename packages/web/src/javascripts/{Application => NativeMobileWeb}/MobileWebReceiver.ts (100%) diff --git a/packages/services/src/Domain/Application/ApplicationInterface.ts b/packages/services/src/Domain/Application/ApplicationInterface.ts index 4dd3d6e48..12e453aad 100644 --- a/packages/services/src/Domain/Application/ApplicationInterface.ts +++ b/packages/services/src/Domain/Application/ApplicationInterface.ts @@ -30,6 +30,7 @@ export interface ApplicationInterface { createDecryptedBackupFile(): Promise hasPasscode(): boolean lock(): Promise + softLockBiometrics(): void setValue(key: string, value: unknown, mode?: StorageValueModes): void getValue(key: string, mode?: StorageValueModes): unknown removeValue(key: string, mode?: StorageValueModes): Promise diff --git a/packages/services/src/Domain/Event/ApplicationEvent.ts b/packages/services/src/Domain/Event/ApplicationEvent.ts index 0c814fe21..250b76806 100644 --- a/packages/services/src/Domain/Event/ApplicationEvent.ts +++ b/packages/services/src/Domain/Event/ApplicationEvent.ts @@ -62,4 +62,6 @@ export enum ApplicationEvent { UnprotectedSessionExpired = 29, /** Called when the app first launches and after first sync request made after sign in */ CompletedInitialSync = 30, + BiometricsSoftLockEngaged = 31, + BiometricsSoftLockDisengaged = 32, } diff --git a/packages/snjs/lib/Application/Application.ts b/packages/snjs/lib/Application/Application.ts index 42f1ad07f..e530f788e 100644 --- a/packages/snjs/lib/Application/Application.ts +++ b/packages/snjs/lib/Application/Application.ts @@ -51,6 +51,7 @@ import { SubscriptionManager, WorkspaceClientInterface, WorkspaceManager, + ChallengePrompt, } from '@standardnotes/services' import { FilesClientInterface } from '@standardnotes/files' import { ComputePrivateUsername } from '@standardnotes/encryption' @@ -929,6 +930,24 @@ export class SNApplication return this.deinit(this.getDeinitMode(), DeinitSource.Lock) } + public softLockBiometrics(): void { + const challenge = new Challenge( + [new ChallengePrompt(ChallengeValidation.Biometric)], + ChallengeReason.ApplicationUnlock, + false, + ) + + void this.promptForCustomChallenge(challenge) + + void this.notifyEvent(ApplicationEvent.BiometricsSoftLockEngaged) + + this.addChallengeObserver(challenge, { + onComplete: () => { + void this.notifyEvent(ApplicationEvent.BiometricsSoftLockDisengaged) + }, + }) + } + isNativeMobileWeb() { return this.environment === Environment.NativeMobileWeb } diff --git a/packages/web/src/javascripts/Application/Application.ts b/packages/web/src/javascripts/Application/Application.ts index ea2111acb..37f011528 100644 --- a/packages/web/src/javascripts/Application/Application.ts +++ b/packages/web/src/javascripts/Application/Application.ts @@ -33,7 +33,7 @@ import { ThemeManager, WebAlertService, } from '@standardnotes/ui-services' -import { MobileWebReceiver } from './MobileWebReceiver' +import { MobileWebReceiver } from '../NativeMobileWeb/MobileWebReceiver' import { AndroidBackHandler } from '@/NativeMobileWeb/AndroidBackHandler' import { PrefDefaults } from '@/Constants/PrefDefaults' import { setViewportHeightWithFallback } from '@/setViewportHeightWithFallback' @@ -289,8 +289,10 @@ export class WebApplication extends SNApplication implements WebApplicationInter const passcodeLockImmediately = hasPasscode && passcodeTiming === MobileUnlockTiming.Immediately const biometricsLockImmediately = hasBiometrics && biometricsTiming === MobileUnlockTiming.Immediately - if (passcodeLockImmediately || biometricsLockImmediately) { + if (passcodeLockImmediately) { await this.lock() + } else if (biometricsLockImmediately) { + this.softLockBiometrics() } } diff --git a/packages/web/src/javascripts/Components/ApplicationView/ApplicationView.tsx b/packages/web/src/javascripts/Components/ApplicationView/ApplicationView.tsx index 597f5c57a..d858217ce 100644 --- a/packages/web/src/javascripts/Components/ApplicationView/ApplicationView.tsx +++ b/packages/web/src/javascripts/Components/ApplicationView/ApplicationView.tsx @@ -121,6 +121,10 @@ const ApplicationView: FunctionComponent = ({ application, mainApplicatio alertDialog({ text: 'Unable to write to local database. Please restart the app and try again.', }).catch(console.error) + } else if (eventName === ApplicationEvent.BiometricsSoftLockEngaged) { + setNeedsUnlock(true) + } else if (eventName === ApplicationEvent.BiometricsSoftLockDisengaged) { + setNeedsUnlock(false) } }) @@ -197,7 +201,7 @@ const ApplicationView: FunctionComponent = ({ application, mainApplicatio return ( - +
diff --git a/packages/web/src/javascripts/Components/ResponsivePane/ResponsivePaneProvider.tsx b/packages/web/src/javascripts/Components/ResponsivePane/ResponsivePaneProvider.tsx index 1cd38ab9a..6e0de054a 100644 --- a/packages/web/src/javascripts/Components/ResponsivePane/ResponsivePaneProvider.tsx +++ b/packages/web/src/javascripts/Components/ResponsivePane/ResponsivePaneProvider.tsx @@ -1,6 +1,5 @@ import { ElementIds } from '@/Constants/ElementIDs' import { useAndroidBackHandler } from '@/NativeMobileWeb/useAndroidBackHandler' -import { isMobileScreen } from '@/Utils' import { useEffect, ReactNode, @@ -15,6 +14,8 @@ import { MutableRefObject, } from 'react' import { AppPaneId } from './AppPaneMetadata' +import { PaneController } from '../../Controllers/PaneController' +import { observer } from 'mobx-react-lite' type ResponsivePaneData = { selectedPane: AppPaneId @@ -39,6 +40,10 @@ type ChildrenProps = { children: ReactNode } +type ProviderProps = { + paneController: PaneController +} & ChildrenProps + function useStateRef(state: State): MutableRefObject { const ref = useRef(state) @@ -51,21 +56,17 @@ function useStateRef(state: State): MutableRefObject { const MemoizedChildren = memo(({ children }: ChildrenProps) =>
{children}
) -const ResponsivePaneProvider = ({ children }: ChildrenProps) => { - const [currentSelectedPane, setCurrentSelectedPane] = useState( - isMobileScreen() ? AppPaneId.Items : AppPaneId.Editor, - ) +const ResponsivePaneProvider = ({ paneController, children }: ProviderProps) => { + const currentSelectedPane = paneController.currentPane + const previousSelectedPane = paneController.previousPane const currentSelectedPaneRef = useStateRef(currentSelectedPane) - const [previousSelectedPane, setPreviousSelectedPane] = useState( - isMobileScreen() ? AppPaneId.Items : AppPaneId.Editor, - ) const toggleAppPane = useCallback( (paneId: AppPaneId) => { - setPreviousSelectedPane(currentSelectedPane) - setCurrentSelectedPane(paneId) + paneController.setPreviousPane(currentSelectedPane) + paneController.setCurrentPane(paneId) }, - [currentSelectedPane], + [paneController, currentSelectedPane], ) useEffect(() => { @@ -122,4 +123,4 @@ const ResponsivePaneProvider = ({ children }: ChildrenProps) => { ) } -export default ResponsivePaneProvider +export default observer(ResponsivePaneProvider) diff --git a/packages/web/src/javascripts/Controllers/PaneController.ts b/packages/web/src/javascripts/Controllers/PaneController.ts new file mode 100644 index 000000000..fc96f3f54 --- /dev/null +++ b/packages/web/src/javascripts/Controllers/PaneController.ts @@ -0,0 +1,26 @@ +import { AppPaneId } from './../Components/ResponsivePane/AppPaneMetadata' +import { isMobileScreen } from '@/Utils' +import { makeObservable, observable, action } from 'mobx' + +export class PaneController { + currentPane: AppPaneId = isMobileScreen() ? AppPaneId.Items : AppPaneId.Editor + previousPane: AppPaneId = isMobileScreen() ? AppPaneId.Items : AppPaneId.Editor + + constructor() { + makeObservable(this, { + currentPane: observable, + previousPane: observable, + + setCurrentPane: action, + setPreviousPane: action, + }) + } + + setCurrentPane(pane: AppPaneId): void { + this.currentPane = pane + } + + setPreviousPane(pane: AppPaneId): void { + this.previousPane = pane + } +} diff --git a/packages/web/src/javascripts/Controllers/ViewControllerManager.ts b/packages/web/src/javascripts/Controllers/ViewControllerManager.ts index e27f3c411..6695a77e3 100644 --- a/packages/web/src/javascripts/Controllers/ViewControllerManager.ts +++ b/packages/web/src/javascripts/Controllers/ViewControllerManager.ts @@ -1,3 +1,4 @@ +import { PaneController } from './PaneController' import { RouteType, storage, StorageKey } from '@standardnotes/ui-services' import { WebApplication } from '@/Application/Application' import { AccountMenuController } from '@/Controllers/AccountMenu/AccountMenuController' @@ -56,6 +57,7 @@ export class ViewControllerManager { readonly selectionController: SelectedItemsController readonly historyModalController: HistoryModalController readonly linkingController: LinkingController + readonly paneController: PaneController public isSessionsModalVisible = false @@ -71,6 +73,8 @@ export class ViewControllerManager { this.subscriptionManager = application.subscriptions + this.paneController = new PaneController() + this.preferencesController = new PreferencesController(application, this.eventBus) this.selectionController = new SelectedItemsController(application, this.eventBus) @@ -207,6 +211,7 @@ export class ViewControllerManager { this.historyModalController.deinit() ;(this.historyModalController as unknown) = undefined + ;(this.paneController as unknown) = undefined destroyAllObjectProperties(this) } diff --git a/packages/web/src/javascripts/Application/MobileWebReceiver.ts b/packages/web/src/javascripts/NativeMobileWeb/MobileWebReceiver.ts similarity index 100% rename from packages/web/src/javascripts/Application/MobileWebReceiver.ts rename to packages/web/src/javascripts/NativeMobileWeb/MobileWebReceiver.ts