feat: soft biometrics lock (#1793)

This commit is contained in:
Mo
2022-10-13 15:48:36 -05:00
committed by GitHub
parent 44a556adf6
commit 1a2dde2e0e
9 changed files with 75 additions and 15 deletions

View File

@@ -30,6 +30,7 @@ export interface ApplicationInterface {
createDecryptedBackupFile(): Promise<BackupFile | undefined> createDecryptedBackupFile(): Promise<BackupFile | undefined>
hasPasscode(): boolean hasPasscode(): boolean
lock(): Promise<void> lock(): Promise<void>
softLockBiometrics(): void
setValue(key: string, value: unknown, mode?: StorageValueModes): void setValue(key: string, value: unknown, mode?: StorageValueModes): void
getValue(key: string, mode?: StorageValueModes): unknown getValue(key: string, mode?: StorageValueModes): unknown
removeValue(key: string, mode?: StorageValueModes): Promise<void> removeValue(key: string, mode?: StorageValueModes): Promise<void>

View File

@@ -62,4 +62,6 @@ export enum ApplicationEvent {
UnprotectedSessionExpired = 29, UnprotectedSessionExpired = 29,
/** Called when the app first launches and after first sync request made after sign in */ /** Called when the app first launches and after first sync request made after sign in */
CompletedInitialSync = 30, CompletedInitialSync = 30,
BiometricsSoftLockEngaged = 31,
BiometricsSoftLockDisengaged = 32,
} }

View File

@@ -51,6 +51,7 @@ import {
SubscriptionManager, SubscriptionManager,
WorkspaceClientInterface, WorkspaceClientInterface,
WorkspaceManager, WorkspaceManager,
ChallengePrompt,
} from '@standardnotes/services' } from '@standardnotes/services'
import { FilesClientInterface } from '@standardnotes/files' import { FilesClientInterface } from '@standardnotes/files'
import { ComputePrivateUsername } from '@standardnotes/encryption' import { ComputePrivateUsername } from '@standardnotes/encryption'
@@ -929,6 +930,24 @@ export class SNApplication
return this.deinit(this.getDeinitMode(), DeinitSource.Lock) 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() { isNativeMobileWeb() {
return this.environment === Environment.NativeMobileWeb return this.environment === Environment.NativeMobileWeb
} }

View File

@@ -33,7 +33,7 @@ import {
ThemeManager, ThemeManager,
WebAlertService, WebAlertService,
} from '@standardnotes/ui-services' } from '@standardnotes/ui-services'
import { MobileWebReceiver } from './MobileWebReceiver' import { MobileWebReceiver } from '../NativeMobileWeb/MobileWebReceiver'
import { AndroidBackHandler } from '@/NativeMobileWeb/AndroidBackHandler' import { AndroidBackHandler } from '@/NativeMobileWeb/AndroidBackHandler'
import { PrefDefaults } from '@/Constants/PrefDefaults' import { PrefDefaults } from '@/Constants/PrefDefaults'
import { setViewportHeightWithFallback } from '@/setViewportHeightWithFallback' import { setViewportHeightWithFallback } from '@/setViewportHeightWithFallback'
@@ -289,8 +289,10 @@ export class WebApplication extends SNApplication implements WebApplicationInter
const passcodeLockImmediately = hasPasscode && passcodeTiming === MobileUnlockTiming.Immediately const passcodeLockImmediately = hasPasscode && passcodeTiming === MobileUnlockTiming.Immediately
const biometricsLockImmediately = hasBiometrics && biometricsTiming === MobileUnlockTiming.Immediately const biometricsLockImmediately = hasBiometrics && biometricsTiming === MobileUnlockTiming.Immediately
if (passcodeLockImmediately || biometricsLockImmediately) { if (passcodeLockImmediately) {
await this.lock() await this.lock()
} else if (biometricsLockImmediately) {
this.softLockBiometrics()
} }
} }

View File

