const React = require('react'); const { TextInput, TouchableOpacity, Linking, View, StyleSheet, Text, Button, ScrollView } = require('react-native'); const { connect } = require('react-redux'); import ScreenHeader from '../ScreenHeader'; const { themeStyle } = require('../global-style.js'); const DialogBox = require('react-native-dialogbox').default; const { dialogs } = require('../../utils/dialogs.js'); import EncryptionService from '@joplin/lib/services/e2ee/EncryptionService'; import { _ } from '@joplin/lib/locale'; import time from '@joplin/lib/time'; import { decryptedStatText, enableEncryptionConfirmationMessages, onSavePasswordClick, useInputMasterPassword, useInputPasswords, usePasswordChecker, useStats } from '@joplin/lib/components/EncryptionConfigScreen/utils'; import { MasterKeyEntity } from '@joplin/lib/services/e2ee/types'; import { State } from '@joplin/lib/reducer'; import { SyncInfo } from '@joplin/lib/services/synchronizer/syncInfoUtils'; import { getDefaultMasterKey, setupAndDisableEncryption, toggleAndSetupEncryption } from '@joplin/lib/services/e2ee/utils'; import { useMemo, useRef, useState } from 'react'; interface Props { themeId: any; masterKeys: MasterKeyEntity[]; passwords: Record; notLoadedMasterKeys: string[]; encryptionEnabled: boolean; shouldReencrypt: boolean; activeMasterKeyId: string; masterPassword: string; } const EncryptionConfigScreen = (props: Props) => { const [passwordPromptShow, setPasswordPromptShow] = useState(false); const [passwordPromptAnswer, setPasswordPromptAnswer] = useState(''); const [passwordPromptConfirmAnswer, setPasswordPromptConfirmAnswer] = useState(''); const stats = useStats(); const { passwordChecks, masterPasswordKeys } = usePasswordChecker(props.masterKeys, props.activeMasterKeyId, props.masterPassword, props.passwords); const { inputPasswords, onInputPasswordChange } = useInputPasswords(props.passwords); const { inputMasterPassword, onMasterPasswordSave, onMasterPasswordChange } = useInputMasterPassword(props.masterKeys, props.activeMasterKeyId); const dialogBoxRef = useRef(null); const mkComps = []; const nonExistingMasterKeyIds = props.notLoadedMasterKeys.slice(); const theme: any = useMemo(() => { return themeStyle(props.themeId); }, [props.themeId]); const rootStyle = useMemo(() => { return { flex: 1, backgroundColor: theme.backgroundColor, }; }, [theme]); const styles = useMemo(() => { const styles = { titleText: { flex: 1, fontWeight: 'bold', flexDirection: 'column', fontSize: theme.fontSize, paddingTop: 5, paddingBottom: 5, marginTop: theme.marginTop, marginBottom: 5, color: theme.color, }, normalText: { flex: 1, fontSize: theme.fontSize, color: theme.color, }, normalTextInput: { margin: 10, color: theme.color, borderWidth: 1, borderColor: theme.dividerColor, }, container: { flex: 1, padding: theme.margin, }, }; return StyleSheet.create(styles); }, [theme]); const decryptedItemsInfo = props.encryptionEnabled ? {decryptedStatText(stats)} : null; const renderMasterKey = (_num: number, mk: MasterKeyEntity) => { const theme = themeStyle(props.themeId); const password = inputPasswords[mk.id] ? inputPasswords[mk.id] : ''; const passwordOk = passwordChecks[mk.id] === true ? '✔' : '❌'; const inputStyle: any = { flex: 1, marginRight: 10, color: theme.color }; inputStyle.borderBottomWidth = 1; inputStyle.borderBottomColor = theme.dividerColor; const renderPasswordInput = (masterKeyId: string) => { if (masterPasswordKeys[masterKeyId] || !passwordChecks['master']) { return ( ({_('Master password')}) ); } else { return ( onInputPasswordChange(mk, text)} style={inputStyle}> {passwordOk} ); } }; return ( {_('Master Key %s', mk.id.substr(0, 6))} {_('Created: %s', time.formatMsToLocal(mk.created_time))} {_('Password:')} {renderPasswordInput(mk.id)} ); }; const renderPasswordPrompt = () => { const theme = themeStyle(props.themeId); const masterKey = getDefaultMasterKey(); const hasMasterPassword = !!props.masterPassword; const onEnableClick = async () => { try { const password = passwordPromptAnswer; if (!password) throw new Error(_('Password cannot be empty')); const password2 = passwordPromptConfirmAnswer; if (!password2) throw new Error(_('Confirm password cannot be empty')); if (password !== password2) throw new Error(_('Passwords do not match!')); await toggleAndSetupEncryption(EncryptionService.instance(), true, masterKey, password); // await generateMasterKeyAndEnableEncryption(EncryptionService.instance(), password); setPasswordPromptShow(false); } catch (error) { alert(error.message); } }; const messages = enableEncryptionConfirmationMessages(masterKey, hasMasterPassword); const messageComps = messages.map((msg: string) => { return {msg}; }); return ( {messageComps} {_('Password:')} { setPasswordPromptAnswer(text); }} > {_('Confirm password:')} { setPasswordPromptConfirmAnswer(text); }} > ); }; const renderMasterPassword = () => { if (!props.encryptionEnabled && !props.masterKeys.length) return null; const inputStyle: any = { flex: 1, marginRight: 10, color: theme.color }; inputStyle.borderBottomWidth = 1; inputStyle.borderBottomColor = theme.dividerColor; if (passwordChecks['master']) { return ( {_('Master password:')} {_('Loaded')} ); } else { return ( {'The master password is not set or is invalid. Please type it below:'} onMasterPasswordChange(text)} style={inputStyle}> ) : null; return ( { {_('For more information about End-To-End Encryption (E2EE) and advice on how to enable it please check the documentation:')} { Linking.openURL('https://joplinapp.org/help/apps/sync/e2ee'); }} > https://joplinapp.org/help/apps/sync/e2ee } {_('Status')} {_('Encryption is: %s', props.encryptionEnabled ? _('Enabled') : _('Disabled'))} {decryptedItemsInfo} {renderMasterPassword()} {toggleButton} {passwordPromptComp} {mkComps} {nonExistingMasterKeySection} ); }; export default connect((state: State) => { const syncInfo = new SyncInfo(state.settings['syncInfoCache']); return { themeId: state.settings.theme, masterKeys: syncInfo.masterKeys, passwords: state.settings['encryption.passwordCache'], encryptionEnabled: syncInfo.e2ee, activeMasterKeyId: syncInfo.activeMasterKeyId, notLoadedMasterKeys: state.notLoadedMasterKeys, masterPassword: state.settings['encryption.masterPassword'], }; })(EncryptionConfigScreen);