refactor: format and lint codebase (#971)
This commit is contained in:
@@ -0,0 +1,72 @@
|
||||
import { Icon } from '@/Components/Icon'
|
||||
import { STRING_E2E_ENABLED, STRING_ENC_NOT_ENABLED, STRING_LOCAL_ENC_ENABLED } from '@/Strings'
|
||||
import { AppState } from '@/UIModels/AppState'
|
||||
import { observer } from 'mobx-react-lite'
|
||||
import { ComponentChild, FunctionComponent } from 'preact'
|
||||
import { PreferencesGroup, PreferencesSegment, Text, Title } from '@/Components/Preferences/PreferencesComponents'
|
||||
|
||||
const formatCount = (count: number, itemType: string) => `${count} / ${count} ${itemType}`
|
||||
|
||||
const EncryptionStatusItem: FunctionComponent<{
|
||||
icon: ComponentChild
|
||||
status: string
|
||||
}> = ({ icon, status }) => (
|
||||
<div className="w-full rounded py-1.5 px-3 text-input my-1 h-8 flex flex-row items-center bg-contrast no-border focus-within:ring-info">
|
||||
{icon}
|
||||
<div className="min-w-3 min-h-1" />
|
||||
<div className="flex-grow color-text text-sm">{status}</div>
|
||||
<div className="min-w-3 min-h-1" />
|
||||
<Icon className="success min-w-4 min-h-4" type="check-bold" />
|
||||
</div>
|
||||
)
|
||||
|
||||
const EncryptionEnabled: FunctionComponent<{ appState: AppState }> = observer(({ appState }) => {
|
||||
const count = appState.accountMenu.structuredNotesAndTagsCount
|
||||
const notes = formatCount(count.notes, 'notes')
|
||||
const tags = formatCount(count.tags, 'tags')
|
||||
const archived = formatCount(count.archived, 'archived notes')
|
||||
const deleted = formatCount(count.deleted, 'trashed notes')
|
||||
|
||||
const noteIcon = <Icon type="rich-text" className="min-w-5 min-h-5" />
|
||||
const tagIcon = <Icon type="hashtag" className="min-w-5 min-h-5" />
|
||||
const archiveIcon = <Icon type="archive" className="min-w-5 min-h-5" />
|
||||
const trashIcon = <Icon type="trash" className="min-w-5 min-h-5" />
|
||||
return (
|
||||
<>
|
||||
<div className="flex flex-row pb-1 pt-1.5">
|
||||
<EncryptionStatusItem status={notes} icon={[noteIcon]} />
|
||||
<div className="min-w-3" />
|
||||
<EncryptionStatusItem status={tags} icon={[tagIcon]} />
|
||||
</div>
|
||||
<div className="flex flex-row">
|
||||
<EncryptionStatusItem status={archived} icon={[archiveIcon]} />
|
||||
<div className="min-w-3" />
|
||||
<EncryptionStatusItem status={deleted} icon={[trashIcon]} />
|
||||
</div>
|
||||
</>
|
||||
)
|
||||
})
|
||||
|
||||
export const Encryption: FunctionComponent<{ appState: AppState }> = observer(({ appState }) => {
|
||||
const app = appState.application
|
||||
const hasUser = app.hasAccount()
|
||||
const hasPasscode = app.hasPasscode()
|
||||
const isEncryptionEnabled = app.isEncryptionAvailable()
|
||||
|
||||
const encryptionStatusString = hasUser
|
||||
? STRING_E2E_ENABLED
|
||||
: hasPasscode
|
||||
? STRING_LOCAL_ENC_ENABLED
|
||||
: STRING_ENC_NOT_ENABLED
|
||||
|
||||
return (
|
||||
<PreferencesGroup>
|
||||
<PreferencesSegment>
|
||||
<Title>Encryption</Title>
|
||||
<Text>{encryptionStatusString}</Text>
|
||||
|
||||
{isEncryptionEnabled && <EncryptionEnabled appState={appState} />}
|
||||
</PreferencesSegment>
|
||||
</PreferencesGroup>
|
||||
)
|
||||
})
|
||||
@@ -0,0 +1,284 @@
|
||||
import {
|
||||
STRING_CONFIRM_APP_QUIT_DURING_PASSCODE_CHANGE,
|
||||
STRING_CONFIRM_APP_QUIT_DURING_PASSCODE_REMOVAL,
|
||||
STRING_E2E_ENABLED,
|
||||
STRING_ENC_NOT_ENABLED,
|
||||
STRING_LOCAL_ENC_ENABLED,
|
||||
STRING_NON_MATCHING_PASSCODES,
|
||||
StringUtils,
|
||||
Strings,
|
||||
} from '@/Strings'
|
||||
import { WebApplication } from '@/UIModels/Application'
|
||||
import { preventRefreshing } from '@/Utils'
|
||||
import { JSXInternal } from 'preact/src/jsx'
|
||||
import TargetedEvent = JSXInternal.TargetedEvent
|
||||
import TargetedMouseEvent = JSXInternal.TargetedMouseEvent
|
||||
import { alertDialog } from '@/Services/AlertService'
|
||||
import { useCallback, useEffect, useRef, useState } from 'preact/hooks'
|
||||
import { ApplicationEvent } from '@standardnotes/snjs'
|
||||
import { observer } from 'mobx-react-lite'
|
||||
import { AppState } from '@/UIModels/AppState'
|
||||
import {
|
||||
PreferencesSegment,
|
||||
Title,
|
||||
Text,
|
||||
PreferencesGroup,
|
||||
} from '@/Components/Preferences/PreferencesComponents'
|
||||
import { Button } from '@/Components/Button/Button'
|
||||
|
||||
type Props = {
|
||||
application: WebApplication
|
||||
appState: AppState
|
||||
}
|
||||
|
||||
export const PasscodeLock = observer(({ application, appState }: Props) => {
|
||||
const keyStorageInfo = StringUtils.keyStorageInfo(application)
|
||||
const passcodeAutoLockOptions = application.getAutolockService().getAutoLockIntervalOptions()
|
||||
|
||||
const { setIsEncryptionEnabled, setIsBackupEncrypted, setEncryptionStatusString } =
|
||||
appState.accountMenu
|
||||
|
||||
const passcodeInputRef = useRef<HTMLInputElement>(null)
|
||||
|
||||
const [passcode, setPasscode] = useState<string | undefined>(undefined)
|
||||
const [passcodeConfirmation, setPasscodeConfirmation] = useState<string | undefined>(undefined)
|
||||
const [selectedAutoLockInterval, setSelectedAutoLockInterval] = useState<unknown>(null)
|
||||
const [isPasscodeFocused, setIsPasscodeFocused] = useState(false)
|
||||
const [showPasscodeForm, setShowPasscodeForm] = useState(false)
|
||||
const [canAddPasscode, setCanAddPasscode] = useState(!application.isEphemeralSession())
|
||||
const [hasPasscode, setHasPasscode] = useState(application.hasPasscode())
|
||||
|
||||
const handleAddPassCode = () => {
|
||||
setShowPasscodeForm(true)
|
||||
setIsPasscodeFocused(true)
|
||||
}
|
||||
|
||||
const changePasscodePressed = () => {
|
||||
handleAddPassCode()
|
||||
}
|
||||
|
||||
const reloadAutoLockInterval = useCallback(async () => {
|
||||
const interval = await application.getAutolockService().getAutoLockInterval()
|
||||
setSelectedAutoLockInterval(interval)
|
||||
}, [application])
|
||||
|
||||
const refreshEncryptionStatus = useCallback(() => {
|
||||
const hasUser = application.hasAccount()
|
||||
const hasPasscode = application.hasPasscode()
|
||||
|
||||
setHasPasscode(hasPasscode)
|
||||
|
||||
const encryptionEnabled = hasUser || hasPasscode
|
||||
|
||||
const encryptionStatusString = hasUser
|
||||
? STRING_E2E_ENABLED
|
||||
: hasPasscode
|
||||
? STRING_LOCAL_ENC_ENABLED
|
||||
: STRING_ENC_NOT_ENABLED
|
||||
|
||||
setEncryptionStatusString(encryptionStatusString)
|
||||
setIsEncryptionEnabled(encryptionEnabled)
|
||||
setIsBackupEncrypted(encryptionEnabled)
|
||||
}, [application, setEncryptionStatusString, setIsBackupEncrypted, setIsEncryptionEnabled])
|
||||
|
||||
const selectAutoLockInterval = async (interval: number) => {
|
||||
if (!(await application.authorizeAutolockIntervalChange())) {
|
||||
return
|
||||
}
|
||||
await application.getAutolockService().setAutoLockInterval(interval)
|
||||
reloadAutoLockInterval().catch(console.error)
|
||||
}
|
||||
|
||||
const removePasscodePressed = async () => {
|
||||
await preventRefreshing(STRING_CONFIRM_APP_QUIT_DURING_PASSCODE_REMOVAL, async () => {
|
||||
if (await application.removePasscode()) {
|
||||
await application.getAutolockService().deleteAutolockPreference()
|
||||
await reloadAutoLockInterval()
|
||||
refreshEncryptionStatus()
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
const handlePasscodeChange = (event: TargetedEvent<HTMLInputElement>) => {
|
||||
const { value } = event.target as HTMLInputElement
|
||||
setPasscode(value)
|
||||
}
|
||||
|
||||
const handleConfirmPasscodeChange = (event: TargetedEvent<HTMLInputElement>) => {
|
||||
const { value } = event.target as HTMLInputElement
|
||||
setPasscodeConfirmation(value)
|
||||
}
|
||||
|
||||
const submitPasscodeForm = async (
|
||||
event: TargetedEvent<HTMLFormElement> | TargetedMouseEvent<HTMLButtonElement>,
|
||||
) => {
|
||||
event.preventDefault()
|
||||
|
||||
if (!passcode || passcode.length === 0) {
|
||||
await alertDialog({
|
||||
text: Strings.enterPasscode,
|
||||
})
|
||||
}
|
||||
|
||||
if (passcode !== passcodeConfirmation) {
|
||||
await alertDialog({
|
||||
text: STRING_NON_MATCHING_PASSCODES,
|
||||
})
|
||||
setIsPasscodeFocused(true)
|
||||
return
|
||||
}
|
||||
|
||||
await preventRefreshing(STRING_CONFIRM_APP_QUIT_DURING_PASSCODE_CHANGE, async () => {
|
||||
const successful = application.hasPasscode()
|
||||
? await application.changePasscode(passcode as string)
|
||||
: await application.addPasscode(passcode as string)
|
||||
|
||||
if (!successful) {
|
||||
setIsPasscodeFocused(true)
|
||||
}
|
||||
})
|
||||
|
||||
setPasscode(undefined)
|
||||
setPasscodeConfirmation(undefined)
|
||||
setShowPasscodeForm(false)
|
||||
|
||||
refreshEncryptionStatus()
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
refreshEncryptionStatus()
|
||||
}, [refreshEncryptionStatus])
|
||||
|
||||
// `reloadAutoLockInterval` gets interval asynchronously, therefore we call `useEffect` to set initial
|
||||
// value of `selectedAutoLockInterval`
|
||||
useEffect(() => {
|
||||
reloadAutoLockInterval().catch(console.error)
|
||||
}, [reloadAutoLockInterval])
|
||||
|
||||
useEffect(() => {
|
||||
if (isPasscodeFocused) {
|
||||
passcodeInputRef.current?.focus()
|
||||
setIsPasscodeFocused(false)
|
||||
}
|
||||
}, [isPasscodeFocused])
|
||||
|
||||
// Add the required event observers
|
||||
useEffect(() => {
|
||||
const removeKeyStatusChangedObserver = application.addEventObserver(async () => {
|
||||
setCanAddPasscode(!application.isEphemeralSession())
|
||||
setHasPasscode(application.hasPasscode())
|
||||
setShowPasscodeForm(false)
|
||||
}, ApplicationEvent.KeyStatusChanged)
|
||||
|
||||
return () => {
|
||||
removeKeyStatusChangedObserver()
|
||||
}
|
||||
}, [application])
|
||||
|
||||
return (
|
||||
<>
|
||||
<PreferencesGroup>
|
||||
<PreferencesSegment>
|
||||
<Title>Passcode Lock</Title>
|
||||
|
||||
{!hasPasscode && canAddPasscode && (
|
||||
<>
|
||||
<Text className="mb-3">
|
||||
Add a passcode to lock the application and encrypt on-device key storage.
|
||||
</Text>
|
||||
|
||||
{keyStorageInfo && <Text className="mb-3">{keyStorageInfo}</Text>}
|
||||
|
||||
{!showPasscodeForm && (
|
||||
<Button label="Add passcode" onClick={handleAddPassCode} variant="primary" />
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
|
||||
{!hasPasscode && !canAddPasscode && (
|
||||
<Text>
|
||||
Adding a passcode is not supported in temporary sessions. Please sign out, then sign
|
||||
back in with the "Stay signed in" option checked.
|
||||
</Text>
|
||||
)}
|
||||
|
||||
{showPasscodeForm && (
|
||||
<form className="sk-panel-form" onSubmit={submitPasscodeForm}>
|
||||
<div className="sk-panel-row" />
|
||||
<input
|
||||
className="sk-input contrast"
|
||||
type="password"
|
||||
ref={passcodeInputRef}
|
||||
value={passcode}
|
||||
onChange={handlePasscodeChange}
|
||||
placeholder="Passcode"
|
||||
/>
|
||||
<input
|
||||
className="sk-input contrast"
|
||||
type="password"
|
||||
value={passcodeConfirmation}
|
||||
onChange={handleConfirmPasscodeChange}
|
||||
placeholder="Confirm Passcode"
|
||||
/>
|
||||
<div className="min-h-2" />
|
||||
<Button
|
||||
variant="primary"
|
||||
onClick={submitPasscodeForm}
|
||||
label="Set Passcode"
|
||||
className="mr-3"
|
||||
/>
|
||||
<Button variant="normal" onClick={() => setShowPasscodeForm(false)} label="Cancel" />
|
||||
</form>
|
||||
)}
|
||||
|
||||
{hasPasscode && !showPasscodeForm && (
|
||||
<>
|
||||
<Text>Passcode lock is enabled.</Text>
|
||||
<div className="flex flex-row mt-3">
|
||||
<Button
|
||||
variant="normal"
|
||||
label="Change Passcode"
|
||||
onClick={changePasscodePressed}
|
||||
className="mr-3"
|
||||
/>
|
||||
<Button
|
||||
dangerStyle={true}
|
||||
label="Remove Passcode"
|
||||
onClick={removePasscodePressed}
|
||||
/>
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
</PreferencesSegment>
|
||||
</PreferencesGroup>
|
||||
|
||||
{hasPasscode && (
|
||||
<>
|
||||
<div className="min-h-3" />
|
||||
<PreferencesGroup>
|
||||
<PreferencesSegment>
|
||||
<Title>Autolock</Title>
|
||||
<Text className="mb-3">
|
||||
The autolock timer begins when the window or tab loses focus.
|
||||
</Text>
|
||||
<div className="flex flex-row items-center">
|
||||
{passcodeAutoLockOptions.map((option) => {
|
||||
return (
|
||||
<a
|
||||
className={`sk-a info mr-3 ${
|
||||
option.value === selectedAutoLockInterval ? 'boxed' : ''
|
||||
}`}
|
||||
onClick={() => selectAutoLockInterval(option.value)}
|
||||
>
|
||||
{option.label}
|
||||
</a>
|
||||
)
|
||||
})}
|
||||
</div>
|
||||
</PreferencesSegment>
|
||||
</PreferencesGroup>
|
||||
</>
|
||||
)}
|
||||
</>
|
||||
)
|
||||
})
|
||||
@@ -0,0 +1,146 @@
|
||||
import { HorizontalSeparator } from '@/Components/Shared/HorizontalSeparator'
|
||||
import { Switch } from '@/Components/Switch'
|
||||
import {
|
||||
PreferencesGroup,
|
||||
PreferencesSegment,
|
||||
Subtitle,
|
||||
Text,
|
||||
Title,
|
||||
} from '@/Components/Preferences/PreferencesComponents'
|
||||
import { WebApplication } from '@/UIModels/Application'
|
||||
import { MuteSignInEmailsOption, LogSessionUserAgentOption, SettingName } from '@standardnotes/snjs'
|
||||
import { observer } from 'mobx-react-lite'
|
||||
import { FunctionalComponent } from 'preact'
|
||||
import { useCallback, useEffect, useState } from 'preact/hooks'
|
||||
import { STRING_FAILED_TO_UPDATE_USER_SETTING } from '@/Strings'
|
||||
|
||||
type Props = {
|
||||
application: WebApplication
|
||||
}
|
||||
|
||||
export const Privacy: FunctionalComponent<Props> = observer(({ application }: Props) => {
|
||||
const [signInEmailsMutedValue, setSignInEmailsMutedValue] = useState<MuteSignInEmailsOption>(
|
||||
MuteSignInEmailsOption.NotMuted,
|
||||
)
|
||||
const [sessionUaLoggingValue, setSessionUaLoggingValue] = useState<LogSessionUserAgentOption>(
|
||||
LogSessionUserAgentOption.Enabled,
|
||||
)
|
||||
const [isLoading, setIsLoading] = useState(false)
|
||||
|
||||
const updateSetting = async (settingName: SettingName, payload: string): Promise<boolean> => {
|
||||
try {
|
||||
await application.settings.updateSetting(settingName, payload, false)
|
||||
return true
|
||||
} catch (e) {
|
||||
application.alertService.alert(STRING_FAILED_TO_UPDATE_USER_SETTING).catch(console.error)
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
const loadSettings = useCallback(async () => {
|
||||
if (!application.getUser()) {
|
||||
return
|
||||
}
|
||||
setIsLoading(true)
|
||||
|
||||
try {
|
||||
const userSettings = await application.settings.listSettings()
|
||||
setSignInEmailsMutedValue(
|
||||
userSettings.getSettingValue<MuteSignInEmailsOption>(
|
||||
SettingName.MuteSignInEmails,
|
||||
MuteSignInEmailsOption.NotMuted,
|
||||
),
|
||||
)
|
||||
setSessionUaLoggingValue(
|
||||
userSettings.getSettingValue<LogSessionUserAgentOption>(
|
||||
SettingName.LogSessionUserAgent,
|
||||
LogSessionUserAgentOption.Enabled,
|
||||
),
|
||||
)
|
||||
} catch (error) {
|
||||
console.error(error)
|
||||
} finally {
|
||||
setIsLoading(false)
|
||||
}
|
||||
}, [application])
|
||||
|
||||
useEffect(() => {
|
||||
loadSettings().catch(console.error)
|
||||
}, [loadSettings])
|
||||
|
||||
const toggleMuteSignInEmails = async () => {
|
||||
const previousValue = signInEmailsMutedValue
|
||||
const newValue =
|
||||
previousValue === MuteSignInEmailsOption.Muted
|
||||
? MuteSignInEmailsOption.NotMuted
|
||||
: MuteSignInEmailsOption.Muted
|
||||
setSignInEmailsMutedValue(newValue)
|
||||
|
||||
const updateResult = await updateSetting(SettingName.MuteSignInEmails, newValue)
|
||||
|
||||
if (!updateResult) {
|
||||
setSignInEmailsMutedValue(previousValue)
|
||||
}
|
||||
}
|
||||
|
||||
const toggleSessionLogging = async () => {
|
||||
const previousValue = sessionUaLoggingValue
|
||||
const newValue =
|
||||
previousValue === LogSessionUserAgentOption.Enabled
|
||||
? LogSessionUserAgentOption.Disabled
|
||||
: LogSessionUserAgentOption.Enabled
|
||||
setSessionUaLoggingValue(newValue)
|
||||
|
||||
const updateResult = await updateSetting(SettingName.LogSessionUserAgent, newValue)
|
||||
|
||||
if (!updateResult) {
|
||||
setSessionUaLoggingValue(previousValue)
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<PreferencesGroup>
|
||||
<PreferencesSegment>
|
||||
<Title>Privacy</Title>
|
||||
<div>
|
||||
<div className="flex items-center justify-between">
|
||||
<div className="flex flex-col">
|
||||
<Subtitle>Disable sign-in notification emails</Subtitle>
|
||||
<Text>
|
||||
Disables email notifications when a new sign-in occurs on your account. (Email
|
||||
notifications are available to paid subscribers).
|
||||
</Text>
|
||||
</div>
|
||||
{isLoading ? (
|
||||
<div className={'sk-spinner info small'} />
|
||||
) : (
|
||||
<Switch
|
||||
onChange={toggleMuteSignInEmails}
|
||||
checked={signInEmailsMutedValue === MuteSignInEmailsOption.Muted}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
<HorizontalSeparator classes="mt-5 mb-3" />
|
||||
<div className="flex items-center justify-between">
|
||||
<div className="flex flex-col">
|
||||
<Subtitle>Session user agent logging</Subtitle>
|
||||
<Text>
|
||||
User agent logging allows you to identify the devices or browsers signed into your
|
||||
account. For increased privacy, you can disable this feature, which will remove all
|
||||
saved user agent values from our server, and disable future logging of this value.
|
||||
</Text>
|
||||
</div>
|
||||
{isLoading ? (
|
||||
<div className={'sk-spinner info small'} />
|
||||
) : (
|
||||
<Switch
|
||||
onChange={toggleSessionLogging}
|
||||
checked={sessionUaLoggingValue === LogSessionUserAgentOption.Enabled}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</PreferencesSegment>
|
||||
</PreferencesGroup>
|
||||
)
|
||||
})
|
||||
@@ -0,0 +1,103 @@
|
||||
import { WebApplication } from '@/UIModels/Application'
|
||||
import { FunctionalComponent } from 'preact'
|
||||
import { useCallback, useState, useEffect } from 'preact/hooks'
|
||||
import { ApplicationEvent } from '@standardnotes/snjs'
|
||||
import { isSameDay } from '@/Utils'
|
||||
import {
|
||||
PreferencesGroup,
|
||||
PreferencesSegment,
|
||||
Title,
|
||||
Text,
|
||||
} from '@/Components/Preferences/PreferencesComponents'
|
||||
import { Button } from '@/Components/Button/Button'
|
||||
|
||||
type Props = {
|
||||
application: WebApplication
|
||||
}
|
||||
|
||||
export const Protections: FunctionalComponent<Props> = ({ application }) => {
|
||||
const enableProtections = () => {
|
||||
application.clearProtectionSession().catch(console.error)
|
||||
}
|
||||
|
||||
const [hasProtections, setHasProtections] = useState(() => application.hasProtectionSources())
|
||||
|
||||
const getProtectionsDisabledUntil = useCallback((): string | null => {
|
||||
const protectionExpiry = application.getProtectionSessionExpiryDate()
|
||||
const now = new Date()
|
||||
if (protectionExpiry > now) {
|
||||
let f: Intl.DateTimeFormat
|
||||
if (isSameDay(protectionExpiry, now)) {
|
||||
f = new Intl.DateTimeFormat(undefined, {
|
||||
hour: 'numeric',
|
||||
minute: 'numeric',
|
||||
})
|
||||
} else {
|
||||
f = new Intl.DateTimeFormat(undefined, {
|
||||
weekday: 'long',
|
||||
day: 'numeric',
|
||||
month: 'short',
|
||||
hour: 'numeric',
|
||||
minute: 'numeric',
|
||||
})
|
||||
}
|
||||
|
||||
return f.format(protectionExpiry)
|
||||
}
|
||||
return null
|
||||
}, [application])
|
||||
|
||||
const [protectionsDisabledUntil, setProtectionsDisabledUntil] = useState(
|
||||
getProtectionsDisabledUntil(),
|
||||
)
|
||||
|
||||
useEffect(() => {
|
||||
const removeUnprotectedSessionBeginObserver = application.addEventObserver(async () => {
|
||||
setProtectionsDisabledUntil(getProtectionsDisabledUntil())
|
||||
}, ApplicationEvent.UnprotectedSessionBegan)
|
||||
|
||||
const removeUnprotectedSessionEndObserver = application.addEventObserver(async () => {
|
||||
setProtectionsDisabledUntil(getProtectionsDisabledUntil())
|
||||
}, ApplicationEvent.UnprotectedSessionExpired)
|
||||
|
||||
const removeKeyStatusChangedObserver = application.addEventObserver(async () => {
|
||||
setHasProtections(application.hasProtectionSources())
|
||||
}, ApplicationEvent.KeyStatusChanged)
|
||||
|
||||
return () => {
|
||||
removeUnprotectedSessionBeginObserver()
|
||||
removeUnprotectedSessionEndObserver()
|
||||
removeKeyStatusChangedObserver()
|
||||
}
|
||||
}, [application, getProtectionsDisabledUntil])
|
||||
|
||||
if (!hasProtections) {
|
||||
return null
|
||||
}
|
||||
|
||||
return (
|
||||
<PreferencesGroup>
|
||||
<PreferencesSegment>
|
||||
<Title>Protections</Title>
|
||||
{protectionsDisabledUntil ? (
|
||||
<Text className="info">Unprotected access expires at {protectionsDisabledUntil}.</Text>
|
||||
) : (
|
||||
<Text className="info">Protections are enabled.</Text>
|
||||
)}
|
||||
<Text className="mt-2">
|
||||
Actions like viewing or searching protected notes, exporting decrypted backups, or
|
||||
revoking an active session require additional authentication such as entering your account
|
||||
password or application passcode.
|
||||
</Text>
|
||||
{protectionsDisabledUntil && (
|
||||
<Button
|
||||
className="mt-3"
|
||||
variant="primary"
|
||||
label="End Unprotected Access"
|
||||
onClick={enableProtections}
|
||||
/>
|
||||
)}
|
||||
</PreferencesSegment>
|
||||
</PreferencesGroup>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
import { WebApplication } from '@/UIModels/Application'
|
||||
import { AppState } from '@/UIModels/AppState'
|
||||
import { FunctionComponent } from 'preact'
|
||||
import { PreferencesPane } from '@/Components/Preferences/PreferencesComponents'
|
||||
import { TwoFactorAuthWrapper } from '../TwoFactorAuth'
|
||||
import { MfaProps } from '../TwoFactorAuth/MfaProps'
|
||||
import { Encryption } from './Encryption'
|
||||
import { PasscodeLock } from './PasscodeLock'
|
||||
import { Privacy } from './Privacy'
|
||||
import { Protections } from './Protections'
|
||||
|
||||
interface SecurityProps extends MfaProps {
|
||||
appState: AppState
|
||||
application: WebApplication
|
||||
}
|
||||
|
||||
export const Security: FunctionComponent<SecurityProps> = (props) => (
|
||||
<PreferencesPane>
|
||||
<Encryption appState={props.appState} />
|
||||
<Protections application={props.application} />
|
||||
<TwoFactorAuthWrapper mfaProvider={props.mfaProvider} userProvider={props.userProvider} />
|
||||
<PasscodeLock appState={props.appState} application={props.application} />
|
||||
<Privacy application={props.application} />
|
||||
</PreferencesPane>
|
||||
)
|
||||
Reference in New Issue
Block a user