@@ -121,6 +121,10 @@ const ApplicationView: FunctionComponent<Props> = ({ application, mainApplicatio
alertDialog({ alertDialog({
text: 'Unable to write to local database. Please restart the app and try again.', text: 'Unable to write to local database. Please restart the app and try again.',
}).catch(console.error) }).catch(console.error)
} else if (eventName === ApplicationEvent.BiometricsSoftLockEngaged) {
setNeedsUnlock(true)
} else if (eventName === ApplicationEvent.BiometricsSoftLockDisengaged) {
setNeedsUnlock(false)
} }
}) })
@@ -197,7 +201,7 @@ const ApplicationView: FunctionComponent<Props> = ({ application, mainApplicatio
return ( return (
<AndroidBackHandlerProvider application={application}> <AndroidBackHandlerProvider application={application}>
<DarkModeHandler application={application} /> <DarkModeHandler application={application} />
<ResponsivePaneProvider> <ResponsivePaneProvider paneController={application.getViewControllerManager().paneController}>
<PremiumModalProvider application={application} viewControllerManager={viewControllerManager}> <PremiumModalProvider application={application} viewControllerManager={viewControllerManager}>
<div className={platformString + ' main-ui-view sn-component'}> <div className={platformString + ' main-ui-view sn-component'}>
<div id="app" className="app app-column-container" ref={appColumnContainerRef}> <div id="app" className="app app-column-container" ref={appColumnContainerRef}>

View File

@@ -1,6 +1,5 @@
import { ElementIds } from '@/Constants/ElementIDs' import { ElementIds } from '@/Constants/ElementIDs'
import { useAndroidBackHandler } from '@/NativeMobileWeb/useAndroidBackHandler' import { useAndroidBackHandler } from '@/NativeMobileWeb/useAndroidBackHandler'
import { isMobileScreen } from '@/Utils'
import { import {
useEffect, useEffect,
ReactNode, ReactNode,
@@ -15,6 +14,8 @@ import {
MutableRefObject, MutableRefObject,
} from 'react' } from 'react'
import { AppPaneId } from './AppPaneMetadata' import { AppPaneId } from './AppPaneMetadata'
import { PaneController } from '../../Controllers/PaneController'
import { observer } from 'mobx-react-lite'
type ResponsivePaneData = { type ResponsivePaneData = {
selectedPane: AppPaneId selectedPane: AppPaneId
@@ -39,6 +40,10 @@ type ChildrenProps = {
children: ReactNode children: ReactNode
} }
type ProviderProps = {
paneController: PaneController
} & ChildrenProps
function useStateRef<State>(state: State): MutableRefObject<State> { function useStateRef<State>(state: State): MutableRefObject<State> {
const ref = useRef<State>(state) const ref = useRef<State>(state)
@@ -51,21 +56,17 @@ function useStateRef<State>(state: State): MutableRefObject<State> {
const MemoizedChildren = memo(({ children }: ChildrenProps) => <div>{children}</div>) const MemoizedChildren = memo(({ children }: ChildrenProps) => <div>{children}</div>)
const ResponsivePaneProvider = ({ children }: ChildrenProps) => { const ResponsivePaneProvider = ({ paneController, children }: ProviderProps) => {
const [currentSelectedPane, setCurrentSelectedPane] = useState<AppPaneId>( const currentSelectedPane = paneController.currentPane
isMobileScreen() ? AppPaneId.Items : AppPaneId.Editor, const previousSelectedPane = paneController.previousPane
)
const currentSelectedPaneRef = useStateRef<AppPaneId>(currentSelectedPane) const currentSelectedPaneRef = useStateRef<AppPaneId>(currentSelectedPane)
const [previousSelectedPane, setPreviousSelectedPane] = useState<AppPaneId>(
isMobileScreen() ? AppPaneId.Items : AppPaneId.Editor,
)
const toggleAppPane = useCallback( const toggleAppPane = useCallback(
(paneId: AppPaneId) => { (paneId: AppPaneId) => {
setPreviousSelectedPane(currentSelectedPane) paneController.setPreviousPane(currentSelectedPane)
setCurrentSelectedPane(paneId) paneController.setCurrentPane(paneId)
}, },
[currentSelectedPane], [paneController, currentSelectedPane],
) )
useEffect(() => { useEffect(() => {
@@ -122,4 +123,4 @@ const ResponsivePaneProvider = ({ children }: ChildrenProps) => {
) )
} }
export default ResponsivePaneProvider export default observer(ResponsivePaneProvider)

View File

@@ -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
}
}

View File

@@ -1,3 +1,4 @@
import { PaneController } from './PaneController'
import { RouteType, storage, StorageKey } from '@standardnotes/ui-services' import { RouteType, storage, StorageKey } from '@standardnotes/ui-services'
import { WebApplication } from '@/Application/Application' import { WebApplication } from '@/Application/Application'
import { AccountMenuController } from '@/Controllers/AccountMenu/AccountMenuController' import { AccountMenuController } from '@/Controllers/AccountMenu/AccountMenuController'
@@ -56,6 +57,7 @@ export class ViewControllerManager {
readonly selectionController: SelectedItemsController readonly selectionController: SelectedItemsController
readonly historyModalController: HistoryModalController readonly historyModalController: HistoryModalController
readonly linkingController: LinkingController readonly linkingController: LinkingController
readonly paneController: PaneController
public isSessionsModalVisible = false public isSessionsModalVisible = false
@@ -71,6 +73,8 @@ export class ViewControllerManager {
this.subscriptionManager = application.subscriptions this.subscriptionManager = application.subscriptions
this.paneController = new PaneController()
this.preferencesController = new PreferencesController(application, this.eventBus) this.preferencesController = new PreferencesController(application, this.eventBus)
this.selectionController = new SelectedItemsController(application, this.eventBus) this.selectionController = new SelectedItemsController(application, this.eventBus)
@@ -207,6 +211,7 @@ export class ViewControllerManager {
this.historyModalController.deinit() this.historyModalController.deinit()
;(this.historyModalController as unknown) = undefined ;(this.historyModalController as unknown) = undefined
;(this.paneController as unknown) = undefined
destroyAllObjectProperties(this) destroyAllObjectProperties(this)
} }