fix(mobile): passcode timing options (#1744)

This commit is contained in:
Mo
2022-10-05 10:08:54 -05:00
committed by GitHub
parent a4de9a05a9
commit 6c26b96cdc
14 changed files with 151 additions and 130 deletions

View File

@@ -483,7 +483,7 @@ export class ApplicationState extends ApplicationService {
return return
} }
const hasBiometrics = this.application.hasBiometrics() const hasBiometrics = this.application.protections.hasBiometricsEnabled()
const hasPasscode = this.application.hasPasscode() const hasPasscode = this.application.hasPasscode()
const passcodeLockImmediately = hasPasscode && this.passcodeTiming === MobileUnlockTiming.Immediately const passcodeLockImmediately = hasPasscode && this.passcodeTiming === MobileUnlockTiming.Immediately
const biometricsLockImmediately = const biometricsLockImmediately =
@@ -571,19 +571,19 @@ export class ApplicationState extends ApplicationService {
} }
private async getScreenshotPrivacyEnabled(): Promise<boolean> { private async getScreenshotPrivacyEnabled(): Promise<boolean> {
return this.application.getMobileScreenshotPrivacyEnabled() return this.application.protections.getMobileScreenshotPrivacyEnabled()
} }
private async getPasscodeTiming(): Promise<MobileUnlockTiming | undefined> { private async getPasscodeTiming(): Promise<MobileUnlockTiming | undefined> {
return this.application.getMobilePasscodeTiming() return this.application.protections.getMobilePasscodeTiming()
} }
private async getBiometricsTiming(): Promise<MobileUnlockTiming | undefined> { private async getBiometricsTiming(): Promise<MobileUnlockTiming | undefined> {
return this.application.getMobileBiometricsTiming() return this.application.protections.getMobileBiometricsTiming()
} }
public async setScreenshotPrivacyEnabled(enabled: boolean) { public async setScreenshotPrivacyEnabled(enabled: boolean) {
await this.application.setMobileScreenshotPrivacyEnabled(enabled) await this.application.protections.setMobileScreenshotPrivacyEnabled(enabled)
this.screenshotPrivacyEnabled = enabled this.screenshotPrivacyEnabled = enabled
await (this.application.deviceInterface as MobileDevice).setAndroidScreenshotPrivacy(enabled) await (this.application.deviceInterface as MobileDevice).setAndroidScreenshotPrivacy(enabled)
} }

View File

@@ -197,7 +197,7 @@ export const Authenticate = ({
state: AuthenticationValueStateType.Pending, state: AuthenticationValueStateType.Pending,
}) })
if (await application?.getMobileScreenshotPrivacyEnabled()) { if (application?.protections.getMobileScreenshotPrivacyEnabled()) {
hide() hide()
} }

View File

