diff --git a/.eslintignore b/.eslintignore index 828f8d1f5..04660bc7f 100644 --- a/.eslintignore +++ b/.eslintignore @@ -1191,9 +1191,15 @@ packages/lib/services/database/types.js.map packages/lib/services/debug/populateDatabase.d.ts packages/lib/services/debug/populateDatabase.js 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.js 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.js 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.js 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.js packages/lib/services/synchronizer/synchronizer_LockHandler.test.js.map diff --git a/.gitignore b/.gitignore index 148b8c67a..e4ab7aaab 100644 --- a/.gitignore +++ b/.gitignore @@ -1176,9 +1176,15 @@ packages/lib/services/database/types.js.map packages/lib/services/debug/populateDatabase.d.ts packages/lib/services/debug/populateDatabase.js 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.js 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.js 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.js 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.js packages/lib/services/synchronizer/synchronizer_LockHandler.test.js.map diff --git a/packages/app-desktop/app.ts b/packages/app-desktop/app.ts index b89c63fa2..b2183b0ac 100644 --- a/packages/app-desktop/app.ts +++ b/packages/app-desktop/app.ts @@ -859,10 +859,10 @@ class Application extends BaseApplication { // type: 'NAV_GO', // routeName: 'Config', // props: { - // defaultSection: 'plugins', + // defaultSection: 'encryption', // }, // }); - // }, 5000); + // }, 2000); // setTimeout(() => { diff --git a/packages/app-desktop/gui/EncryptionConfigScreen.tsx b/packages/app-desktop/gui/EncryptionConfigScreen.tsx index 16829f5e3..f1a80b858 100644 --- a/packages/app-desktop/gui/EncryptionConfigScreen.tsx +++ b/packages/app-desktop/gui/EncryptionConfigScreen.tsx @@ -10,8 +10,8 @@ import shim from '@joplin/lib/shim'; import dialogs from './dialogs'; import bridge from '../services/bridge'; import shared from '@joplin/lib/components/shared/encryption-config-shared'; -import { MasterKeyEntity } from '@joplin/lib/services/database/types'; -import { getEncryptionEnabled, SyncInfo } from '@joplin/lib/services/synchronizer/syncInfoUtils'; +import { MasterKeyEntity } from '@joplin/lib/services/e2ee/types'; +import { getEncryptionEnabled, masterKeyEnabled, SyncInfo } from '@joplin/lib/services/synchronizer/syncInfoUtils'; import { toggleAndSetupEncryption } from '@joplin/lib/services/e2ee/utils'; import MasterKey from '@joplin/lib/models/MasterKey'; @@ -42,7 +42,7 @@ class EncryptionConfigScreenComponent extends React.Component { return shared.checkPasswords(this); } - renderMasterKey(mk: MasterKeyEntity) { + private renderMasterKey(mk: MasterKeyEntity, isDefault: boolean) { const theme = themeStyle(this.props.themeId); const passwordStyle = { @@ -60,17 +60,20 @@ class EncryptionConfigScreenComponent extends React.Component { 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 active = this.props.activeMasterKeyId === mk.id ? '✔' : ''; + const isActive = this.props.activeMasterKeyId === mk.id; + const activeIcon = isActive ? '✔' : ''; const passwordOk = this.state.passwordChecks[mk.id] === true ? '✔' : '❌'; return ( - {active} - {mk.id} - {mk.source_application} - {time.formatMsToLocal(mk.created_time)} - {time.formatMsToLocal(mk.updated_time)} + {activeIcon} + {mk.id}
{_('Source: ')}{mk.source_application} + {_('Created: ')}{time.formatMsToLocal(mk.created_time)}
{_('Updated: ')}{time.formatMsToLocal(mk.updated_time)} onPasswordChange(event)} />{' '} {passwordOk} + + + ); } @@ -121,6 +127,7 @@ class EncryptionConfigScreenComponent extends React.Component { renderReencryptData() { if (!shim.isElectron()) return null; + if (!this.props.shouldReencrypt) return null; const theme = themeStyle(this.props.themeId); const buttonLabel = _('Re-encrypt data'); @@ -146,9 +153,51 @@ class EncryptionConfigScreenComponent extends React.Component { ); } + 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 ?

{_('Master Keys')}

: shared.toggleShowDisabledMasterKeys(this) } style={{ ...theme.urlStyle, display: 'inline-block', marginBottom: 10 }} href="#">{showTable ? _('Hide disabled master keys') : _('Show disabled master keys')}; + const infoComp = isEnabledMasterKeys ?

