const React = require('react'); const { TextInput, TouchableOpacity, Linking, View, StyleSheet, Text, Button, ScrollView } = require('react-native'); const EncryptionService = require('lib/services/EncryptionService'); const { connect } = require('react-redux'); const { ScreenHeader } = require('lib/components/screen-header.js'); const { _ } = require('lib/locale.js'); const { BaseScreenComponent } = require('lib/components/base-screen.js'); const { themeStyle } = require('lib/components/global-style.js'); const { time } = require('lib/time-utils.js'); const shared = require('lib/components/shared/encryption-config-shared.js'); const { dialogs } = require('lib/dialogs.js'); const DialogBox = require('react-native-dialogbox').default; class EncryptionConfigScreenComponent extends BaseScreenComponent { static navigationOptions() { return { header: null }; } constructor() { super(); this.state = { passwordPromptShow: false, passwordPromptAnswer: '', passwordPromptConfirmAnswer: '', }; shared.constructor(this); this.styles_ = {}; } componentWillUnmount() { this.isMounted_ = false; } initState(props) { return shared.initState(this, props); } async refreshStats() { return shared.refreshStats(this); } componentDidMount() { this.isMounted_ = true; shared.componentDidMount(this); } componentDidUpdate(prevProps) { shared.componentDidUpdate(this, prevProps); } async checkPasswords() { return shared.checkPasswords(this); } styles() { const themeId = this.props.theme; const theme = themeStyle(themeId); if (this.styles_[themeId]) return this.styles_[themeId]; this.styles_ = {}; 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, }, }; this.styles_[themeId] = StyleSheet.create(styles); return this.styles_[themeId]; } renderMasterKey(num, mk) { const theme = themeStyle(this.props.theme); const onSaveClick = () => { return shared.onSavePasswordClick(this, mk); }; const onPasswordChange = text => { return shared.onPasswordChange(this, mk, text); }; const password = this.props.passwords[mk.id] ? this.props.passwords[mk.id] : ''; const passwordOk = this.state.passwordChecks[mk.id] === true ? '✔' : '❌'; const inputStyle = { flex: 1, marginRight: 10, color: theme.color }; inputStyle.borderBottomWidth = 1; inputStyle.borderBottomColor = theme.strongDividerColor; return ( {_('Master Key %s', mk.id.substr(0, 6))} {_('Created: %s', time.formatMsToLocal(mk.created_time))} {_('Password:')} onPasswordChange(text)} style={inputStyle}> {passwordOk} ); } passwordPromptComponent() { const theme = themeStyle(this.props.theme); const onEnableClick = async () => { try { const password = this.state.passwordPromptAnswer; if (!password) throw new Error(_('Password cannot be empty')); const password2 = this.state.passwordPromptConfirmAnswer; if (!password2) throw new Error(_('Confirm password cannot be empty')); if (password !== password2) throw new Error(_('Passwords do not match!')); await EncryptionService.instance().generateMasterKeyAndEnableEncryption(password); this.setState({ passwordPromptShow: false }); } catch (error) { await dialogs.error(this, error.message); } }; return ( {_('Enabling encryption means *all* your notes and attachments are going to be re-synchronised and sent encrypted to the sync target. Do not lose the password as, for security purposes, this will be the *only* way to decrypt the data! To enable encryption, please enter your password below.')} {_('Password:')} { this.setState({ passwordPromptAnswer: text }); }} > {_('Confirm password:')} { this.setState({ passwordPromptConfirmAnswer: text }); }} > ); } render() { const theme = themeStyle(this.props.theme); const masterKeys = this.props.masterKeys; const decryptedItemsInfo = this.props.encryptionEnabled ? {shared.decryptedStatText(this)} : null; const mkComps = []; const nonExistingMasterKeyIds = this.props.notLoadedMasterKeys.slice(); for (let i = 0; i < masterKeys.length; i++) { const mk = masterKeys[i]; mkComps.push(this.renderMasterKey(i + 1, mk)); const idx = nonExistingMasterKeyIds.indexOf(mk.id); if (idx >= 0) nonExistingMasterKeyIds.splice(idx, 1); } const onToggleButtonClick = async () => { if (this.props.encryptionEnabled) { const ok = await dialogs.confirm(this, _('Disabling encryption means *all* your notes and attachments are going to be re-synchronised and sent unencrypted to the sync target. Do you wish to continue?')); if (!ok) return; try { await EncryptionService.instance().disableEncryption(); } catch (error) { await dialogs.error(this, error.message); } } else { this.setState({ passwordPromptShow: true, passwordPromptAnswer: '', passwordPromptConfirmAnswer: '', }); return; } }; let nonExistingMasterKeySection = null; if (nonExistingMasterKeyIds.length) { const rows = []; for (let i = 0; i < nonExistingMasterKeyIds.length; i++) { const id = nonExistingMasterKeyIds[i]; rows.push( {id} ); } nonExistingMasterKeySection = ( {_('Missing Master Keys')} {_('The master keys with these IDs are used to encrypt some of your items, however the application does not currently have access to them. It is likely they will eventually be downloaded via synchronisation.')} {rows} ); } const passwordPromptComp = this.state.passwordPromptShow ? this.passwordPromptComponent() : null; const toggleButton = !this.state.passwordPromptShow ? ( ) : 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/e2ee/'); }} > https://joplinapp.org/e2ee/ } {_('Status')} {_('Encryption is: %s', this.props.encryptionEnabled ? _('Enabled') : _('Disabled'))} {decryptedItemsInfo} {toggleButton} {passwordPromptComp} {mkComps} {nonExistingMasterKeySection} { this.dialogbox = dialogbox; }} /> ); } } const EncryptionConfigScreen = connect(state => { return { theme: state.settings.theme, masterKeys: state.masterKeys, passwords: state.settings['encryption.passwordCache'], encryptionEnabled: state.settings['encryption.enabled'], activeMasterKeyId: state.settings['encryption.activeMasterKeyId'], notLoadedMasterKeys: state.notLoadedMasterKeys, }; })(EncryptionConfigScreenComponent); module.exports = { EncryptionConfigScreen };