mirror of
https://github.com/laurent22/joplin.git
synced 2025-04-07 21:38:58 +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/UpgradeSyncTargetScreen.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/status.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/UpgradeSyncTargetScreen.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/status.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');
|
||||
const { TextInput, TouchableOpacity, Linking, View, StyleSheet, Text, Button, ScrollView } = require('react-native');
|
||||
const { connect } = require('react-redux');
|
||||
import * as React from 'react';
|
||||
import { TextInput, TouchableOpacity, Linking, View, StyleSheet, Text, Button, ScrollView } from 'react-native';
|
||||
import { connect } from 'react-redux';
|
||||
import ScreenHeader from '../ScreenHeader';
|
||||
import { themeStyle } from '../global-style';
|
||||
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';
|
||||
|
||||
interface Props {
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- Old code before rule was applied
|
||||
themeId: any;
|
||||
themeId: number;
|
||||
masterKeys: MasterKeyEntity[];
|
||||
passwords: Record<string, string>;
|
||||
notLoadedMasterKeys: string[];
|
||||
encryptionEnabled: boolean;
|
||||
shouldReencrypt: boolean;
|
||||
activeMasterKeyId: string;
|
||||
masterPassword: string;
|
||||
}
|
||||
@ -54,7 +52,7 @@ const EncryptionConfigScreen = (props: Props) => {
|
||||
}, [theme]);
|
||||
|
||||
const styles = useMemo(() => {
|
||||
const styles = {
|
||||
return StyleSheet.create({
|
||||
titleText: {
|
||||
flex: 1,
|
||||
fontWeight: 'bold',
|
||||
@ -85,9 +83,7 @@ const EncryptionConfigScreen = (props: Props) => {
|
||||
paddingLeft: theme.margin,
|
||||
paddingRight: theme.margin,
|
||||
},
|
||||
};
|
||||
|
||||
return StyleSheet.create(styles);
|
||||
});
|
||||
}, [theme]);
|
||||
|
||||
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 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
|
||||
const inputStyle: any = { flex: 1, marginRight: 10, color: theme.color };
|
||||
@ -111,8 +108,19 @@ const EncryptionConfigScreen = (props: Props) => {
|
||||
} else {
|
||||
return (
|
||||
<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>
|
||||
<Text style={{ fontSize: theme.fontSize, marginRight: 10, color: theme.color }}>{passwordOk}</Text>
|
||||
<TextInput
|
||||
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>
|
||||
</View>
|
||||
);
|
||||
@ -121,7 +129,10 @@ const EncryptionConfigScreen = (props: Props) => {
|
||||
|
||||
return (
|
||||
<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>
|
||||
<View style={{ flexDirection: 'row', alignItems: 'center' }}>
|
||||
<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>;
|
||||
});
|
||||
|
||||
const passwordLabelId = 'password-label';
|
||||
const confirmPasswordLabelId = 'confirm-password';
|
||||
return (
|
||||
<View style={{ flex: 1, flexBasis: 'auto', borderColor: theme.dividerColor, borderWidth: 1, padding: 10, marginTop: 10, marginBottom: 10 }}>
|
||||
<View>{messageComps}</View>
|
||||
<Text style={styles.normalText}>{_('Password:')}</Text>
|
||||
<Text nativeID={passwordLabelId} style={styles.normalText}>{_('Password:')}</Text>
|
||||
<TextInput
|
||||
accessibilityLabelledBy={passwordLabelId}
|
||||
selectionColor={theme.textSelectionColor}
|
||||
keyboardAppearance={theme.keyboardAppearance}
|
||||
style={styles.normalTextInput}
|
||||
@ -172,8 +186,9 @@ const EncryptionConfigScreen = (props: Props) => {
|
||||
}}
|
||||
></TextInput>
|
||||
|
||||
<Text style={styles.normalText}>{_('Confirm password:')}</Text>
|
||||
<Text nativeID={confirmPasswordLabelId} style={styles.normalText}>{_('Confirm password:')}</Text>
|
||||
<TextInput
|
||||
accessibilityLabelledBy={confirmPasswordLabelId}
|
||||
selectionColor={theme.textSelectionColor}
|
||||
keyboardAppearance={theme.keyboardAppearance}
|
||||
style={styles.normalTextInput}
|
||||
@ -221,11 +236,23 @@ const EncryptionConfigScreen = (props: Props) => {
|
||||
</View>
|
||||
);
|
||||
} else {
|
||||
const labelId = 'master-password-label';
|
||||
return (
|
||||
<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 }}>
|
||||
<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')} />
|
||||
</View>
|
||||
</View>
|
||||
@ -293,6 +320,17 @@ const EncryptionConfigScreen = (props: Props) => {
|
||||
</View>
|
||||
) : 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 (
|
||||
<View style={rootStyle}>
|
||||
<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>
|
||||
<TouchableOpacity
|
||||
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>
|
||||
</TouchableOpacity>
|
||||
</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>
|
||||
{decryptedItemsInfo}
|
||||
{renderMasterPassword()}
|
||||
@ -319,16 +361,7 @@ const EncryptionConfigScreen = (props: Props) => {
|
||||
{nonExistingMasterKeySection}
|
||||
</View>
|
||||
<Divider />
|
||||
<List.Accordion
|
||||
title={_('Disabled keys')}
|
||||
titleStyle={styles.titleText}
|
||||
expanded={showDisabledKeys}
|
||||
onPress={() => setShowDisabledKeys(st => !st)}
|
||||
>
|
||||
<View style={styles.disabledContainer}>
|
||||
{disabledMkComps}
|
||||
</View>
|
||||
</List.Accordion>
|
||||
{disabledMasterKeyList}
|
||||
</ScrollView>
|
||||
</View>
|
||||
);
|
||||
|
Loading…
x
Reference in New Issue
Block a user