@@ -31,21 +31,23 @@ export const SecuritySection = (props: Props) => {
const [hasBiometrics, setHasBiometrics] = useState(false) const [hasBiometrics, setHasBiometrics] = useState(false)
const [supportsBiometrics, setSupportsBiometrics] = useState(false) const [supportsBiometrics, setSupportsBiometrics] = useState(false)
const [biometricsTimingOptions, setBiometricsTimingOptions] = useState(() => const [biometricsTimingOptions, setBiometricsTimingOptions] = useState(() =>
application!.getBiometricsTimingOptions(), application!.protections.getMobileBiometricsTimingOptions(),
)
const [passcodeTimingOptions, setPasscodeTimingOptions] = useState(() =>
application!.protections.getMobilePasscodeTimingOptions(),
) )
const [passcodeTimingOptions, setPasscodeTimingOptions] = useState(() => application!.getPasscodeTimingOptions())
useEffect(() => { useEffect(() => {
let mounted = true let mounted = true
const getHasScreenshotPrivacy = async () => { const getHasScreenshotPrivacy = async () => {
const hasScreenshotPrivacyEnabled = application?.getMobileScreenshotPrivacyEnabled() const hasScreenshotPrivacyEnabled = application?.protections.getMobileScreenshotPrivacyEnabled()
if (mounted) { if (mounted) {
setHasScreenshotPrivacy(hasScreenshotPrivacyEnabled) setHasScreenshotPrivacy(hasScreenshotPrivacyEnabled)
} }
} }
void getHasScreenshotPrivacy() void getHasScreenshotPrivacy()
const getHasBiometrics = async () => { const getHasBiometrics = async () => {
const appHasBiometrics = application!.hasBiometrics() const appHasBiometrics = application!.protections.hasBiometricsEnabled()
if (mounted) { if (mounted) {
setHasBiometrics(appHasBiometrics) setHasBiometrics(appHasBiometrics)
} }
@@ -68,7 +70,7 @@ export const SecuritySection = (props: Props) => {
useFocusEffect( useFocusEffect(
useCallback(() => { useCallback(() => {
if (props.hasPasscode) { if (props.hasPasscode) {
setPasscodeTimingOptions(() => application!.getPasscodeTimingOptions()) setPasscodeTimingOptions(() => application!.protections.getMobilePasscodeTimingOptions())
} }
}, [application, props.hasPasscode]), }, [application, props.hasPasscode]),
) )
@@ -126,12 +128,12 @@ export const SecuritySection = (props: Props) => {
const setBiometricsTiming = async (timing: MobileUnlockTiming) => { const setBiometricsTiming = async (timing: MobileUnlockTiming) => {
await application?.getAppState().setBiometricsTiming(timing) await application?.getAppState().setBiometricsTiming(timing)
setBiometricsTimingOptions(() => application!.getBiometricsTimingOptions()) setBiometricsTimingOptions(() => application!.protections.getMobileBiometricsTimingOptions())
} }
const setPasscodeTiming = async (timing: MobileUnlockTiming) => { const setPasscodeTiming = async (timing: MobileUnlockTiming) => {
await application?.getAppState().setPasscodeTiming(timing) await application?.getAppState().setPasscodeTiming(timing)
setPasscodeTimingOptions(() => application!.getPasscodeTimingOptions()) setPasscodeTimingOptions(() => application!.protections.getMobilePasscodeTimingOptions())
} }
const onScreenshotPrivacyPress = async () => { const onScreenshotPrivacyPress = async () => {
@@ -153,14 +155,14 @@ export const SecuritySection = (props: Props) => {
void disableAuthentication('biometrics') void disableAuthentication('biometrics')
} else { } else {
setHasBiometrics(true) setHasBiometrics(true)
await application?.enableBiometrics() await application?.protections.enableBiometrics()
await setBiometricsTiming(MobileUnlockTiming.OnQuit) await setBiometricsTiming(MobileUnlockTiming.OnQuit)
props.updateProtectionsAvailable() props.updateProtectionsAvailable()
} }
} }
const disableBiometrics = useCallback(async () => { const disableBiometrics = useCallback(async () => {
if (await application?.disableBiometrics()) { if (await application?.protections.disableBiometrics()) {
setHasBiometrics(false) setHasBiometrics(false)
props.updateProtectionsAvailable() props.updateProtectionsAvailable()
} }

View File

@@ -45,7 +45,6 @@ import {
FileService, FileService,
SubscriptionClientInterface, SubscriptionClientInterface,
SubscriptionManager, SubscriptionManager,
StorageValueModes,
} from '@standardnotes/services' } from '@standardnotes/services'
import { FilesClientInterface } from '@standardnotes/files' import { FilesClientInterface } from '@standardnotes/files'
import { ComputePrivateWorkspaceIdentifier } from '@standardnotes/encryption' import { ComputePrivateWorkspaceIdentifier } from '@standardnotes/encryption'
@@ -65,7 +64,6 @@ import { SNLog } from '../Log'
import { Challenge, ChallengeResponse } from '../Services' import { Challenge, ChallengeResponse } from '../Services'
import { ApplicationConstructorOptions, FullyResolvedApplicationOptions } from './Options/ApplicationOptions' import { ApplicationConstructorOptions, FullyResolvedApplicationOptions } from './Options/ApplicationOptions'
import { ApplicationOptionsDefaults } from './Options/Defaults' import { ApplicationOptionsDefaults } from './Options/Defaults'
import { MobileUnlockTiming } from '@Lib/Services/Protection/MobileUnlockTiming'
/** How often to automatically sync, in milliseconds */ /** How often to automatically sync, in milliseconds */
const DEFAULT_AUTO_SYNC_INTERVAL = 30_000 const DEFAULT_AUTO_SYNC_INTERVAL = 30_000
@@ -899,24 +897,6 @@ export class SNApplication
return this.launched return this.launched
} }
public hasBiometrics(): boolean {
return this.protectionService.hasBiometricsEnabled()
}
/**
* @returns whether the operation was successful or not
*/
public enableBiometrics(): boolean {
return this.protectionService.enableBiometrics()
}
/**
* @returns whether the operation was successful or not
*/
public disableBiometrics(): Promise<boolean> {
return this.protectionService.disableBiometrics()
}
public hasPasscode(): boolean { public hasPasscode(): boolean {
return this.protocolService.hasPasscode() return this.protocolService.hasPasscode()
} }
@@ -936,38 +916,6 @@ export class SNApplication
return this.deinit(this.getDeinitMode(), DeinitSource.Lock) return this.deinit(this.getDeinitMode(), DeinitSource.Lock)
} }
setBiometricsTiming(timing: MobileUnlockTiming) {
return this.protectionService.setBiometricsTiming(timing)
}
getMobileScreenshotPrivacyEnabled(): boolean {
return this.protectionService.getMobileScreenshotPrivacyEnabled()
}
setMobileScreenshotPrivacyEnabled(isEnabled: boolean) {
return this.protectionService.setMobileScreenshotPrivacyEnabled(isEnabled)
}
getMobilePasscodeTiming(): MobileUnlockTiming | undefined {
return this.getValue(StorageKey.MobilePasscodeTiming, StorageValueModes.Nonwrapped) as
| MobileUnlockTiming
| undefined
}
getMobileBiometricsTiming(): MobileUnlockTiming | undefined {
return this.getValue(StorageKey.MobileBiometricsTiming, StorageValueModes.Nonwrapped) as
| MobileUnlockTiming
| undefined
}
getBiometricsTimingOptions() {
return this.protectionService.getBiometricsTimingOptions()
}
getPasscodeTimingOptions() {
return this.protectionService.getPasscodeTimingOptions()
}
isNativeMobileWeb() { isNativeMobileWeb() {
return this.environment === Environment.NativeMobileWeb return this.environment === Environment.NativeMobileWeb
} }

View File

@@ -1,8 +1,19 @@
import { ChallengeReason } from '@standardnotes/services' import { ChallengeReason } from '@standardnotes/services'
import { DecryptedItem } from '@standardnotes/models' import { DecryptedItem } from '@standardnotes/models'
import { TimingDisplayOption, MobileUnlockTiming } from './MobileUnlockTiming'
export interface ProtectionsClientInterface { export interface ProtectionsClientInterface {
authorizeProtectedActionForItems<T extends DecryptedItem>(files: T[], challengeReason: ChallengeReason): Promise<T[]> authorizeProtectedActionForItems<T extends DecryptedItem>(files: T[], challengeReason: ChallengeReason): Promise<T[]>
authorizeItemAccess(item: DecryptedItem): Promise<boolean> authorizeItemAccess(item: DecryptedItem): Promise<boolean>
getMobileBiometricsTiming(): MobileUnlockTiming | undefined
getMobilePasscodeTiming(): MobileUnlockTiming | undefined
setMobileBiometricsTiming(timing: MobileUnlockTiming): void
setMobilePasscodeTiming(timing: MobileUnlockTiming): void
setMobileScreenshotPrivacyEnabled(isEnabled: boolean): void
getMobileScreenshotPrivacyEnabled(): boolean
getMobilePasscodeTimingOptions(): TimingDisplayOption[]
getMobileBiometricsTimingOptions(): TimingDisplayOption[]
hasBiometricsEnabled(): boolean
enableBiometrics(): boolean
disableBiometrics(): Promise<boolean>
} }

View File

@@ -2,3 +2,9 @@ export enum MobileUnlockTiming {
Immediately = 'immediately', Immediately = 'immediately',
OnQuit = 'on-quit', OnQuit = 'on-quit',
} }
export type TimingDisplayOption = {
title: string
key: MobileUnlockTiming
selected: boolean
}

View File

@@ -18,7 +18,7 @@ import {
} from '@standardnotes/services' } from '@standardnotes/services'
import { ProtectionsClientInterface } from './ClientInterface' import { ProtectionsClientInterface } from './ClientInterface'
import { ContentType } from '@standardnotes/common' import { ContentType } from '@standardnotes/common'
import { MobileUnlockTiming } from './MobileUnlockTiming' import { MobileUnlockTiming, TimingDisplayOption } from './MobileUnlockTiming'
export enum ProtectionEvent { export enum ProtectionEvent {
UnprotectedSessionBegan = 'UnprotectedSessionBegan', UnprotectedSessionBegan = 'UnprotectedSessionBegan',
@@ -64,8 +64,8 @@ export const ProtectionSessionDurations = [
*/ */
export class SNProtectionService extends AbstractService<ProtectionEvent> implements ProtectionsClientInterface { export class SNProtectionService extends AbstractService<ProtectionEvent> implements ProtectionsClientInterface {
private sessionExpiryTimeout = -1 private sessionExpiryTimeout = -1
private mobilePasscodeTiming: MobileUnlockTiming | undefined = MobileUnlockTiming.Immediately private mobilePasscodeTiming: MobileUnlockTiming | undefined = MobileUnlockTiming.OnQuit
private mobileBiometricsTiming: MobileUnlockTiming | undefined = MobileUnlockTiming.Immediately private mobileBiometricsTiming: MobileUnlockTiming | undefined = MobileUnlockTiming.OnQuit
constructor( constructor(
private protocolService: EncryptionService, private protocolService: EncryptionService,
@@ -87,8 +87,8 @@ export class SNProtectionService extends AbstractService<ProtectionEvent> implem
override handleApplicationStage(stage: ApplicationStage): Promise<void> { override handleApplicationStage(stage: ApplicationStage): Promise<void> {
if (stage === ApplicationStage.LoadedDatabase_12) { if (stage === ApplicationStage.LoadedDatabase_12) {
this.updateSessionExpiryTimer(this.getSessionExpiryDate()) this.updateSessionExpiryTimer(this.getSessionExpiryDate())
this.mobilePasscodeTiming = this.getPasscodeTiming() this.mobilePasscodeTiming = this.getMobilePasscodeTiming()
this.mobileBiometricsTiming = this.getBiometricsTiming() this.mobileBiometricsTiming = this.getMobileBiometricsTiming()
} }
return Promise.resolve() return Promise.resolve()
} }
@@ -229,7 +229,7 @@ export class SNProtectionService extends AbstractService<ProtectionEvent> implem
}) })
} }
getPasscodeTimingOptions() { getMobilePasscodeTimingOptions(): TimingDisplayOption[] {
return [ return [
{ {
title: 'Immediately', title: 'Immediately',
@@ -244,7 +244,7 @@ export class SNProtectionService extends AbstractService<ProtectionEvent> implem
] ]
} }
getBiometricsTimingOptions() { getMobileBiometricsTimingOptions(): TimingDisplayOption[] {
return [ return [
{ {
title: 'Immediately', title: 'Immediately',
@@ -259,25 +259,32 @@ export class SNProtectionService extends AbstractService<ProtectionEvent> implem
] ]
} }
private getBiometricsTiming(): MobileUnlockTiming | undefined { getMobileBiometricsTiming(): MobileUnlockTiming | undefined {
return this.storageService.getValue<MobileUnlockTiming | undefined>( return this.storageService.getValue<MobileUnlockTiming | undefined>(
StorageKey.MobileBiometricsTiming, StorageKey.MobileBiometricsTiming,
StorageValueModes.Nonwrapped, StorageValueModes.Nonwrapped,
MobileUnlockTiming.OnQuit,
) )
} }
private getPasscodeTiming(): MobileUnlockTiming | undefined { getMobilePasscodeTiming(): MobileUnlockTiming | undefined {
return this.storageService.getValue<MobileUnlockTiming | undefined>( return this.storageService.getValue<MobileUnlockTiming | undefined>(
StorageKey.MobilePasscodeTiming, StorageKey.MobilePasscodeTiming,
StorageValueModes.Nonwrapped, StorageValueModes.Nonwrapped,
MobileUnlockTiming.OnQuit,
) )
} }
async setBiometricsTiming(timing: MobileUnlockTiming) { setMobileBiometricsTiming(timing: MobileUnlockTiming): void {
this.storageService.setValue(StorageKey.MobileBiometricsTiming, timing, StorageValueModes.Nonwrapped) this.storageService.setValue(StorageKey.MobileBiometricsTiming, timing, StorageValueModes.Nonwrapped)
this.mobileBiometricsTiming = timing this.mobileBiometricsTiming = timing
} }
setMobilePasscodeTiming(timing: MobileUnlockTiming): void {
this.storageService.setValue(StorageKey.MobilePasscodeTiming, timing, StorageValueModes.Nonwrapped)
this.mobilePasscodeTiming = timing
}
setMobileScreenshotPrivacyEnabled(isEnabled: boolean) { setMobileScreenshotPrivacyEnabled(isEnabled: boolean) {
return this.storageService.setValue(StorageKey.MobileScreenshotPrivacyEnabled, isEnabled, StorageValueModes.Default) return this.storageService.setValue(StorageKey.MobileScreenshotPrivacyEnabled, isEnabled, StorageValueModes.Default)
} }

View File

@@ -62,7 +62,7 @@ describe('device authentication', function () {
const passcode = 'foobar' const passcode = 'foobar'
const wrongPasscode = 'barfoo' const wrongPasscode = 'barfoo'
await application.addPasscode(passcode) await application.addPasscode(passcode)
await application.protectionService.enableBiometrics() await application.protections.enableBiometrics()
expect(await application.hasPasscode()).to.equal(true) expect(await application.hasPasscode()).to.equal(true)
expect((await application.protectionService.createLaunchChallenge()).prompts.length).to.equal(2) expect((await application.protectionService.createLaunchChallenge()).prompts.length).to.equal(2)
expect(application.protocolService.rootKeyEncryption.keyMode).to.equal(KeyMode.WrapperOnly) expect(application.protocolService.rootKeyEncryption.keyMode).to.equal(KeyMode.WrapperOnly)

View File

@@ -311,7 +311,7 @@ describe('protections', function () {
it('no account, no passcode, biometrics', async function () { it('no account, no passcode, biometrics', async function () {
application = await Factory.createInitAppWithFakeCrypto() application = await Factory.createInitAppWithFakeCrypto()
await application.enableBiometrics() await application.protections.enableBiometrics()
expect(application.hasProtectionSources()).to.be.true expect(application.hasProtectionSources()).to.be.true
}) })
@@ -324,7 +324,7 @@ describe('protections', function () {
it('no account, passcode, biometrics', async function () { it('no account, passcode, biometrics', async function () {
application = await Factory.createInitAppWithFakeCrypto() application = await Factory.createInitAppWithFakeCrypto()
await application.addPasscode('passcode') await application.addPasscode('passcode')
await application.enableBiometrics() await application.protections.enableBiometrics()
expect(application.hasProtectionSources()).to.be.true expect(application.hasProtectionSources()).to.be.true
}) })
@@ -345,7 +345,7 @@ describe('protections', function () {
email: UuidGenerator.GenerateUuid(), email: UuidGenerator.GenerateUuid(),
password: UuidGenerator.GenerateUuid(), password: UuidGenerator.GenerateUuid(),
}) })
await application.enableBiometrics() await application.protections.enableBiometrics()
expect(application.hasProtectionSources()).to.be.true expect(application.hasProtectionSources()).to.be.true
}) })
@@ -372,7 +372,7 @@ describe('protections', function () {
}) })
Factory.handlePasswordChallenges(application, password) Factory.handlePasswordChallenges(application, password)
await application.addPasscode('passcode') await application.addPasscode('passcode')
await application.enableBiometrics() await application.protections.enableBiometrics()
expect(application.hasProtectionSources()).to.be.true expect(application.hasProtectionSources()).to.be.true
}) })
}) })

