feat: implement Protections in prefs (#645)
This commit is contained in:
@@ -2,7 +2,7 @@ import { WebApplication } from '@/ui_models/application';
|
|||||||
import { AppState } from '@/ui_models/app_state';
|
import { AppState } from '@/ui_models/app_state';
|
||||||
import { FunctionComponent } from 'preact';
|
import { FunctionComponent } from 'preact';
|
||||||
import { PreferencesPane } from '../components';
|
import { PreferencesPane } from '../components';
|
||||||
import { Encryption, PasscodeLock } from './security-segments';
|
import { Encryption, PasscodeLock, Protections } from './security-segments';
|
||||||
import { TwoFactorAuthWrapper } from './two-factor-auth';
|
import { TwoFactorAuthWrapper } from './two-factor-auth';
|
||||||
import { MfaProps } from './two-factor-auth/MfaProps';
|
import { MfaProps } from './two-factor-auth/MfaProps';
|
||||||
|
|
||||||
@@ -14,6 +14,7 @@ interface SecurityProps extends MfaProps {
|
|||||||
export const Security: FunctionComponent<SecurityProps> = (props) => (
|
export const Security: FunctionComponent<SecurityProps> = (props) => (
|
||||||
<PreferencesPane>
|
<PreferencesPane>
|
||||||
<Encryption appState={props.appState} />
|
<Encryption appState={props.appState} />
|
||||||
|
<Protections application={props.application} />
|
||||||
<TwoFactorAuthWrapper
|
<TwoFactorAuthWrapper
|
||||||
mfaProvider={props.mfaProvider}
|
mfaProvider={props.mfaProvider}
|
||||||
userProvider={props.userProvider}
|
userProvider={props.userProvider}
|
||||||
|
|||||||
@@ -0,0 +1,93 @@
|
|||||||
|
import { WebApplication } from '@/ui_models/application';
|
||||||
|
import { FunctionalComponent } from 'preact';
|
||||||
|
import { useCallback, useState } from 'preact/hooks';
|
||||||
|
import { useEffect } from 'preact/hooks';
|
||||||
|
import { ApplicationEvent } from '@standardnotes/snjs';
|
||||||
|
import { isSameDay } from '@/utils';
|
||||||
|
import { PreferencesGroup, PreferencesSegment, Title, Text } from '@/preferences/components';
|
||||||
|
import { Button } from '@/components/Button';
|
||||||
|
import { Switch } from '@/components/Switch';
|
||||||
|
|
||||||
|
type Props = {
|
||||||
|
application: WebApplication;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const Protections: FunctionalComponent<Props> = ({ application }) => {
|
||||||
|
const enableProtections = () => {
|
||||||
|
application.clearProtectionSession();
|
||||||
|
};
|
||||||
|
|
||||||
|
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 removeProtectionSessionExpiryDateChangedObserver = application.addEventObserver(
|
||||||
|
async () => {
|
||||||
|
setProtectionsDisabledUntil(getProtectionsDisabledUntil());
|
||||||
|
},
|
||||||
|
ApplicationEvent.ProtectionSessionExpiryDateChanged
|
||||||
|
);
|
||||||
|
|
||||||
|
const removeKeyStatusChangedObserver = application.addEventObserver(
|
||||||
|
async () => {
|
||||||
|
setHasProtections(application.hasProtectionSources());
|
||||||
|
},
|
||||||
|
ApplicationEvent.KeyStatusChanged
|
||||||
|
);
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
removeProtectionSessionExpiryDateChangedObserver();
|
||||||
|
removeKeyStatusChangedObserver();
|
||||||
|
};
|
||||||
|
}, [application, getProtectionsDisabledUntil]);
|
||||||
|
|
||||||
|
if (!hasProtections) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<PreferencesGroup>
|
||||||
|
<PreferencesSegment>
|
||||||
|
<Title>Protections</Title>
|
||||||
|
{protectionsDisabledUntil
|
||||||
|
? <Text className="info">Protections are disabled until {protectionsDisabledUntil}.</Text>
|
||||||
|
: <Text className="info">Protections are enabled.</Text>
|
||||||
|
}
|
||||||
|
<Text className="mt-2">
|
||||||
|
Actions like viewing protected notes, exporting decrypted backups,
|
||||||
|
or revoking an active session, require additional authentication
|
||||||
|
like entering your account password or application passcode.
|
||||||
|
</Text>
|
||||||
|
{protectionsDisabledUntil &&
|
||||||
|
<Button className="mt-3" type="primary" label="Enable Protections" onClick={enableProtections} />
|
||||||
|
}
|
||||||
|
</PreferencesSegment>
|
||||||
|
</PreferencesGroup >
|
||||||
|
);
|
||||||
|
};
|
||||||
@@ -1,2 +1,3 @@
|
|||||||
export * from './Encryption';
|
export * from './Encryption';
|
||||||
export * from './PasscodeLock';
|
export * from './PasscodeLock';
|
||||||
|
export * from './Protections';
|
||||||
|
|||||||
Reference in New Issue
Block a user