// @ts-strict-ignore import React, { useState } from 'react'; import { useDispatch } from 'react-redux'; import { css } from 'glamor'; import { loadAllFiles, loadGlobalPrefs, sync } from 'loot-core/client/actions'; import { send } from 'loot-core/src/platform/client/fetch'; import { getCreateKeyError } from 'loot-core/src/shared/errors'; import { useResponsive } from '../../ResponsiveProvider'; import { styles, theme } from '../../style'; import { ButtonWithLoading } from '../common/Button'; import { InitialFocus } from '../common/InitialFocus'; import { Input } from '../common/Input'; import { Link } from '../common/Link'; import { Modal, ModalButtons } from '../common/Modal'; import { Paragraph } from '../common/Paragraph'; import { Text } from '../common/Text'; import { View } from '../common/View'; import { type CommonModalProps } from '../Modals'; type CreateEncryptionKeyModalProps = { modalProps: CommonModalProps; options: { recreate?: boolean; }; }; export function CreateEncryptionKeyModal({ modalProps, options = {}, }: CreateEncryptionKeyModalProps) { const [password, setPassword] = useState(''); const [loading, setLoading] = useState(false); const [error, setError] = useState(''); const [showPassword, setShowPassword] = useState(false); const { isNarrowWidth } = useResponsive(); const dispatch = useDispatch(); const isRecreating = options.recreate; async function onCreateKey() { if (password !== '' && !loading) { setLoading(true); setError(null); const res = await send('key-make', { password }); if (res.error) { setLoading(null); setError(getCreateKeyError(res.error)); return; } dispatch(loadGlobalPrefs()); dispatch(loadAllFiles()); dispatch(sync()); setLoading(false); modalProps.onClose(); } } return ( <Modal {...modalProps} style={{ flex: 1, padding: '0 10px', borderRadius: '6px', }} padding={10} title={isRecreating ? 'Generate new key' : 'Enable encryption'} onClose={modalProps.onClose} > <View style={{ maxWidth: 600, overflowX: 'hidden', overflowY: 'auto', flex: 1, }} > {!isRecreating ? ( <> <Paragraph style={{ marginTop: 5 }}> To enable end-to-end encryption, you need to create a key. We will generate a key based on a password and use it to encrypt from now on. <strong>This requires a sync reset</strong> and all other devices will have to revert to this version of your data.{' '} <Link variant="external" to="https://actualbudget.org/docs/getting-started/sync/#end-to-end-encryption" linkColor="purple" > Learn more </Link> </Paragraph> <Paragraph> <ul className={`${css({ marginTop: 0, '& li': { marginBottom: 8 }, })}`} > <li> <strong>Important:</strong> if you forget this password{' '} <em>and</em> you don’t have any local copies of your data, you will lose access to all your data. The data cannot be decrypted without the password. </li> <li> This key only applies to this file. You will need to generate a new key for each file you want to encrypt. </li> <li> If you’ve already downloaded your data on other devices, you will need to reset them. Actual will automatically take you through this process. </li> <li> It is recommended for the encryption password to be different than the log-in password in order to better protect your data. </li> </ul> </Paragraph> </> ) : ( <> <Paragraph style={{ marginTop: 5 }}> This will generate a new key for encrypting your data.{' '} <strong>This requires a sync reset</strong> and all other devices will have to revert to this version of your data. Actual will take you through that process on those devices.{' '} <Link variant="external" to="https://actualbudget.org/docs/getting-started/sync/#end-to-end-encryption" linkColor="purple" > Learn more </Link> </Paragraph> <Paragraph> Key generation is randomized. The same password will create different keys, so this will change your key regardless of the password being different. </Paragraph> </> )} </View> <form onSubmit={e => { e.preventDefault(); onCreateKey(); }} > <View style={{ alignItems: 'center' }}> <Text style={{ fontWeight: 600, marginBottom: 3 }}>Password</Text> {error && ( <View style={{ color: theme.errorText, textAlign: 'center', fontSize: 13, marginBottom: 3, }} > {error} </View> )} <InitialFocus> <Input type={showPassword ? 'text' : 'password'} style={{ width: isNarrowWidth ? '100%' : '50%', height: isNarrowWidth ? styles.mobileMinHeight : undefined, }} onChange={e => setPassword(e.target.value)} /> </InitialFocus> <Text style={{ marginTop: 5 }}> <label style={{ userSelect: 'none' }}> <input type="checkbox" onClick={() => setShowPassword(!showPassword)} />{' '} Show password </label> </Text> </View> <ModalButtons style={{ marginTop: 20 }}> <ButtonWithLoading style={{ height: isNarrowWidth ? styles.mobileMinHeight : undefined, }} loading={loading} type="primary" > Enable </ButtonWithLoading> </ModalButtons> </form> </Modal> ); }