View File

@@ -32,7 +32,7 @@ import { PrefDefaults } from '@/Constants/PrefDefaults'
type WebServices = { type WebServices = {
viewControllerManager: ViewControllerManager viewControllerManager: ViewControllerManager
desktopService?: DesktopManager desktopService?: DesktopManager
autolockService: AutolockService autolockService?: AutolockService
archiveService: ArchiveManager archiveService: ArchiveManager
themeService: ThemeManager themeService: ThemeManager
io: IOService io: IOService
@@ -237,7 +237,7 @@ export class WebApplication extends SNApplication implements WebApplicationInter
async handleMobileGainingFocusEvent(): Promise<void> {} async handleMobileGainingFocusEvent(): Promise<void> {}
async handleMobileLosingFocusEvent(): Promise<void> { async handleMobileLosingFocusEvent(): Promise<void> {
if (this.getMobileScreenshotPrivacyEnabled()) { if (this.protections.getMobileScreenshotPrivacyEnabled()) {
this.mobileDevice().stopHidingMobileInterfaceFromScreenshots() this.mobileDevice().stopHidingMobileInterfaceFromScreenshots()
} }
@@ -245,7 +245,7 @@ export class WebApplication extends SNApplication implements WebApplicationInter
} }
async handleMobileResumingFromBackgroundEvent(): Promise<void> { async handleMobileResumingFromBackgroundEvent(): Promise<void> {
if (this.getMobileScreenshotPrivacyEnabled()) { if (this.protections.getMobileScreenshotPrivacyEnabled()) {
this.mobileDevice().hideMobileInterfaceFromScreenshots() this.mobileDevice().hideMobileInterfaceFromScreenshots()
} }
} }
@@ -256,10 +256,10 @@ export class WebApplication extends SNApplication implements WebApplicationInter
return return
} }
const hasBiometrics = this.hasBiometrics() const hasBiometrics = this.protections.hasBiometricsEnabled()
const hasPasscode = this.hasPasscode() const hasPasscode = this.hasPasscode()
const passcodeTiming = await this.getMobilePasscodeTiming() const passcodeTiming = this.protections.getMobilePasscodeTiming()
const biometricsTiming = await this.getMobileBiometricsTiming() const biometricsTiming = this.protections.getMobileBiometricsTiming()
const passcodeLockImmediately = hasPasscode && passcodeTiming === MobileUnlockTiming.Immediately const passcodeLockImmediately = hasPasscode && passcodeTiming === MobileUnlockTiming.Immediately
const biometricsLockImmediately = hasBiometrics && biometricsTiming === MobileUnlockTiming.Immediately const biometricsLockImmediately = hasBiometrics && biometricsTiming === MobileUnlockTiming.Immediately

