42 changes: 42 additions & 0 deletions packages/lib/services/e2ee/utils.test.ts
Original file line number Diff line number Diff line change
@@ -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);
});

});
17 changes: 15 additions & 2 deletions packages/lib/services/e2ee/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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');

Expand Down Expand Up @@ -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;
}
37 changes: 37 additions & 0 deletions packages/lib/services/synchronizer/syncInfoUtils.test.ts
Original file line number Diff line number Diff line change
@@ -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);
});

});
23 changes: 22 additions & 1 deletion packages/lib/services/synchronizer/syncInfoUtils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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;
}
1 change: 1 addition & 0 deletions packages/tools/generate-database-types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ async function main() {
'main.notes_fts_segdir',
'main.notes_fts_docsize',
'main.notes_fts_stat',
'main.master_keys',
],
};

Expand Down