mirror of
https://github.com/laurent22/joplin.git
synced 2025-04-23 11:52:59 +02:00
This commit is contained in:
parent
69b24b4437
commit
55a57f7baf
@ -787,6 +787,7 @@ packages/app-mobile/components/screens/ShareManager/index.test.js
|
|||||||
packages/app-mobile/components/screens/ShareManager/index.js
|
packages/app-mobile/components/screens/ShareManager/index.js
|
||||||
packages/app-mobile/components/screens/UpgradeSyncTargetScreen.js
|
packages/app-mobile/components/screens/UpgradeSyncTargetScreen.js
|
||||||
packages/app-mobile/components/screens/dropbox-login.js
|
packages/app-mobile/components/screens/dropbox-login.js
|
||||||
|
packages/app-mobile/components/screens/encryption-config.test.js
|
||||||
packages/app-mobile/components/screens/encryption-config.js
|
packages/app-mobile/components/screens/encryption-config.js
|
||||||
packages/app-mobile/components/screens/status.js
|
packages/app-mobile/components/screens/status.js
|
||||||
packages/app-mobile/components/screens/tags.js
|
packages/app-mobile/components/screens/tags.js
|
||||||
|
1
.gitignore
vendored
1
.gitignore
vendored
@ -762,6 +762,7 @@ packages/app-mobile/components/screens/ShareManager/index.test.js
|
|||||||
packages/app-mobile/components/screens/ShareManager/index.js
|
packages/app-mobile/components/screens/ShareManager/index.js
|
||||||
packages/app-mobile/components/screens/UpgradeSyncTargetScreen.js
|
packages/app-mobile/components/screens/UpgradeSyncTargetScreen.js
|
||||||
packages/app-mobile/components/screens/dropbox-login.js
|
packages/app-mobile/components/screens/dropbox-login.js
|
||||||
|
packages/app-mobile/components/screens/encryption-config.test.js
|
||||||
packages/app-mobile/components/screens/encryption-config.js
|
packages/app-mobile/components/screens/encryption-config.js
|
||||||
packages/app-mobile/components/screens/status.js
|
packages/app-mobile/components/screens/status.js
|
||||||
packages/app-mobile/components/screens/tags.js
|
packages/app-mobile/components/screens/tags.js
|
||||||
|
@ -0,0 +1,74 @@
|
|||||||
|
import * as React from 'react';
|
||||||
|
import { Store } from 'redux';
|
||||||
|
import { AppState } from '../../utils/types';
|
||||||
|
import TestProviderStack from '../testing/TestProviderStack';
|
||||||
|
import EncryptionConfig from './encryption-config';
|
||||||
|
import { loadEncryptionMasterKey, setupDatabaseAndSynchronizer, switchClient, synchronizerStart } from '@joplin/lib/testing/test-utils';
|
||||||
|
import createMockReduxStore from '../../utils/testing/createMockReduxStore';
|
||||||
|
import setupGlobalStore from '../../utils/testing/setupGlobalStore';
|
||||||
|
import { getActiveMasterKeyId, setEncryptionEnabled, setMasterKeyEnabled } from '@joplin/lib/services/synchronizer/syncInfoUtils';
|
||||||
|
import { act, render, screen } from '@testing-library/react-native';
|
||||||
|
import '@testing-library/jest-native/extend-expect';
|
||||||
|
|
||||||
|
interface WrapperProps { }
|
||||||
|
|
||||||
|
let store: Store<AppState>;
|
||||||
|
const WrappedEncryptionConfigScreen: React.FC<WrapperProps> = _props => {
|
||||||
|
return <TestProviderStack store={store}>
|
||||||
|
<EncryptionConfig/>
|
||||||
|
</TestProviderStack>;
|
||||||
|
};
|
||||||
|
|
||||||
|
describe('encryption-config', () => {
|
||||||
|
beforeEach(async () => {
|
||||||
|
await setupDatabaseAndSynchronizer(1);
|
||||||
|
await setupDatabaseAndSynchronizer(0);
|
||||||
|
await switchClient(0);
|
||||||
|
|
||||||
|
setEncryptionEnabled(true);
|
||||||
|
await loadEncryptionMasterKey();
|
||||||
|
|
||||||
|
store = createMockReduxStore();
|
||||||
|
setupGlobalStore(store);
|
||||||
|
});
|
||||||
|
afterEach(() => {
|
||||||
|
screen.unmount();
|
||||||
|
});
|
||||||
|
|
||||||
|
test('should show an input for entering the master password after an initial sync', async () => {
|
||||||
|
// Switch to the other client and sync so that there's a master key missing
|
||||||
|
// a password
|
||||||
|
await synchronizerStart();
|
||||||
|
await switchClient(1);
|
||||||
|
await synchronizerStart();
|
||||||
|
|
||||||
|
const { unmount } = render(<WrappedEncryptionConfigScreen/>);
|
||||||
|
|
||||||
|
// Should auto-enable encryption
|
||||||
|
expect(screen.getByText('Encryption is: Enabled')).toBeVisible();
|
||||||
|
const passwordInput = screen.getByLabelText(/The master password is not set/);
|
||||||
|
expect(passwordInput).toBeVisible();
|
||||||
|
|
||||||
|
// Unmount here to prevent "An update to EncryptionConfigScreen inside a test was not wrapped in act(...)"
|
||||||
|
// errors
|
||||||
|
unmount();
|
||||||
|
});
|
||||||
|
|
||||||
|
test('should not show the "disabled keys" dropdown unless there are disabled keys', async () => {
|
||||||
|
const masterKeyId = getActiveMasterKeyId();
|
||||||
|
setMasterKeyEnabled(masterKeyId, false);
|
||||||
|
|
||||||
|
const { unmount } = render(<WrappedEncryptionConfigScreen/>);
|
||||||
|
|
||||||
|
const queryDisabledKeysButton = () => screen.queryByRole('button', { name: 'Disabled keys' });
|
||||||
|
|
||||||
|
// Should be visible when there are disabled keys
|
||||||
|
expect(queryDisabledKeysButton()).toBeVisible();
|
||||||
|
|
||||||
|
// Enabling the key should hide the button
|
||||||
|
act(() => setMasterKeyEnabled(masterKeyId, true));
|
||||||
|
expect(queryDisabledKeysButton()).toBeNull();
|
||||||
|
|
||||||
|
unmount();
|
||||||
|
});
|
||||||
|
});
|
@ -1,6 +1,6 @@
|
|||||||
const React = require('react');
|
import * as React from 'react';
|
||||||
const { TextInput, TouchableOpacity, Linking, View, StyleSheet, Text, Button, ScrollView } = require('react-native');
|
import { TextInput, TouchableOpacity, Linking, View, StyleSheet, Text, Button, ScrollView } from 'react-native';
|
||||||
const { connect } = require('react-redux');
|
import { connect } from 'react-redux';
|
||||||
import ScreenHeader from '../ScreenHeader';
|
import ScreenHeader from '../ScreenHeader';
|
||||||
import { themeStyle } from '../global-style';
|
import { themeStyle } from '../global-style';
|
||||||
import EncryptionService from '@joplin/lib/services/e2ee/EncryptionService';
|
import EncryptionService from '@joplin/lib/services/e2ee/EncryptionService';
|
||||||
@ -16,13 +16,11 @@ import { Divider, List } from 'react-native-paper';
|
|||||||
import shim from '@joplin/lib/shim';
|
import shim from '@joplin/lib/shim';
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- Old code before rule was applied
|
themeId: number;
|
||||||
themeId: any;
|
|
||||||
masterKeys: MasterKeyEntity[];
|
masterKeys: MasterKeyEntity[];
|
||||||
passwords: Record<string, string>;
|
passwords: Record<string, string>;
|
||||||
notLoadedMasterKeys: string[];
|
notLoadedMasterKeys: string[];
|
||||||
encryptionEnabled: boolean;
|
encryptionEnabled: boolean;
|
||||||
shouldReencrypt: boolean;
|
|
||||||
activeMasterKeyId: string;
|
activeMasterKeyId: string;
|
||||||
masterPassword: string;
|
masterPassword: string;
|
||||||
}
|
}
|
||||||
@ -54,7 +52,7 @@ const EncryptionConfigScreen = (props: Props) => {
|
|||||||
}, [theme]);
|
}, [theme]);
|
||||||
|
|
||||||
const styles = useMemo(() => {
|
const styles = useMemo(() => {
|
||||||
const styles = {
|
return StyleSheet.create({
|
||||||
titleText: {
|
titleText: {
|
||||||
flex: 1,
|
flex: 1,
|
||||||
fontWeight: 'bold',
|
fontWeight: 'bold',
|
||||||
@ -85,9 +83,7 @@ const EncryptionConfigScreen = (props: Props) => {
|
|||||||
paddingLeft: theme.margin,
|
paddingLeft: theme.margin,
|
||||||
paddingRight: theme.margin,
|
paddingRight: theme.margin,
|
||||||
},
|
},
|
||||||
};
|
});
|
||||||
|
|
||||||
return StyleSheet.create(styles);
|
|
||||||
}, [theme]);
|
}, [theme]);
|
||||||
|
|
||||||
const decryptedItemsInfo = props.encryptionEnabled ? <Text style={styles.normalText}>{decryptedStatText(stats)}</Text> : null;
|
const decryptedItemsInfo = props.encryptionEnabled ? <Text style={styles.normalText}>{decryptedStatText(stats)}</Text> : null;
|
||||||
@ -96,7 +92,8 @@ const EncryptionConfigScreen = (props: Props) => {
|
|||||||
const theme = themeStyle(props.themeId);
|
const theme = themeStyle(props.themeId);
|
||||||
|
|
||||||
const password = inputPasswords[mk.id] ? inputPasswords[mk.id] : '';
|
const password = inputPasswords[mk.id] ? inputPasswords[mk.id] : '';
|
||||||
const passwordOk = passwordChecks[mk.id] === true ? '✔' : '❌';
|
const passwordOk = passwordChecks[mk.id] === true;
|
||||||
|
const passwordOkIcon = passwordOk ? '✔' : '❌';
|
||||||
|
|
||||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- Old code before rule was applied
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- Old code before rule was applied
|
||||||
const inputStyle: any = { flex: 1, marginRight: 10, color: theme.color };
|
const inputStyle: any = { flex: 1, marginRight: 10, color: theme.color };
|
||||||
@ -111,8 +108,19 @@ const EncryptionConfigScreen = (props: Props) => {
|
|||||||
} else {
|
} else {
|
||||||
return (
|
return (
|
||||||
<View style={{ flex: 1, flexDirection: 'row', alignItems: 'center' }}>
|
<View style={{ flex: 1, flexDirection: 'row', alignItems: 'center' }}>
|
||||||
<TextInput selectionColor={theme.textSelectionColor} keyboardAppearance={theme.keyboardAppearance} secureTextEntry={true} value={password} onChangeText={(text: string) => onInputPasswordChange(mk, text)} style={inputStyle}></TextInput>
|
<TextInput
|
||||||
<Text style={{ fontSize: theme.fontSize, marginRight: 10, color: theme.color }}>{passwordOk}</Text>
|
selectionColor={theme.textSelectionColor}
|
||||||
|
keyboardAppearance={theme.keyboardAppearance}
|
||||||
|
secureTextEntry={true}
|
||||||
|
value={password}
|
||||||
|
onChangeText={(text: string) => onInputPasswordChange(mk, text)}
|
||||||
|
style={inputStyle}
|
||||||
|
/>
|
||||||
|
<Text
|
||||||
|
style={{ fontSize: theme.fontSize, marginRight: 10, color: theme.color }}
|
||||||
|
accessibilityRole='image'
|
||||||
|
accessibilityLabel={passwordOk ? _('Valid') : _('Invalid password')}
|
||||||
|
>{passwordOkIcon}</Text>
|
||||||
<Button title={_('Save')} onPress={() => onSavePasswordClick(mk, inputPasswords)}></Button>
|
<Button title={_('Save')} onPress={() => onSavePasswordClick(mk, inputPasswords)}></Button>
|
||||||
</View>
|
</View>
|
||||||
);
|
);
|
||||||
@ -121,7 +129,10 @@ const EncryptionConfigScreen = (props: Props) => {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<View key={mk.id}>
|
<View key={mk.id}>
|
||||||
<Text style={styles.titleText}>{_('Master Key %s', mk.id.substr(0, 6))}</Text>
|
<Text
|
||||||
|
style={styles.titleText}
|
||||||
|
accessibilityRole='header'
|
||||||
|
>{_('Master Key %s', mk.id.substr(0, 6))}</Text>
|
||||||
<Text style={styles.normalText}>{_('Created: %s', time.formatMsToLocal(mk.created_time))}</Text>
|
<Text style={styles.normalText}>{_('Created: %s', time.formatMsToLocal(mk.created_time))}</Text>
|
||||||
<View style={{ flexDirection: 'row', alignItems: 'center' }}>
|
<View style={{ flexDirection: 'row', alignItems: 'center' }}>
|
||||||
<Text style={{ flex: 0, fontSize: theme.fontSize, marginRight: 10, color: theme.color }}>{_('Password:')}</Text>
|
<Text style={{ flex: 0, fontSize: theme.fontSize, marginRight: 10, color: theme.color }}>{_('Password:')}</Text>
|
||||||
@ -157,11 +168,14 @@ const EncryptionConfigScreen = (props: Props) => {
|
|||||||
return <Text key={msg} style={{ fontSize: theme.fontSize, color: theme.color, marginBottom: 10 }}>{msg}</Text>;
|
return <Text key={msg} style={{ fontSize: theme.fontSize, color: theme.color, marginBottom: 10 }}>{msg}</Text>;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const passwordLabelId = 'password-label';
|
||||||
|
const confirmPasswordLabelId = 'confirm-password';
|
||||||
return (
|
return (
|
||||||
<View style={{ flex: 1, flexBasis: 'auto', borderColor: theme.dividerColor, borderWidth: 1, padding: 10, marginTop: 10, marginBottom: 10 }}>
|
<View style={{ flex: 1, flexBasis: 'auto', borderColor: theme.dividerColor, borderWidth: 1, padding: 10, marginTop: 10, marginBottom: 10 }}>
|
||||||
<View>{messageComps}</View>
|
<View>{messageComps}</View>
|
||||||
<Text style={styles.normalText}>{_('Password:')}</Text>
|
<Text nativeID={passwordLabelId} style={styles.normalText}>{_('Password:')}</Text>
|
||||||
<TextInput
|
<TextInput
|
||||||
|
accessibilityLabelledBy={passwordLabelId}
|
||||||
selectionColor={theme.textSelectionColor}
|
selectionColor={theme.textSelectionColor}
|
||||||
keyboardAppearance={theme.keyboardAppearance}
|
keyboardAppearance={theme.keyboardAppearance}
|
||||||
style={styles.normalTextInput}
|
style={styles.normalTextInput}
|
||||||
@ -172,8 +186,9 @@ const EncryptionConfigScreen = (props: Props) => {
|
|||||||
}}
|
}}
|
||||||
></TextInput>
|
></TextInput>
|
||||||
|
|
||||||
<Text style={styles.normalText}>{_('Confirm password:')}</Text>
|
<Text nativeID={confirmPasswordLabelId} style={styles.normalText}>{_('Confirm password:')}</Text>
|
||||||
<TextInput
|
<TextInput
|
||||||
|
accessibilityLabelledBy={confirmPasswordLabelId}
|
||||||
selectionColor={theme.textSelectionColor}
|
selectionColor={theme.textSelectionColor}
|
||||||
keyboardAppearance={theme.keyboardAppearance}
|
keyboardAppearance={theme.keyboardAppearance}
|
||||||
style={styles.normalTextInput}
|
style={styles.normalTextInput}
|
||||||
@ -221,11 +236,23 @@ const EncryptionConfigScreen = (props: Props) => {
|
|||||||
</View>
|
</View>
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
|
const labelId = 'master-password-label';
|
||||||
return (
|
return (
|
||||||
<View style={{ display: 'flex', flexDirection: 'column', marginTop: 10 }}>
|
<View style={{ display: 'flex', flexDirection: 'column', marginTop: 10 }}>
|
||||||
<Text style={styles.normalText}>{'The master password is not set or is invalid. Please type it below:'}</Text>
|
<Text
|
||||||
|
style={styles.normalText}
|
||||||
|
nativeID={labelId}
|
||||||
|
>{'The master password is not set or is invalid. Please type it below:'}</Text>
|
||||||
<View style={{ display: 'flex', flexDirection: 'row', marginTop: 10 }}>
|
<View style={{ display: 'flex', flexDirection: 'row', marginTop: 10 }}>
|
||||||
<TextInput selectionColor={theme.textSelectionColor} keyboardAppearance={theme.keyboardAppearance} secureTextEntry={true} value={inputMasterPassword} onChangeText={(text: string) => onMasterPasswordChange(text)} style={inputStyle}></TextInput>
|
<TextInput
|
||||||
|
accessibilityLabelledBy={labelId}
|
||||||
|
selectionColor={theme.textSelectionColor}
|
||||||
|
keyboardAppearance={theme.keyboardAppearance}
|
||||||
|
secureTextEntry={true}
|
||||||
|
value={inputMasterPassword}
|
||||||
|
onChangeText={(text: string) => onMasterPasswordChange(text)}
|
||||||
|
style={inputStyle}
|
||||||
|
/>
|
||||||
<Button onPress={onMasterPasswordSave} title={_('Save')} />
|
<Button onPress={onMasterPasswordSave} title={_('Save')} />
|
||||||
</View>
|
</View>
|
||||||
</View>
|
</View>
|
||||||
@ -293,6 +320,17 @@ const EncryptionConfigScreen = (props: Props) => {
|
|||||||
</View>
|
</View>
|
||||||
) : null;
|
) : null;
|
||||||
|
|
||||||
|
const disabledMasterKeyList = disabledMkComps.length ? <List.Accordion
|
||||||
|
title={_('Disabled keys')}
|
||||||
|
titleStyle={styles.titleText}
|
||||||
|
expanded={showDisabledKeys}
|
||||||
|
onPress={() => setShowDisabledKeys(st => !st)}
|
||||||
|
>
|
||||||
|
<View style={styles.disabledContainer}>
|
||||||
|
{disabledMkComps}
|
||||||
|
</View>
|
||||||
|
</List.Accordion> : null;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<View style={rootStyle}>
|
<View style={rootStyle}>
|
||||||
<ScreenHeader title={_('Encryption Config')} />
|
<ScreenHeader title={_('Encryption Config')} />
|
||||||
@ -302,14 +340,18 @@ const EncryptionConfigScreen = (props: Props) => {
|
|||||||
<Text>{_('For more information about End-To-End Encryption (E2EE) and advice on how to enable it please check the documentation:')}</Text>
|
<Text>{_('For more information about End-To-End Encryption (E2EE) and advice on how to enable it please check the documentation:')}</Text>
|
||||||
<TouchableOpacity
|
<TouchableOpacity
|
||||||
onPress={() => {
|
onPress={() => {
|
||||||
Linking.openURL('https://joplinapp.org/help/apps/sync/e2ee');
|
void Linking.openURL('https://joplinapp.org/help/apps/sync/e2ee');
|
||||||
}}
|
}}
|
||||||
|
accessibilityRole='link'
|
||||||
>
|
>
|
||||||
<Text>https://joplinapp.org/help/apps/sync/e2ee</Text>
|
<Text>https://joplinapp.org/help/apps/sync/e2ee</Text>
|
||||||
</TouchableOpacity>
|
</TouchableOpacity>
|
||||||
</View>
|
</View>
|
||||||
|
|
||||||
<Text style={styles.titleText}>{_('Status')}</Text>
|
<Text
|
||||||
|
style={styles.titleText}
|
||||||
|
accessibilityRole='header'
|
||||||
|
>{_('Status')}</Text>
|
||||||
<Text style={styles.normalText}>{_('Encryption is: %s', props.encryptionEnabled ? _('Enabled') : _('Disabled'))}</Text>
|
<Text style={styles.normalText}>{_('Encryption is: %s', props.encryptionEnabled ? _('Enabled') : _('Disabled'))}</Text>
|
||||||
{decryptedItemsInfo}
|
{decryptedItemsInfo}
|
||||||
{renderMasterPassword()}
|
{renderMasterPassword()}
|
||||||
@ -319,16 +361,7 @@ const EncryptionConfigScreen = (props: Props) => {
|
|||||||
{nonExistingMasterKeySection}
|
{nonExistingMasterKeySection}
|
||||||
</View>
|
</View>
|
||||||
<Divider />
|
<Divider />
|
||||||
<List.Accordion
|
{disabledMasterKeyList}
|
||||||
title={_('Disabled keys')}
|
|
||||||
titleStyle={styles.titleText}
|
|
||||||
expanded={showDisabledKeys}
|
|
||||||
onPress={() => setShowDisabledKeys(st => !st)}
|
|
||||||
>
|
|
||||||
<View style={styles.disabledContainer}>
|
|
||||||
{disabledMkComps}
|
|
||||||
</View>
|
|
||||||
</List.Accordion>
|
|
||||||
</ScrollView>
|
</ScrollView>
|
||||||
</View>
|
</View>
|
||||||
);
|
);
|
||||||
|
Loading…
x
Reference in New Issue
Block a user