Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

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

Merged
merged 5 commits into from Mar 22, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
@@ -1,8 +1,7 @@
import { NativeFeatureIdentifier, FeatureStatus } from '@standardnotes/snjs'
import { FunctionComponent, useState } from 'react'
import { FunctionComponent, useEffect, useState } from 'react'

import { WebApplication } from '@/Application/WebApplication'
import TwoFactorAuthWrapper from './TwoFactorAuth/TwoFactorAuthWrapper'
import Encryption from './Encryption'
import PasscodeLock from './PasscodeLock'
import Privacy from './Privacy'
Expand All @@ -11,8 +10,9 @@ 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'
import U2FWrapper from './U2F/U2FWrapper'
import { TwoFactorAuth, is2FAEnabled as checkIf2FAIsEnabled } from './TwoFactorAuth/TwoFactorAuth'
import U2FView from './U2F/U2FView/U2FView'
import TwoFactorAuthView from './TwoFactorAuth/TwoFactorAuthView/TwoFactorAuthView'

interface SecurityProps {
application: WebApplication
Expand All @@ -21,14 +21,22 @@ interface SecurityProps {
const Security: FunctionComponent<SecurityProps> = (props) => {
const isNativeMobileWeb = props.application.isNativeMobileWeb()
const [is2FAEnabled, setIs2FAEnabled] = useState(false)
const [canDisable2FA, setCanDisable2FA] = useState(true)

const [auth] = useState(
() =>
new TwoFactorAuth(props.application.sessions, props.application.mfa, (status) =>
setIs2FAEnabled(checkIf2FAIsEnabled(status)),
),
)
auth.fetchStatus()

useEffect(() => {
auth.fetchStatus()
}, [auth])

const onU2FDevicesLoaded = (devices: Array<{ id: string; name: string }>) => {
setCanDisable2FA(devices.length === 0)
}

const isU2FFeatureAvailable =
props.application.features.getFeatureStatus(
Expand All @@ -40,8 +48,14 @@ const Security: FunctionComponent<SecurityProps> = (props) => {
<Encryption />
{props.application.items.invalidNonVaultedItems.length > 0 && <ErroredItems />}
<Protections application={props.application} />
<TwoFactorAuthWrapper auth={auth} application={props.application} />
{isU2FFeatureAvailable && <U2FWrapper application={props.application} is2FAEnabled={is2FAEnabled} />}
<TwoFactorAuthView auth={auth} application={props.application} canDisable2FA={canDisable2FA} />
{isU2FFeatureAvailable && (
<U2FView
application={props.application}
is2FAEnabled={is2FAEnabled}
loadAuthenticatorsCallback={onU2FDevicesLoaded}
/>
)}
{isNativeMobileWeb && <MultitaskingPrivacy application={props.application} />}
<PasscodeLock application={props.application} />
{isNativeMobileWeb && <BiometricsLock application={props.application} />}
Expand Down

This file was deleted.

Expand Up @@ -16,9 +16,10 @@ import ModalOverlay from '@/Components/Modal/ModalOverlay'
type Props = {
auth: TwoFactorAuth
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 activationModalTitle = shouldShowActivationModal
Expand Down Expand Up @@ -96,7 +97,7 @@ const TwoFactorAuthView: FunctionComponent<Props> = ({ auth, application }) => {
<TwoFactorTitle auth={auth} />
<TwoFactorDescription auth={auth} />
</div>
<TwoFactorSwitch auth={auth} />
<TwoFactorSwitch auth={auth} canDisable2FA={canDisable2FA} />
</div>
</PreferencesSegment>

Expand Down
Expand Up @@ -6,9 +6,10 @@ import Spinner from '@/Components/Spinner/Spinner'

type Props = {
auth: TwoFactorAuth
canDisable2FA: boolean
}

const TwoFactorSwitch: FunctionComponent<Props> = ({ auth }) => {
const TwoFactorSwitch: FunctionComponent<Props> = ({ auth, canDisable2FA }) => {
if (!auth.isLoggedIn()) {
return null
}
Expand All @@ -17,7 +18,9 @@ const TwoFactorSwitch: FunctionComponent<Props> = ({ auth }) => {
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)

This file was deleted.

This file was deleted.

Expand Up @@ -16,9 +16,10 @@ import RecoveryCodeBanner from '@/Components/RecoveryCodeBanner/RecoveryCodeBann
type Props = {
application: WebApplication
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 [devices, setDevices] = useState<Array<{ id: string; name: string }>>([])
const [error, setError] = useState('')
Expand All @@ -35,8 +36,10 @@ const U2FView: FunctionComponent<Props> = ({ application, is2FAEnabled }) => {
return
}

setDevices(authenticatorListOrError.getValue())
}, [setError, setDevices, application])
const authenticatorList = authenticatorListOrError.getValue()
setDevices(authenticatorList)
loadAuthenticatorsCallback(authenticatorList)
}, [setError, setDevices, application, loadAuthenticatorsCallback])

useEffect(() => {
loadAuthenticatorDevices().catch(console.error)
Expand Down

This file was deleted.