You've already forked joplin
mirror of
https://github.com/laurent22/joplin.git
synced 2025-06-18 23:07:45 +02:00
Assets
CliClient
CliClientDemo
ElectronClient
ReactNativeClient
android
ios
lib
components
screens
NoteTagsDialog.js
config.js
dropbox-login.js
encryption-config.js
folder.js
log.js
note.js
notes.js
onedrive-login.js
search.js
status.js
tag.js
welcome.js
shared
Dropdown.js
ItemList.js
ModalDialog.js
SaveIcon.png
action-button.js
app-nav.js
base-screen.js
checkbox.js
global-style.js
note-body-viewer.js
note-item.js
note-list.js
screen-header.js
select-date-time-dialog.js
side-menu-content.js
side-menu.js
images
models
services
vendor
ArrayUtils.js
BaseApplication.js
BaseModel.js
BaseSyncTarget.js
Cache.js
DropboxApi.js
EventDispatcher.js
HtmlToMarkdownParser.js
JoplinError.js
MdToHtml.js
MdToHtml_Katex.js
ModelCache.js
ObjectUtils.js
SyncTargetDropbox.js
SyncTargetFilesystem.js
SyncTargetMemory.js
SyncTargetNextcloud.js
SyncTargetOneDrive.js
SyncTargetOneDriveDev.js
SyncTargetRegistry.js
SyncTargetWebDAV.js
WebDavApi.js
database-driver-node.js
database-driver-react-native.js
database.js
dialogs.js
file-api-driver-dropbox.js
file-api-driver-local.js
file-api-driver-memory.js
file-api-driver-onedrive.js
file-api-driver-webdav.js
file-api.js
folders-screen-utils.js
fs-driver-base.js
fs-driver-dummy.js
fs-driver-node.js
fs-driver-rn.js
geolocation-node.js
geolocation-react.js
import-enex-md-gen.js
import-enex.js
joplin-database.js
layout-utils.js
locale.js
logger.js
markdown-utils.js
mime-utils.js
net-utils.js
onedrive-api.js
package.json
parameters.js
parseUri.js
path-utils.js
poor-man-intervals.js
promise-utils.js
react-logger.js
reducer.js
registry.js
shim-init-node.js
shim-init-react.js
shim.js
string-utils.js
synchronizer.js
time-utils.js
urlUtils.js
uuid.js
locales
.babelrc
.buckconfig
.flowconfig
.gitattributes
.gitignore
.watchmanconfig
app.json
build_android.bat
build_android_prod.bat
clean_build.bat
debug_log.bat
debug_log.sh
index.android.js
index.ios.js
index.js
main.js
package-lock.json
package.json
root.js
start_emulator.bat
start_server.bat
start_server.sh
Tools
docs
readme
.gitignore
.travis.yml
BUILD.md
CONTRIBUTING.md
LICENSE
README.md
_config.yml
appveyor.yml
joplin.sublime-project
linkToLocal.sh
259 lines
9.0 KiB
JavaScript
259 lines
9.0 KiB
JavaScript
const React = require('react'); const Component = React.Component;
|
|
const { TextInput, TouchableOpacity, Linking, View, Switch, Slider, StyleSheet, Text, Button, ScrollView, Platform } = 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 { Dropdown } = require('lib/components/Dropdown.js');
|
|
const { themeStyle } = require('lib/components/global-style.js');
|
|
const { time } = require('lib/time-utils.js');
|
|
const Setting = require('lib/models/Setting.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(options) {
|
|
return { header: null };
|
|
}
|
|
|
|
constructor() {
|
|
super();
|
|
|
|
this.state = {
|
|
passwordPromptShow: false,
|
|
passwordPromptAnswer: '',
|
|
};
|
|
|
|
shared.constructor(this);
|
|
|
|
this.styles_ = {};
|
|
}
|
|
|
|
componentDidMount() {
|
|
this.isMounted_ = true;
|
|
}
|
|
|
|
componentWillUnmount() {
|
|
this.isMounted_ = false;
|
|
}
|
|
|
|
initState(props) {
|
|
return shared.initState(this, props);
|
|
}
|
|
|
|
async refreshStats() {
|
|
return shared.refreshStats(this);
|
|
}
|
|
|
|
UNSAFE_componentWillMount() {
|
|
this.initState(this.props);
|
|
}
|
|
|
|
UNSAFE_componentWillReceiveProps(nextProps) {
|
|
this.initState(nextProps);
|
|
}
|
|
|
|
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_ = {};
|
|
|
|
let 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,
|
|
},
|
|
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.state.passwords[mk.id] ? this.state.passwords[mk.id] : '';
|
|
const passwordOk = this.state.passwordChecks[mk.id] === true ? '✔' : '❌';
|
|
const active = this.props.activeMasterKeyId === mk.id ? '✔' : '';
|
|
|
|
const inputStyle = {flex:1, marginRight: 10, color: theme.color};
|
|
|
|
if (Platform.OS === 'ios') {
|
|
inputStyle.borderBottomWidth = 1;
|
|
inputStyle.borderBottomColor = theme.dividerColor;
|
|
}
|
|
|
|
return (
|
|
<View key={mk.id}>
|
|
<Text style={this.styles().titleText}>{_('Master Key %s', mk.id.substr(0,6))}</Text>
|
|
<Text style={this.styles().normalText}>{_('Created: %s', time.formatMsToLocal(mk.created_time))}</Text>
|
|
<View style={{flexDirection: 'row', alignItems: 'center'}}>
|
|
<Text style={{flex:0, fontSize: theme.fontSize, marginRight: 10, color: theme.color}}>{_('Password:')}</Text>
|
|
<TextInput selectionColor={theme.textSelectionColor} secureTextEntry={true} value={password} onChangeText={(text) => onPasswordChange(text)} style={inputStyle}></TextInput>
|
|
<Text style={{fontSize: theme.fontSize, marginRight: 10, color: theme.color}}>{passwordOk}</Text>
|
|
<Button title={_('Save')} onPress={() => onSaveClick()}></Button>
|
|
</View>
|
|
</View>
|
|
);
|
|
}
|
|
|
|
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'));
|
|
await EncryptionService.instance().generateMasterKeyAndEnableEncryption(password);
|
|
this.setState({ passwordPromptShow: false });
|
|
} catch (error) {
|
|
await dialogs.error(this, error.message);
|
|
}
|
|
}
|
|
|
|
return (
|
|
<View style={{flex:1, borderColor: theme.dividerColor, borderWidth: 1, padding: 10, marginTop: 10, marginBottom: 10}}>
|
|
<Text style={{fontSize: theme.fontSize, color: theme.color}}>{_('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.')}</Text>
|
|
<TextInput selectionColor={theme.textSelectionColor} style={{margin: 10, color: theme.color, borderWidth: 1, borderColor: theme.dividerColor }} secureTextEntry={true} value={this.state.passwordPromptAnswer} onChangeText={(text) => { this.setState({ passwordPromptAnswer: text }) }}></TextInput>
|
|
<View style={{flexDirection: 'row'}}>
|
|
<View style={{flex:1 , marginRight:10}} >
|
|
<Button title={_('Enable')} onPress={() => { onEnableClick() }}></Button>
|
|
</View>
|
|
<View style={{flex:1}} >
|
|
<Button title={_('Cancel')} onPress={() => { this.setState({ passwordPromptShow: false}) }}></Button>
|
|
</View>
|
|
</View>
|
|
</View>
|
|
);
|
|
}
|
|
|
|
render() {
|
|
const theme = themeStyle(this.props.theme);
|
|
const masterKeys = this.state.masterKeys;
|
|
const decryptedItemsInfo = this.props.encryptionEnabled ? <Text style={this.styles().normalText}>{shared.decryptedStatText(this)}</Text> : null;
|
|
|
|
const mkComps = [];
|
|
|
|
let 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: '',
|
|
});
|
|
return;
|
|
}
|
|
};
|
|
|
|
let nonExistingMasterKeySection = null;
|
|
|
|
if (nonExistingMasterKeyIds.length) {
|
|
const rows = [];
|
|
for (let i = 0; i < nonExistingMasterKeyIds.length; i++) {
|
|
const id = nonExistingMasterKeyIds[i];
|
|
rows.push(<Text style={this.styles().normalText} key={id}>{id}</Text>);
|
|
}
|
|
|
|
nonExistingMasterKeySection = (
|
|
<View>
|
|
<Text style={this.styles().titleText}>{_('Missing Master Keys')}</Text>
|
|
<Text style={this.styles().normalText}>{_('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.')}</Text>
|
|
<View style={{marginTop: 10}}>{rows}</View>
|
|
</View>
|
|
);
|
|
}
|
|
|
|
const passwordPromptComp = this.state.passwordPromptShow ? this.passwordPromptComponent() : null;
|
|
const toggleButton = !this.state.passwordPromptShow ? <View style={{marginTop: 10}}><Button title={this.props.encryptionEnabled ? _('Disable encryption') : _('Enable encryption')} onPress={() => onToggleButtonClick()}></Button></View> : null;
|
|
|
|
return (
|
|
<View style={this.rootStyle(this.props.theme).root}>
|
|
<ScreenHeader title={_('Encryption Config')}/>
|
|
<ScrollView style={this.styles().container}>
|
|
|
|
{<View style={{backgroundColor: theme.warningBackgroundColor, paddingTop: 5, paddingBottom: 5, paddingLeft: 10, paddingRight: 10 }}>
|
|
<Text>{_('For more information about End-To-End Encryption (E2EE) and advices on how to enable it please check the documentation:')}</Text>
|
|
<TouchableOpacity onPress={() => { Linking.openURL('https://joplin.cozic.net/e2ee') }}><Text>https://joplin.cozic.net/e2ee</Text></TouchableOpacity>
|
|
</View>}
|
|
|
|
<Text style={this.styles().titleText}>{_('Status')}</Text>
|
|
<Text style={this.styles().normalText}>{_('Encryption is: %s', this.props.encryptionEnabled ? _('Enabled') : _('Disabled'))}</Text>
|
|
{decryptedItemsInfo}
|
|
{toggleButton}
|
|
{passwordPromptComp}
|
|
{mkComps}
|
|
{nonExistingMasterKeySection}
|
|
<View style={{flex:1, height: 20}}></View>
|
|
</ScrollView>
|
|
<DialogBox ref={dialogbox => { this.dialogbox = dialogbox }}/>
|
|
</View>
|
|
);
|
|
}
|
|
|
|
}
|
|
|
|
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 }; |