chore: block disabling 2FA if U2F authenticators present - skip e2e (#2862)

* chore: block disabling 2FA if U2F authenticators present

* chore: remove the u2f wrapper component

* chore: adjust fetching 2fa status

* chore: fix property name

* chore: remove wrapper
This commit is contained in:
Karol Sójko
2024-03-22 09:45:15 +01:00
committed by GitHub
parent 4068d5b480
commit a22000242a
8 changed files with 34 additions and 46 deletions

View File

@@ -1,8 +1,7 @@
import { NativeFeatureIdentifier, FeatureStatus } from '@standardnotes/snjs' import { NativeFeatureIdentifier, FeatureStatus } from '@standardnotes/snjs'
import { FunctionComponent, useState } from 'react' import { FunctionComponent, useEffect, useState } from 'react'
import { WebApplication } from '@/Application/WebApplication' import { WebApplication } from '@/Application/WebApplication'
import TwoFactorAuthWrapper from './TwoFactorAuth/TwoFactorAuthWrapper'
import Encryption from './Encryption' import Encryption from './Encryption'
import PasscodeLock from './PasscodeLock' import PasscodeLock from './PasscodeLock'
import Privacy from './Privacy' import Privacy from './Privacy'
@@ -11,8 +10,9 @@ import ErroredItems from './ErroredItems'
import PreferencesPane from '@/Components/Preferences/PreferencesComponents/PreferencesPane' import PreferencesPane from '@/Components/Preferences/PreferencesComponents/PreferencesPane'
import BiometricsLock from '@/Components/Preferences/Panes/Security/BiometricsLock' import BiometricsLock from '@/Components/Preferences/Panes/Security/BiometricsLock'
import MultitaskingPrivacy from '@/Components/Preferences/Panes/Security/MultitaskingPrivacy' import MultitaskingPrivacy from '@/Components/Preferences/Panes/Security/MultitaskingPrivacy'
import U2FWrapper from './U2F/U2FWrapper'
import { TwoFactorAuth, is2FAEnabled as checkIf2FAIsEnabled } from './TwoFactorAuth/TwoFactorAuth' import { TwoFactorAuth, is2FAEnabled as checkIf2FAIsEnabled } from './TwoFactorAuth/TwoFactorAuth'
import U2FView from './U2F/U2FView/U2FView'
import TwoFactorAuthView from './TwoFactorAuth/TwoFactorAuthView/TwoFactorAuthView'
interface SecurityProps { interface SecurityProps {
application: WebApplication application: WebApplication
@@ -21,6 +21,7 @@ interface SecurityProps {
const Security: FunctionComponent<SecurityProps> = (props) => { const Security: FunctionComponent<SecurityProps> = (props) => {
const isNativeMobileWeb = props.application.isNativeMobileWeb() const isNativeMobileWeb = props.application.isNativeMobileWeb()
const [is2FAEnabled, setIs2FAEnabled] = useState(false) const [is2FAEnabled, setIs2FAEnabled] = useState(false)
const [canDisable2FA, setCanDisable2FA] = useState(true)
const [auth] = useState( const [auth] = useState(
() => () =>
@@ -28,7 +29,14 @@ const Security: FunctionComponent<SecurityProps> = (props) => {
setIs2FAEnabled(checkIf2FAIsEnabled(status)), setIs2FAEnabled(checkIf2FAIsEnabled(status)),
), ),
) )
auth.fetchStatus()
useEffect(() => {
auth.fetchStatus()
}, [auth])
const onU2FDevicesLoaded = (devices: Array<{ id: string; name: string }>) => {
setCanDisable2FA(devices.length === 0)
}
const isU2FFeatureAvailable = const isU2FFeatureAvailable =
props.application.features.getFeatureStatus( props.application.features.getFeatureStatus(
@@ -40,8 +48,14 @@ const Security: FunctionComponent<SecurityProps> = (props) => {
<Encryption /> <Encryption />
{props.application.items.invalidNonVaultedItems.length > 0 && <ErroredItems />} {props.application.items.invalidNonVaultedItems.length > 0 && <ErroredItems />}
<Protections application={props.application} /> <Protections application={props.application} />
<TwoFactorAuthWrapper auth={auth} application={props.application} /> <TwoFactorAuthView auth={auth} application={props.application} canDisable2FA={canDisable2FA} />
{isU2FFeatureAvailable && <U2FWrapper application={props.application} is2FAEnabled={is2FAEnabled} />} {isU2FFeatureAvailable && (
<U2FView
application={props.application}
is2FAEnabled={is2FAEnabled}
loadAuthenticatorsCallback={onU2FDevicesLoaded}
/>
)}
{isNativeMobileWeb && <MultitaskingPrivacy application={props.application} />} {isNativeMobileWeb && <MultitaskingPrivacy application={props.application} />}
<PasscodeLock application={props.application} /> <PasscodeLock application={props.application} />
{isNativeMobileWeb && <BiometricsLock application={props.application} />} {isNativeMobileWeb && <BiometricsLock application={props.application} />}

View File

@@ -1,7 +0,0 @@
import { WebApplication } from '@/Application/WebApplication'
import { TwoFactorAuth } from './TwoFactorAuth'
export interface MfaProps {
application: WebApplication
auth: TwoFactorAuth
}

View File

@@ -16,9 +16,10 @@ import ModalOverlay from '@/Components/Modal/ModalOverlay'
type Props = { type Props = {
auth: TwoFactorAuth auth: TwoFactorAuth
application: WebApplication application: WebApplication
canDisable2FA: boolean
} }
const TwoFactorAuthView: FunctionComponent<Props> = ({ auth, application }) => { const TwoFactorAuthView: FunctionComponent<Props> = ({ auth, application, canDisable2FA }) => {
const shouldShowActivationModal = auth.status !== 'fetching' && is2FAActivation(auth.status) const shouldShowActivationModal = auth.status !== 'fetching' && is2FAActivation(auth.status)
const activationModalTitle = shouldShowActivationModal const activationModalTitle = shouldShowActivationModal
@@ -96,7 +97,7 @@ const TwoFactorAuthView: FunctionComponent<Props> = ({ auth, application }) => {
<TwoFactorTitle auth={auth} /> <TwoFactorTitle auth={auth} />
<TwoFactorDescription auth={auth} /> <TwoFactorDescription auth={auth} />
</div> </div>
<TwoFactorSwitch auth={auth} /> <TwoFactorSwitch auth={auth} canDisable2FA={canDisable2FA} />
</div> </div>
</PreferencesSegment> </PreferencesSegment>

View File

@@ -6,9 +6,10 @@ import Spinner from '@/Components/Spinner/Spinner'
type Props = { type Props = {
auth: TwoFactorAuth auth: TwoFactorAuth
canDisable2FA: boolean
} }
const TwoFactorSwitch: FunctionComponent<Props> = ({ auth }) => { const TwoFactorSwitch: FunctionComponent<Props> = ({ auth, canDisable2FA }) => {
if (!auth.isLoggedIn()) { if (!auth.isLoggedIn()) {
return null return null
} }
@@ -17,7 +18,9 @@ const TwoFactorSwitch: FunctionComponent<Props> = ({ auth }) => {
return <Spinner className="h-4 w-4" /> return <Spinner className="h-4 w-4" />
} }
return <Switch checked={!is2FADisabled(auth.status)} onChange={auth.toggle2FA} /> const shouldSwitchBeDisabled = auth.status === 'two-factor-enabled' && !canDisable2FA
return <Switch checked={!is2FADisabled(auth.status)} onChange={auth.toggle2FA} disabled={shouldSwitchBeDisabled} />
} }
export default observer(TwoFactorSwitch) export default observer(TwoFactorSwitch)

View File

@@ -1,10 +0,0 @@
import { FunctionComponent } from 'react'
import { MfaProps } from './MfaProps'
import TwoFactorAuthView from './TwoFactorAuthView/TwoFactorAuthView'
const TwoFactorAuthWrapper: FunctionComponent<MfaProps> = (props) => {
return <TwoFactorAuthView auth={props.auth} application={props.application} />
}
export default TwoFactorAuthWrapper

View File

@@ -1,6 +0,0 @@
import { WebApplication } from '@/Application/WebApplication'
export interface U2FProps {
application: WebApplication
is2FAEnabled: boolean
}

View File

@@ -16,9 +16,10 @@ import RecoveryCodeBanner from '@/Components/RecoveryCodeBanner/RecoveryCodeBann
type Props = { type Props = {
application: WebApplication application: WebApplication
is2FAEnabled: boolean is2FAEnabled: boolean
loadAuthenticatorsCallback: (devices: Array<{ id: string; name: string }>) => void
} }
const U2FView: FunctionComponent<Props> = ({ application, is2FAEnabled }) => { const U2FView: FunctionComponent<Props> = ({ application, is2FAEnabled, loadAuthenticatorsCallback }) => {
const [showDeviceAddingModal, setShowDeviceAddingModal] = useState(false) const [showDeviceAddingModal, setShowDeviceAddingModal] = useState(false)
const [devices, setDevices] = useState<Array<{ id: string; name: string }>>([]) const [devices, setDevices] = useState<Array<{ id: string; name: string }>>([])
const [error, setError] = useState('') const [error, setError] = useState('')
@@ -35,8 +36,10 @@ const U2FView: FunctionComponent<Props> = ({ application, is2FAEnabled }) => {
return return
} }
setDevices(authenticatorListOrError.getValue()) const authenticatorList = authenticatorListOrError.getValue()
}, [setError, setDevices, application]) setDevices(authenticatorList)
loadAuthenticatorsCallback(authenticatorList)
}, [setError, setDevices, application, loadAuthenticatorsCallback])
useEffect(() => { useEffect(() => {
loadAuthenticatorDevices().catch(console.error) loadAuthenticatorDevices().catch(console.error)

View File

@@ -1,10 +0,0 @@
import { FunctionComponent } from 'react'
import { U2FProps } from './U2FProps'
import U2FView from './U2FView/U2FView'
const U2FWrapper: FunctionComponent<U2FProps> = (props) => {
return <U2FView application={props.application} is2FAEnabled={props.is2FAEnabled} />
}
export default U2FWrapper