feat: mobile security prefs (#1496)

* feat: move mobile-specific security items to Web when rendered in WebView

* feat: better UI for biometrics section

* feat: move Multitasking Privacy section to WebView (mostly UI)

* feat: move Multitasking Privacy section to WebView (going to understand why in WebView multitasking privacy value is auto-changed after reopening the WebView)

* feat: store MultitaskingPrivacy value as "NonWrapped" so that it's the same both on mobile and WebView

* feat: open WebView correctly when "Storage Encryption" is disabled on mobile

* fix: remove unnecessary changes and comments

* chore: revert ios-related unneeded changes

* fix: let Android to correctly recognize the NativeMobileWeb environment when opening WebView on Android

* fix: correct styles for the selected state of Biometrics/Passcode options

* chore: code cleanup

* fix: store Multitasking/Screenshot Privacy in the `Default` storage value mode

* chore: remove comment

* fix: use application's method instead of directly updating Screenshot Privacy preference

* fix: remove unused variable

* fix: use methods from Application and MobileDeviceInterface in all places, remove duplicate code

* fix: hide Multitasking Privacy and Biometrics Lock in WebView

Co-authored-by: Aman Harwara
This commit is contained in:
Vardan Hakobyan
2022-09-14 13:07:10 +04:00
committed by GitHub
parent e73d187b65
commit d7aca2c13a
14 changed files with 324 additions and 92 deletions

View File

@@ -98,6 +98,7 @@ if (IsWebPlatform) {
setTimeout(() => {
const device = window.reactNativeDevice || new WebDevice(WebAppVersion)
startApplication(window.defaultSyncServer, device, window.enabledUnfinishedFeatures, window.websocketUrl).catch(
console.error,
)

View File

@@ -0,0 +1,97 @@
import { observer } from 'mobx-react-lite'
import { useCallback, useEffect, useState } from 'react'
import { WebApplication } from '@/Application/Application'
import { MobileDeviceInterface } from '@standardnotes/services'
import { MobileUnlockTiming } from '@standardnotes/snjs'
import PreferencesSegment from '@/Components/Preferences/PreferencesComponents/PreferencesSegment'
import { Title } from '@/Components/Preferences/PreferencesComponents/Content'
import PreferencesGroup from '@/Components/Preferences/PreferencesComponents/PreferencesGroup'
import Button from '@/Components/Button/Button'
import { classNames } from '@/Utils/ConcatenateClassNames'
type Props = {
application: WebApplication
}
const BiometricsLock = ({ application }: Props) => {
const [hasBiometrics, setHasBiometrics] = useState(false)
const [supportsBiometrics, setSupportsBiometrics] = useState(false)
const [biometricsTimingOptions, setBiometricsTimingOptions] = useState(() => application.getBiometricsTimingOptions())
useEffect(() => {
const getHasBiometrics = async () => {
const appHasBiometrics = await application.hasBiometrics()
setHasBiometrics(appHasBiometrics)
}
const hasBiometricsSupport = async () => {
const hasBiometricsAvailable = await (
application.deviceInterface as MobileDeviceInterface
).getDeviceBiometricsAvailability?.()
setSupportsBiometrics(hasBiometricsAvailable)
}
void getHasBiometrics()
void hasBiometricsSupport()
}, [application])
const setBiometricsTimingValue = async (timing: MobileUnlockTiming) => {
await application.setBiometricsTiming(timing)
setBiometricsTimingOptions(() => application.getBiometricsTimingOptions())
}
const disableBiometrics = useCallback(async () => {
if (await application.disableBiometrics()) {
setHasBiometrics(false)
}
}, [application])
const onBiometricsPress = async () => {
if (hasBiometrics) {
await disableBiometrics()
} else {
setHasBiometrics(true)
await application.enableBiometrics()
await setBiometricsTimingValue(MobileUnlockTiming.OnQuit)
}
}
const biometricTitle = hasBiometrics ? 'Disable Biometrics Lock' : 'Enable Biometrics Lock'
if (!supportsBiometrics) {
return null
}
return (
<div>
<PreferencesGroup>
<PreferencesSegment>
<Title>Biometrics Lock</Title>
<Button className={'mt-1'} label={biometricTitle} onClick={onBiometricsPress} primary />
{hasBiometrics && (
<div className="mt-2 flex flex-row items-center">
<div className={'mr-3'}>Require Biometrics</div>
{biometricsTimingOptions.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 setBiometricsTimingValue(option.key as MobileUnlockTiming)
}}
>
{option.title}
</a>
)
})}
</div>
)}
</PreferencesSegment>
</PreferencesGroup>
</div>
)
}
export default observer(BiometricsLock)