View File

@@ -34,7 +34,6 @@ const createApplication = (
const archiveService = new ArchiveManager(application) const archiveService = new ArchiveManager(application)
const io = new IOService(platform === Platform.MacWeb || platform === Platform.MacDesktop) const io = new IOService(platform === Platform.MacWeb || platform === Platform.MacDesktop)
const internalEventBus = new InternalEventBus() const internalEventBus = new InternalEventBus()
const autolockService = new AutolockService(application, internalEventBus)
const themeService = new ThemeManager(application, internalEventBus) const themeService = new ThemeManager(application, internalEventBus)
application.setWebServices({ application.setWebServices({
@@ -42,7 +41,7 @@ const createApplication = (
archiveService, archiveService,
desktopService: isDesktopDevice(device) ? new DesktopManager(application, device) : undefined, desktopService: isDesktopDevice(device) ? new DesktopManager(application, device) : undefined,
io, io,
autolockService, autolockService: application.isNativeMobileWeb() ? undefined : new AutolockService(application, internalEventBus),
themeService, themeService,
}) })

View File

@@ -16,10 +16,12 @@ type Props = {
const BiometricsLock = ({ application }: Props) => { const BiometricsLock = ({ application }: Props) => {
const [hasBiometrics, setHasBiometrics] = useState(false) const [hasBiometrics, setHasBiometrics] = useState(false)
const [supportsBiometrics, setSupportsBiometrics] = useState(false) const [supportsBiometrics, setSupportsBiometrics] = useState(false)
const [biometricsTimingOptions, setBiometricsTimingOptions] = useState(() => application.getBiometricsTimingOptions()) const [biometricsTimingOptions, setBiometricsTimingOptions] = useState(() =>
application.protections.getMobileBiometricsTimingOptions(),
)
useEffect(() => { useEffect(() => {
const appHasBiometrics = application.hasBiometrics() const appHasBiometrics = application.protections.hasBiometricsEnabled()
setHasBiometrics(appHasBiometrics) setHasBiometrics(appHasBiometrics)
const hasBiometricsSupport = async () => { const hasBiometricsSupport = async () => {
@@ -32,12 +34,12 @@ const BiometricsLock = ({ application }: Props) => {
}, [application]) }, [application])
const setBiometricsTimingValue = async (timing: MobileUnlockTiming) => { const setBiometricsTimingValue = async (timing: MobileUnlockTiming) => {
await application.setBiometricsTiming(timing) application.protections.setMobileBiometricsTiming(timing)
setBiometricsTimingOptions(() => application.getBiometricsTimingOptions()) setBiometricsTimingOptions(() => application.protections.getMobileBiometricsTimingOptions())
} }
const disableBiometrics = useCallback(async () => { const disableBiometrics = useCallback(async () => {
if (await application.disableBiometrics()) { if (await application.protections.disableBiometrics()) {
setHasBiometrics(false) setHasBiometrics(false)
} }
}, [application]) }, [application])
@@ -47,7 +49,7 @@ const BiometricsLock = ({ application }: Props) => {
await disableBiometrics() await disableBiometrics()
} else { } else {
setHasBiometrics(true) setHasBiometrics(true)
application.enableBiometrics() application.protections.enableBiometrics()
await setBiometricsTimingValue(MobileUnlockTiming.OnQuit) await setBiometricsTimingValue(MobileUnlockTiming.OnQuit)
} }
} }

View File

@@ -16,7 +16,7 @@ const MultitaskingPrivacy = ({ application }: Props) => {
const [hasScreenshotPrivacy, setHasScreenshotPrivacy] = useState<boolean>(false) const [hasScreenshotPrivacy, setHasScreenshotPrivacy] = useState<boolean>(false)
useEffect(() => { useEffect(() => {
const hasScreenshotPrivacyEnabled = application.getMobileScreenshotPrivacyEnabled() const hasScreenshotPrivacyEnabled = application.protections.getMobileScreenshotPrivacyEnabled()
setHasScreenshotPrivacy(hasScreenshotPrivacyEnabled) setHasScreenshotPrivacy(hasScreenshotPrivacyEnabled)
}, [application]) }, [application])
@@ -24,7 +24,7 @@ const MultitaskingPrivacy = ({ application }: Props) => {
const enable = !hasScreenshotPrivacy const enable = !hasScreenshotPrivacy
setHasScreenshotPrivacy(enable) setHasScreenshotPrivacy(enable)
application.setMobileScreenshotPrivacyEnabled(enable) application.protections.setMobileScreenshotPrivacyEnabled(enable)
;(application.deviceInterface as MobileDeviceInterface).setAndroidScreenshotPrivacy(enable) ;(application.deviceInterface as MobileDeviceInterface).setAndroidScreenshotPrivacy(enable)
} }

View File

@@ -12,7 +12,7 @@ import { WebApplication } from '@/Application/Application'
import { preventRefreshing } from '@/Utils' import { preventRefreshing } from '@/Utils'
import { alertDialog } from '@standardnotes/ui-services' import { alertDialog } from '@standardnotes/ui-services'
import { FormEvent, useCallback, useEffect, useRef, useState } from 'react' import { FormEvent, useCallback, useEffect, useRef, useState } from 'react'
import { ApplicationEvent } from '@standardnotes/snjs' import { ApplicationEvent, MobileUnlockTiming } from '@standardnotes/snjs'
import { observer } from 'mobx-react-lite' import { observer } from 'mobx-react-lite'
import { ViewControllerManager } from '@/Controllers/ViewControllerManager' import { ViewControllerManager } from '@/Controllers/ViewControllerManager'
import { Title, Text } from '@/Components/Preferences/PreferencesComponents/Content' import { Title, Text } from '@/Components/Preferences/PreferencesComponents/Content'
@@ -28,8 +28,8 @@ type Props = {
} }
const PasscodeLock = ({ application, viewControllerManager }: Props) => { const PasscodeLock = ({ application, viewControllerManager }: Props) => {
const isNativeMobileWeb = application.isNativeMobileWeb()
const keyStorageInfo = StringUtils.keyStorageInfo(application) const keyStorageInfo = StringUtils.keyStorageInfo(application)
const passcodeAutoLockOptions = application.getAutolockService().getAutoLockIntervalOptions()
const { setIsEncryptionEnabled, setIsBackupEncrypted, setEncryptionStatusString } = const { setIsEncryptionEnabled, setIsBackupEncrypted, setEncryptionStatusString } =
viewControllerManager.accountMenuController viewControllerManager.accountMenuController
@@ -44,6 +44,10 @@ const PasscodeLock = ({ application, viewControllerManager }: Props) => {
const [canAddPasscode, setCanAddPasscode] = useState(!application.isEphemeralSession()) const [canAddPasscode, setCanAddPasscode] = useState(!application.isEphemeralSession())
const [hasPasscode, setHasPasscode] = useState(application.hasPasscode()) const [hasPasscode, setHasPasscode] = useState(application.hasPasscode())
const [mobilePasscodeTimingOptions, setMobilePasscodeTimingOptions] = useState(() =>
application.protections.getMobilePasscodeTimingOptions(),
)
const handleAddPassCode = () => { const handleAddPassCode = () => {
setShowPasscodeForm(true) setShowPasscodeForm(true)
setIsPasscodeFocused(true) setIsPasscodeFocused(true)
@@ -53,8 +57,8 @@ const PasscodeLock = ({ application, viewControllerManager }: Props) => {
handleAddPassCode() handleAddPassCode()
} }
const reloadAutoLockInterval = useCallback(async () => { const reloadDesktopAutoLockInterval = useCallback(async () => {
const interval = await application.getAutolockService().getAutoLockInterval() const interval = await application.getAutolockService()!.getAutoLockInterval()
setSelectedAutoLockInterval(interval) setSelectedAutoLockInterval(interval)
}, [application]) }, [application])
@@ -77,19 +81,27 @@ const PasscodeLock = ({ application, viewControllerManager }: Props) => {
setIsBackupEncrypted(encryptionEnabled) setIsBackupEncrypted(encryptionEnabled)
}, [application, setEncryptionStatusString, setIsBackupEncrypted, setIsEncryptionEnabled]) }, [application, setEncryptionStatusString, setIsBackupEncrypted, setIsEncryptionEnabled])
const selectAutoLockInterval = async (interval: number) => { const selectDesktopAutoLockInterval = async (interval: number) => {
if (!(await application.authorizeAutolockIntervalChange())) { if (!(await application.authorizeAutolockIntervalChange())) {
return return
} }
await application.getAutolockService().setAutoLockInterval(interval)
reloadAutoLockInterval().catch(console.error) await application.getAutolockService()!.setAutoLockInterval(interval)
reloadDesktopAutoLockInterval().catch(console.error)
}
const setMobilePasscodeTiming = (timing: MobileUnlockTiming) => {
application.protections.setMobilePasscodeTiming(timing)
setMobilePasscodeTimingOptions(application.protections.getMobilePasscodeTimingOptions())
} }
const removePasscodePressed = async () => { const removePasscodePressed = async () => {
await preventRefreshing(STRING_CONFIRM_APP_QUIT_DURING_PASSCODE_REMOVAL, async () => { await preventRefreshing(STRING_CONFIRM_APP_QUIT_DURING_PASSCODE_REMOVAL, async () => {
if (await application.removePasscode()) { if (await application.removePasscode()) {
await application.getAutolockService().deleteAutolockPreference() if (!isNativeMobileWeb) {
await reloadAutoLockInterval() await application.getAutolockService()?.deleteAutolockPreference()
await reloadDesktopAutoLockInterval()
}
refreshEncryptionStatus() refreshEncryptionStatus()
} }
}) })
@@ -141,11 +153,11 @@ const PasscodeLock = ({ application, viewControllerManager }: Props) => {
refreshEncryptionStatus() refreshEncryptionStatus()
}, [refreshEncryptionStatus]) }, [refreshEncryptionStatus])
// `reloadAutoLockInterval` gets interval asynchronously, therefore we call `useEffect` to set initial
// value of `selectedAutoLockInterval`
useEffect(() => { useEffect(() => {
reloadAutoLockInterval().catch(console.error) if (!isNativeMobileWeb) {
}, [reloadAutoLockInterval]) reloadDesktopAutoLockInterval().catch(console.error)
}
}, [reloadDesktopAutoLockInterval, isNativeMobileWeb])
useEffect(() => { useEffect(() => {
if (isPasscodeFocused) { if (isPasscodeFocused) {
@@ -154,7 +166,6 @@ const PasscodeLock = ({ application, viewControllerManager }: Props) => {
} }
}, [isPasscodeFocused]) }, [isPasscodeFocused])
// Add the required event observers
useEffect(() => { useEffect(() => {
const removeKeyStatusChangedObserver = application.addEventObserver(async () => { const removeKeyStatusChangedObserver = application.addEventObserver(async () => {
setCanAddPasscode(!application.isEphemeralSession()) setCanAddPasscode(!application.isEphemeralSession())
@@ -229,7 +240,7 @@ const PasscodeLock = ({ application, viewControllerManager }: Props) => {
</PreferencesSegment> </PreferencesSegment>
</PreferencesGroup> </PreferencesGroup>
{hasPasscode && ( {hasPasscode && !isNativeMobileWeb && (
<> <>
<div className="min-h-3" /> <div className="min-h-3" />
<PreferencesGroup> <PreferencesGroup>
@@ -237,22 +248,57 @@ const PasscodeLock = ({ application, viewControllerManager }: Props) => {
<Title>Autolock</Title> <Title>Autolock</Title>
<Text className="mb-3">The autolock timer begins when the window or tab loses focus.</Text> <Text className="mb-3">The autolock timer begins when the window or tab loses focus.</Text>
<div className="flex flex-row items-center"> <div className="flex flex-row items-center">
{passcodeAutoLockOptions.map((option) => { {application
return ( .getAutolockService()!
<a .getAutoLockIntervalOptions()
key={option.value} .map((option) => {
className={classNames( return (
'mr-3 cursor-pointer rounded', <a
option.value === selectedAutoLockInterval key={option.value}
? 'bg-info px-1.5 py-0.5 text-info-contrast' className={classNames(
: 'text-info', 'mr-3 cursor-pointer rounded',
)} option.value === selectedAutoLockInterval
onClick={() => selectAutoLockInterval(option.value)} ? 'bg-info px-1.5 py-0.5 text-info-contrast'
> : 'text-info',
{option.label} )}
</a> onClick={() => selectDesktopAutoLockInterval(option.value)}
) >
})} {option.label}
</a>
)
})}
</div>
</PreferencesSegment>
</PreferencesGroup>
</>
)}
{hasPasscode && isNativeMobileWeb && (
<>
<div className="min-h-3" />
<PreferencesGroup>
<PreferencesSegment>
<Title>Passcode Autolock</Title>
<div className="flex flex-row items-center">
<div className="mt-2 flex flex-row items-center">
<div className={'mr-3'}>Require Passcode</div>
{mobilePasscodeTimingOptions.map((option) => {
return (
<a
key={option.key}
className={classNames(
'mr-3 cursor-pointer rounded px-1.5 py-0.5',
option.selected ? 'bg-info text-info-contrast' : 'text-info',
)}
onClick={() => {
void setMobilePasscodeTiming(option.key as MobileUnlockTiming)
}}
>
{option.title}
</a>
)
})}
</div>
</div> </div>
</PreferencesSegment> </PreferencesSegment>
</PreferencesGroup> </PreferencesGroup>