refactor: format and lint codebase (#971)

This commit is contained in:
Aman Harwara
2022-04-13 22:02:34 +05:30
committed by GitHub
parent dc9c1ea0fc
commit 8e467f9e6d
367 changed files with 13778 additions and 16093 deletions

View File

@@ -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>
)
})

View File

@@ -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>
</>
)}
</>
)
})

View File

@@ -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>
)
})

View File

@@ -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>
)
}

View File

@@ -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>
)