View File

@@ -0,0 +1,51 @@
import { observer } from 'mobx-react-lite'
import { WebApplication } from '@/Application/Application'
import { isIOS } from '@/Utils'
import { useEffect, useState } from 'react'
import { MobileDeviceInterface } from '@standardnotes/services'
import PreferencesSegment from '@/Components/Preferences/PreferencesComponents/PreferencesSegment'
import { Title } from '@/Components/Preferences/PreferencesComponents/Content'
import Button from '@/Components/Button/Button'
import PreferencesGroup from '@/Components/Preferences/PreferencesComponents/PreferencesGroup'
type Props = {
application: WebApplication
}
const MultitaskingPrivacy = ({ application }: Props) => {
const [hasScreenshotPrivacy, setHasScreenshotPrivacy] = useState<boolean | undefined>(false)
useEffect(() => {
const getHasScreenshotPrivacy = async () => {
const hasScreenshotPrivacyEnabled = (await application.getMobileScreenshotPrivacyEnabled()) ?? true
setHasScreenshotPrivacy(hasScreenshotPrivacyEnabled)
}
void getHasScreenshotPrivacy()
}, [application])
const onScreenshotPrivacyPress = async () => {
const enable = !hasScreenshotPrivacy
setHasScreenshotPrivacy(enable)
await application.setMobileScreenshotPrivacyEnabled(enable)
await (application.deviceInterface as MobileDeviceInterface).setAndroidScreenshotPrivacy(enable)
}
const screenshotPrivacyFeatureText = isIOS() ? 'Multitasking Privacy' : 'Multitasking/Screenshot Privacy'
const screenshotPrivacyTitle = hasScreenshotPrivacy
? `Disable ${screenshotPrivacyFeatureText}`
: `Enable ${screenshotPrivacyFeatureText}`
return (
<div>
<PreferencesGroup>
<PreferencesSegment>
<Title>{screenshotPrivacyFeatureText}</Title>
<Button className={'mt-1'} label={screenshotPrivacyTitle} onClick={onScreenshotPrivacyPress} primary />
</PreferencesSegment>
</PreferencesGroup>
</div>
)
}
export default observer(MultitaskingPrivacy)

View File

@@ -9,23 +9,34 @@ import Privacy from './Privacy'
import Protections from './Protections'
import ErroredItems from './ErroredItems'
import PreferencesPane from '@/Components/Preferences/PreferencesComponents/PreferencesPane'
import BiometricsLock from '@/Components/Preferences/Panes/Security/BiometricsLock'
import MultitaskingPrivacy from '@/Components/Preferences/Panes/Security/MultitaskingPrivacy'
interface SecurityProps extends MfaProps {
viewControllerManager: ViewControllerManager
application: WebApplication
}
const Security: FunctionComponent<SecurityProps> = (props) => (
<PreferencesPane>
<Encryption viewControllerManager={props.viewControllerManager} />
{props.application.items.invalidItems.length > 0 && (
<ErroredItems viewControllerManager={props.viewControllerManager} />
)}
<Protections application={props.application} />
<TwoFactorAuthWrapper mfaProvider={props.mfaProvider} userProvider={props.userProvider} />
<PasscodeLock viewControllerManager={props.viewControllerManager} application={props.application} />
{props.application.getUser() && <Privacy application={props.application} />}
</PreferencesPane>
)
const SHOW_MULTITASKING_PRIVACY = false
const SHOW_BIOMETRICS_LOCK = false
const Security: FunctionComponent<SecurityProps> = (props) => {
const isNativeMobileWeb = props.application.isNativeMobileWeb()
return (
<PreferencesPane>
<Encryption viewControllerManager={props.viewControllerManager} />
{props.application.items.invalidItems.length > 0 && (
<ErroredItems viewControllerManager={props.viewControllerManager} />
)}
<Protections application={props.application} />
<TwoFactorAuthWrapper mfaProvider={props.mfaProvider} userProvider={props.userProvider} />
{SHOW_MULTITASKING_PRIVACY && isNativeMobileWeb && <MultitaskingPrivacy application={props.application} />}
<PasscodeLock viewControllerManager={props.viewControllerManager} application={props.application} />
{SHOW_BIOMETRICS_LOCK && isNativeMobileWeb && <BiometricsLock application={props.application} />}
{props.application.getUser() && <Privacy application={props.application} />}
</PreferencesPane>
)
}
export default Security