{_('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.')}

: null; + const tableComp = !showTable ? null : ( + + + + + + + + + + + {mkComps} + +
{_('Active')}{_('ID')}{_('Date')}{_('Password')}{_('Valid')}{_('Actions')}
+ ); + + if (mkComps.length) { + return ( +
+ {headerComp} + {tableComp} + {infoComp} +
+ ); + } + + return null; + } + render() { const theme = themeStyle(this.props.themeId); - const masterKeys = this.props.masterKeys; + const masterKeys: MasterKeyEntity[] = this.props.masterKeys; const containerStyle = Object.assign({}, theme.containerStyle, { padding: theme.configScreenPadding, @@ -156,13 +205,10 @@ class EncryptionConfigScreenComponent extends React.Component { backgroundColor: theme.backgroundColor3, }); - const mkComps = []; const nonExistingMasterKeyIds = this.props.notLoadedMasterKeys.slice(); for (let i = 0; i < masterKeys.length; i++) { const mk = masterKeys[i]; - mkComps.push(this.renderMasterKey(mk)); - const idx = nonExistingMasterKeyIds.indexOf(mk.id); if (idx >= 0) nonExistingMasterKeyIds.splice(idx, 1); } @@ -203,30 +249,8 @@ class EncryptionConfigScreenComponent extends React.Component { const needUpgradeSection = this.renderNeedUpgradeSection(); const reencryptDataSection = this.renderReencryptData(); - let masterKeySection = null; - - if (mkComps.length) { - masterKeySection = ( -
-

{_('Master Keys')}

- - - - - - - - - - - - {mkComps} - -
{_('Active')}{_('ID')}{_('Source')}{_('Created')}{_('Updated')}{_('Password')}{_('Password OK')}
-

{_('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.')}

-
- ); - } + const enabledMasterKeySection = this.renderMasterKeySection(masterKeys.filter(mk => masterKeyEnabled(mk)), true); + const disabledMasterKeySection = this.renderMasterKeySection(masterKeys.filter(mk => !masterKeyEnabled(mk)), false); let nonExistingMasterKeySection = null; @@ -284,7 +308,8 @@ class EncryptionConfigScreenComponent extends React.Component { {toggleButton} {needUpgradeSection} {this.props.shouldReencrypt ? reencryptDataSection : null} - {masterKeySection} + {enabledMasterKeySection} + {disabledMasterKeySection} {nonExistingMasterKeySection} {!this.props.shouldReencrypt ? reencryptDataSection : null} diff --git a/packages/app-desktop/gui/MainScreen/MainScreen.tsx b/packages/app-desktop/gui/MainScreen/MainScreen.tsx index d82125dce..7660f6432 100644 --- a/packages/app-desktop/gui/MainScreen/MainScreen.tsx +++ b/packages/app-desktop/gui/MainScreen/MainScreen.tsx @@ -36,6 +36,7 @@ import ShareService from '@joplin/lib/services/share/ShareService'; import { reg } from '@joplin/lib/registry'; import removeKeylessItems from '../ResizableLayout/utils/removeKeylessItems'; import { localSyncInfoFromState } from '@joplin/lib/services/synchronizer/syncInfoUtils'; +import { showMissingMasterKeyMessage } from '@joplin/lib/services/e2ee/utils'; const { connect } = require('react-redux'); const { PromptDialog } = require('../PromptDialog.min.js'); @@ -866,7 +867,7 @@ const mapStateToProps = (state: AppState) => { notes: state.notes, hasDisabledSyncItems: state.hasDisabledSyncItems, hasDisabledEncryptionItems: state.hasDisabledEncryptionItems, - showMissingMasterKeyMessage: state.notLoadedMasterKeys.length && syncInfo.masterKeys.length, + showMissingMasterKeyMessage: showMissingMasterKeyMessage(syncInfo, state.notLoadedMasterKeys), showNeedUpgradingMasterKeyMessage: !!EncryptionService.instance().masterKeysThatNeedUpgrading(syncInfo.masterKeys).length, showShouldReencryptMessage: state.settings['encryption.shouldReencrypt'] >= Setting.SHOULD_REENCRYPT_YES, shouldUpgradeSyncTarget: state.settings['sync.upgradeState'] === Setting.SYNC_UPGRADE_STATE_SHOULD_DO, diff --git a/packages/app-mobile/components/screen-header.js b/packages/app-mobile/components/screen-header.js index f20c8738f..1edea3d4c 100644 --- a/packages/app-mobile/components/screen-header.js +++ b/packages/app-mobile/components/screen-header.js @@ -15,6 +15,7 @@ const { Dropdown } = require('./Dropdown.js'); const { dialogs } = require('../utils/dialogs.js'); const DialogBox = require('react-native-dialogbox').default; const { localSyncInfoFromState } = require('@joplin/lib/services/synchronizer/syncInfoUtils'); +const { showMissingMasterKeyMessage } = require('@joplin/lib/services/e2ee/utils'); Icon.loadFont(); @@ -538,7 +539,7 @@ const ScreenHeader = connect(state => { themeId: state.settings.theme, noteSelectionEnabled: state.noteSelectionEnabled, selectedNoteIds: state.selectedNoteIds, - showMissingMasterKeyMessage: state.notLoadedMasterKeys.length && syncInfo.masterKeys.length, + showMissingMasterKeyMessage: showMissingMasterKeyMessage(syncInfo, state.notLoadedMasterKeys), hasDisabledSyncItems: state.hasDisabledSyncItems, shouldUpgradeSyncTarget: state.settings['sync.upgradeState'] === Setting.SYNC_UPGRADE_STATE_SHOULD_DO, }; diff --git a/packages/app-mobile/components/screens/encryption-config.tsx b/packages/app-mobile/components/screens/encryption-config.tsx index a81b7071f..6e9c20866 100644 --- a/packages/app-mobile/components/screens/encryption-config.tsx +++ b/packages/app-mobile/components/screens/encryption-config.tsx @@ -10,7 +10,7 @@ import EncryptionService from '@joplin/lib/services/EncryptionService'; import { _ } from '@joplin/lib/locale'; import time from '@joplin/lib/time'; 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 { SyncInfo } from '@joplin/lib/services/synchronizer/syncInfoUtils'; import { setupAndDisableEncryption, toggleAndSetupEncryption } from '@joplin/lib/services/e2ee/utils'; diff --git a/packages/lib/components/shared/encryption-config-shared.ts b/packages/lib/components/shared/encryption-config-shared.ts index 153ace38f..d5f6cb28e 100644 --- a/packages/lib/components/shared/encryption-config-shared.ts +++ b/packages/lib/components/shared/encryption-config-shared.ts @@ -5,8 +5,9 @@ import Setting from '../../models/Setting'; import MasterKey from '../../models/MasterKey'; import { reg } from '../../registry.js'; import shim from '../../shim'; -import { MasterKeyEntity } from '../../services/database/types'; +import { MasterKeyEntity } from '../../services/e2ee/types'; import time from '../../time'; +import { masterKeyEnabled, setMasterKeyEnabled } from '../../services/synchronizer/syncInfoUtils'; class Shared { @@ -20,6 +21,7 @@ class Shared { total: null, }, passwords: Object.assign({}, props.passwords), + showDisabledMasterKeys: false, }; comp.isMounted_ = false; @@ -33,6 +35,10 @@ class Shared { }); } + public async toggleShowDisabledMasterKeys(comp: any) { + comp.setState({ showDisabledMasterKeys: !comp.state.showDisabledMasterKeys }); + } + public async reencryptData() { const ok = confirm(_('Please confirm that you would like to re-encrypt your complete database.')); if (!ok) return; @@ -138,6 +144,10 @@ class Shared { comp.setState({ passwords: passwords }); } + public onToggleEnabledClick(_comp: any, mk: MasterKeyEntity) { + setMasterKeyEnabled(mk.id, !masterKeyEnabled(mk)); + } + 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.')]; if (masterKey) msg.push(_('Encryption will be enabled using the master key created on %s', time.unixMsToLocalDateTime(masterKey.created_time))); diff --git a/packages/lib/models/MasterKey.ts b/packages/lib/models/MasterKey.ts index 711100cc8..e059dbd58 100644 --- a/packages/lib/models/MasterKey.ts +++ b/packages/lib/models/MasterKey.ts @@ -1,5 +1,5 @@ import BaseModel from '../BaseModel'; -import { MasterKeyEntity } from '../services/database/types'; +import { MasterKeyEntity } from '../services/e2ee/types'; import { localSyncInfo, saveLocalSyncInfo } from '../services/synchronizer/syncInfoUtils'; import BaseItem from './BaseItem'; import uuid from '../uuid'; diff --git a/packages/lib/reducer.ts b/packages/lib/reducer.ts index b4b5d7e10..53f005736 100644 --- a/packages/lib/reducer.ts +++ b/packages/lib/reducer.ts @@ -55,7 +55,7 @@ export interface State { folders: any[]; tags: any[]; masterKeys: any[]; - notLoadedMasterKeys: any[]; + notLoadedMasterKeys: string[]; searches: any[]; highlightedWords: string[]; selectedNoteIds: string[]; diff --git a/packages/lib/services/EncryptionService.ts b/packages/lib/services/EncryptionService.ts index edaa3f524..314951a8f 100644 --- a/packages/lib/services/EncryptionService.ts +++ b/packages/lib/services/EncryptionService.ts @@ -1,4 +1,4 @@ -import { MasterKeyEntity } from './database/types'; +import { MasterKeyEntity } from './e2ee/types'; import Logger from '../Logger'; import shim from '../shim'; import Setting from '../models/Setting'; diff --git a/packages/lib/services/database/types.ts b/packages/lib/services/database/types.ts index 6de6a6f2c..250316a2d 100644 --- a/packages/lib/services/database/types.ts +++ b/packages/lib/services/database/types.ts @@ -14,6 +14,10 @@ export interface BaseItemEntity { + + + + // AUTO-GENERATED BY packages/tools/generate-database-types.js /* @@ -66,16 +70,6 @@ export interface KeyValueEntity { "updated_time"?: 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 { "id"?: number | null "number"?: number diff --git a/packages/lib/services/e2ee/types.ts b/packages/lib/services/e2ee/types.ts new file mode 100644 index 000000000..264bee2b7 --- /dev/null +++ b/packages/lib/services/e2ee/types.ts @@ -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; +} diff --git a/packages/lib/services/e2ee/utils.test.ts b/packages/lib/services/e2ee/utils.test.ts new file mode 100644 index 000000000..95dfe0489 --- /dev/null +++ b/packages/lib/services/e2ee/utils.test.ts @@ -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); + }); + +}); diff --git a/packages/lib/services/e2ee/utils.ts b/packages/lib/services/e2ee/utils.ts index 753b35b8f..5234dab5a 100644 --- a/packages/lib/services/e2ee/utils.ts +++ b/packages/lib/services/e2ee/utils.ts @@ -2,9 +2,9 @@ import Logger from '../../Logger'; import BaseItem from '../../models/BaseItem'; import MasterKey from '../../models/MasterKey'; import Setting from '../../models/Setting'; -import { MasterKeyEntity } from '../database/types'; +import { MasterKeyEntity } from './types'; import EncryptionService from '../EncryptionService'; -import { getActiveMasterKeyId, setEncryptionEnabled } from '../synchronizer/syncInfoUtils'; +import { getActiveMasterKeyId, masterKeyEnabled, setEncryptionEnabled, SyncInfo } from '../synchronizer/syncInfoUtils'; const logger = Logger.create('e2ee/utils'); @@ -90,3 +90,16 @@ export async function loadMasterKeysFromSettings(service: EncryptionService) { 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; +} diff --git a/packages/lib/services/synchronizer/syncInfoUtils.test.ts b/packages/lib/services/synchronizer/syncInfoUtils.test.ts new file mode 100644 index 000000000..2a0db899e --- /dev/null +++ b/packages/lib/services/synchronizer/syncInfoUtils.test.ts @@ -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); + }); + +}); diff --git a/packages/lib/services/synchronizer/syncInfoUtils.ts b/packages/lib/services/synchronizer/syncInfoUtils.ts index 966bf3bd1..29c8dc214 100644 --- a/packages/lib/services/synchronizer/syncInfoUtils.ts +++ b/packages/lib/services/synchronizer/syncInfoUtils.ts @@ -2,7 +2,7 @@ import { FileApi } from '../../file-api'; import JoplinDatabase from '../../JoplinDatabase'; import Setting from '../../models/Setting'; import { State } from '../../reducer'; -import { MasterKeyEntity } from '../database/types'; +import { MasterKeyEntity } from '../e2ee/types'; export interface SyncInfoValueBoolean { value: boolean; @@ -233,3 +233,24 @@ export function getActiveMasterKey(s: SyncInfo = null): MasterKeyEntity | null { if (!s.activeMasterKeyId) return null; 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; +} diff --git a/packages/tools/generate-database-types.ts b/packages/tools/generate-database-types.ts index b35226d73..9e87ff917 100644 --- a/packages/tools/generate-database-types.ts +++ b/packages/tools/generate-database-types.ts @@ -22,6 +22,7 @@ async function main() { 'main.notes_fts_segdir', 'main.notes_fts_docsize', 'main.notes_fts_stat', + 'main.master_keys', ], };