mirror of
https://github.com/laurent22/joplin.git
synced 2024-12-21 09:38:01 +02:00
All: Add a way to disable a master key
This commit is contained in:
parent
ea1269d122
commit
7faa58e0f9
@ -1191,9 +1191,15 @@ packages/lib/services/database/types.js.map
|
|||||||
packages/lib/services/debug/populateDatabase.d.ts
|
packages/lib/services/debug/populateDatabase.d.ts
|
||||||
packages/lib/services/debug/populateDatabase.js
|
packages/lib/services/debug/populateDatabase.js
|
||||||
packages/lib/services/debug/populateDatabase.js.map
|
packages/lib/services/debug/populateDatabase.js.map
|
||||||
|
packages/lib/services/e2ee/types.d.ts
|
||||||
|
packages/lib/services/e2ee/types.js
|
||||||
|
packages/lib/services/e2ee/types.js.map
|
||||||
packages/lib/services/e2ee/utils.d.ts
|
packages/lib/services/e2ee/utils.d.ts
|
||||||
packages/lib/services/e2ee/utils.js
|
packages/lib/services/e2ee/utils.js
|
||||||
packages/lib/services/e2ee/utils.js.map
|
packages/lib/services/e2ee/utils.js.map
|
||||||
|
packages/lib/services/e2ee/utils.test.d.ts
|
||||||
|
packages/lib/services/e2ee/utils.test.js
|
||||||
|
packages/lib/services/e2ee/utils.test.js.map
|
||||||
packages/lib/services/interop/InteropService.d.ts
|
packages/lib/services/interop/InteropService.d.ts
|
||||||
packages/lib/services/interop/InteropService.js
|
packages/lib/services/interop/InteropService.js
|
||||||
packages/lib/services/interop/InteropService.js.map
|
packages/lib/services/interop/InteropService.js.map
|
||||||
@ -1527,6 +1533,9 @@ packages/lib/services/synchronizer/migrations/3.js.map
|
|||||||
packages/lib/services/synchronizer/syncInfoUtils.d.ts
|
packages/lib/services/synchronizer/syncInfoUtils.d.ts
|
||||||
packages/lib/services/synchronizer/syncInfoUtils.js
|
packages/lib/services/synchronizer/syncInfoUtils.js
|
||||||
packages/lib/services/synchronizer/syncInfoUtils.js.map
|
packages/lib/services/synchronizer/syncInfoUtils.js.map
|
||||||
|
packages/lib/services/synchronizer/syncInfoUtils.test.d.ts
|
||||||
|
packages/lib/services/synchronizer/syncInfoUtils.test.js
|
||||||
|
packages/lib/services/synchronizer/syncInfoUtils.test.js.map
|
||||||
packages/lib/services/synchronizer/synchronizer_LockHandler.test.d.ts
|
packages/lib/services/synchronizer/synchronizer_LockHandler.test.d.ts
|
||||||
packages/lib/services/synchronizer/synchronizer_LockHandler.test.js
|
packages/lib/services/synchronizer/synchronizer_LockHandler.test.js
|
||||||
packages/lib/services/synchronizer/synchronizer_LockHandler.test.js.map
|
packages/lib/services/synchronizer/synchronizer_LockHandler.test.js.map
|
||||||
|
9
.gitignore
vendored
9
.gitignore
vendored
@ -1176,9 +1176,15 @@ packages/lib/services/database/types.js.map
|
|||||||
packages/lib/services/debug/populateDatabase.d.ts
|
packages/lib/services/debug/populateDatabase.d.ts
|
||||||
packages/lib/services/debug/populateDatabase.js
|
packages/lib/services/debug/populateDatabase.js
|
||||||
packages/lib/services/debug/populateDatabase.js.map
|
packages/lib/services/debug/populateDatabase.js.map
|
||||||
|
packages/lib/services/e2ee/types.d.ts
|
||||||
|
packages/lib/services/e2ee/types.js
|
||||||
|
packages/lib/services/e2ee/types.js.map
|
||||||
packages/lib/services/e2ee/utils.d.ts
|
packages/lib/services/e2ee/utils.d.ts
|
||||||
packages/lib/services/e2ee/utils.js
|
packages/lib/services/e2ee/utils.js
|
||||||
packages/lib/services/e2ee/utils.js.map
|
packages/lib/services/e2ee/utils.js.map
|
||||||
|
packages/lib/services/e2ee/utils.test.d.ts
|
||||||
|
packages/lib/services/e2ee/utils.test.js
|
||||||
|
packages/lib/services/e2ee/utils.test.js.map
|
||||||
packages/lib/services/interop/InteropService.d.ts
|
packages/lib/services/interop/InteropService.d.ts
|
||||||
packages/lib/services/interop/InteropService.js
|
packages/lib/services/interop/InteropService.js
|
||||||
packages/lib/services/interop/InteropService.js.map
|
packages/lib/services/interop/InteropService.js.map
|
||||||
@ -1512,6 +1518,9 @@ packages/lib/services/synchronizer/migrations/3.js.map
|
|||||||
packages/lib/services/synchronizer/syncInfoUtils.d.ts
|
packages/lib/services/synchronizer/syncInfoUtils.d.ts
|
||||||
packages/lib/services/synchronizer/syncInfoUtils.js
|
packages/lib/services/synchronizer/syncInfoUtils.js
|
||||||
packages/lib/services/synchronizer/syncInfoUtils.js.map
|
packages/lib/services/synchronizer/syncInfoUtils.js.map
|
||||||
|
packages/lib/services/synchronizer/syncInfoUtils.test.d.ts
|
||||||
|
packages/lib/services/synchronizer/syncInfoUtils.test.js
|
||||||
|
packages/lib/services/synchronizer/syncInfoUtils.test.js.map
|
||||||
packages/lib/services/synchronizer/synchronizer_LockHandler.test.d.ts
|
packages/lib/services/synchronizer/synchronizer_LockHandler.test.d.ts
|
||||||
packages/lib/services/synchronizer/synchronizer_LockHandler.test.js
|
packages/lib/services/synchronizer/synchronizer_LockHandler.test.js
|
||||||
packages/lib/services/synchronizer/synchronizer_LockHandler.test.js.map
|
packages/lib/services/synchronizer/synchronizer_LockHandler.test.js.map
|
||||||
|
@ -859,10 +859,10 @@ class Application extends BaseApplication {
|
|||||||
// type: 'NAV_GO',
|
// type: 'NAV_GO',
|
||||||
// routeName: 'Config',
|
// routeName: 'Config',
|
||||||
// props: {
|
// props: {
|
||||||
// defaultSection: 'plugins',
|
// defaultSection: 'encryption',
|
||||||
// },
|
// },
|
||||||
// });
|
// });
|
||||||
// }, 5000);
|
// }, 2000);
|
||||||
|
|
||||||
|
|
||||||
// setTimeout(() => {
|
// setTimeout(() => {
|
||||||
|
@ -10,8 +10,8 @@ import shim from '@joplin/lib/shim';
|
|||||||
import dialogs from './dialogs';
|
import dialogs from './dialogs';
|
||||||
import bridge from '../services/bridge';
|
import bridge from '../services/bridge';
|
||||||
import shared from '@joplin/lib/components/shared/encryption-config-shared';
|
import shared from '@joplin/lib/components/shared/encryption-config-shared';
|
||||||
import { MasterKeyEntity } from '@joplin/lib/services/database/types';
|
import { MasterKeyEntity } from '@joplin/lib/services/e2ee/types';
|
||||||
import { getEncryptionEnabled, SyncInfo } from '@joplin/lib/services/synchronizer/syncInfoUtils';
|
import { getEncryptionEnabled, masterKeyEnabled, SyncInfo } from '@joplin/lib/services/synchronizer/syncInfoUtils';
|
||||||
import { toggleAndSetupEncryption } from '@joplin/lib/services/e2ee/utils';
|
import { toggleAndSetupEncryption } from '@joplin/lib/services/e2ee/utils';
|
||||||
import MasterKey from '@joplin/lib/models/MasterKey';
|
import MasterKey from '@joplin/lib/models/MasterKey';
|
||||||
|
|
||||||
@ -42,7 +42,7 @@ class EncryptionConfigScreenComponent extends React.Component<Props> {
|
|||||||
return shared.checkPasswords(this);
|
return shared.checkPasswords(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
renderMasterKey(mk: MasterKeyEntity) {
|
private renderMasterKey(mk: MasterKeyEntity, isDefault: boolean) {
|
||||||
const theme = themeStyle(this.props.themeId);
|
const theme = themeStyle(this.props.themeId);
|
||||||
|
|
||||||
const passwordStyle = {
|
const passwordStyle = {
|
||||||
@ -60,17 +60,20 @@ class EncryptionConfigScreenComponent extends React.Component<Props> {
|
|||||||
return shared.onPasswordChange(this, mk, event.target.value);
|
return shared.onPasswordChange(this, mk, event.target.value);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const onToggleEnabledClick = () => {
|
||||||
|
return shared.onToggleEnabledClick(this, mk);
|
||||||
|
};
|
||||||
|
|
||||||
const password = this.state.passwords[mk.id] ? this.state.passwords[mk.id] : '';
|
const password = this.state.passwords[mk.id] ? this.state.passwords[mk.id] : '';
|
||||||
const active = this.props.activeMasterKeyId === mk.id ? '✔' : '';
|
const isActive = this.props.activeMasterKeyId === mk.id;
|
||||||
|
const activeIcon = isActive ? '✔' : '';
|
||||||
const passwordOk = this.state.passwordChecks[mk.id] === true ? '✔' : '❌';
|
const passwordOk = this.state.passwordChecks[mk.id] === true ? '✔' : '❌';
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<tr key={mk.id}>
|
<tr key={mk.id}>
|
||||||
<td style={theme.textStyle}>{active}</td>
|
<td style={theme.textStyle}>{activeIcon}</td>
|
||||||
<td style={theme.textStyle}>{mk.id}</td>
|
<td style={theme.textStyle}>{mk.id}<br/>{_('Source: ')}{mk.source_application}</td>
|
||||||
<td style={theme.textStyle}>{mk.source_application}</td>
|
<td style={theme.textStyle}>{_('Created: ')}{time.formatMsToLocal(mk.created_time)}<br/>{_('Updated: ')}{time.formatMsToLocal(mk.updated_time)}</td>
|
||||||
<td style={theme.textStyle}>{time.formatMsToLocal(mk.created_time)}</td>
|
|
||||||
<td style={theme.textStyle}>{time.formatMsToLocal(mk.updated_time)}</td>
|
|
||||||
<td style={theme.textStyle}>
|
<td style={theme.textStyle}>
|
||||||
<input type="password" style={passwordStyle} value={password} onChange={event => onPasswordChange(event)} />{' '}
|
<input type="password" style={passwordStyle} value={password} onChange={event => onPasswordChange(event)} />{' '}
|
||||||
<button style={theme.buttonStyle} onClick={() => onSaveClick()}>
|
<button style={theme.buttonStyle} onClick={() => onSaveClick()}>
|
||||||
@ -78,6 +81,9 @@ class EncryptionConfigScreenComponent extends React.Component<Props> {
|
|||||||
</button>
|
</button>
|
||||||
</td>
|
</td>
|
||||||
<td style={theme.textStyle}>{passwordOk}</td>
|
<td style={theme.textStyle}>{passwordOk}</td>
|
||||||
|
<td style={theme.textStyle}>
|
||||||
|
<button disabled={isActive || isDefault} style={theme.buttonStyle} onClick={() => onToggleEnabledClick()}>{masterKeyEnabled(mk) ? _('Disable') : _('Enable')}</button>
|
||||||
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@ -121,6 +127,7 @@ class EncryptionConfigScreenComponent extends React.Component<Props> {
|
|||||||
|
|
||||||
renderReencryptData() {
|
renderReencryptData() {
|
||||||
if (!shim.isElectron()) return null;
|
if (!shim.isElectron()) return null;
|
||||||
|
if (!this.props.shouldReencrypt) return null;
|
||||||
|
|
||||||
const theme = themeStyle(this.props.themeId);
|
const theme = themeStyle(this.props.themeId);
|
||||||
const buttonLabel = _('Re-encrypt data');
|
const buttonLabel = _('Re-encrypt data');
|
||||||
@ -146,9 +153,51 @@ class EncryptionConfigScreenComponent extends React.Component<Props> {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private renderMasterKeySection(masterKeys: MasterKeyEntity[], isEnabledMasterKeys: boolean) {
|
||||||
|
const theme = themeStyle(this.props.themeId);
|
||||||
|
const mkComps = [];
|
||||||
|
const showTable = isEnabledMasterKeys || this.state.showDisabledMasterKeys;
|
||||||
|
const latestMasterKey = MasterKey.latest();
|
||||||
|
|
||||||
|
for (let i = 0; i < masterKeys.length; i++) {
|
||||||
|
const mk = masterKeys[i];
|
||||||
|
mkComps.push(this.renderMasterKey(mk, isEnabledMasterKeys && latestMasterKey && mk.id === latestMasterKey.id));
|
||||||
|
}
|
||||||
|
|
||||||
|
const headerComp = isEnabledMasterKeys ? <h1 style={theme.h1Style}>{_('Master Keys')}</h1> : <a onClick={() => shared.toggleShowDisabledMasterKeys(this) } style={{ ...theme.urlStyle, display: 'inline-block', marginBottom: 10 }} href="#">{showTable ? _('Hide disabled master keys') : _('Show disabled master keys')}</a>;
|
||||||
|
const infoComp = isEnabledMasterKeys ? <p style={theme.textStyle}>{_('Note: Only one master key is going to be used for encryption (the one marked as "active"). Any of the keys might be used for decryption, depending on how the notes or notebooks were originally encrypted.')}</p> : null;
|
||||||
|
const tableComp = !showTable ? null : (
|
||||||
|
<table>
|
||||||
|
<tbody>
|
||||||
|
<tr>
|
||||||
|
<th style={theme.textStyle}>{_('Active')}</th>
|
||||||
|
<th style={theme.textStyle}>{_('ID')}</th>
|
||||||
|
<th style={theme.textStyle}>{_('Date')}</th>
|
||||||
|
<th style={theme.textStyle}>{_('Password')}</th>
|
||||||
|
<th style={theme.textStyle}>{_('Valid')}</th>
|
||||||
|
<th style={theme.textStyle}>{_('Actions')}</th>
|
||||||
|
</tr>
|
||||||
|
{mkComps}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
);
|
||||||
|
|
||||||
|
if (mkComps.length) {
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
{headerComp}
|
||||||
|
{tableComp}
|
||||||
|
{infoComp}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const theme = themeStyle(this.props.themeId);
|
const theme = themeStyle(this.props.themeId);
|
||||||
const masterKeys = this.props.masterKeys;
|
const masterKeys: MasterKeyEntity[] = this.props.masterKeys;
|
||||||
|
|
||||||
const containerStyle = Object.assign({}, theme.containerStyle, {
|
const containerStyle = Object.assign({}, theme.containerStyle, {
|
||||||
padding: theme.configScreenPadding,
|
padding: theme.configScreenPadding,
|
||||||
@ -156,13 +205,10 @@ class EncryptionConfigScreenComponent extends React.Component<Props> {
|
|||||||
backgroundColor: theme.backgroundColor3,
|
backgroundColor: theme.backgroundColor3,
|
||||||
});
|
});
|
||||||
|
|
||||||
const mkComps = [];
|
|
||||||
const nonExistingMasterKeyIds = this.props.notLoadedMasterKeys.slice();
|
const nonExistingMasterKeyIds = this.props.notLoadedMasterKeys.slice();
|
||||||
|
|
||||||
for (let i = 0; i < masterKeys.length; i++) {
|
for (let i = 0; i < masterKeys.length; i++) {
|
||||||
const mk = masterKeys[i];
|
const mk = masterKeys[i];
|
||||||
mkComps.push(this.renderMasterKey(mk));
|
|
||||||
|
|
||||||
const idx = nonExistingMasterKeyIds.indexOf(mk.id);
|
const idx = nonExistingMasterKeyIds.indexOf(mk.id);
|
||||||
if (idx >= 0) nonExistingMasterKeyIds.splice(idx, 1);
|
if (idx >= 0) nonExistingMasterKeyIds.splice(idx, 1);
|
||||||
}
|
}
|
||||||
@ -203,30 +249,8 @@ class EncryptionConfigScreenComponent extends React.Component<Props> {
|
|||||||
const needUpgradeSection = this.renderNeedUpgradeSection();
|
const needUpgradeSection = this.renderNeedUpgradeSection();
|
||||||
const reencryptDataSection = this.renderReencryptData();
|
const reencryptDataSection = this.renderReencryptData();
|
||||||
|
|
||||||
let masterKeySection = null;
|
const enabledMasterKeySection = this.renderMasterKeySection(masterKeys.filter(mk => masterKeyEnabled(mk)), true);
|
||||||
|
const disabledMasterKeySection = this.renderMasterKeySection(masterKeys.filter(mk => !masterKeyEnabled(mk)), false);
|
||||||
if (mkComps.length) {
|
|
||||||
masterKeySection = (
|
|
||||||
<div>
|
|
||||||
<h1 style={theme.h1Style}>{_('Master Keys')}</h1>
|
|
||||||
<table>
|
|
||||||
<tbody>
|
|
||||||
<tr>
|
|
||||||
<th style={theme.textStyle}>{_('Active')}</th>
|
|
||||||
<th style={theme.textStyle}>{_('ID')}</th>
|
|
||||||
<th style={theme.textStyle}>{_('Source')}</th>
|
|
||||||
<th style={theme.textStyle}>{_('Created')}</th>
|
|
||||||
<th style={theme.textStyle}>{_('Updated')}</th>
|
|
||||||
<th style={theme.textStyle}>{_('Password')}</th>
|
|
||||||
<th style={theme.textStyle}>{_('Password OK')}</th>
|
|
||||||
</tr>
|
|
||||||
{mkComps}
|
|
||||||
</tbody>
|
|
||||||
</table>
|
|
||||||
<p style={theme.textStyle}>{_('Note: Only one master key is going to be used for encryption (the one marked as "active"). Any of the keys might be used for decryption, depending on how the notes or notebooks were originally encrypted.')}</p>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
let nonExistingMasterKeySection = null;
|
let nonExistingMasterKeySection = null;
|
||||||
|
|
||||||
@ -284,7 +308,8 @@ class EncryptionConfigScreenComponent extends React.Component<Props> {
|
|||||||
{toggleButton}
|
{toggleButton}
|
||||||
{needUpgradeSection}
|
{needUpgradeSection}
|
||||||
{this.props.shouldReencrypt ? reencryptDataSection : null}
|
{this.props.shouldReencrypt ? reencryptDataSection : null}
|
||||||
{masterKeySection}
|
{enabledMasterKeySection}
|
||||||
|
{disabledMasterKeySection}
|
||||||
{nonExistingMasterKeySection}
|
{nonExistingMasterKeySection}
|
||||||
{!this.props.shouldReencrypt ? reencryptDataSection : null}
|
{!this.props.shouldReencrypt ? reencryptDataSection : null}
|
||||||
</div>
|
</div>
|
||||||
|
@ -36,6 +36,7 @@ import ShareService from '@joplin/lib/services/share/ShareService';
|
|||||||
import { reg } from '@joplin/lib/registry';
|
import { reg } from '@joplin/lib/registry';
|
||||||
import removeKeylessItems from '../ResizableLayout/utils/removeKeylessItems';
|
import removeKeylessItems from '../ResizableLayout/utils/removeKeylessItems';
|
||||||
import { localSyncInfoFromState } from '@joplin/lib/services/synchronizer/syncInfoUtils';
|
import { localSyncInfoFromState } from '@joplin/lib/services/synchronizer/syncInfoUtils';
|
||||||
|
import { showMissingMasterKeyMessage } from '@joplin/lib/services/e2ee/utils';
|
||||||
|
|
||||||
const { connect } = require('react-redux');
|
const { connect } = require('react-redux');
|
||||||
const { PromptDialog } = require('../PromptDialog.min.js');
|
const { PromptDialog } = require('../PromptDialog.min.js');
|
||||||
@ -866,7 +867,7 @@ const mapStateToProps = (state: AppState) => {
|
|||||||
notes: state.notes,
|
notes: state.notes,
|
||||||
hasDisabledSyncItems: state.hasDisabledSyncItems,
|
hasDisabledSyncItems: state.hasDisabledSyncItems,
|
||||||
hasDisabledEncryptionItems: state.hasDisabledEncryptionItems,
|
hasDisabledEncryptionItems: state.hasDisabledEncryptionItems,
|
||||||
showMissingMasterKeyMessage: state.notLoadedMasterKeys.length && syncInfo.masterKeys.length,
|
showMissingMasterKeyMessage: showMissingMasterKeyMessage(syncInfo, state.notLoadedMasterKeys),
|
||||||
showNeedUpgradingMasterKeyMessage: !!EncryptionService.instance().masterKeysThatNeedUpgrading(syncInfo.masterKeys).length,
|
showNeedUpgradingMasterKeyMessage: !!EncryptionService.instance().masterKeysThatNeedUpgrading(syncInfo.masterKeys).length,
|
||||||
showShouldReencryptMessage: state.settings['encryption.shouldReencrypt'] >= Setting.SHOULD_REENCRYPT_YES,
|
showShouldReencryptMessage: state.settings['encryption.shouldReencrypt'] >= Setting.SHOULD_REENCRYPT_YES,
|
||||||
shouldUpgradeSyncTarget: state.settings['sync.upgradeState'] === Setting.SYNC_UPGRADE_STATE_SHOULD_DO,
|
shouldUpgradeSyncTarget: state.settings['sync.upgradeState'] === Setting.SYNC_UPGRADE_STATE_SHOULD_DO,
|
||||||
|
@ -15,6 +15,7 @@ const { Dropdown } = require('./Dropdown.js');
|
|||||||
const { dialogs } = require('../utils/dialogs.js');
|
const { dialogs } = require('../utils/dialogs.js');
|
||||||
const DialogBox = require('react-native-dialogbox').default;
|
const DialogBox = require('react-native-dialogbox').default;
|
||||||
const { localSyncInfoFromState } = require('@joplin/lib/services/synchronizer/syncInfoUtils');
|
const { localSyncInfoFromState } = require('@joplin/lib/services/synchronizer/syncInfoUtils');
|
||||||
|
const { showMissingMasterKeyMessage } = require('@joplin/lib/services/e2ee/utils');
|
||||||
|
|
||||||
Icon.loadFont();
|
Icon.loadFont();
|
||||||
|
|
||||||
@ -538,7 +539,7 @@ const ScreenHeader = connect(state => {
|
|||||||
themeId: state.settings.theme,
|
themeId: state.settings.theme,
|
||||||
noteSelectionEnabled: state.noteSelectionEnabled,
|
noteSelectionEnabled: state.noteSelectionEnabled,
|
||||||
selectedNoteIds: state.selectedNoteIds,
|
selectedNoteIds: state.selectedNoteIds,
|
||||||
showMissingMasterKeyMessage: state.notLoadedMasterKeys.length && syncInfo.masterKeys.length,
|
showMissingMasterKeyMessage: showMissingMasterKeyMessage(syncInfo, state.notLoadedMasterKeys),
|
||||||
hasDisabledSyncItems: state.hasDisabledSyncItems,
|
hasDisabledSyncItems: state.hasDisabledSyncItems,
|
||||||
shouldUpgradeSyncTarget: state.settings['sync.upgradeState'] === Setting.SYNC_UPGRADE_STATE_SHOULD_DO,
|
shouldUpgradeSyncTarget: state.settings['sync.upgradeState'] === Setting.SYNC_UPGRADE_STATE_SHOULD_DO,
|
||||||
};
|
};
|
||||||
|
@ -10,7 +10,7 @@ import EncryptionService from '@joplin/lib/services/EncryptionService';
|
|||||||
import { _ } from '@joplin/lib/locale';
|
import { _ } from '@joplin/lib/locale';
|
||||||
import time from '@joplin/lib/time';
|
import time from '@joplin/lib/time';
|
||||||
import shared from '@joplin/lib/components/shared/encryption-config-shared';
|
import shared from '@joplin/lib/components/shared/encryption-config-shared';
|
||||||
import { MasterKeyEntity } from '@joplin/lib/services/database/types';
|
import { MasterKeyEntity } from '@joplin/lib/services/e2ee/types';
|
||||||
import { State } from '@joplin/lib/reducer';
|
import { State } from '@joplin/lib/reducer';
|
||||||
import { SyncInfo } from '@joplin/lib/services/synchronizer/syncInfoUtils';
|
import { SyncInfo } from '@joplin/lib/services/synchronizer/syncInfoUtils';
|
||||||
import { setupAndDisableEncryption, toggleAndSetupEncryption } from '@joplin/lib/services/e2ee/utils';
|
import { setupAndDisableEncryption, toggleAndSetupEncryption } from '@joplin/lib/services/e2ee/utils';
|
||||||
|
@ -5,8 +5,9 @@ import Setting from '../../models/Setting';
|
|||||||
import MasterKey from '../../models/MasterKey';
|
import MasterKey from '../../models/MasterKey';
|
||||||
import { reg } from '../../registry.js';
|
import { reg } from '../../registry.js';
|
||||||
import shim from '../../shim';
|
import shim from '../../shim';
|
||||||
import { MasterKeyEntity } from '../../services/database/types';
|
import { MasterKeyEntity } from '../../services/e2ee/types';
|
||||||
import time from '../../time';
|
import time from '../../time';
|
||||||
|
import { masterKeyEnabled, setMasterKeyEnabled } from '../../services/synchronizer/syncInfoUtils';
|
||||||
|
|
||||||
class Shared {
|
class Shared {
|
||||||
|
|
||||||
@ -20,6 +21,7 @@ class Shared {
|
|||||||
total: null,
|
total: null,
|
||||||
},
|
},
|
||||||
passwords: Object.assign({}, props.passwords),
|
passwords: Object.assign({}, props.passwords),
|
||||||
|
showDisabledMasterKeys: false,
|
||||||
};
|
};
|
||||||
comp.isMounted_ = false;
|
comp.isMounted_ = false;
|
||||||
|
|
||||||
@ -33,6 +35,10 @@ class Shared {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public async toggleShowDisabledMasterKeys(comp: any) {
|
||||||
|
comp.setState({ showDisabledMasterKeys: !comp.state.showDisabledMasterKeys });
|
||||||
|
}
|
||||||
|
|
||||||
public async reencryptData() {
|
public async reencryptData() {
|
||||||
const ok = confirm(_('Please confirm that you would like to re-encrypt your complete database.'));
|
const ok = confirm(_('Please confirm that you would like to re-encrypt your complete database.'));
|
||||||
if (!ok) return;
|
if (!ok) return;
|
||||||
@ -138,6 +144,10 @@ class Shared {
|
|||||||
comp.setState({ passwords: passwords });
|
comp.setState({ passwords: passwords });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public onToggleEnabledClick(_comp: any, mk: MasterKeyEntity) {
|
||||||
|
setMasterKeyEnabled(mk.id, !masterKeyEnabled(mk));
|
||||||
|
}
|
||||||
|
|
||||||
public enableEncryptionConfirmationMessages(masterKey: MasterKeyEntity) {
|
public enableEncryptionConfirmationMessages(masterKey: MasterKeyEntity) {
|
||||||
const msg = [_('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.')];
|
const msg = [_('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.')];
|
||||||
if (masterKey) msg.push(_('Encryption will be enabled using the master key created on %s', time.unixMsToLocalDateTime(masterKey.created_time)));
|
if (masterKey) msg.push(_('Encryption will be enabled using the master key created on %s', time.unixMsToLocalDateTime(masterKey.created_time)));
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
import BaseModel from '../BaseModel';
|
import BaseModel from '../BaseModel';
|
||||||
import { MasterKeyEntity } from '../services/database/types';
|
import { MasterKeyEntity } from '../services/e2ee/types';
|
||||||
import { localSyncInfo, saveLocalSyncInfo } from '../services/synchronizer/syncInfoUtils';
|
import { localSyncInfo, saveLocalSyncInfo } from '../services/synchronizer/syncInfoUtils';
|
||||||
import BaseItem from './BaseItem';
|
import BaseItem from './BaseItem';
|
||||||
import uuid from '../uuid';
|
import uuid from '../uuid';
|
||||||
|
@ -55,7 +55,7 @@ export interface State {
|
|||||||
folders: any[];
|
folders: any[];
|
||||||
tags: any[];
|
tags: any[];
|
||||||
masterKeys: any[];
|
masterKeys: any[];
|
||||||
notLoadedMasterKeys: any[];
|
notLoadedMasterKeys: string[];
|
||||||
searches: any[];
|
searches: any[];
|
||||||
highlightedWords: string[];
|
highlightedWords: string[];
|
||||||
selectedNoteIds: string[];
|
selectedNoteIds: string[];
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import { MasterKeyEntity } from './database/types';
|
import { MasterKeyEntity } from './e2ee/types';
|
||||||
import Logger from '../Logger';
|
import Logger from '../Logger';
|
||||||
import shim from '../shim';
|
import shim from '../shim';
|
||||||
import Setting from '../models/Setting';
|
import Setting from '../models/Setting';
|
||||||
|
@ -14,6 +14,10 @@ export interface BaseItemEntity {
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
// AUTO-GENERATED BY packages/tools/generate-database-types.js
|
// AUTO-GENERATED BY packages/tools/generate-database-types.js
|
||||||
|
|
||||||
/*
|
/*
|
||||||
@ -66,16 +70,6 @@ export interface KeyValueEntity {
|
|||||||
"updated_time"?: number
|
"updated_time"?: number
|
||||||
"type_"?: number
|
"type_"?: number
|
||||||
}
|
}
|
||||||
export interface MasterKeyEntity {
|
|
||||||
"id"?: string | null
|
|
||||||
"created_time"?: number
|
|
||||||
"updated_time"?: number
|
|
||||||
"source_application"?: string
|
|
||||||
"encryption_method"?: number
|
|
||||||
"checksum"?: string
|
|
||||||
"content"?: string
|
|
||||||
"type_"?: number
|
|
||||||
}
|
|
||||||
export interface MigrationEntity {
|
export interface MigrationEntity {
|
||||||
"id"?: number | null
|
"id"?: number | null
|
||||||
"number"?: number
|
"number"?: number
|
||||||
|
11
packages/lib/services/e2ee/types.ts
Normal file
11
packages/lib/services/e2ee/types.ts
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
export interface MasterKeyEntity {
|
||||||
|
id?: string | null;
|
||||||
|
created_time?: number;
|
||||||
|
updated_time?: number;
|
||||||
|
source_application?: string;
|
||||||
|
encryption_method?: number;
|
||||||
|
checksum?: string;
|
||||||
|
content?: string;
|
||||||
|
type_?: number;
|
||||||
|
enabled?: number;
|
||||||
|
}
|
42
packages/lib/services/e2ee/utils.test.ts
Normal file
42
packages/lib/services/e2ee/utils.test.ts
Normal file
@ -0,0 +1,42 @@
|
|||||||
|
import { afterAllCleanUp, setupDatabaseAndSynchronizer, switchClient, encryptionService } from '../../testing/test-utils';
|
||||||
|
import MasterKey from '../../models/MasterKey';
|
||||||
|
import { showMissingMasterKeyMessage } from './utils';
|
||||||
|
import { localSyncInfo, setMasterKeyEnabled } from '../synchronizer/syncInfoUtils';
|
||||||
|
|
||||||
|
describe('e2ee/utils', function() {
|
||||||
|
|
||||||
|
beforeEach(async (done) => {
|
||||||
|
await setupDatabaseAndSynchronizer(1);
|
||||||
|
await switchClient(1);
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
|
||||||
|
afterAll(async () => {
|
||||||
|
await afterAllCleanUp();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should tell if the missing master key message should be shown', async () => {
|
||||||
|
const mk1 = await MasterKey.save(await encryptionService().generateMasterKey('111111'));
|
||||||
|
const mk2 = await MasterKey.save(await encryptionService().generateMasterKey('111111'));
|
||||||
|
|
||||||
|
expect(showMissingMasterKeyMessage(localSyncInfo(), [mk1.id])).toBe(true);
|
||||||
|
expect(showMissingMasterKeyMessage(localSyncInfo(), [mk1.id, mk2.id])).toBe(true);
|
||||||
|
expect(showMissingMasterKeyMessage(localSyncInfo(), [])).toBe(false);
|
||||||
|
|
||||||
|
setMasterKeyEnabled(mk1.id, false);
|
||||||
|
expect(showMissingMasterKeyMessage(localSyncInfo(), [mk1.id])).toBe(false);
|
||||||
|
expect(showMissingMasterKeyMessage(localSyncInfo(), [mk1.id, mk2.id])).toBe(true);
|
||||||
|
|
||||||
|
setMasterKeyEnabled(mk2.id, false);
|
||||||
|
expect(showMissingMasterKeyMessage(localSyncInfo(), [mk1.id, mk2.id])).toBe(false);
|
||||||
|
|
||||||
|
setMasterKeyEnabled(mk1.id, true);
|
||||||
|
setMasterKeyEnabled(mk2.id, true);
|
||||||
|
expect(showMissingMasterKeyMessage(localSyncInfo(), [mk1.id, mk2.id])).toBe(true);
|
||||||
|
|
||||||
|
const syncInfo = localSyncInfo();
|
||||||
|
syncInfo.masterKeys = [];
|
||||||
|
expect(showMissingMasterKeyMessage(syncInfo, [mk1.id, mk2.id])).toBe(false);
|
||||||
|
});
|
||||||
|
|
||||||
|
});
|
@ -2,9 +2,9 @@ import Logger from '../../Logger';
|
|||||||
import BaseItem from '../../models/BaseItem';
|
import BaseItem from '../../models/BaseItem';
|
||||||
import MasterKey from '../../models/MasterKey';
|
import MasterKey from '../../models/MasterKey';
|
||||||
import Setting from '../../models/Setting';
|
import Setting from '../../models/Setting';
|
||||||
import { MasterKeyEntity } from '../database/types';
|
import { MasterKeyEntity } from './types';
|
||||||
import EncryptionService from '../EncryptionService';
|
import EncryptionService from '../EncryptionService';
|
||||||
import { getActiveMasterKeyId, setEncryptionEnabled } from '../synchronizer/syncInfoUtils';
|
import { getActiveMasterKeyId, masterKeyEnabled, setEncryptionEnabled, SyncInfo } from '../synchronizer/syncInfoUtils';
|
||||||
|
|
||||||
const logger = Logger.create('e2ee/utils');
|
const logger = Logger.create('e2ee/utils');
|
||||||
|
|
||||||
@ -90,3 +90,16 @@ export async function loadMasterKeysFromSettings(service: EncryptionService) {
|
|||||||
|
|
||||||
logger.info(`Loaded master keys: ${service.loadedMasterKeysCount()}`);
|
logger.info(`Loaded master keys: ${service.loadedMasterKeysCount()}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function showMissingMasterKeyMessage(syncInfo: SyncInfo, notLoadedMasterKeys: string[]) {
|
||||||
|
if (!syncInfo.masterKeys.length) return false;
|
||||||
|
|
||||||
|
notLoadedMasterKeys = notLoadedMasterKeys.slice();
|
||||||
|
|
||||||
|
for (let i = notLoadedMasterKeys.length - 1; i >= 0; i--) {
|
||||||
|
const mk = syncInfo.masterKeys.find(mk => mk.id === notLoadedMasterKeys[i]);
|
||||||
|
if (!masterKeyEnabled(mk)) notLoadedMasterKeys.pop();
|
||||||
|
}
|
||||||
|
|
||||||
|
return !!notLoadedMasterKeys.length;
|
||||||
|
}
|
||||||
|
37
packages/lib/services/synchronizer/syncInfoUtils.test.ts
Normal file
37
packages/lib/services/synchronizer/syncInfoUtils.test.ts
Normal file
@ -0,0 +1,37 @@
|
|||||||
|
import { afterAllCleanUp, setupDatabaseAndSynchronizer, switchClient, encryptionService } from '../../testing/test-utils';
|
||||||
|
import MasterKey from '../../models/MasterKey';
|
||||||
|
import { masterKeyEnabled, setMasterKeyEnabled } from './syncInfoUtils';
|
||||||
|
|
||||||
|
describe('syncInfoUtils', function() {
|
||||||
|
|
||||||
|
beforeEach(async (done) => {
|
||||||
|
await setupDatabaseAndSynchronizer(1);
|
||||||
|
await switchClient(1);
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
|
||||||
|
afterAll(async () => {
|
||||||
|
await afterAllCleanUp();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should enable or disable a master key', async () => {
|
||||||
|
const mk1 = await MasterKey.save(await encryptionService().generateMasterKey('111111'));
|
||||||
|
const mk2 = await MasterKey.save(await encryptionService().generateMasterKey('111111'));
|
||||||
|
|
||||||
|
setMasterKeyEnabled(mk2.id, false);
|
||||||
|
|
||||||
|
expect(masterKeyEnabled(await MasterKey.load(mk1.id))).toBe(true);
|
||||||
|
expect(masterKeyEnabled(await MasterKey.load(mk2.id))).toBe(false);
|
||||||
|
|
||||||
|
setMasterKeyEnabled(mk1.id, false);
|
||||||
|
|
||||||
|
expect(masterKeyEnabled(await MasterKey.load(mk1.id))).toBe(false);
|
||||||
|
expect(masterKeyEnabled(await MasterKey.load(mk2.id))).toBe(false);
|
||||||
|
|
||||||
|
setMasterKeyEnabled(mk1.id, true);
|
||||||
|
|
||||||
|
expect(masterKeyEnabled(await MasterKey.load(mk1.id))).toBe(true);
|
||||||
|
expect(masterKeyEnabled(await MasterKey.load(mk2.id))).toBe(false);
|
||||||
|
});
|
||||||
|
|
||||||
|
});
|
@ -2,7 +2,7 @@ import { FileApi } from '../../file-api';
|
|||||||
import JoplinDatabase from '../../JoplinDatabase';
|
import JoplinDatabase from '../../JoplinDatabase';
|
||||||
import Setting from '../../models/Setting';
|
import Setting from '../../models/Setting';
|
||||||
import { State } from '../../reducer';
|
import { State } from '../../reducer';
|
||||||
import { MasterKeyEntity } from '../database/types';
|
import { MasterKeyEntity } from '../e2ee/types';
|
||||||
|
|
||||||
export interface SyncInfoValueBoolean {
|
export interface SyncInfoValueBoolean {
|
||||||
value: boolean;
|
value: boolean;
|
||||||
@ -233,3 +233,24 @@ export function getActiveMasterKey(s: SyncInfo = null): MasterKeyEntity | null {
|
|||||||
if (!s.activeMasterKeyId) return null;
|
if (!s.activeMasterKeyId) return null;
|
||||||
return s.masterKeys.find(mk => mk.id === s.activeMasterKeyId);
|
return s.masterKeys.find(mk => mk.id === s.activeMasterKeyId);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function setMasterKeyEnabled(mkId: string, enabled: boolean = true) {
|
||||||
|
const s = localSyncInfo();
|
||||||
|
const idx = s.masterKeys.findIndex(mk => mk.id === mkId);
|
||||||
|
if (idx < 0) throw new Error(`No such master key: ${mkId}`);
|
||||||
|
|
||||||
|
if (mkId === getActiveMasterKeyId() && !enabled) throw new Error('The active master key cannot be disabled');
|
||||||
|
|
||||||
|
s.masterKeys[idx] = {
|
||||||
|
...s.masterKeys[idx],
|
||||||
|
enabled: enabled ? 1 : 0,
|
||||||
|
updated_time: Date.now(),
|
||||||
|
};
|
||||||
|
|
||||||
|
saveLocalSyncInfo(s);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function masterKeyEnabled(mk: MasterKeyEntity): boolean {
|
||||||
|
if ('enabled' in mk) return !!mk.enabled;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
@ -22,6 +22,7 @@ async function main() {
|
|||||||
'main.notes_fts_segdir',
|
'main.notes_fts_segdir',
|
||||||
'main.notes_fts_docsize',
|
'main.notes_fts_docsize',
|
||||||
'main.notes_fts_stat',
|
'main.notes_fts_stat',
|
||||||
|
'main.master_keys',
|
||||||
],
|
],
|
||||||
};
|
};
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user