From 77aa1acaf551fb6b73a3e8e476419dfd33bda54f Mon Sep 17 00:00:00 2001 From: Roman Shevchenko Date: Fri, 19 Feb 2021 09:31:42 -0500 Subject: [PATCH 01/53] refactored out index_has_pgp index usage --- extension/js/common/platform/store/contact-store.ts | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/extension/js/common/platform/store/contact-store.ts b/extension/js/common/platform/store/contact-store.ts index 51aeb17a3da..82e844b367c 100644 --- a/extension/js/common/platform/store/contact-store.ts +++ b/extension/js/common/platform/store/contact-store.ts @@ -278,11 +278,15 @@ export class ContactStore extends AbstractStore { if (typeof query.has_pgp === 'undefined') { // any query.has_pgp value search = contacts.openCursor(); // no substring, already covered in `typeof query.has_pgp === 'undefined' && query.substring` above } else { // specific query.has_pgp value + let range: IDBKeyRange; if (query.substring) { - search = contacts.index('search').openCursor(IDBKeyRange.only(ContactStore.dbIndex(query.has_pgp, query.substring))); + range = IDBKeyRange.only(ContactStore.dbIndex(query.has_pgp, query.substring)); + } else if (query.has_pgp) { + range = IDBKeyRange.lowerBound('t:', false); // starting with 't:' inclusive } else { - search = contacts.index('index_has_pgp').openCursor(IDBKeyRange.only(Number(query.has_pgp))); + range = IDBKeyRange.upperBound('t:', true); // up to 't:', excluding it gives false range } + search = contacts.index('search').openCursor(range); } const found: unknown[] = []; search.onsuccess = () => { From 10a9876050e2f59db02eaccaf5bb8b620198a690 Mon Sep 17 00:00:00 2001 From: Roman Shevchenko Date: Thu, 25 Feb 2021 06:17:06 -0500 Subject: [PATCH 02/53] wip --- extension/chrome/elements/add_pubkey.ts | 2 +- .../compose-my-pubkey-module.ts | 2 +- .../compose-recipients-module.ts | 9 +- .../compose-modules/compose-render-module.ts | 1 - .../compose-modules/compose-storage-module.ts | 5 +- .../pgp-block-signature-module.ts | 6 +- extension/chrome/elements/pgp_pubkey.ts | 3 +- extension/chrome/settings/modules/contacts.ts | 6 +- extension/chrome/settings/setup.ts | 1 - extension/js/common/core/crypto/key.ts | 6 +- .../js/common/platform/store/contact-store.ts | 368 ++++++++++-------- test/source/platform/store/contact-store.ts | 39 +- test/source/tests/unit-node.ts | 75 +++- 13 files changed, 282 insertions(+), 241 deletions(-) diff --git a/extension/chrome/elements/add_pubkey.ts b/extension/chrome/elements/add_pubkey.ts index 644cf9ca38f..c8ff3349531 100644 --- a/extension/chrome/elements/add_pubkey.ts +++ b/extension/chrome/elements/add_pubkey.ts @@ -87,7 +87,7 @@ View.run(class AddPubkeyView extends View { try { const keyImportUi = new KeyImportUi({ checkEncryption: true }); const normalized = await keyImportUi.checkPub(String($('.pubkey').val())); - await ContactStore.save(undefined, await ContactStore.obj({ email: String($('select.email').val()), client: 'pgp', pubkey: normalized })); + await ContactStore.save(undefined, await ContactStore.obj({ email: String($('select.email').val()), pubkey: normalized })); this.closeDialog(); } catch (e) { if (e instanceof UserAlert) { diff --git a/extension/chrome/elements/compose-modules/compose-my-pubkey-module.ts b/extension/chrome/elements/compose-modules/compose-my-pubkey-module.ts index 094a49fa718..9ff5beaf140 100644 --- a/extension/chrome/elements/compose-modules/compose-my-pubkey-module.ts +++ b/extension/chrome/elements/compose-modules/compose-my-pubkey-module.ts @@ -46,7 +46,7 @@ export class ComposeMyPubkeyModule extends ViewModule { (async () => { const contacts = await ContactStore.get(undefined, this.view.recipientsModule.getRecipients().map(r => r.email)); for (const contact of contacts) { - if (contact?.has_pgp && contact.client !== 'cryptup') { + if (contact?.has_pgp) { // new message, and my key is not uploaded where the recipient would look for it if (! await this.view.recipientsModule.doesRecipientHaveMyPubkey(contact.email)) { // either don't know if they need pubkey (can_read_emails false), or they do need pubkey diff --git a/extension/chrome/elements/compose-modules/compose-recipients-module.ts b/extension/chrome/elements/compose-modules/compose-recipients-module.ts index e23ff55c7d2..08857932ad6 100644 --- a/extension/chrome/elements/compose-modules/compose-recipients-module.ts +++ b/extension/chrome/elements/compose-modules/compose-recipients-module.ts @@ -736,15 +736,8 @@ export class ComposeRecipientsModule extends ViewModule { const contact = await ContactStore.obj({ email: input.email, name: input.name }); const [storedContact] = await ContactStore.get(undefined, [contact.email]); if (storedContact) { - const toUpdate: ContactUpdate = {}; if (!storedContact.name && contact.name) { - toUpdate.name = contact.name; - } - if (storedContact.searchable.length !== contact.searchable.length && storedContact.searchable.join(',') !== contact.searchable.join(',')) { - toUpdate.searchable = contact.searchable; - } - if (Object.keys(toUpdate).length) { - await ContactStore.update(undefined, contact.email, toUpdate); + await ContactStore.update(undefined, contact.email, { name: contact.name } as ContactUpdate); } } else if (!this.failedLookupEmails.includes(contact.email)) { toLookup.push(contact); diff --git a/extension/chrome/elements/compose-modules/compose-render-module.ts b/extension/chrome/elements/compose-modules/compose-render-module.ts index 55cebc77c17..f441f31b472 100644 --- a/extension/chrome/elements/compose-modules/compose-render-module.ts +++ b/extension/chrome/elements/compose-modules/compose-render-module.ts @@ -307,7 +307,6 @@ export class ComposeRenderModule extends ViewModule { await ContactStore.save(undefined, await ContactStore.obj({ email: key.emails[0], name: Str.parseEmail(key.identities[0]).name, - client: 'pgp', pubkey: normalizedPub, lastCheck: Date.now(), })); diff --git a/extension/chrome/elements/compose-modules/compose-storage-module.ts b/extension/chrome/elements/compose-modules/compose-storage-module.ts index 41cbb3c4ca5..7d99ef21958 100644 --- a/extension/chrome/elements/compose-modules/compose-storage-module.ts +++ b/extension/chrome/elements/compose-modules/compose-storage-module.ts @@ -110,12 +110,11 @@ export class ComposeStorageModule extends ViewModule { lookupResult.pubkey = null; // tslint:disable-line:no-null-keyword } } - const client = lookupResult.pgpClient === 'flowcrypt' ? 'cryptup' : 'pgp'; // todo - clean up as "flowcrypt|pgp-other'. Already in storage, fixing involves migration const ksContact = await ContactStore.obj({ email, name, pubkey: lookupResult.pubkey, - client: lookupResult.pubkey ? client : undefined, + // client: // todo: decide whether we need this field lastCheck: Date.now(), }); if (ksContact.pubkey) { @@ -142,7 +141,7 @@ export class ComposeStorageModule extends ViewModule { if (!contact.pubkey_last_sig) { const lastSig = Number(contact.pubkey.lastModified); contact.pubkey_last_sig = lastSig; - await ContactStore.update(undefined, contact.email, { pubkey_last_sig: lastSig }); + await ContactStore.update(undefined, contact.email, { pubkey: contact.pubkey }); } const lastCheckOverWeekAgoOrNever = !contact.pubkey_last_check || new Date(contact.pubkey_last_check).getTime() < Date.now() - (1000 * 60 * 60 * 24 * 7); const isExpired = contact.expiresOn && contact.expiresOn < Date.now(); diff --git a/extension/chrome/elements/pgp_block_modules/pgp-block-signature-module.ts b/extension/chrome/elements/pgp_block_modules/pgp-block-signature-module.ts index 944a8bd24ad..6d20150b399 100644 --- a/extension/chrome/elements/pgp_block_modules/pgp-block-signature-module.ts +++ b/extension/chrome/elements/pgp_block_modules/pgp-block-signature-module.ts @@ -55,11 +55,11 @@ export class PgpBlockViewSignatureModule { if (senderEmail) { // we know who sent it const [senderContactByEmail] = await ContactStore.get(undefined, [senderEmail]); if (senderContactByEmail && senderContactByEmail.pubkey) { - render(`Fetched the right pubkey ${signerLongid} from keyserver, but will not use it because you have conflicting pubkey ${senderContactByEmail.longid} loaded.`, () => undefined); + render(`Fetched the right pubkey ${signerLongid} from keyserver, but will not use it because you have conflicting pubkey ${senderContactByEmail.pubkey.id} loaded.`, () => undefined); return; } // ---> and user doesn't have pubkey for that email addr - const { pubkey, pgpClient } = await this.view.pubLookup.lookupEmail(senderEmail); + const { pubkey } = await this.view.pubLookup.lookupEmail(senderEmail); if (!pubkey) { render(`Missing pubkey ${signerLongid}`, () => undefined); return; @@ -71,7 +71,7 @@ export class PgpBlockViewSignatureModule { return; } // ---> and longid it matches signature - await ContactStore.save(undefined, await ContactStore.obj({ email: senderEmail, pubkey, client: pgpClient })); // <= TOFU auto-import + await ContactStore.save(undefined, await ContactStore.obj({ email: senderEmail, pubkey })); // <= TOFU auto-import render('Fetched pubkey, click to verify', () => window.location.reload()); } else { // don't know who sent it render('Cannot verify: missing pubkey, missing sender info', () => undefined); diff --git a/extension/chrome/elements/pgp_pubkey.ts b/extension/chrome/elements/pgp_pubkey.ts index 98cb3c93bde..ab16c4ab5ee 100644 --- a/extension/chrome/elements/pgp_pubkey.ts +++ b/extension/chrome/elements/pgp_pubkey.ts @@ -140,7 +140,7 @@ View.run(class PgpPubkeyView extends View { for (const pubkey of this.publicKeys!) { const email = pubkey.emails[0]; if (email) { - contacts.push(await ContactStore.obj({ email, client: 'pgp', pubkey: KeyUtil.armor(pubkey), lastSig: Number(pubkey.lastModified) })); + contacts.push(await ContactStore.obj({ email, pubkey: KeyUtil.armor(pubkey), lastSig: Number(pubkey.lastModified) })); } } await ContactStore.save(undefined, contacts); @@ -154,7 +154,6 @@ View.run(class PgpPubkeyView extends View { if (Str.isEmailValid(String($('.input_email').val()))) { const contact = await ContactStore.obj({ email: String($('.input_email').val()), - client: 'pgp', pubkey: KeyUtil.armor(this.publicKeys![0]), lastSig: Number(this.publicKeys![0].lastModified) }); diff --git a/extension/chrome/settings/modules/contacts.ts b/extension/chrome/settings/modules/contacts.ts index 6b415622e0c..8db926dc7e8 100644 --- a/extension/chrome/settings/modules/contacts.ts +++ b/extension/chrome/settings/modules/contacts.ts @@ -118,11 +118,7 @@ View.run(class ContactsView extends View { const [contact] = await ContactStore.get(undefined, [$(viewPubkeyButton).closest('tr').attr('email')!]); // defined above $('.hide_when_rendering_subpage').css('display', 'none'); Xss.sanitizeRender('h1', `${this.backBtn}${this.space}${contact!.email}`); // should exist - from list of contacts - if (contact!.client === 'cryptup') { - Xss.sanitizeAppend('h1', '    '); - } else { - Xss.sanitizeAppend('h1', '      '); - } + Xss.sanitizeAppend('h1', '      '); // todo: do we need this line? $('#view_contact .key_dump').text(KeyUtil.armor(contact!.pubkey!)); // should exist - from list of contacts && should have pgp - filtered $('#view_contact #container-pubkey-details').text([ `Type: ${contact?.pubkey?.type}`, diff --git a/extension/chrome/settings/setup.ts b/extension/chrome/settings/setup.ts index ab7de6fac04..4d2673ff0a8 100644 --- a/extension/chrome/settings/setup.ts +++ b/extension/chrome/settings/setup.ts @@ -209,7 +209,6 @@ export class SetupView extends View { myOwnEmailAddrsAsContacts.push(await ContactStore.obj({ email, name, - client: 'cryptup', pubkey: KeyUtil.armor(await KeyUtil.asPublicKey(prvs[0])), lastSig: Number(prvs[0].lastModified) })); diff --git a/extension/js/common/core/crypto/key.ts b/extension/js/common/core/crypto/key.ts index 6ca0c16644a..5a873eb024e 100644 --- a/extension/js/common/core/crypto/key.ts +++ b/extension/js/common/core/crypto/key.ts @@ -51,13 +51,9 @@ export type PubkeyResult = { pubkey: Key, email: string, isMine: boolean }; export type Contact = { email: string; name: string | null; - pubkey: Key | null; + pubkey: Key | undefined; has_pgp: 0 | 1; - searchable: string[]; - client: string | null; fingerprint: string | null; - longid: string | null; - longids: string[]; pending_lookup: number; last_use: number | null; pubkey_last_sig: number | null; diff --git a/extension/js/common/platform/store/contact-store.ts b/extension/js/common/platform/store/contact-store.ts index 82e844b367c..1d1eb03655d 100644 --- a/extension/js/common/platform/store/contact-store.ts +++ b/extension/js/common/platform/store/contact-store.ts @@ -1,6 +1,5 @@ /* ©️ 2016 - present FlowCrypt a.s. Limitations apply. Contact human@flowcrypt.com */ -import { PgpClient } from '../../api/pub-lookup.js'; import { AbstractStore } from './abstract-store.js'; import { Catch } from '../catch.js'; import { opgp } from '../../core/crypto/pgp/openpgpjs-custom.js'; @@ -14,7 +13,6 @@ import { OpenPGPKey } from '../../core/crypto/pgp/openpgp-key.js'; type DbContactObjArg = { email: string, name?: string | null, - client?: 'pgp' | 'cryptup' | PgpClient | null, pubkey?: string | null, pendingLookup?: boolean | number | null, lastUse?: number | null, // when was this contact last used to send an email @@ -22,6 +20,24 @@ type DbContactObjArg = { lastCheck?: number | null; // when was the local copy of the pubkey last updated (or checked against Attester) }; +type Email = { + email: string; + name: string | null; + searchable: string[]; + fingerprints: string[]; + pendingLookup: number; + lastUse: number | null; +}; + +type Pubkey = { + fingerprint: string; + armoredKey: string; + longids: string[]; + lastCheck: number | null, + lastSig: number; + expiresOn: number; +}; + export type ContactPreview = { email: string; name: string | null; @@ -33,16 +49,9 @@ export type ContactUpdate = { email?: string; name?: string | null; pubkey?: Key; - has_pgp?: 0 | 1; - searchable?: string[]; - client?: string | null; - fingerprint?: string | null; - longid?: string | null; pending_lookup?: number; last_use?: number | null; - pubkey_last_sig?: number | null; pubkey_last_check?: number | null; - expiresOn?: number | null; }; type DbContactFilter = { has_pgp?: boolean, substring?: string, limit?: number }; @@ -61,27 +70,23 @@ export class ContactStore extends AbstractStore { public static dbOpen = async (): Promise => { return await new Promise((resolve, reject) => { let openDbReq: IDBOpenDBRequest; - openDbReq = indexedDB.open('cryptup', 3); + openDbReq = indexedDB.open('cryptup', 4); openDbReq.onupgradeneeded = (event) => { - let contacts: IDBObjectStore; - if (event.oldVersion < 1) { - contacts = openDbReq.result.createObjectStore('contacts', { keyPath: 'email' }); // tslint:disable-line:no-unsafe-any - contacts.createIndex('search', 'searchable', { multiEntry: true }); - contacts.createIndex('index_has_pgp', 'has_pgp'); - contacts.createIndex('index_pending_lookup', 'pending_lookup'); - } - if (event.oldVersion < 2) { - contacts = openDbReq.transaction!.objectStore('contacts'); - contacts.createIndex('index_longid', 'longid'); // longid of the first public key packet, no subkeys + if (event.oldVersion < 4) { + const emails = openDbReq.result.createObjectStore('emails', { keyPath: 'email' }); + const pubkeys = openDbReq.result.createObjectStore('pubkeys', { keyPath: 'fingerprint' }); + emails.createIndex('search', 'searchable', { multiEntry: true }); + emails.createIndex('index_pending_lookup', 'pendingLookup'); + emails.createIndex('index_fingerprints', 'fingerprints', { multiEntry: true }); // fingerprints of all connected pubkeys + pubkeys.createIndex('index_longids', 'longids', { multiEntry: true }); // longids of all public key packets in armored pubkey } - if (event.oldVersion < 3) { - contacts = openDbReq.transaction!.objectStore('contacts'); - contacts.createIndex('index_longids', 'longids', { multiEntry: true }); // longids of all public key packets in armored pubkey - } - }; - openDbReq.onsuccess = () => resolve(openDbReq.result as IDBDatabase); - openDbReq.onblocked = () => reject(ContactStore.errCategorize(openDbReq.error)); - openDbReq.onerror = () => reject(ContactStore.errCategorize(openDbReq.error)); + if (event.oldVersion < 5) { // delete when migrating from 4 to 5? + // openDbReq.result.deleteObjectStore('contacts'); // tslint:disable-line:no-unsafe-any + }; + openDbReq.onsuccess = () => resolve(openDbReq.result as IDBDatabase); + openDbReq.onblocked = () => reject(ContactStore.errCategorize(openDbReq.error)); + openDbReq.onerror = () => reject(ContactStore.errCategorize(openDbReq.error)); + } }); } @@ -93,9 +98,9 @@ export class ContactStore extends AbstractStore { return { email: validEmail, name: name || null, has_pgp: 0, last_use: null }; } - public static obj = async ({ email, name, client, pubkey, pendingLookup, lastUse, lastCheck, lastSig }: DbContactObjArg): Promise => { + public static obj = async ({ email, name, pubkey, pendingLookup, lastUse, lastCheck, lastSig }: DbContactObjArg): Promise => { if (typeof opgp === 'undefined') { - return await BrowserMsg.send.bg.await.db({ f: 'obj', args: [{ email, name, client, pubkey, pendingLookup, lastUse, lastSig, lastCheck }] }) as Contact; + return await BrowserMsg.send.bg.await.db({ f: 'obj', args: [{ email, name, pubkey, pendingLookup, lastUse, lastSig, lastCheck }] }) as Contact; } else { const validEmail = Str.parseEmail(email).email; if (!validEmail) { @@ -106,13 +111,9 @@ export class ContactStore extends AbstractStore { email: validEmail, name: name || null, pending_lookup: (pendingLookup ? 1 : 0), - pubkey: null, + pubkey: undefined, has_pgp: 0, // number because we use it for sorting - searchable: ContactStore.dbCreateSearchIndexList(validEmail, name || null, false), - client: null, fingerprint: null, - longid: null, - longids: [], last_use: lastUse || null, pubkey_last_sig: null, pubkey_last_check: null, @@ -120,22 +121,15 @@ export class ContactStore extends AbstractStore { }; } const pk = await KeyUtil.parse(pubkey); - const expiresOnMs = Number(pk.expiration) || undefined; return { email: validEmail, name: name || null, pubkey: pk, has_pgp: 1, // number because we use it for sorting - searchable: ContactStore.dbCreateSearchIndexList(validEmail, name || null, true), - client: ContactStore.storablePgpClient(client || 'pgp'), - fingerprint: pk.id, - longid: OpenPGPKey.fingerprintToLongid(pk.id), - longids: pk.allIds.map(id => OpenPGPKey.fingerprintToLongid(id)), pending_lookup: 0, last_use: lastUse || null, - pubkey_last_sig: Number(pk.lastModified) || null, pubkey_last_check: lastCheck || null, - expiresOn: expiresOnMs || null + ...ContactStore.getKeyAttributes(pk) }; } } @@ -152,15 +146,7 @@ export class ContactStore extends AbstractStore { await Promise.all(contact.map(oneContact => ContactStore.save(db, oneContact))); return; } - // serializing for storage - const prepared = contact.pubkey ? { ...contact, pubkey: KeyUtil.armor(contact.pubkey) as unknown as Key } : contact; - return await new Promise((resolve, reject) => { - const tx = db.transaction('contacts', 'readwrite'); - const contactsTable = tx.objectStore('contacts'); - contactsTable.put(prepared); - tx.oncomplete = () => resolve(); - tx.onabort = () => reject(ContactStore.errCategorize(tx.error)); - }); + await ContactStore.update(db, contact.email, contact); } /** @@ -175,43 +161,58 @@ export class ContactStore extends AbstractStore { await Promise.all(email.map(oneEmail => ContactStore.update(db, oneEmail, update))); return; } - let [existing] = await ContactStore.get(db, [email]); - if (!existing) { // updating a non-existing contact, insert it first - await ContactStore.save(db, await ContactStore.obj({ email })); - [existing] = await ContactStore.get(db, [email]); - if (!existing) { - throw new Error('contact not found right after inserting it'); - } - } if (update.pubkey?.isPrivate) { Catch.report('Wrongly updating prv as contact - converting to pubkey'); update.pubkey = await KeyUtil.asPublicKey(update.pubkey); } - if (!update.searchable && (update.name !== existing.name || update.has_pgp !== existing.has_pgp)) { // update searchable index based on new name or new has_pgp - const newHasPgp = Boolean(typeof update.has_pgp !== 'undefined' && update.has_pgp !== null ? update.has_pgp : existing.has_pgp); - const newName = typeof update.name !== 'undefined' && update.name !== null ? update.name : existing.name; - update.searchable = ContactStore.dbCreateSearchIndexList(existing.email, newName, newHasPgp); - } - const updated = existing; - if (update.pubkey) { - const key = typeof update.pubkey === 'string' ? await KeyUtil.parse(update.pubkey) : update.pubkey; - update.fingerprint = key.id; - update.longid = OpenPGPKey.fingerprintToLongid(key.id); - update.pubkey_last_sig = key.lastModified ? Number(key.lastModified) : null; - update.expiresOn = key.expiration ? Number(key.expiration) : null; - update.pubkey = KeyUtil.armor(key) as unknown as Key; // serialising for storage - update.has_pgp = 1; - } - for (const k of Object.keys(update)) { - // @ts-ignore - updated[k] = update[k]; - } - return await new Promise((resolve, reject) => { - const tx = db.transaction('contacts', 'readwrite'); - const contactsTable = tx.objectStore('contacts'); - contactsTable.put(updated); - tx.oncomplete = () => resolve(); - tx.onabort = () => reject(ContactStore.errCategorize(tx.error)); + await new Promise((resolve, reject) => { + const tx = db.transaction(['emails', 'pubkeys'], 'readwrite'); + const req = tx.objectStore('emails').get(email); + ContactStore.setReqPipe(req, + (emailEntity: Email) => { + if (!emailEntity) { + const validEmail = Str.parseEmail(email).email; + if (!validEmail) { + reject(`Cannot save contact because email is not valid: ${email}`); + return; + } + emailEntity = { email, name: null, searchable: [], fingerprints: [], pendingLookup: 0, lastUse: null }; + } + if (update.pubkey) { + // todo: get the pubkey record if it exists to retrieve lastCheck + /* const req1 = tx.objectStore('pubkeys').get(update.pubkey.id); + ContactStore.setReqPipe(req1, ... */ + const keyAttrs = ContactStore.getKeyAttributes(update.pubkey); + const pubkeyEntity = { + fingerprint: update.pubkey.id, + lastCheck: null, + lastSig: keyAttrs.pubkey_last_sig, + expiresOn: keyAttrs.expiresOn, + longids: update.pubkey.allIds.map(id => OpenPGPKey.fingerprintToLongid(id)), + armoredKey: KeyUtil.armor(update.pubkey) + } as Pubkey; + if (!emailEntity.fingerprints.includes(pubkeyEntity.fingerprint)) { + emailEntity.fingerprints.push(pubkeyEntity.fingerprint); + } + if (Object.keys(update).includes('pubkey_last_check') && update.pubkey_last_check) { + pubkeyEntity.lastCheck = update.pubkey_last_check; + } + tx.objectStore('pubkeys').put(pubkeyEntity); + } + if (Object.keys(update).includes('name')) { + emailEntity.name = update.name ?? null; + } + if (Object.keys(update).includes('pending_lookup')) { + emailEntity.pendingLookup = update.pending_lookup ?? 0; + } + if (Object.keys(update).includes('last_use')) { + emailEntity.lastUse = update.last_use ?? null; + } + ContactStore.updateSearchable(emailEntity); + tx.objectStore('emails').put(emailEntity); + tx.oncomplete = () => resolve(undefined); + tx.onabort = () => reject(ContactStore.errCategorize(tx.error)); + }, reject); }); } @@ -220,20 +221,11 @@ export class ContactStore extends AbstractStore { return ContactStore.recreateDates(await BrowserMsg.send.bg.await.db({ f: 'get', args: [emailOrLongid] }) as (Contact | undefined)[]); } if (emailOrLongid.length === 1) { - // contacts imported before August 2019 may have only primary longid recorded, in index_longid (string) - // contacts imported after August 2019 have both index_longid (string) and index_longids (string[] containing all subkeys) - // below we search contact by first trying to only search by primary longid - // (or by email - such searches are not affected by longid indexing) - const contact = await ContactStore.dbContactInternalGetOne(db, emailOrLongid[0], false); - if (contact || !/^[A-F0-9]{16}$/.test(emailOrLongid[0])) { - // if we found something, return it - // or if we were searching by email, return found contact or nothing - return ContactStore.recreateDates([contact]); - } else { - // not found any key by primary longid, and searching by longid -> search by any subkey longid - // it may not find pubkeys imported before August 2019, re-importing such pubkeys will make them findable - return ContactStore.recreateDates([await ContactStore.dbContactInternalGetOne(db, emailOrLongid[0], true)]); + const contact = await ContactStore.dbContactInternalGetOne(db, emailOrLongid[0]); + if (!contact) { + return [contact]; } + return ContactStore.recreateDates([contact]); } else { const results: (Contact | undefined)[] = []; for (const singleEmailOrLongid of emailOrLongid) { @@ -249,12 +241,36 @@ export class ContactStore extends AbstractStore { } public static searchPubkeys = async (db: IDBDatabase | undefined, query: DbContactFilter): Promise => { - return (await ContactStore.rawSearch(db, query)).filter(Boolean).map(ContactStore.toArmoredPubkey).filter(Boolean); + const fingerprints = (await ContactStore.rawSearch(db, query)).filter(Boolean).map(email => email.fingerprints).reduce((a, b) => a.concat(b)); + return (await ContactStore.extractPubkeys(db, fingerprints)).map(pubkey => pubkey?.armoredKey).filter(Boolean); } - private static rawSearch = async (db: IDBDatabase | undefined, query: DbContactFilter): Promise => { + private static extractPubkeys = async (db: IDBDatabase | undefined, fingerprints: string[]): Promise => { if (!db) { // relay op through background process - return await BrowserMsg.send.bg.await.db({ f: 'rawSearch', args: [query] }) as unknown[]; + return await BrowserMsg.send.bg.await.db({ f: 'extractPubkeys', args: [fingerprints] }) as Pubkey[]; + } + const raw: Pubkey[] = await new Promise((resolve, reject) => { + const tx = db.transaction(['pubkeys'], 'readonly'); + const search = tx.objectStore('pubkeys').openCursor(fingerprints); + const found: Pubkey[] = []; + ContactStore.setReqPipe(search, + () => { + const cursor = search.result as IDBCursorWithValue | undefined; + if (!cursor) { + resolve(found); + } else { + found.push(cursor.value); // tslint:disable-line:no-unsafe-any + cursor.continue(); + } + }, + reject); + }); + return raw; + } + + private static rawSearch = async (db: IDBDatabase | undefined, query: DbContactFilter): Promise => { + if (!db) { // relay op through background process + return await BrowserMsg.send.bg.await.db({ f: 'rawSearch', args: [query] }) as Email[]; } for (const key of Object.keys(query)) { if (!ContactStore.dbQueryKeys.includes(key)) { @@ -272,11 +288,11 @@ export class ContactStore extends AbstractStore { return resultsWithPgp.concat(resultsWithoutPgp); } } - const raw: unknown[] = await new Promise((resolve, reject) => { - const contacts = db.transaction('contacts', 'readonly').objectStore('contacts'); + const raw: Email[] = await new Promise((resolve, reject) => { + const emails = db.transaction(['emails'], 'readonly').objectStore('emails'); let search: IDBRequest; if (typeof query.has_pgp === 'undefined') { // any query.has_pgp value - search = contacts.openCursor(); // no substring, already covered in `typeof query.has_pgp === 'undefined' && query.substring` above + search = emails.openCursor(); // no substring, already covered in `typeof query.has_pgp === 'undefined' && query.substring` above } else { // specific query.has_pgp value let range: IDBKeyRange; if (query.substring) { @@ -286,28 +302,24 @@ export class ContactStore extends AbstractStore { } else { range = IDBKeyRange.upperBound('t:', true); // up to 't:', excluding it gives false range } - search = contacts.index('search').openCursor(range); + search = emails.index('search').openCursor(range); } - const found: unknown[] = []; - search.onsuccess = () => { - try { + const found: Email[] = []; + ContactStore.setReqPipe(search, + () => { const cursor = search.result as IDBCursorWithValue | undefined; if (!cursor) { resolve(found); } else { - found.push(cursor.value); + found.push(cursor.value); // tslint:disable-line:no-unsafe-any if (query.limit && found.length >= query.limit) { resolve(found); } else { cursor.continue(); } } - } catch (codeErr) { - reject(codeErr); - Catch.reportErr(codeErr); - } - }; - search.onerror = () => reject(ContactStore.errCategorize(search!.error!)); // todo - added ! after ts3 upgrade - investigate + }, + reject); }); return raw; } @@ -323,9 +335,9 @@ export class ContactStore extends AbstractStore { return (hasPgp ? 't:' : 'f:') + substring; } - private static dbCreateSearchIndexList = (email: string, name: string | null, hasPgp: boolean) => { - email = email.toLowerCase(); - name = name ? name.toLowerCase() : ''; + private static updateSearchable = (emailEntity: Email) => { + const email = emailEntity.email.toLowerCase(); + const name = emailEntity.name ? emailEntity.name.toLowerCase() : ''; const parts = [email, name]; const domain: string = email.split('@').pop() || ''; parts.push(domain, ...email.split(/[^a-z0-9]/)); @@ -337,70 +349,100 @@ export class ContactStore extends AbstractStore { substring += letter; const normalized = ContactStore.normalizeString(substring); if (!index.includes(normalized)) { - index.push(ContactStore.dbIndex(hasPgp, normalized)); + index.push(ContactStore.dbIndex(emailEntity.fingerprints.length > 0, normalized)); } } } - return index; + emailEntity.searchable = index; } - private static storablePgpClient = (rawPgpClient: 'pgp' | 'cryptup' | PgpClient | null): 'pgp' | 'cryptup' | null => { - if (rawPgpClient === 'flowcrypt') { - return 'cryptup'; - } else if (rawPgpClient === 'pgp-other') { - return 'pgp'; - } else { - return rawPgpClient; - } + private static setReqOnError(req: IDBRequest, reject: (reason?: any) => void) { + req.onerror = () => reject(ContactStore.errCategorize(req.error || new Error('Unknown db error'))); } - private static dbContactInternalGetOne = async (db: IDBDatabase, emailOrLongid: string, searchSubkeyLongids: boolean): Promise => { + private static setReqPipe(req: IDBRequest, pipe: (value?: T) => void, reject: (reason?: any) => void) { + req.onsuccess = () => { + try { + pipe(req.result as T); + } catch (codeErr) { + reject(codeErr); + Catch.reportErr(codeErr); + } + }; + this.setReqOnError(req, reject); + } + + private static dbContactInternalGetOne = async (db: IDBDatabase, emailOrLongid: string): Promise => { return await new Promise((resolve, reject) => { - let tx: IDBRequest; + const tx = db.transaction(['emails', 'pubkeys'], 'readonly'); if (!/^[A-F0-9]{16}$/.test(emailOrLongid)) { // email - tx = db.transaction('contacts', 'readonly').objectStore('contacts').get(emailOrLongid); - } else if (searchSubkeyLongids) { // search all longids - tx = db.transaction('contacts', 'readonly').objectStore('contacts').index('index_longids').get(emailOrLongid); - } else { // search primary longid - tx = db.transaction('contacts', 'readonly').objectStore('contacts').index('index_longid').get(emailOrLongid); + const req = tx.objectStore('emails').get(emailOrLongid); + ContactStore.setReqPipe(req, + (email: Email) => { + if (!email) { + resolve(undefined); + return; + } + if (!email.fingerprints || email.fingerprints.length === 0) { + resolve(undefined); + return; + } + // todo: load all fingerprints and pick a valid one? + const req2 = tx.objectStore('pubkeys').get(email.fingerprints[0]); + ContactStore.setReqPipe(req2, + (pubkey: Pubkey) => { + resolve(ContactStore.toContact(email, pubkey)); + }, + reject); + }, + reject); + } else { // search all longids + const req = tx.objectStore('pubkeys').index('index_longids').get(emailOrLongid); + ContactStore.setReqPipe(req, + (pubkey: Pubkey) => { + if (!pubkey) { + resolve(undefined); + return; + } + const req2 = tx.objectStore('emails').index('fingerprints').get(pubkey.fingerprint!); + ContactStore.setReqPipe(req2, + (email: Email) => { + if (!email) { + resolve(undefined); + } else { + resolve(ContactStore.toContact(email, pubkey)); + } + }, + reject); + }, + reject); } - tx.onsuccess = () => { - try { - resolve(ContactStore.toContact(tx.result)); - } catch (codeErr) { - reject(codeErr); - Catch.reportErr(codeErr); - } - }; - tx.onerror = () => reject(ContactStore.errCategorize(tx.error || new Error('Unknown db error'))); }); } - private static getArmoredPubkey = (pubkey: Key | string): string => { - // tslint:disable-next-line:no-unsafe-any - return (pubkey && typeof pubkey === 'object') ? KeyUtil.armor(pubkey as Key) : pubkey as string; + private static getKeyAttributes = (key: Key | undefined): { fingerprint: string | null, expiresOn: number | null, pubkey_last_sig: number | null } => { + return { fingerprint: key?.id ?? null, expiresOn: Number(key?.expiration) || null, pubkey_last_sig: Number(key?.lastModified) || null }; } - private static toContact = async (result: any): Promise => { - if (!result) { + private static toContact = async (email: Email, pubkey: Pubkey): Promise => { + if (!email) { return; } - // tslint:disable-next-line:no-unsafe-any - const armoredPubkey = ContactStore.getArmoredPubkey(result.pubkey); - // parse again to re-calculate expiration-related fields etc. - // tslint:disable-next-line:no-null-keyword - const pubkey = armoredPubkey ? await KeyUtil.parse(armoredPubkey) : null; - return { ...result, pubkey }; // tslint:disable-line:no-unsafe-any - } - - private static toContactPreview = (result: any): ContactPreview => { - // tslint:disable-next-line:no-unsafe-any - return { email: result.email, name: result.name, has_pgp: result.has_pgp, last_use: result.last_use }; + const parsed = pubkey ? await KeyUtil.parse(pubkey.armoredKey) : undefined; + return { + email: email.email, + name: email.name, + pubkey: parsed, + has_pgp: pubkey ? 1 : 0, + pending_lookup: email.pendingLookup, + last_use: email.lastUse, + pubkey_last_check: pubkey?.lastCheck, + ...ContactStore.getKeyAttributes(parsed) + }; } - private static toArmoredPubkey = (result: any): string => { - // tslint:disable-next-line:no-unsafe-any - return ContactStore.getArmoredPubkey(result.pubkey); + private static toContactPreview = (result: Email): ContactPreview => { + return { email: result.email, name: result.name, has_pgp: result.fingerprints.length > 0 ? 1 : 0, last_use: result.lastUse }; } private static recreateDates = (contacts: (Contact | undefined)[]) => { diff --git a/test/source/platform/store/contact-store.ts b/test/source/platform/store/contact-store.ts index 91e36d43ea8..cf90a77e2fb 100644 --- a/test/source/platform/store/contact-store.ts +++ b/test/source/platform/store/contact-store.ts @@ -11,23 +11,15 @@ export type ContactUpdate = { email?: string; name?: string | null; pubkey?: Key; - has_pgp?: 0 | 1; - searchable?: string[]; - client?: string | null; - fingerprint?: string | null; - longid?: string | null; pending_lookup?: number; last_use?: number | null; - pubkey_last_sig?: number | null; - pubkey_last_check?: number | null; - expiresOn?: number | null; }; export class ContactStore { public static get = async (db: void, emailOrLongid: string[]): Promise<(Contact | undefined)[]> => { const result = DATA.filter(x => emailOrLongid.includes(x.email) || - (x.longid && emailOrLongid.includes(x.longid!))); + (x.pubkey && emailOrLongid.includes(OpenPGPKey.fingerprintToLongid(x.pubkey.id)))); return result; } @@ -44,34 +36,29 @@ export class ContactStore { if (update.pubkey?.isPrivate) { update.pubkey = await KeyUtil.asPublicKey(update.pubkey); } - if (update.pubkey) { - const key = typeof update.pubkey === 'string' ? await KeyUtil.parse(update.pubkey) : update.pubkey; - update.fingerprint = key.id; - update.longid = OpenPGPKey.fingerprintToLongid(key.id); - update.pubkey_last_sig = key.lastModified ? Number(key.lastModified) : null; - update.expiresOn = key.expiration ? Number(key.expiration) : null; - update.pubkey = key; - update.has_pgp = 1; - } for (const k of Object.keys(update)) { // @ts-ignore updated[k] = update[k]; } + if (update.pubkey) { + const key = typeof update.pubkey === 'string' ? await KeyUtil.parse(update.pubkey) : update.pubkey; + updated.pubkey = key; + updated.fingerprint = key.id; + updated.pubkey_last_sig = key.lastModified ? Number(key.lastModified) : null; + updated.expiresOn = key.expiration ? Number(key.expiration) : null; + updated.has_pgp = 1; + } } - public static obj = async ({ email, name, client, pubkey, pendingLookup, lastUse, lastCheck, lastSig }: any): Promise => { + public static obj = async ({ email, name, pubkey, pendingLookup, lastUse, lastCheck, lastSig }: any): Promise => { if (!pubkey) { return { email, name: name || null, pending_lookup: (pendingLookup ? 1 : 0), - pubkey: null, - searchable: [], + pubkey: undefined, has_pgp: 0, // number because we use it for sorting - client: null, fingerprint: null, - longid: null, - longids: [], last_use: lastUse || null, pubkey_last_sig: null, pubkey_last_check: null, @@ -82,11 +69,9 @@ export class ContactStore { const contact = { email, name, - client, pubkey: pk, + has_pgp: 1, // number because we use it for sorting fingerprint: pk.id, - longid: OpenPGPKey.fingerprintToLongid(pk.id), - longids: pk.allIds.map(id => OpenPGPKey.fingerprintToLongid(id)), pending_lookup: pendingLookup, last_use: lastUse, pubkey_last_check: lastCheck, diff --git a/test/source/tests/unit-node.ts b/test/source/tests/unit-node.ts index 17f8c468621..eeedac4f7e8 100644 --- a/test/source/tests/unit-node.ts +++ b/test/source/tests/unit-node.ts @@ -559,7 +559,7 @@ vpQiyk4ceuTNkUZ/qmgiMpQLxXZnDDo= .match(/\-\-\-\-\-BEGIN PGP SIGNED MESSAGE\-\-\-\-\-.*\-\-\-\-\-END PGP SIGNATURE\-\-\-\-\-/s)![0]; const encryptedData = Buf.fromUtfStr(enc); const pubkey = await KeyUtil.parse(pubkey2864E326A5BE488A); - await ContactStore.update(undefined, 'president@forged.com', { name: 'President', pubkey, client: 'pgp' }); + await ContactStore.update(undefined, 'president@forged.com', { name: 'President', pubkey }); const decrypted = await MsgUtil.decryptMessage({ kisWithPp: [], encryptedData }); expect(decrypted.success).to.equal(true); const verifyRes = (decrypted as DecryptSuccess).signature!; @@ -582,7 +582,7 @@ vpQiyk4ceuTNkUZ/qmgiMpQLxXZnDDo= .match(/\-\-\-\-\-BEGIN PGP PUBLIC KEY BLOCK\-\-\-\-\-.*\-\-\-\-\-END PGP PUBLIC KEY BLOCK\-\-\-\-\-/s)![0] .replace(/=\r\n/g, '').replace(/=3D/g, '='); const from = GmailParser.findHeader(msg, "from"); - const contact = await ContactStore.obj({ email: from, pubkey, client: 'pgp' }); + const contact = await ContactStore.obj({ email: from, pubkey }); await ContactStore.save(undefined, contact); const result = await MsgUtil.verifyDetached({ plaintext: Buf.fromUtfStr(plaintext), sigText: Buf.fromUtfStr(sigText) }); expect(result.match).to.be.true; @@ -603,7 +603,7 @@ vpQiyk4ceuTNkUZ/qmgiMpQLxXZnDDo= .match(/\-\-\-\-\-BEGIN PGP PUBLIC KEY BLOCK\-\-\-\-\-.*\-\-\-\-\-END PGP PUBLIC KEY BLOCK\-\-\-\-\-/s)![0] .replace(/=\r\n/g, '').replace(/=3D/g, '='); const from = GmailParser.findHeader(msg, "from"); - const contact = await ContactStore.obj({ email: from, pubkey, client: 'pgp' }); + const contact = await ContactStore.obj({ email: from, pubkey }); await ContactStore.save(undefined, contact); const result = await MsgUtil.verifyDetached({ plaintext: Buf.fromUtfStr(plaintext), sigText: Buf.fromUtfStr(sigText) }); expect(result.match).to.be.true; @@ -679,8 +679,7 @@ WlK7v7H/kIqy9Ggvz6j/seqokN7X4nuc7xOTub6WI1sNRQePIuw2um+Yp14n Bk66Izujnvwa9bVz3nuXhI90WDLnu8OQyAe/N4Pv9pXu1IGg4Nx8yYBLuMuc eg== =CvEL ------END PGP PUBLIC KEY BLOCK-----`, - client: 'pgp' +-----END PGP PUBLIC KEY BLOCK-----` }); await ContactStore.save(undefined, contact); } @@ -753,29 +752,28 @@ eg== t.pass(); }); + // public key that allows to encrypt for primary key - to simulate a bug in other implementation that wrongly encrypts for primary when it shouldn't + // sec rsa2048/F90C76AE611AFDEE + // created: 2020-10-15 expires: never usage: SCE + // trust: ultimate validity: ultimate + // ssb rsa2048/4BA880ECE71397FC + // created: 2020-10-15 expires: never usage: E + const pubEncryptForPrimaryIsFine = `-----BEGIN PGP PUBLIC KEY BLOCK-----\n\nmQENBF+IL20BCACTJLnno0xB29YeNP9xV4bdkEE0zSo/UoFzRKpUupG+0El17oDw\nQDUeW2YjZwLxMJVlRyo+eongpFYFbC+d5cwiHE/YP6uQPmniiEpa3ICZw87Jk/R2\n5dTAVk9QuAlvkI1lWA0+1SDTFxuWD1LTEjcSS6so8pr2VOF6xFu5QKCkbX0/aQe5\npoHryZ/RkUW4d+B3aTC56RnXSAfeegwn1VDF+J+t0jZ0rMzKs2IaDgqX5HzBqOOI\nlIrr43ROHmceuTMZp19aoLYhFNn1lseyug/YQm4b6Hf6VVypNNUFdgbK8xrxowOq\nb2cgSajgcZVMkTF5IQuyS/IIlobJGZeqZ33nABEBAAG0aVRlc3QgS2V5IFdoZW4g\nTWVzc2FnZSBXcm9uZ2x5IGVuY3J5cHRlZCBmb3IgUHJpbWFyeSBLZXkgPHRlc3Qu\nZmlsZS5lbmNyeXB0ZWQuZm9yLnByaW1hcnkua2V5QGV4YW1wbGUuY29tPokBTgQT\nAQoAOAULCQgHAgYVCgkICwIEFgIDAQIeAQIXgBYhBL+zmJKJcURh2km3GfkMdq5h\nGv3uBQJfjVbfAhsPAAoJEPkMdq5hGv3uqCEH/3gbq7JwKQf0NV0muZysc0aNt000\nG3NtZkuYi83l8JMwlDq50lOMgL7gCngTB9ed822d27ClMsj8eP9XuKtw6e7gpvMc\njMF2rACiQKYuZ0iVUK23Zi0fb17zN0BJ0gJ9BpEv5MjaYJ1G4QZDOKG23a/hVUUv\nfRmwbBynSFMgVWQJHGQ9KcY2Jt8M3sLcxpuPO3QLWGivitbZDB2QrL/fALRQpc1Y\nnNkgdUxpZE5dkos01IR5GjZeSmrYpP7UaHa/O3lCdLiskjtCNwWcTr1yJZdzmbZ4\npw6Hu+kEIiYgmwPNodJpRYxZ8rR6ChJ4q1SE6J3iJ4SlGVdU0TM4L5nuJxy5AQ0E\nX4gvbQEIANUO63F2tdT4zOt8gP2XBZwo8fbI59AEEgBaq7o3sluujAak3mK71LyT\n4S4gvJLyGlAU9TV4JQxRuky6oCcyA1D6PNCYGiR6OJbmmzosrh34bYkfz3xjDu/d\nNAKPDCJz2arcVuVbE5onjQd9afjaZh+4pVKs3lKn1UdBXIrei2LC98CemRWxUwfH\nG0LswvnIg24ByvFBvOzBiB7m9340ComMnKGRpeze8uEubYNNQDexL2zCo2itUFKB\nuPkQbCN7jXg/vnNLk2GXFlUYt20puEH4iyaJ/QFIZzzeqFRQWvI63JJ7zQZIGeok\nS/0MLq1udNYxUqk014TEso0jvC1evX0AEQEAAYkBNgQYAQoAIBYhBL+zmJKJcURh\n2km3GfkMdq5hGv3uBQJfiC9tAhsMAAoJEPkMdq5hGv3usZ4H/1N12NiLOVwQ3Zeq\nVxUocwC/UjZX6JlAPg0h1Spx0RGdNuu4WMLnlF/1yzK+LE84WFYkvXXIzNi1LIyX\nPh3YCPGFEec82MkLQFkLm7sjE4Xc3APYZJK2s5LSjyloZkprb7sbVjdWoBwAPClv\nQsgAlHBeCrlWcLo7fzZdxmpvmJFHd/J7ajKsMCn5f9DXFCoCNdrv+s5Qf4jo6KaE\nhZrQ75+T52Iq9R5Z2gS5G4jY3eW+iK2/xW5Q0x0UeoJG7u8WR56LSl0jS9lufuOS\nyFkO3XIWLzDfz51EVy7ApK33D3GQTfOQ8tJEqW2p17rQTcXuhmg4Dgcf1b0dyVac\n7jV1Tgs=\n=4gfr\n-----END PGP PUBLIC KEY BLOCK-----\n`; + // a normal keypair + // sec rsa2048/F90C76AE611AFDEE + // created: 2020-10-15 expires: never usage: SC + // trust: ultimate validity: ultimate + // ssb rsa2048/4BA880ECE71397FC + // created: 2020-10-15 expires: never usage: E + const prvEncryptForSubkeyOnly = `-----BEGIN PGP PRIVATE KEY BLOCK-----\n\nlQOYBF+IL20BCACTJLnno0xB29YeNP9xV4bdkEE0zSo/UoFzRKpUupG+0El17oDw\nQDUeW2YjZwLxMJVlRyo+eongpFYFbC+d5cwiHE/YP6uQPmniiEpa3ICZw87Jk/R2\n5dTAVk9QuAlvkI1lWA0+1SDTFxuWD1LTEjcSS6so8pr2VOF6xFu5QKCkbX0/aQe5\npoHryZ/RkUW4d+B3aTC56RnXSAfeegwn1VDF+J+t0jZ0rMzKs2IaDgqX5HzBqOOI\nlIrr43ROHmceuTMZp19aoLYhFNn1lseyug/YQm4b6Hf6VVypNNUFdgbK8xrxowOq\nb2cgSajgcZVMkTF5IQuyS/IIlobJGZeqZ33nABEBAAEAB/4zgTuBlWtv8h9022A+\nsECI9aGddeM/3wVo77QfjF7Px+Cu4xlG/3KYea263qfs/PCOTua+j+4LL/rcUw4n\n2vQlTHu2WjMXfoFZxhMg0uZA7IVJkfyUUcayvINu4byLzLFxs+yO/dNLkF8bm6mG\nMG4OfWYgIyuS5gs3CdyBb9nLM/Av2vszE5vSMWzkylSkB8uo4oU3yRNxHC2iyye0\nlbhX1xLjr8RJkPTcMi7tc4zO2cJUhMvb5GI1vHCVdUJyREaWOZrC/6LW75hgvldP\nsP56dWdMQ65HxShBYNx2i6iblYIgfpah/R1bZfHmPvcG4fUxRtH40CqAqAaoyB3Q\nEcsBBADB28BDBmICC+neLgJ8YntvG3oul0zNRJVfi+O7XzCQzO/E3Pw4/vKpI2M7\nro51Sr+v4jOzZbs0itsAk10oejtO8fRRVpqSb+6CineskBP62l47TDh8A4yrskBt\nCGoOyyIVfem4G3d9JPjOFouaQjlwUD2Fiu2CavqiGA/5hRfaxwQAwk99+Iv/0Erb\nnYB7FcZV5rSPjGYIgr1JdZSZJP8hgNZEmEYo+Qab2PYFWKRW+1yxnt7A2HWEJPDf\nUH0iMy0CdQXRIT9/+y0sEBU1ET9kcI0As+LkrGzE2iMtvufXnhs+z+iUHww52hW0\nbY6Qh2gpSQwB+cVRz5+LeV9RlxdBI+ED/AyjC59SV5b/UlMAfrA+kUIWyoX5SuB2\nVBkvyDcJtSbpXtFtVvSO+bko6gq/0b9pd0RDspeOEoJ2JvPeNEyqNhoghrwAu4mJ\nOMU8FzbPoPeW6Tp2sWCN4WPBP3i6wKNftS/D7XEGOtpQj4pnWArWSk4KN9iC9bgl\n8m25asqaNihwRqG0aVRlc3QgS2V5IFdoZW4gTWVzc2FnZSBXcm9uZ2x5IGVuY3J5\ncHRlZCBmb3IgUHJpbWFyeSBLZXkgPHRlc3QuZmlsZS5lbmNyeXB0ZWQuZm9yLnBy\naW1hcnkua2V5QGV4YW1wbGUuY29tPokBTgQTAQoAOAULCQgHAgYVCgkICwIEFgID\nAQIeAQIXgBYhBL+zmJKJcURh2km3GfkMdq5hGv3uBQJfjVWYAhsDAAoJEPkMdq5h\nGv3uNY4H/jjic/McuUDaU1YMJdqJsb8AMU6j+XAw/agKu/d4BvQqeGhJvQAh7Ufo\n+2ikyPbQ51+s5AvlxW3DQ1tA0F56Si5B7ilVYwocQ55fC5TtvmcyouRujttoPqQN\nmrDvUYHwip7IBm6ITmn5yOmL9i27bAt1MgETD2Qrpn404mGkvwBCM1oPLK0QhkuX\niRqDTjm+B91Fx86EeS801UR9XChX6MqP0oNe9vVBCFzmsCPu+IYzz2NOuOHbVZ62\nBWflsoElEFiMaEx2J1gkwMAU0dTQg2KTD8M0gJG5HgmrYOPY1+q7CGzy53nGq6Wl\nzOvDRUClvpjBGcpUKDDIH/KQjzSEDRCdA5gEX4gvbQEIANUO63F2tdT4zOt8gP2X\nBZwo8fbI59AEEgBaq7o3sluujAak3mK71LyT4S4gvJLyGlAU9TV4JQxRuky6oCcy\nA1D6PNCYGiR6OJbmmzosrh34bYkfz3xjDu/dNAKPDCJz2arcVuVbE5onjQd9afja\nZh+4pVKs3lKn1UdBXIrei2LC98CemRWxUwfHG0LswvnIg24ByvFBvOzBiB7m9340\nComMnKGRpeze8uEubYNNQDexL2zCo2itUFKBuPkQbCN7jXg/vnNLk2GXFlUYt20p\nuEH4iyaJ/QFIZzzeqFRQWvI63JJ7zQZIGeokS/0MLq1udNYxUqk014TEso0jvC1e\nvX0AEQEAAQAH/jxozI0RUaEfIksqtBAy/941JdYJROEQJmJ/Uu2r2SBxrzY7DOsF\nwt3tOA2yLoWjq55FMvmEJU0G50HWMI6seZA+Q3wJhHAPT3hJzn2CKaRJyhT1NglY\ntOWB3LtU/+XM30y4yNKjLj2pNS2Ie8GZexdHbWixpx/cgnZ/q9OcIf1QMaUt3pda\ngeRaMT+H/CQNG0q000+2xpQBjEDfXGRJsMTlYZROoHV7HzBW4IxdeolDU/gjdGeB\nhC+O8BTpuMCb7qq5UXckeXII+4DzqCkDePdqkBmDkns+2L1WV2xNVyT0Xu2r7ZCm\nGGeparwuxttmdgrLfiRbDyHeYXZbVPZ2C2kEANWwabDtkuQ1+5Rs9GWD21JaX0Go\n69lUhZVWVSrdfbCXKFjZySiilzvv5W+GRhfmm5Tzv3UgfKEIU7wbRYlCZ+yhmNWC\n6fy0xMjOGskpNZvfSmYqDA8MgExluHapaEO/QOivhkdGmIRhHV0bIJU5fN56XvbZ\nwtDPw2dwLsmuXBh7BAD/PofmvBD4N5quBVFXCkkCWTS8Ma9vHXQufHjRgnUXCeuZ\n6sX4s3UyQIc5LxCYj0ZNFQdObHqyovESY0O9n0wDRzxpsLu8VXF8bKJ+JA02Yj7x\n7bM+5bEK8ILYmw2EFjCJsdG9rK25OG93QCHywGL6VUxFKdUBbnmEzNH2r+dsZwQA\n+aYSgMASH2uxWuK33rFDL+NFZC3tpaRCcm2t17ssRAGJ/xQdG+HrPREJTSCtA+xd\niF//rFnucl4apc2HE6s2CK/Oparov1+NWzd5MATtXAA5Cu04UBN16Em4/yFf+jY7\nqwJD8NwELoDH5p11ymK4/Z+5N4/uFBEGMG4EkQEnUbQ2VYkBNgQYAQoAIBYhBL+z\nmJKJcURh2km3GfkMdq5hGv3uBQJfiC9tAhsMAAoJEPkMdq5hGv3usZ4H/1N12NiL\nOVwQ3ZeqVxUocwC/UjZX6JlAPg0h1Spx0RGdNuu4WMLnlF/1yzK+LE84WFYkvXXI\nzNi1LIyXPh3YCPGFEec82MkLQFkLm7sjE4Xc3APYZJK2s5LSjyloZkprb7sbVjdW\noBwAPClvQsgAlHBeCrlWcLo7fzZdxmpvmJFHd/J7ajKsMCn5f9DXFCoCNdrv+s5Q\nf4jo6KaEhZrQ75+T52Iq9R5Z2gS5G4jY3eW+iK2/xW5Q0x0UeoJG7u8WR56LSl0j\nS9lufuOSyFkO3XIWLzDfz51EVy7ApK33D3GQTfOQ8tJEqW2p17rQTcXuhmg4Dgcf\n1b0dyVac7jV1Tgs=\n=4Jfy\n-----END PGP PRIVATE KEY BLOCK-----\n`; + ava.default('[MsgUtil.encryptMessage] do not decrypt message when encrypted for key not meant for encryption', async t => { const data = Buf.fromUtfStr('hello'); const passphrase = 'pass phrase'; - // a normal keypair - // sec rsa2048/F90C76AE611AFDEE - // created: 2020-10-15 expires: never usage: SC - // trust: ultimate validity: ultimate - // ssb rsa2048/4BA880ECE71397FC - // created: 2020-10-15 expires: never usage: E - const prvEncryptForSubkeyOnly = `-----BEGIN PGP PRIVATE KEY BLOCK-----\n\n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\n1b0dyVac7jV1Tgs=\n=4Jfy\n-----END PGP PRIVATE KEY BLOCK-----\n`; const tmpPrv = await KeyUtil.parse(prvEncryptForSubkeyOnly); await KeyUtil.encrypt(tmpPrv, passphrase); expect(tmpPrv.fullyEncrypted).to.equal(true); const prvEncryptForSubkeyOnlyProtected = KeyUtil.armor(tmpPrv); - // const pubEncryptForSubkeyOnly = `-----BEGIN PGP PUBLIC KEY BLOCK-----\n\nmQENBF+IL20BCACTJLnno0xB29YeNP9xV4bdkEE0zSo/UoFzRKpUupG+0El17oDw\nQDUeW2YjZwLxMJVlRyo+eongpFYFbC+d5cwiHE/YP6uQPmniiEpa3ICZw87Jk/R2\n5dTAVk9QuAlvkI1lWA0+1SDTFxuWD1LTEjcSS6so8pr2VOF6xFu5QKCkbX0/aQe5\npoHryZ/RkUW4d+B3aTC56RnXSAfeegwn1VDF+J+t0jZ0rMzKs2IaDgqX5HzBqOOI\nlIrr43ROHmceuTMZp19aoLYhFNn1lseyug/YQm4b6Hf6VVypNNUFdgbK8xrxowOq\nb2cgSajgcZVMkTF5IQuyS/IIlobJGZeqZ33nABEBAAG0aVRlc3QgS2V5IFdoZW4g\nTWVzc2FnZSBXcm9uZ2x5IGVuY3J5cHRlZCBmb3IgUHJpbWFyeSBLZXkgPHRlc3Qu\nZmlsZS5lbmNyeXB0ZWQuZm9yLnByaW1hcnkua2V5QGV4YW1wbGUuY29tPokBTgQT\nAQoAOAULCQgHAgYVCgkICwIEFgIDAQIeAQIXgBYhBL+zmJKJcURh2km3GfkMdq5h\nGv3uBQJfjVWYAhsDAAoJEPkMdq5hGv3uNY4H/jjic/McuUDaU1YMJdqJsb8AMU6j\n+XAw/agKu/d4BvQqeGhJvQAh7Ufo+2ikyPbQ51+s5AvlxW3DQ1tA0F56Si5B7ilV\nYwocQ55fC5TtvmcyouRujttoPqQNmrDvUYHwip7IBm6ITmn5yOmL9i27bAt1MgET\nD2Qrpn404mGkvwBCM1oPLK0QhkuXiRqDTjm+B91Fx86EeS801UR9XChX6MqP0oNe\n9vVBCFzmsCPu+IYzz2NOuOHbVZ62BWflsoElEFiMaEx2J1gkwMAU0dTQg2KTD8M0\ngJG5HgmrYOPY1+q7CGzy53nGq6WlzOvDRUClvpjBGcpUKDDIH/KQjzSEDRC5AQ0E\nX4gvbQEIANUO63F2tdT4zOt8gP2XBZwo8fbI59AEEgBaq7o3sluujAak3mK71LyT\n4S4gvJLyGlAU9TV4JQxRuky6oCcyA1D6PNCYGiR6OJbmmzosrh34bYkfz3xjDu/d\nNAKPDCJz2arcVuVbE5onjQd9afjaZh+4pVKs3lKn1UdBXIrei2LC98CemRWxUwfH\nG0LswvnIg24ByvFBvOzBiB7m9340ComMnKGRpeze8uEubYNNQDexL2zCo2itUFKB\nuPkQbCN7jXg/vnNLk2GXFlUYt20puEH4iyaJ/QFIZzzeqFRQWvI63JJ7zQZIGeok\nS/0MLq1udNYxUqk014TEso0jvC1evX0AEQEAAYkBNgQYAQoAIBYhBL+zmJKJcURh\n2km3GfkMdq5hGv3uBQJfiC9tAhsMAAoJEPkMdq5hGv3usZ4H/1N12NiLOVwQ3Zeq\nVxUocwC/UjZX6JlAPg0h1Spx0RGdNuu4WMLnlF/1yzK+LE84WFYkvXXIzNi1LIyX\nPh3YCPGFEec82MkLQFkLm7sjE4Xc3APYZJK2s5LSjyloZkprb7sbVjdWoBwAPClv\nQsgAlHBeCrlWcLo7fzZdxmpvmJFHd/J7ajKsMCn5f9DXFCoCNdrv+s5Qf4jo6KaE\nhZrQ75+T52Iq9R5Z2gS5G4jY3eW+iK2/xW5Q0x0UeoJG7u8WR56LSl0jS9lufuOS\nyFkO3XIWLzDfz51EVy7ApK33D3GQTfOQ8tJEqW2p17rQTcXuhmg4Dgcf1b0dyVac\n7jV1Tgs=\n=APwK\n-----END PGP PUBLIC KEY BLOCK-----\n`; - // public key that allows to encrypt for primary key - to simulate a bug in other implementation that wrongly encrypts for primary when it shouldn't - // sec rsa2048/F90C76AE611AFDEE - // created: 2020-10-15 expires: never usage: SCE - // trust: ultimate validity: ultimate - // ssb rsa2048/4BA880ECE71397FC - // created: 2020-10-15 expires: never usage: E - // const prvEncryptForPrimaryIsFine = `-----BEGIN PGP PRIVATE KEY BLOCK-----\n\n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\n1b0dyVac7jV1Tgs=\n=s/pD\n-----END PGP PRIVATE KEY BLOCK-----`; - const pubEncryptForPrimaryIsFine = `-----BEGIN PGP PUBLIC KEY BLOCK-----\n\nmQENBF+IL20BCACTJLnno0xB29YeNP9xV4bdkEE0zSo/UoFzRKpUupG+0El17oDw\nQDUeW2YjZwLxMJVlRyo+eongpFYFbC+d5cwiHE/YP6uQPmniiEpa3ICZw87Jk/R2\n5dTAVk9QuAlvkI1lWA0+1SDTFxuWD1LTEjcSS6so8pr2VOF6xFu5QKCkbX0/aQe5\npoHryZ/RkUW4d+B3aTC56RnXSAfeegwn1VDF+J+t0jZ0rMzKs2IaDgqX5HzBqOOI\nlIrr43ROHmceuTMZp19aoLYhFNn1lseyug/YQm4b6Hf6VVypNNUFdgbK8xrxowOq\nb2cgSajgcZVMkTF5IQuyS/IIlobJGZeqZ33nABEBAAG0aVRlc3QgS2V5IFdoZW4g\nTWVzc2FnZSBXcm9uZ2x5IGVuY3J5cHRlZCBmb3IgUHJpbWFyeSBLZXkgPHRlc3Qu\nZmlsZS5lbmNyeXB0ZWQuZm9yLnByaW1hcnkua2V5QGV4YW1wbGUuY29tPokBTgQT\nAQoAOAULCQgHAgYVCgkICwIEFgIDAQIeAQIXgBYhBL+zmJKJcURh2km3GfkMdq5h\nGv3uBQJfjVbfAhsPAAoJEPkMdq5hGv3uqCEH/3gbq7JwKQf0NV0muZysc0aNt000\nG3NtZkuYi83l8JMwlDq50lOMgL7gCngTB9ed822d27ClMsj8eP9XuKtw6e7gpvMc\njMF2rACiQKYuZ0iVUK23Zi0fb17zN0BJ0gJ9BpEv5MjaYJ1G4QZDOKG23a/hVUUv\nfRmwbBynSFMgVWQJHGQ9KcY2Jt8M3sLcxpuPO3QLWGivitbZDB2QrL/fALRQpc1Y\nnNkgdUxpZE5dkos01IR5GjZeSmrYpP7UaHa/O3lCdLiskjtCNwWcTr1yJZdzmbZ4\npw6Hu+kEIiYgmwPNodJpRYxZ8rR6ChJ4q1SE6J3iJ4SlGVdU0TM4L5nuJxy5AQ0E\nX4gvbQEIANUO63F2tdT4zOt8gP2XBZwo8fbI59AEEgBaq7o3sluujAak3mK71LyT\n4S4gvJLyGlAU9TV4JQxRuky6oCcyA1D6PNCYGiR6OJbmmzosrh34bYkfz3xjDu/d\nNAKPDCJz2arcVuVbE5onjQd9afjaZh+4pVKs3lKn1UdBXIrei2LC98CemRWxUwfH\nG0LswvnIg24ByvFBvOzBiB7m9340ComMnKGRpeze8uEubYNNQDexL2zCo2itUFKB\nuPkQbCN7jXg/vnNLk2GXFlUYt20puEH4iyaJ/QFIZzzeqFRQWvI63JJ7zQZIGeok\nS/0MLq1udNYxUqk014TEso0jvC1evX0AEQEAAYkBNgQYAQoAIBYhBL+zmJKJcURh\n2km3GfkMdq5hGv3uBQJfiC9tAhsMAAoJEPkMdq5hGv3usZ4H/1N12NiLOVwQ3Zeq\nVxUocwC/UjZX6JlAPg0h1Spx0RGdNuu4WMLnlF/1yzK+LE84WFYkvXXIzNi1LIyX\nPh3YCPGFEec82MkLQFkLm7sjE4Xc3APYZJK2s5LSjyloZkprb7sbVjdWoBwAPClv\nQsgAlHBeCrlWcLo7fzZdxmpvmJFHd/J7ajKsMCn5f9DXFCoCNdrv+s5Qf4jo6KaE\nhZrQ75+T52Iq9R5Z2gS5G4jY3eW+iK2/xW5Q0x0UeoJG7u8WR56LSl0jS9lufuOS\nyFkO3XIWLzDfz51EVy7ApK33D3GQTfOQ8tJEqW2p17rQTcXuhmg4Dgcf1b0dyVac\n7jV1Tgs=\n=4gfr\n-----END PGP PUBLIC KEY BLOCK-----\n`; const { keys: [tmpPub] } = await opgp.key.readArmored(pubEncryptForPrimaryIsFine); tmpPub.subKeys = []; // removed subkey from the pubkey, which makes the structure into this - forcing opgp to encrypt for the primary @@ -795,6 +793,41 @@ eg== t.pass(); }); + ava.default('[KeyUtil.diagnose] displays PK and SK usage', async t => { + const usageRegex = /\[\-\] \[(.*)\]/; + const result1 = await KeyUtil.diagnose(await KeyUtil.parse(pubEncryptForPrimaryIsFine), ''); + { + const pk0UsageStr = result1.get('Usage flags')!; + const sk0UsageStr = result1.get('SK 0 > Usage flags')!; + const pk0Usage = pk0UsageStr.match(usageRegex)![1].split(', '); + expect(pk0Usage).to.include('certify_keys'); + expect(pk0Usage).to.include('sign_data'); + expect(pk0Usage).to.include('encrypt_storage'); + expect(pk0Usage).to.include('encrypt_communication'); + const sk0Usage = sk0UsageStr.match(usageRegex)![1].split(', '); + expect(sk0Usage).to.not.include('certify_keys'); + expect(sk0Usage).to.not.include('sign_data'); + expect(sk0Usage).to.include('encrypt_storage'); + expect(sk0Usage).to.include('encrypt_communication'); + } + const result2 = await KeyUtil.diagnose(await KeyUtil.parse(prvEncryptForSubkeyOnly), ''); + { + const pk0UsageStr = result2.get('Usage flags')!; + const sk0UsageStr = result2.get('SK 0 > Usage flags')!; + const pk0Usage = pk0UsageStr.match(usageRegex)![1].split(', '); + expect(pk0Usage).to.include('certify_keys'); + expect(pk0Usage).to.include('sign_data'); + expect(pk0Usage).to.not.include('encrypt_storage'); + expect(pk0Usage).to.not.include('encrypt_communication'); + const sk0Usage = sk0UsageStr.match(usageRegex)![1].split(', '); + expect(sk0Usage).to.not.include('certify_keys'); + expect(sk0Usage).to.not.include('sign_data'); + expect(sk0Usage).to.include('encrypt_storage'); + expect(sk0Usage).to.include('encrypt_communication'); + } + t.pass(); + }); + const dsaPrimaryKeyAndSubkeyBothHavePrivateKey = `-----BEGIN PGP PRIVATE KEY BLOCK----- lQOBBF/BQGwRCACcZ4K6ArbIZATaPPBPOywi2KpCIv5HRTlxncS+xpc3YsrzBasM From 39ebd046f4a89b377632b883e9b5635c4bb926c2 Mon Sep 17 00:00:00 2001 From: Roman Shevchenko Date: Thu, 25 Feb 2021 12:16:57 -0500 Subject: [PATCH 03/53] fix --- extension/js/common/platform/store/contact-store.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/extension/js/common/platform/store/contact-store.ts b/extension/js/common/platform/store/contact-store.ts index 1d1eb03655d..a4b85789585 100644 --- a/extension/js/common/platform/store/contact-store.ts +++ b/extension/js/common/platform/store/contact-store.ts @@ -404,7 +404,7 @@ export class ContactStore extends AbstractStore { resolve(undefined); return; } - const req2 = tx.objectStore('emails').index('fingerprints').get(pubkey.fingerprint!); + const req2 = tx.objectStore('emails').index('index_fingerprints').get(pubkey.fingerprint!); ContactStore.setReqPipe(req2, (email: Email) => { if (!email) { From 909473aeaf8df87a95c1ffd84e27796cc940215c Mon Sep 17 00:00:00 2001 From: Roman Shevchenko Date: Fri, 26 Feb 2021 04:09:18 -0500 Subject: [PATCH 04/53] tslint fix --- .../elements/pgp_block_modules/pgp-block-signature-module.ts | 3 ++- extension/js/common/platform/store/contact-store.ts | 4 ++-- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/extension/chrome/elements/pgp_block_modules/pgp-block-signature-module.ts b/extension/chrome/elements/pgp_block_modules/pgp-block-signature-module.ts index 6d20150b399..a1022a9783e 100644 --- a/extension/chrome/elements/pgp_block_modules/pgp-block-signature-module.ts +++ b/extension/chrome/elements/pgp_block_modules/pgp-block-signature-module.ts @@ -55,7 +55,8 @@ export class PgpBlockViewSignatureModule { if (senderEmail) { // we know who sent it const [senderContactByEmail] = await ContactStore.get(undefined, [senderEmail]); if (senderContactByEmail && senderContactByEmail.pubkey) { - render(`Fetched the right pubkey ${signerLongid} from keyserver, but will not use it because you have conflicting pubkey ${senderContactByEmail.pubkey.id} loaded.`, () => undefined); + const foundId = senderContactByEmail.pubkey.id; + render(`Fetched the right pubkey ${signerLongid} from keyserver, but will not use it because you have conflicting pubkey ${foundId} loaded.`, () => undefined); return; } // ---> and user doesn't have pubkey for that email addr diff --git a/extension/js/common/platform/store/contact-store.ts b/extension/js/common/platform/store/contact-store.ts index a4b85789585..ef319874b31 100644 --- a/extension/js/common/platform/store/contact-store.ts +++ b/extension/js/common/platform/store/contact-store.ts @@ -82,11 +82,11 @@ export class ContactStore extends AbstractStore { } if (event.oldVersion < 5) { // delete when migrating from 4 to 5? // openDbReq.result.deleteObjectStore('contacts'); // tslint:disable-line:no-unsafe-any - }; + } openDbReq.onsuccess = () => resolve(openDbReq.result as IDBDatabase); openDbReq.onblocked = () => reject(ContactStore.errCategorize(openDbReq.error)); openDbReq.onerror = () => reject(ContactStore.errCategorize(openDbReq.error)); - } + }; }); } From 69c56c31feedb0ca67d079a2fb02b6c339e630e1 Mon Sep 17 00:00:00 2001 From: Roman Shevchenko Date: Fri, 26 Feb 2021 04:11:46 -0500 Subject: [PATCH 05/53] test fix --- test/source/tests/settings.ts | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/test/source/tests/settings.ts b/test/source/tests/settings.ts index 7b1c260edff..c20d9106563 100644 --- a/test/source/tests/settings.ts +++ b/test/source/tests/settings.ts @@ -81,9 +81,11 @@ export let defineSettingsTests = (testVariant: TestVariant, testWithBrowser: Tes await Util.sleep(1); await contactsFrame.waitAndClick('@action-show-pubkey-flowcryptcompatibilitygmailcom', { confirmGone: true }); await Util.sleep(1); - expect(await contactsFrame.read('@page-contacts')).to.contain('flowcrypt.compatibility@gmail.com'); - expect(await contactsFrame.read('@page-contacts')).to.contain('7FDE 6855 48AE A788'); - expect(await contactsFrame.read('@page-contacts')).to.contain('-----BEGIN PGP PUBLIC KEY BLOCK-----'); + const contacts = await contactsFrame.read('@page-contacts'); + expect(contacts).to.contain('flowcrypt.compatibility@gmail.com'); + // todo: will specify which one of them should appear after finished with #3332 + expect(contacts.includes('7FDE 6855 48AE A788') || contacts.includes('ADAC 279C 9509 3207')).to.be.true; + expect(contacts).to.contain('-----BEGIN PGP PUBLIC KEY BLOCK-----'); await contactsFrame.waitAndClick('@action-back-to-contact-list', { confirmGone: true }); await Util.sleep(1); expect(await contactsFrame.read('@page-contacts')).to.contain('flowcrypt.compatibility@gmail.com'); From 9287548659639687452ee85064efd97986453e3a Mon Sep 17 00:00:00 2001 From: Roman Shevchenko Date: Fri, 26 Feb 2021 04:17:35 -0500 Subject: [PATCH 06/53] tslint fix --- test/source/tests/settings.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/test/source/tests/settings.ts b/test/source/tests/settings.ts index c20d9106563..1edeb3fffa1 100644 --- a/test/source/tests/settings.ts +++ b/test/source/tests/settings.ts @@ -84,6 +84,7 @@ export let defineSettingsTests = (testVariant: TestVariant, testWithBrowser: Tes const contacts = await contactsFrame.read('@page-contacts'); expect(contacts).to.contain('flowcrypt.compatibility@gmail.com'); // todo: will specify which one of them should appear after finished with #3332 + // tslint:disable-next-line:no-unused-expression expect(contacts.includes('7FDE 6855 48AE A788') || contacts.includes('ADAC 279C 9509 3207')).to.be.true; expect(contacts).to.contain('-----BEGIN PGP PUBLIC KEY BLOCK-----'); await contactsFrame.waitAndClick('@action-back-to-contact-list', { confirmGone: true }); From 0c6996baf33b54fc2fc65a4793828143b36926ba Mon Sep 17 00:00:00 2001 From: Roman Shevchenko Date: Fri, 26 Feb 2021 06:26:20 -0500 Subject: [PATCH 07/53] tslint fix --- extension/js/common/platform/store/contact-store.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/extension/js/common/platform/store/contact-store.ts b/extension/js/common/platform/store/contact-store.ts index ef319874b31..c6b5f33b30d 100644 --- a/extension/js/common/platform/store/contact-store.ts +++ b/extension/js/common/platform/store/contact-store.ts @@ -356,7 +356,7 @@ export class ContactStore extends AbstractStore { emailEntity.searchable = index; } - private static setReqOnError(req: IDBRequest, reject: (reason?: any) => void) { + private static setReqOnError = (req: IDBRequest, reject: (reason?: any) => void) => { req.onerror = () => reject(ContactStore.errCategorize(req.error || new Error('Unknown db error'))); } From b39a1d357019212d1aaa8f22aee7841f100d6415 Mon Sep 17 00:00:00 2001 From: Roman Shevchenko Date: Fri, 26 Feb 2021 06:28:31 -0500 Subject: [PATCH 08/53] debug output --- extension/js/common/platform/store/contact-store.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/extension/js/common/platform/store/contact-store.ts b/extension/js/common/platform/store/contact-store.ts index c6b5f33b30d..a071595afbd 100644 --- a/extension/js/common/platform/store/contact-store.ts +++ b/extension/js/common/platform/store/contact-store.ts @@ -162,7 +162,7 @@ export class ContactStore extends AbstractStore { return; } if (update.pubkey?.isPrivate) { - Catch.report('Wrongly updating prv as contact - converting to pubkey'); + Catch.report(`Wrongly updating prv ${update.pubkey.id} as contact - converting to pubkey (email=${update.pubkey.emails[0]})`); update.pubkey = await KeyUtil.asPublicKey(update.pubkey); } await new Promise((resolve, reject) => { From 97e5b382a49b920c909df76472db5927ffc606b3 Mon Sep 17 00:00:00 2001 From: Roman Shevchenko Date: Fri, 26 Feb 2021 07:06:19 -0500 Subject: [PATCH 09/53] fixed isPrivate property of SmimeKey --- extension/js/common/core/crypto/smime/smime-key.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/extension/js/common/core/crypto/smime/smime-key.ts b/extension/js/common/core/crypto/smime/smime-key.ts index 53943368d19..7b8f99fcb00 100644 --- a/extension/js/common/core/crypto/smime/smime-key.ts +++ b/extension/js/common/core/crypto/smime/smime-key.ts @@ -29,8 +29,8 @@ export class SmimeKey { expiration: SmimeKey.dateToNumber(certificate.validity.notAfter), fullyDecrypted: false, fullyEncrypted: false, - isPublic: true, - isPrivate: true, + isPublic: !!certificate.publicKey, + isPrivate: !!certificate.privateKey, } as Key; (key as unknown as { rawArmored: string }).rawArmored = text; return key; From 8146f553eba6054f12587ab19b23f4360eb9f4c5 Mon Sep 17 00:00:00 2001 From: Roman Shevchenko Date: Fri, 26 Feb 2021 07:20:59 -0500 Subject: [PATCH 10/53] removed extra debug info --- extension/js/common/platform/store/contact-store.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/extension/js/common/platform/store/contact-store.ts b/extension/js/common/platform/store/contact-store.ts index a071595afbd..7dc5a844186 100644 --- a/extension/js/common/platform/store/contact-store.ts +++ b/extension/js/common/platform/store/contact-store.ts @@ -162,7 +162,7 @@ export class ContactStore extends AbstractStore { return; } if (update.pubkey?.isPrivate) { - Catch.report(`Wrongly updating prv ${update.pubkey.id} as contact - converting to pubkey (email=${update.pubkey.emails[0]})`); + Catch.report(`Wrongly updating prv ${update.pubkey.id} as contact - converting to pubkey`); update.pubkey = await KeyUtil.asPublicKey(update.pubkey); } await new Promise((resolve, reject) => { From 164d2f055b4d5ae1027d2699ac77e4c31d117a83 Mon Sep 17 00:00:00 2001 From: Roman Shevchenko Date: Fri, 26 Feb 2021 07:49:46 -0500 Subject: [PATCH 11/53] test fix --- test/source/tests/unit-node.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/source/tests/unit-node.ts b/test/source/tests/unit-node.ts index eeedac4f7e8..9c9ae1ed97f 100644 --- a/test/source/tests/unit-node.ts +++ b/test/source/tests/unit-node.ts @@ -147,7 +147,7 @@ yPLCqVTFJQWaCR5ZTekRQPTDZkjxjxbs expect(key.identities.length).to.equal(1); expect(key.identities[0]).to.equal('actalis@meta.33mail.com'); expect(key.isPublic).to.equal(true); - expect(key.isPrivate).to.equal(true); + expect(key.isPrivate).to.equal(false); expect(key.expiration).to.not.equal(undefined); t.pass(); }); From 5ab5b0f97f2446a9af3c17fc4cb147072f43c5db Mon Sep 17 00:00:00 2001 From: Roman Shevchenko Date: Thu, 4 Mar 2021 11:59:00 -0500 Subject: [PATCH 12/53] simplified 'search' index and use a key range search --- .../js/common/platform/store/contact-store.ts | 43 ++++++++----------- 1 file changed, 19 insertions(+), 24 deletions(-) diff --git a/extension/js/common/platform/store/contact-store.ts b/extension/js/common/platform/store/contact-store.ts index 7dc5a844186..2f1ecf62b20 100644 --- a/extension/js/common/platform/store/contact-store.ts +++ b/extension/js/common/platform/store/contact-store.ts @@ -294,14 +294,8 @@ export class ContactStore extends AbstractStore { if (typeof query.has_pgp === 'undefined') { // any query.has_pgp value search = emails.openCursor(); // no substring, already covered in `typeof query.has_pgp === 'undefined' && query.substring` above } else { // specific query.has_pgp value - let range: IDBKeyRange; - if (query.substring) { - range = IDBKeyRange.only(ContactStore.dbIndex(query.has_pgp, query.substring)); - } else if (query.has_pgp) { - range = IDBKeyRange.lowerBound('t:', false); // starting with 't:' inclusive - } else { - range = IDBKeyRange.upperBound('t:', true); // up to 't:', excluding it gives false range - } + const indexRange = ContactStore.dbIndexRange(query.has_pgp, query.substring ?? ''); + const range = IDBKeyRange.bound(indexRange.lowerBound, indexRange.upperBound, false, true); search = emails.index('search').openCursor(range); } const found: Email[] = []; @@ -328,29 +322,29 @@ export class ContactStore extends AbstractStore { return str.normalize('NFKD').replace(/[\u0300-\u036F]/g, '').toLowerCase(); } - private static dbIndex = (hasPgp: boolean, substring: string) => { - if (!substring) { - throw new Error('db_index has to include substring'); - } + private static dbIndex = (hasPgp: boolean, substring: string): string => { return (hasPgp ? 't:' : 'f:') + substring; } + private static dbIndexRange = (hasPgp: boolean, substring: string): { lowerBound: string, upperBound: string } => { + const lowerBound = ContactStore.dbIndex(hasPgp, substring); + let copyLength = lowerBound.length - 1; + let lastChar = lowerBound.charCodeAt(copyLength); + while (lastChar >= 65535) { + lastChar = lowerBound.charCodeAt(--copyLength); + } + const upperBound = lowerBound.substring(0, copyLength) + String.fromCharCode(lastChar + 1); + return { lowerBound, upperBound }; + } + private static updateSearchable = (emailEntity: Email) => { const email = emailEntity.email.toLowerCase(); const name = emailEntity.name ? emailEntity.name.toLowerCase() : ''; - const parts = [email, name]; - const domain: string = email.split('@').pop() || ''; - parts.push(domain, ...email.split(/[^a-z0-9]/)); - parts.push(...name.split(/[^a-z0-9]/)); const index: string[] = []; - for (const part of parts.filter(p => !!p)) { - let substring = ''; - for (const letter of part.split('')) { - substring += letter; - const normalized = ContactStore.normalizeString(substring); - if (!index.includes(normalized)) { - index.push(ContactStore.dbIndex(emailEntity.fingerprints.length > 0, normalized)); - } + for (const part of [...email.split(/[^a-z0-9]/), ...name.split(/[^a-z0-9]/)].filter(p => !!p)) { + const normalized = ContactStore.normalizeString(part); + if (!index.includes(normalized)) { + index.push(ContactStore.dbIndex(emailEntity.fingerprints.length > 0, normalized)); } } emailEntity.searchable = index; @@ -445,6 +439,7 @@ export class ContactStore extends AbstractStore { return { email: result.email, name: result.name, has_pgp: result.fingerprints.length > 0 ? 1 : 0, last_use: result.lastUse }; } + // todo: migration only private static recreateDates = (contacts: (Contact | undefined)[]) => { for (const contact of contacts) { if (contact) { From 866a36d5913d3efef9ae9e39b0cb370efcb43a8d Mon Sep 17 00:00:00 2001 From: Roman Shevchenko Date: Sat, 6 Mar 2021 07:37:45 -0500 Subject: [PATCH 13/53] migrate 'contacts' to 'emails' and 'pubkeys' --- extension/js/background_page/migrations.ts | 52 ++++++++++++++++++- .../common/platform/store/abstract-store.ts | 4 ++ .../js/common/platform/store/contact-store.ts | 28 +++++----- 3 files changed, 70 insertions(+), 14 deletions(-) diff --git a/extension/js/background_page/migrations.ts b/extension/js/background_page/migrations.ts index 0d2dcc31477..0202043ab82 100644 --- a/extension/js/background_page/migrations.ts +++ b/extension/js/background_page/migrations.ts @@ -2,7 +2,9 @@ 'use strict'; -import { KeyInfo, KeyUtil } from '../common/core/crypto/key.js'; +import { Key, KeyInfo, KeyUtil } from '../common/core/crypto/key.js'; +import { AbstractStore } from '../common/platform/store/abstract-store.js'; +import { ContactStore } from '../common/platform/store/contact-store.js'; import { GlobalStore } from '../common/platform/store/global-store.js'; import { KeyStore } from '../common/platform/store/key-store.js'; @@ -17,6 +19,54 @@ const addKeyInfoFingerprints = async () => { } }; +type ContactV3 = { + email: string; + name: string | null; + pubkey: Key | string | null; + has_pgp: 0 | 1; + fingerprint: string | null; + pending_lookup: number; + last_use: number | null; + pubkey_last_sig: number | null; + pubkey_last_check: number | null; + expiresOn: number | null; +}; + +export const moveContactsToEmailsAndPubkeys = async (db: IDBDatabase) => { + if (db.objectStoreNames.contains('contacts')) { + console.info('migrating contacts of ContactStore to emails and pubkeys...'); + await new Promise((resolve, reject) => { + const tx = db.transaction(['contacts'], 'readwrite'); + const contacts = tx.objectStore('contacts'); + const search = contacts.openCursor(); + search.onsuccess = async () => { + const cursor = search.result as IDBCursorWithValue | undefined; + if (!cursor) { + contacts.clear(); + } else { + const entry = cursor.value as ContactV3; // tslint:disable-line:no-unsafe-any + const armoredPubkey = (entry.pubkey && typeof entry.pubkey === 'object') + ? KeyUtil.armor(entry.pubkey as Key) : entry.pubkey as string; + // parse again to re-calculate expiration-related fields etc. + const pubkey = armoredPubkey ? await KeyUtil.parse(armoredPubkey) : undefined; + await ContactStore.update(db, entry.email, { + email: entry.email, + name: entry.name, + pubkey, + pending_lookup: entry.pending_lookup, + last_use: entry.last_use, + pubkey_last_check: entry.pubkey_last_check + }) + cursor.continue(); + } + }; + tx.oncomplete = () => resolve(undefined); + AbstractStore.setReqOnError(tx, reject); + }); + console.info('done migrating'); + } +}; + export const migrateGlobal = async () => { const globalStore = await GlobalStore.get(['key_info_store_fingerprints_added']); if (!globalStore.key_info_store_fingerprints_added) { diff --git a/extension/js/common/platform/store/abstract-store.ts b/extension/js/common/platform/store/abstract-store.ts index 75b4403fd96..37572371708 100644 --- a/extension/js/common/platform/store/abstract-store.ts +++ b/extension/js/common/platform/store/abstract-store.ts @@ -61,6 +61,10 @@ export abstract class AbstractStore { } } + public static setReqOnError = (req: IDBRequest | IDBTransaction, reject: (reason?: any) => void) => { + req.onerror = () => reject(AbstractStore.errCategorize(req.error || new Error('Unknown db error'))); + } + protected static buildSingleAccountStoreFromRawResults = (scope: string, storageObj: RawStore): AcctStoreDict => { const accountStore: AcctStoreDict = {}; for (const k of Object.keys(storageObj)) { diff --git a/extension/js/common/platform/store/contact-store.ts b/extension/js/common/platform/store/contact-store.ts index 2f1ecf62b20..a258e92557b 100644 --- a/extension/js/common/platform/store/contact-store.ts +++ b/extension/js/common/platform/store/contact-store.ts @@ -69,24 +69,30 @@ export class ContactStore extends AbstractStore { public static dbOpen = async (): Promise => { return await new Promise((resolve, reject) => { - let openDbReq: IDBOpenDBRequest; - openDbReq = indexedDB.open('cryptup', 4); + const openDbReq = indexedDB.open('cryptup', 4); openDbReq.onupgradeneeded = (event) => { + const db = openDbReq.result; if (event.oldVersion < 4) { - const emails = openDbReq.result.createObjectStore('emails', { keyPath: 'email' }); - const pubkeys = openDbReq.result.createObjectStore('pubkeys', { keyPath: 'fingerprint' }); + const emails = db.createObjectStore('emails', { keyPath: 'email' }); + const pubkeys = db.createObjectStore('pubkeys', { keyPath: 'fingerprint' }); emails.createIndex('search', 'searchable', { multiEntry: true }); emails.createIndex('index_pending_lookup', 'pendingLookup'); emails.createIndex('index_fingerprints', 'fingerprints', { multiEntry: true }); // fingerprints of all connected pubkeys pubkeys.createIndex('index_longids', 'longids', { multiEntry: true }); // longids of all public key packets in armored pubkey } - if (event.oldVersion < 5) { // delete when migrating from 4 to 5? - // openDbReq.result.deleteObjectStore('contacts'); // tslint:disable-line:no-unsafe-any + if (db.objectStoreNames.contains('contacts')) { + const countRequest = openDbReq.transaction!.objectStore('contacts').count(); + countRequest.onsuccess = () => { + if (countRequest.result === 0) { + console.log('contacts store is now empty, deleting it...'); + db.deleteObjectStore('contacts'); + } + } } - openDbReq.onsuccess = () => resolve(openDbReq.result as IDBDatabase); - openDbReq.onblocked = () => reject(ContactStore.errCategorize(openDbReq.error)); - openDbReq.onerror = () => reject(ContactStore.errCategorize(openDbReq.error)); }; + openDbReq.onsuccess = () => resolve(openDbReq.result as IDBDatabase); + openDbReq.onblocked = () => reject(ContactStore.errCategorize(openDbReq.error)); + openDbReq.onerror = () => reject(ContactStore.errCategorize(openDbReq.error)); }); } @@ -350,10 +356,6 @@ export class ContactStore extends AbstractStore { emailEntity.searchable = index; } - private static setReqOnError = (req: IDBRequest, reject: (reason?: any) => void) => { - req.onerror = () => reject(ContactStore.errCategorize(req.error || new Error('Unknown db error'))); - } - private static setReqPipe(req: IDBRequest, pipe: (value?: T) => void, reject: (reason?: any) => void) { req.onsuccess = () => { try { From 8a17520f1b7ed0dc90e96c304d7473cb056a6be1 Mon Sep 17 00:00:00 2001 From: Roman Shevchenko Date: Sat, 6 Mar 2021 07:47:11 -0500 Subject: [PATCH 14/53] tslint fix --- extension/js/background_page/migrations.ts | 2 +- extension/js/common/platform/store/contact-store.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/extension/js/background_page/migrations.ts b/extension/js/background_page/migrations.ts index 0202043ab82..ee9a11ea913 100644 --- a/extension/js/background_page/migrations.ts +++ b/extension/js/background_page/migrations.ts @@ -56,7 +56,7 @@ export const moveContactsToEmailsAndPubkeys = async (db: IDBDatabase) => { pending_lookup: entry.pending_lookup, last_use: entry.last_use, pubkey_last_check: entry.pubkey_last_check - }) + }); cursor.continue(); } }; diff --git a/extension/js/common/platform/store/contact-store.ts b/extension/js/common/platform/store/contact-store.ts index a258e92557b..75a83caea51 100644 --- a/extension/js/common/platform/store/contact-store.ts +++ b/extension/js/common/platform/store/contact-store.ts @@ -87,7 +87,7 @@ export class ContactStore extends AbstractStore { console.log('contacts store is now empty, deleting it...'); db.deleteObjectStore('contacts'); } - } + }; } }; openDbReq.onsuccess = () => resolve(openDbReq.result as IDBDatabase); From aae19a327e79a7b04554822051695bf51c6fdb0a Mon Sep 17 00:00:00 2001 From: Roman Shevchenko Date: Sat, 6 Mar 2021 07:51:10 -0500 Subject: [PATCH 15/53] activate migration on db open --- extension/js/background_page/background_page.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/extension/js/background_page/background_page.ts b/extension/js/background_page/background_page.ts index c053848fc21..c86446526d7 100644 --- a/extension/js/background_page/background_page.ts +++ b/extension/js/background_page/background_page.ts @@ -9,7 +9,7 @@ import { Catch } from '../common/platform/catch.js'; import { GoogleAuth } from '../common/api/email-provider/gmail/google-auth.js'; import { VERSION } from '../common/core/const.js'; import { injectFcIntoWebmail } from './inject.js'; -import { migrateGlobal } from './migrations.js'; +import { migrateGlobal, moveContactsToEmailsAndPubkeys } from './migrations.js'; import { opgp } from '../common/core/crypto/pgp/openpgpjs-custom.js'; import { GlobalStoreDict, GlobalStore } from '../common/platform/store/global-store.js'; import { ContactStore } from '../common/platform/store/contact-store.js'; @@ -41,6 +41,7 @@ opgp.initWorker({ path: '/lib/openpgp.worker.js' }); try { db = await ContactStore.dbOpen(); // takes 4-10 ms first time + await moveContactsToEmailsAndPubkeys(db); } catch (e) { await BgUtils.handleStoreErr(e); return; From 03cf2a04360f5fdc1f43e1e7e20b2297bfacd0a5 Mon Sep 17 00:00:00 2001 From: Roman Shevchenko Date: Sun, 7 Mar 2021 07:27:16 -0500 Subject: [PATCH 16/53] migration fix --- extension/js/background_page/migrations.ts | 52 +----- .../js/common/platform/store/contact-store.ts | 167 ++++++++++++------ 2 files changed, 110 insertions(+), 109 deletions(-) diff --git a/extension/js/background_page/migrations.ts b/extension/js/background_page/migrations.ts index ee9a11ea913..0d2dcc31477 100644 --- a/extension/js/background_page/migrations.ts +++ b/extension/js/background_page/migrations.ts @@ -2,9 +2,7 @@ 'use strict'; -import { Key, KeyInfo, KeyUtil } from '../common/core/crypto/key.js'; -import { AbstractStore } from '../common/platform/store/abstract-store.js'; -import { ContactStore } from '../common/platform/store/contact-store.js'; +import { KeyInfo, KeyUtil } from '../common/core/crypto/key.js'; import { GlobalStore } from '../common/platform/store/global-store.js'; import { KeyStore } from '../common/platform/store/key-store.js'; @@ -19,54 +17,6 @@ const addKeyInfoFingerprints = async () => { } }; -type ContactV3 = { - email: string; - name: string | null; - pubkey: Key | string | null; - has_pgp: 0 | 1; - fingerprint: string | null; - pending_lookup: number; - last_use: number | null; - pubkey_last_sig: number | null; - pubkey_last_check: number | null; - expiresOn: number | null; -}; - -export const moveContactsToEmailsAndPubkeys = async (db: IDBDatabase) => { - if (db.objectStoreNames.contains('contacts')) { - console.info('migrating contacts of ContactStore to emails and pubkeys...'); - await new Promise((resolve, reject) => { - const tx = db.transaction(['contacts'], 'readwrite'); - const contacts = tx.objectStore('contacts'); - const search = contacts.openCursor(); - search.onsuccess = async () => { - const cursor = search.result as IDBCursorWithValue | undefined; - if (!cursor) { - contacts.clear(); - } else { - const entry = cursor.value as ContactV3; // tslint:disable-line:no-unsafe-any - const armoredPubkey = (entry.pubkey && typeof entry.pubkey === 'object') - ? KeyUtil.armor(entry.pubkey as Key) : entry.pubkey as string; - // parse again to re-calculate expiration-related fields etc. - const pubkey = armoredPubkey ? await KeyUtil.parse(armoredPubkey) : undefined; - await ContactStore.update(db, entry.email, { - email: entry.email, - name: entry.name, - pubkey, - pending_lookup: entry.pending_lookup, - last_use: entry.last_use, - pubkey_last_check: entry.pubkey_last_check - }); - cursor.continue(); - } - }; - tx.oncomplete = () => resolve(undefined); - AbstractStore.setReqOnError(tx, reject); - }); - console.info('done migrating'); - } -}; - export const migrateGlobal = async () => { const globalStore = await GlobalStore.get(['key_info_store_fingerprints_added']); if (!globalStore.key_info_store_fingerprints_added) { diff --git a/extension/js/common/platform/store/contact-store.ts b/extension/js/common/platform/store/contact-store.ts index 75a83caea51..1d6b095990f 100644 --- a/extension/js/common/platform/store/contact-store.ts +++ b/extension/js/common/platform/store/contact-store.ts @@ -38,6 +38,20 @@ type Pubkey = { expiresOn: number; }; +// contact entity prior to version 4 +type ContactV3 = { + email: string; + name: string | null; + pubkey: Key | string | null; + has_pgp: 0 | 1; + fingerprint: string | null; + pending_lookup: number; + last_use: number | null; + pubkey_last_sig: number | null; + pubkey_last_check: number | null; + expiresOn: number | null; +}; + export type ContactPreview = { email: string; name: string | null; @@ -80,15 +94,7 @@ export class ContactStore extends AbstractStore { emails.createIndex('index_fingerprints', 'fingerprints', { multiEntry: true }); // fingerprints of all connected pubkeys pubkeys.createIndex('index_longids', 'longids', { multiEntry: true }); // longids of all public key packets in armored pubkey } - if (db.objectStoreNames.contains('contacts')) { - const countRequest = openDbReq.transaction!.objectStore('contacts').count(); - countRequest.onsuccess = () => { - if (countRequest.result === 0) { - console.log('contacts store is now empty, deleting it...'); - db.deleteObjectStore('contacts'); - } - }; - } + ContactStore.moveContactsToEmailsAndPubkeys(openDbReq); }; openDbReq.onsuccess = () => resolve(openDbReq.result as IDBDatabase); openDbReq.onblocked = () => reject(ContactStore.errCategorize(openDbReq.error)); @@ -171,55 +177,7 @@ export class ContactStore extends AbstractStore { Catch.report(`Wrongly updating prv ${update.pubkey.id} as contact - converting to pubkey`); update.pubkey = await KeyUtil.asPublicKey(update.pubkey); } - await new Promise((resolve, reject) => { - const tx = db.transaction(['emails', 'pubkeys'], 'readwrite'); - const req = tx.objectStore('emails').get(email); - ContactStore.setReqPipe(req, - (emailEntity: Email) => { - if (!emailEntity) { - const validEmail = Str.parseEmail(email).email; - if (!validEmail) { - reject(`Cannot save contact because email is not valid: ${email}`); - return; - } - emailEntity = { email, name: null, searchable: [], fingerprints: [], pendingLookup: 0, lastUse: null }; - } - if (update.pubkey) { - // todo: get the pubkey record if it exists to retrieve lastCheck - /* const req1 = tx.objectStore('pubkeys').get(update.pubkey.id); - ContactStore.setReqPipe(req1, ... */ - const keyAttrs = ContactStore.getKeyAttributes(update.pubkey); - const pubkeyEntity = { - fingerprint: update.pubkey.id, - lastCheck: null, - lastSig: keyAttrs.pubkey_last_sig, - expiresOn: keyAttrs.expiresOn, - longids: update.pubkey.allIds.map(id => OpenPGPKey.fingerprintToLongid(id)), - armoredKey: KeyUtil.armor(update.pubkey) - } as Pubkey; - if (!emailEntity.fingerprints.includes(pubkeyEntity.fingerprint)) { - emailEntity.fingerprints.push(pubkeyEntity.fingerprint); - } - if (Object.keys(update).includes('pubkey_last_check') && update.pubkey_last_check) { - pubkeyEntity.lastCheck = update.pubkey_last_check; - } - tx.objectStore('pubkeys').put(pubkeyEntity); - } - if (Object.keys(update).includes('name')) { - emailEntity.name = update.name ?? null; - } - if (Object.keys(update).includes('pending_lookup')) { - emailEntity.pendingLookup = update.pending_lookup ?? 0; - } - if (Object.keys(update).includes('last_use')) { - emailEntity.lastUse = update.last_use ?? null; - } - ContactStore.updateSearchable(emailEntity); - tx.objectStore('emails').put(emailEntity); - tx.oncomplete = () => resolve(undefined); - tx.onabort = () => reject(ContactStore.errCategorize(tx.error)); - }, reject); - }); + await ContactStore.updateTx(db.transaction(['emails', 'pubkeys'], 'readwrite'), email, update); } public static get = async (db: undefined | IDBDatabase, emailOrLongid: string[]): Promise<(Contact | undefined)[]> => { @@ -324,6 +282,66 @@ export class ContactStore extends AbstractStore { return raw; } + private static updateTx = async (tx: IDBTransaction, email: string, update: ContactUpdate): Promise => { + await new Promise((resolve, reject) => { + const req = tx.objectStore('emails').get(email); + ContactStore.setReqPipe(req, + (emailEntity: Email) => { + if (!emailEntity) { + const validEmail = Str.parseEmail(email).email; + if (!validEmail) { + reject(`Cannot save contact because email is not valid: ${email}`); + return; + } + emailEntity = { email, name: null, searchable: [], fingerprints: [], pendingLookup: 0, lastUse: null }; + } + let pubkeyEntity: Pubkey | undefined; + if (update.pubkey) { + // todo: get the pubkey record if it exists to retrieve lastCheck + /* const req1 = tx.objectStore('pubkeys').get(update.pubkey.id); + ContactStore.setReqPipe(req1, ... */ + const keyAttrs = ContactStore.getKeyAttributes(update.pubkey); + pubkeyEntity = { + fingerprint: update.pubkey.id, + lastCheck: null, + lastSig: keyAttrs.pubkey_last_sig, + expiresOn: keyAttrs.expiresOn, + longids: update.pubkey.allIds.map(id => OpenPGPKey.fingerprintToLongid(id)), + armoredKey: KeyUtil.armor(update.pubkey) + } as Pubkey; + if (!emailEntity.fingerprints.includes(pubkeyEntity.fingerprint)) { + emailEntity.fingerprints.push(pubkeyEntity.fingerprint); + } + if (Object.keys(update).includes('pubkey_last_check') && update.pubkey_last_check) { + pubkeyEntity.lastCheck = update.pubkey_last_check; + } + } + if (Object.keys(update).includes('name')) { + emailEntity.name = update.name ?? null; + } + if (Object.keys(update).includes('pending_lookup')) { + emailEntity.pendingLookup = update.pending_lookup ?? 0; + } + if (Object.keys(update).includes('last_use')) { + emailEntity.lastUse = update.last_use ?? null; + } + ContactStore.updateSearchable(emailEntity); + const updReq1 = tx.objectStore('emails').put(emailEntity); + ContactStore.setReqPipe(updReq1, () => { + if (pubkeyEntity) { + const updReq2 = tx.objectStore('pubkeys').put(pubkeyEntity); + ContactStore.setReqPipe(updReq2, () => { + resolve(undefined); + }, reject); + } else { + resolve(undefined); + } + }, reject); + tx.onerror = () => reject(ContactStore.errCategorize(tx.error)); + }, reject); + }); + } + private static normalizeString = (str: string) => { return str.normalize('NFKD').replace(/[\u0300-\u036F]/g, '').toLowerCase(); } @@ -461,4 +479,37 @@ export class ContactStore extends AbstractStore { return contacts; } + private static moveContactsToEmailsAndPubkeys = (openDbReq: IDBOpenDBRequest) => { + const db = openDbReq.result; + if (db.objectStoreNames.contains('contacts')) { + console.info('migrating contacts of ContactStore to emails and pubkeys...'); + const tx = openDbReq.transaction!; + const contacts = tx.objectStore('contacts'); + const search = contacts.openCursor(); + search.onsuccess = async () => { + const cursor = search.result as IDBCursorWithValue | undefined; + if (!cursor) { + console.log('contacts store is now empty, deleting it...'); + db.deleteObjectStore('contacts'); + } else { + const entry = cursor.value as ContactV3; // tslint:disable-line:no-unsafe-any + const armoredPubkey = (entry.pubkey && typeof entry.pubkey === 'object') + ? KeyUtil.armor(entry.pubkey as Key) : entry.pubkey as string; + // parse again to re-calculate expiration-related fields etc. + const pubkey = armoredPubkey ? await KeyUtil.parse(armoredPubkey) : undefined; + console.log(`Migrating ${entry.email} ...`); + await ContactStore.updateTx(tx, entry.email, { + email: entry.email, + name: entry.name, + pubkey, + pending_lookup: entry.pending_lookup, + last_use: entry.last_use, + pubkey_last_check: entry.pubkey_last_check + }); + cursor.continue(); + } + }; + console.info('done migrating'); + } + } } From 2881dda8c82ba1a0006b0edbd40f6a54f398da70 Mon Sep 17 00:00:00 2001 From: Roman Shevchenko Date: Sun, 7 Mar 2021 07:30:13 -0500 Subject: [PATCH 17/53] migration fix --- extension/js/background_page/background_page.ts | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/extension/js/background_page/background_page.ts b/extension/js/background_page/background_page.ts index c86446526d7..c053848fc21 100644 --- a/extension/js/background_page/background_page.ts +++ b/extension/js/background_page/background_page.ts @@ -9,7 +9,7 @@ import { Catch } from '../common/platform/catch.js'; import { GoogleAuth } from '../common/api/email-provider/gmail/google-auth.js'; import { VERSION } from '../common/core/const.js'; import { injectFcIntoWebmail } from './inject.js'; -import { migrateGlobal, moveContactsToEmailsAndPubkeys } from './migrations.js'; +import { migrateGlobal } from './migrations.js'; import { opgp } from '../common/core/crypto/pgp/openpgpjs-custom.js'; import { GlobalStoreDict, GlobalStore } from '../common/platform/store/global-store.js'; import { ContactStore } from '../common/platform/store/contact-store.js'; @@ -41,7 +41,6 @@ opgp.initWorker({ path: '/lib/openpgp.worker.js' }); try { db = await ContactStore.dbOpen(); // takes 4-10 ms first time - await moveContactsToEmailsAndPubkeys(db); } catch (e) { await BgUtils.handleStoreErr(e); return; From 2135e3a32d34b15dae0f8ecedf4cadb2c3b672b8 Mon Sep 17 00:00:00 2001 From: Roman Shevchenko Date: Sun, 7 Mar 2021 10:27:08 -0500 Subject: [PATCH 18/53] fixed 'pubkey_last_check' update --- .../compose-modules/compose-storage-module.ts | 2 +- .../js/common/platform/store/contact-store.ts | 51 +++++++++++-------- 2 files changed, 32 insertions(+), 21 deletions(-) diff --git a/extension/chrome/elements/compose-modules/compose-storage-module.ts b/extension/chrome/elements/compose-modules/compose-storage-module.ts index 7d99ef21958..e036a1c3485 100644 --- a/extension/chrome/elements/compose-modules/compose-storage-module.ts +++ b/extension/chrome/elements/compose-modules/compose-storage-module.ts @@ -163,7 +163,7 @@ export class ComposeStorageModule extends ViewModule { } } } - await ContactStore.update(undefined, contact.email, { pubkey_last_check: Date.now() }); + await ContactStore.update(undefined, contact.email, { pubkey: contact.pubkey, pubkey_last_check: Date.now() }); // we checked for newer key and it did not result in updating the key, don't check again for another week } catch (e) { ApiErr.reportIfSignificant(e); diff --git a/extension/js/common/platform/store/contact-store.ts b/extension/js/common/platform/store/contact-store.ts index 1d6b095990f..a3a8e607256 100644 --- a/extension/js/common/platform/store/contact-store.ts +++ b/extension/js/common/platform/store/contact-store.ts @@ -62,10 +62,10 @@ export type ContactPreview = { export type ContactUpdate = { email?: string; name?: string | null; - pubkey?: Key; pending_lookup?: number; last_use?: number | null; - pubkey_last_check?: number | null; + pubkey?: Key; + pubkey_last_check?: number | null; // when non-null, `pubkey` must be supplied }; type DbContactFilter = { has_pgp?: boolean, substring?: string, limit?: number }; @@ -282,8 +282,36 @@ export class ContactStore extends AbstractStore { return raw; } + private static getPubkeyTx = async (tx: IDBTransaction, id: string) => { + const existingPubkey: Pubkey = await new Promise((resolve, reject) => { + const req = tx.objectStore('pubkeys').get(id); + ContactStore.setReqPipe(req, + (pubkey: Pubkey) => { + resolve(pubkey); + }, + reject); + }); + return existingPubkey; + } + private static updateTx = async (tx: IDBTransaction, email: string, update: ContactUpdate): Promise => { + let pubkeyEntity: Pubkey | undefined; + if (update.pubkey) { + const keyAttrs = ContactStore.getKeyAttributes(update.pubkey); + // todo: will we benefit anything when not saving pubkey if it isn't modified? + pubkeyEntity = { + fingerprint: update.pubkey.id, + lastCheck: update.pubkey_last_check ?? (await ContactStore.getPubkeyTx(tx, update.pubkey.id))?.lastCheck, + lastSig: keyAttrs.pubkey_last_sig, + expiresOn: keyAttrs.expiresOn, + longids: update.pubkey.allIds.map(id => OpenPGPKey.fingerprintToLongid(id)), + armoredKey: KeyUtil.armor(update.pubkey) + } as Pubkey; + } else if (update.pubkey_last_check) { + Catch.report(`Wrongly updating pubkey_last_check without specifying pubkey for ${email} - ignoring`); + } await new Promise((resolve, reject) => { + tx.addEventListener('error', () => reject(ContactStore.errCategorize(tx.error))); const req = tx.objectStore('emails').get(email); ContactStore.setReqPipe(req, (emailEntity: Email) => { @@ -295,26 +323,10 @@ export class ContactStore extends AbstractStore { } emailEntity = { email, name: null, searchable: [], fingerprints: [], pendingLookup: 0, lastUse: null }; } - let pubkeyEntity: Pubkey | undefined; - if (update.pubkey) { - // todo: get the pubkey record if it exists to retrieve lastCheck - /* const req1 = tx.objectStore('pubkeys').get(update.pubkey.id); - ContactStore.setReqPipe(req1, ... */ - const keyAttrs = ContactStore.getKeyAttributes(update.pubkey); - pubkeyEntity = { - fingerprint: update.pubkey.id, - lastCheck: null, - lastSig: keyAttrs.pubkey_last_sig, - expiresOn: keyAttrs.expiresOn, - longids: update.pubkey.allIds.map(id => OpenPGPKey.fingerprintToLongid(id)), - armoredKey: KeyUtil.armor(update.pubkey) - } as Pubkey; + if (pubkeyEntity) { if (!emailEntity.fingerprints.includes(pubkeyEntity.fingerprint)) { emailEntity.fingerprints.push(pubkeyEntity.fingerprint); } - if (Object.keys(update).includes('pubkey_last_check') && update.pubkey_last_check) { - pubkeyEntity.lastCheck = update.pubkey_last_check; - } } if (Object.keys(update).includes('name')) { emailEntity.name = update.name ?? null; @@ -337,7 +349,6 @@ export class ContactStore extends AbstractStore { resolve(undefined); } }, reject); - tx.onerror = () => reject(ContactStore.errCategorize(tx.error)); }, reject); }); } From 1e33c0c482297ec4681414b094289d29b595be01 Mon Sep 17 00:00:00 2001 From: Roman Shevchenko Date: Thu, 11 Mar 2021 10:11:42 -0500 Subject: [PATCH 19/53] Added a test for partial email search --- extension/chrome/dev/ci_unit_test.ts | 4 +- .../browser-unit-tests/unit-ContactStore.js | 159 ++++++++++++++++++ 2 files changed, 162 insertions(+), 1 deletion(-) create mode 100644 test/source/tests/browser-unit-tests/unit-ContactStore.js diff --git a/extension/chrome/dev/ci_unit_test.ts b/extension/chrome/dev/ci_unit_test.ts index eb00abcf20f..b40e1bb2bfe 100644 --- a/extension/chrome/dev/ci_unit_test.ts +++ b/extension/chrome/dev/ci_unit_test.ts @@ -13,6 +13,7 @@ import { Wkd } from '../../js/common/api/key-server/wkd.js'; import { MsgUtil } from '../../js/common/core/crypto/pgp/msg-util.js'; import { Sks } from '../../js/common/api/key-server/sks.js'; import { Ui } from '../../js/common/browser/ui.js'; +import { ContactStore } from '../../js/common/platform/store/contact-store.js'; /** * importing all libs that are tested in ci tests @@ -29,7 +30,8 @@ const libs: any[] = [ Wkd, Sks, MsgUtil, - Ui + Ui, + ContactStore ]; // add them to global scope so ci can use them diff --git a/test/source/tests/browser-unit-tests/unit-ContactStore.js b/test/source/tests/browser-unit-tests/unit-ContactStore.js new file mode 100644 index 00000000000..b4e206b1569 --- /dev/null +++ b/test/source/tests/browser-unit-tests/unit-ContactStore.js @@ -0,0 +1,159 @@ +/* ©️ 2016 - present FlowCrypt a.s. Limitations apply. Contact human@flowcrypt.com */ + +/* eslint-disable max-len */ + +/** + * These tests use JavaScript instead of TypeScript to avoid dealing with types in cross-environment setup. + * (tests are injected from NodeJS through puppeteer into a browser environment) + * While this makes them less convenient to write, the result is more flexible. + * + * Import your lib to `ci_unit_test.ts` to resolve `ReferenceError: SomeClass is not defined` + * + * Each test must return "pass" to pass. To reject, throw an Error. + * + * Each test must start with one of (depending on which flavors you want it to run): + * - BROWSER_UNIT_TEST_NAME(`some test name`); + * - BROWSER_UNIT_TEST_NAME(`some test name`).enterprise; + * - BROWSER_UNIT_TEST_NAME(`some test name`).consumer; + * + * This is not a JavaScript file. It's a text file that gets parsed, split into chunks, and + * parts of it executed as javascript. The structure is very rigid. The only flexible place is inside + * the async functions. For the rest, do not change the structure or our parser will get confused. + * Do not put any code whatsoever outside of the async functions. + */ + +BROWSER_UNIT_TEST_NAME(`ContactStore is able to search by partial email address`); +(async () => { + + const contactABBDEF = await ContactStore.obj({ + email: 'abbdef@test.com', pubkey: `-----BEGIN PGP PUBLIC KEY BLOCK----- + +xsBNBGBKJpABCAC/EABGGXizvq4j96YsI0olYqS+9wSydO2Wn1AkoCCsyY9d7xrqG8UONylrTv0/ +FpF951TnpQiWK3Z0RZcUhtVvLvmgF9+RwW1G2/KMc5SrjcAEhlIqPlXwd3hJfJgD03XtKT4mr8Y/ +MVKLcIZyfn/45I/kWY88qVIKKkeG6NbCoV0zBczqTUsx+Tfij6eAo9iYb+ml2vyuEgZiNTdfkCxI +CzBo7udOcamziz9x8KINJidjwCv0vGO8vhmTQav1sJP71vd5T/t1jghK3DA6uz5GNFoaGG5F3Pl9 +JrgNWkmufuJVMFyC19GUPxm8EPys9yvo8n4Lf1FugeRuIBZPU8K7ABEBAAHND2FiYmRlZkB0ZXN0 +LmNvbcLAiQQTAQgAMxYhBLeQro9CXcRGM6jAht9jZZw7SoH7BQJgSiaUAhsDBQsJCAcCBhUICQoL +AgUWAgMBAAAKCRDfY2WcO0qB+2ADB/94z3/Y0OQAJqvJSIaPtw7NJdyrsh7guahyGKdMFxVLeGmp +dCbW9wHwBJ4vDd4sSt6ufX3iQfrwZn5RHtX97AAJ0kZOPJBTlhDPClU1sDudbStde4UpJ6EtYWV6 +o2CLiFQA8OT0endU1b6uiGDOGkUz98lzyqvKlP6lT3EwW68xSL3NrewNoYJDQox7N9ATznGSbGaG +Jl2STYYA082bpXbgi2cKKwKo3WkSn4iEBEVdrO5yj8PyoOUwE5RK3mCbNLW5KEhY7hHWLz6IO+NC +7xHj4UZdVxtLjZVtui0Ha9qGO5iTs/S3KwhQ/9uA22RUc438vPBdVJ7kDAD+m2SPobWkzsBNBGBK +JpQBCAC+zvtIhjKvb8tYaDXHJPivBNhWYq2xOuUt/yXZs2DPYLEGbiELz/URPW1ew8aYmrtqHg7z +Q47kXz/P5HxZsrMq3bal4mRW02cC9jZT7FrCNI/IE3og3PbHd43spTR8IAz0PDM1huZ/IU0OOBU5 +xjgTRFTGv7eaA39xY0KU8GKtTCXuzPa/3gYby3em2E6tgrCKoicnMtf4uaWIsd6fJ5i5scSBFD93 +0c44U7QgAKuoB90n6887PtNH0voRfrRpLPGQg55WWCHUsx4WdZvPQOw8UPgNVdu+O4+k2xdJ6mEA +Nz0et1bXDy1b91ywpBTqXzdnwBZ2dFxqsiSTTn11i4XlABEBAAHCwHYEGAEIACAWIQS3kK6PQl3E +RjOowIbfY2WcO0qB+wUCYEommAIbDAAKCRDfY2WcO0qB+6OQB/wInDNjHfjnqiorwbAg7mOg5qUl +a9Lrqz9o6ysw9UUV2aof366sy1B3SaYO6gd5vvLF9TTxpgk4ciAjJ8A0m5Xwywmz0chQ766aye/J +IKcMsL1I47EpMbuxMfZWYEamNxEtpuPVuKpJOwVW//obiqTYBBpsPovi9s1j66aSBO6Ij9h9V8FQ +TIxPYXu77fCgpVkxh4RffsGQ8l7H/aRQp8a9sKdCe5X7uOF/6Amw25Rfzm1RN9Yg04bVPxtjD2L1 +SqxF5hZCC16HX1SL4GLY9MjLv2JUw6vlMqjASXKI5MyAlpKAicSe1P5yd/ysM9YiLQHriF3+hYxX +PsqHPr70z6If +=ack7 +-----END PGP PUBLIC KEY BLOCK----- +` }); + const contactABCDEF = await ContactStore.obj({ + email: 'abcdef@test.com', pubkey: `-----BEGIN PGP PUBLIC KEY BLOCK----- + +xsBNBGBKJS4BCADxtPTToUQsjy8G88QBeU3a+B0l2CHy6OhsHJkuUOEzM0S++rZqLJJCGdgVAdAF +o+vk1SyaQRQRMMl3mmFc0Qf3kDWARAT7TCvnYGfiR6PyAn7jcyFsY6y88jF6YrjEs30Tz0I8pItC +rmsYGWtoDyBBfaXhZUAAHlRELaG/KzhIt66zowJkP4UwrBnOYzkw7yu4KUcmsOrG7t8XXqJ3LHuQ +Hi6eyWceuFz7Ybsy9qw7GYWxU60NfSnUssQTzRZL9ZeNDzxIjKlpC02SeAyexF9bHuoRbR5GB4QW +byLLcH15U9FTxlf8oHVgP8pD4AEwBrhc+rqLXX6wwT/3G0nFX+LhABEBAAHND2FiY2RlZkB0ZXN0 +LmNvbcLAiQQTAQgAMxYhBDFV8Ri25zKzY4oc4WCLzXl6I/uRBQJgSiUyAhsDBQsJCAcCBhUICQoL +AgUWAgMBAAAKCRBgi815eiP7kdFlCACf7Qf9NZAHfE/CfiZHTTvw+RoLLYKu/Xg4s1uKfGVIe6+w +1wtdy/NHTtf2wWRU/oPC5PK8+P2GjpvwaJIIGCS7sdkeRrRfICxvhYSEGDfvZ2ojLBAz4IGggVcu +YkUc8ZLq1wOgh02wbjbkvIbDPLtPFoK/3hWswPPs+UoheCg1QfEKEpzvvg0NDxO8YhmFqedLYBBu +TSH/b1tIXAdujO2o8U7yUtOe+HZ5f9DHrXf8jiySJ1rZehb2srOt+H9+g7zUXBojqsqzJupuaj8f +5i7g0Hb0c4Kq2NdvoEU7dzFu/Tqy6Pv+ZpgktlOqiFyAwXH8sDBLqeWe8gGAk68lPe/tzsBNBGBK +JTIBCADI7CylGW0udtxCk2FM4lgcjEp1IAbcsarLd8TrxRRus/OTCm9e4FEmB5+nYX3uWhh5WSm7 +zxX3ufjGyIx3fEPOrvjvdiZEUjTansMz+smuMk9+4sWVZcGT1BC0f7zGNfu3Hd2VbXOA763HdQnd +4S4oycj/aOBCjth1pgVMaEJEJAozC8uzzYBp4guMolreC2Xu1UixpPn2N/+ZjJzHDdIwC4yXjYT4 +xz3IoPwl/XVsXZOofQR+v88AhmsHtXbJy0pGpY9jtd8gncq3DMFcOhACoU0o1wu7FwRDLtjR5W8R +yBAShf78spBzuBCunSfxw2Bv3ak+b43jtN77TrTZrF2xABEBAAHCwHYEGAEIACAWIQQxVfEYtucy +s2OKHOFgi815eiP7kQUCYEolOQIbDAAKCRBgi815eiP7kUMoB/0SvYvjthGGhzrHXHC2WusC6rEN +Szp7FrUbc5upp2dktVmH62jC649K9lsoJUhitcE8E2C+lLToIJMhsNIXgPP7Ai+a6dJn6LKwT95b +RZGNIk/dQehU53g0BNdsWDCBUa92vFmtngQ34nwM40iiYLraioCah9/yZGdANFAEFr4iA2mmfBlt +j3kOljjta/iqbEO0hWSVwUT7D7ljitU4/BOmyT0n10ra7FtUMfMzVHrvJZGjEkrk8DjVLunPkkqj +kM3d32EJ1lZdub6GcDURdWNaOd9FmNGizKYYu1Wgeik0SnrhCy6DGLT+JDfv1/arwK2s1Usi2SOq +X7O4C+D4oKVA +=a/tS +-----END PGP PUBLIC KEY BLOCK----- +` }); + const contactABCDDF = await ContactStore.obj({ + email: 'abcddf@test.com', pubkey: `-----BEGIN PGP PUBLIC KEY BLOCK----- + +xsBNBGBKJS4BCADxtPTToUQsjy8G88QBeU3a+B0l2CHy6OhsHJkuUOEzM0S++rZqLJJCGdgVAdAF +o+vk1SyaQRQRMMl3mmFc0Qf3kDWARAT7TCvnYGfiR6PyAn7jcyFsY6y88jF6YrjEs30Tz0I8pItC +rmsYGWtoDyBBfaXhZUAAHlRELaG/KzhIt66zowJkP4UwrBnOYzkw7yu4KUcmsOrG7t8XXqJ3LHuQ +Hi6eyWceuFz7Ybsy9qw7GYWxU60NfSnUssQTzRZL9ZeNDzxIjKlpC02SeAyexF9bHuoRbR5GB4QW +byLLcH15U9FTxlf8oHVgP8pD4AEwBrhc+rqLXX6wwT/3G0nFX+LhABEBAAHND2FiY2RlZkB0ZXN0 +LmNvbcLAiQQTAQgAMxYhBDFV8Ri25zKzY4oc4WCLzXl6I/uRBQJgSiUyAhsDBQsJCAcCBhUICQoL +AgUWAgMBAAAKCRBgi815eiP7kdFlCACf7Qf9NZAHfE/CfiZHTTvw+RoLLYKu/Xg4s1uKfGVIe6+w +1wtdy/NHTtf2wWRU/oPC5PK8+P2GjpvwaJIIGCS7sdkeRrRfICxvhYSEGDfvZ2ojLBAz4IGggVcu +YkUc8ZLq1wOgh02wbjbkvIbDPLtPFoK/3hWswPPs+UoheCg1QfEKEpzvvg0NDxO8YhmFqedLYBBu +TSH/b1tIXAdujO2o8U7yUtOe+HZ5f9DHrXf8jiySJ1rZehb2srOt+H9+g7zUXBojqsqzJupuaj8f +5i7g0Hb0c4Kq2NdvoEU7dzFu/Tqy6Pv+ZpgktlOqiFyAwXH8sDBLqeWe8gGAk68lPe/tzsBNBGBK +JTIBCADI7CylGW0udtxCk2FM4lgcjEp1IAbcsarLd8TrxRRus/OTCm9e4FEmB5+nYX3uWhh5WSm7 +zxX3ufjGyIx3fEPOrvjvdiZEUjTansMz+smuMk9+4sWVZcGT1BC0f7zGNfu3Hd2VbXOA763HdQnd +4S4oycj/aOBCjth1pgVMaEJEJAozC8uzzYBp4guMolreC2Xu1UixpPn2N/+ZjJzHDdIwC4yXjYT4 +xz3IoPwl/XVsXZOofQR+v88AhmsHtXbJy0pGpY9jtd8gncq3DMFcOhACoU0o1wu7FwRDLtjR5W8R +yBAShf78spBzuBCunSfxw2Bv3ak+b43jtN77TrTZrF2xABEBAAHCwHYEGAEIACAWIQQxVfEYtucy +s2OKHOFgi815eiP7kQUCYEolOQIbDAAKCRBgi815eiP7kUMoB/0SvYvjthGGhzrHXHC2WusC6rEN +Szp7FrUbc5upp2dktVmH62jC649K9lsoJUhitcE8E2C+lLToIJMhsNIXgPP7Ai+a6dJn6LKwT95b +RZGNIk/dQehU53g0BNdsWDCBUa92vFmtngQ34nwM40iiYLraioCah9/yZGdANFAEFr4iA2mmfBlt +j3kOljjta/iqbEO0hWSVwUT7D7ljitU4/BOmyT0n10ra7FtUMfMzVHrvJZGjEkrk8DjVLunPkkqj +kM3d32EJ1lZdub6GcDURdWNaOd9FmNGizKYYu1Wgeik0SnrhCy6DGLT+JDfv1/arwK2s1Usi2SOq +X7O4C+D4oKVA +=a/tS +-----END PGP PUBLIC KEY BLOCK----- +` }); + const contactABDDEF = await ContactStore.obj({ + email: 'abddef@test.com', pubkey: `-----BEGIN PGP PUBLIC KEY BLOCK----- + +xsBNBGBKJqQBCACzNxbtfJMZdrhzTpV34rEy4t50Q/8jwo4+z7GLPX6vSmHGy/Y4fOBsae5rXMr9 +v02IAdoLgGTbPqSa5fDPWAbiyNL/M/5ojBwAzHBChWyD2543M1XOOOAgUm2dKospBww4RyavkE4t +Ng3HIY/eWtm0sDGuYYkwvrgu5Puc+1kMegdBkE1CkkNd/jC/EJnnYs3WDaVd1h1is/IxKJ8xjTQD +Rc2+YJYxCT5+KxRFlApqXogJDhPQEK+S8Rl/nMunxMVq9ls2ixdsnWdvA3+4xbRN6WLDKy/mx8XD +OqvOpXQ8f9rXiwwjW8EsoCFrTvCdh2JaY0uPqRtfcrQodhidAUMXABEBAAHND2FiZGRlZkB0ZXN0 +LmNvbcLAiQQTAQgAMxYhBJ4CDZt1L9P/8X7Ztl/MFUHPKClRBQJgSiaoAhsDBQsJCAcCBhUICQoL +AgUWAgMBAAAKCRBfzBVBzygpUYZ7CACxwRjeDlaHQCNsV+yG3gwXorKBHmMVZ++pO2fjCWRIwQA6 +DfkQ//tjudLwLIZRNFgdn9T04XEX3p65wkhK8vbyhTk18VS57NPLFpSjOrkhXd0JNgMNI0LVcOp9 +gPkgQZ7qBlRh1rpZiZyO/sccJAb0RfLzbaMl7BOKOKsAvUOGT4eiIjp+37/HsrYvOaJkzt8vI4dx +RPuJ5rWJPrlnJuPO1im0hsi7dj9XrVdWth58AyMvQ5JjbAid9b42VZ4HuB1P+PSiDeUQq4O9ISWA +ZtKsfTusZSQP/Bq9jZf+ucdRIM7eo6NCY3X4jFefjsWdi7mofcFZCowMTc+PkCMCBsmlzsBNBGBK +JqgBCADSGmHgTuMTfCvoRbzJ14i2WtFFODl7BwY4U8NZG6YcNv7QsCWIqJkwBIzX1OquO8PiZd4D +AKuYpuG2KCF/vLNFkkq5BWkiMrGIZ7QYvtQFD+BwbAfREcs6ZUMm22eTrdqgs1o5vsDYGGsN36Qh +ClIDFcUwlpb/35ryrp50GjLFaKjdgBFhksKOY6ZJRJNZcq+i+ii4FizEiJ23vfrPWPByVip1jx4L ++MlYCG102pNPrnaBnU02tj+tXwfHDXVT5QygO7nX2YM96wTIVxH8seatyjDUK668PQYmT5vGQKl2 +Ikr+orTzqJhMWN0gjA/EHRcpuQn2EJrTVi4+4oU6dZBzABEBAAHCwHYEGAEIACAWIQSeAg2bdS/T +//F+2bZfzBVBzygpUQUCYEomrQIbDAAKCRBfzBVBzygpUQFYB/9WfGAfJb3lIWEBIUE8viIH74B1 +/E1lpWbGbyzSjJAUsFiEAwM0gRaYr9pY2iVsRJwr0dmmhSsESwSy0/dD97jCqjD4d/AkiSxmEMlA +F9PCnKC7HizaM33lA1S0pADBBEVtwfLd4t0bAo4TnJWnjb/fd9osyPEZGU1zF/fFsfLAIb9GC9VB +5nRZgXIUeTZDCypk0fCc25kGVO3i8H37eRXonV3TcmNEgYUBvi/3Pk3s/7GUkpp1cKtn4s7MnHzO +wBff8jybIDc7uGSzTW5qc/3qcgbfH0FGCoIy20H7zgnEJ6PnkENlb/WfynSHAXvfMc8r9YLTCrkv +WmiyOmaRmLP+ +=Iusd +-----END PGP PUBLIC KEY BLOCK-----` }); + await ContactStore.save(undefined, contactABBDEF); + await ContactStore.save(undefined, contactABCDEF); + await ContactStore.save(undefined, contactABCDDF); + await ContactStore.save(undefined, contactABDDEF); + const contactsABC = await ContactStore.search(undefined, { has_pgp: true, substring: 'abc' }); + if (contactsABC.length !== 2) { + throw Error(`Expected 2 contacts to match "abc" but got "${contactsABC.length}"`); + } + const contactsABCD = await ContactStore.search(undefined, { has_pgp: true, substring: 'abcd' }); + if (contactsABCD.length !== 2) { + throw Error(`Expected 2 contacts to match "abcd" but got "${contactsABCD.length}"`); + } + const contactsABCDE = await ContactStore.search(undefined, { has_pgp: true, substring: 'abcde' }); + if (contactsABCDE.length !== 1) { + throw Error(`Expected 1 contact to match "abcde" but got "${contactsABCDE.length}"`); + } + if (contactsABCDE[0].email !== 'abcdef@test.com') { + throw Error(`Expected "abcdef@test.com" but got "${contactsABCDE[0].email}"`); + } + return 'pass'; +})(); From 91cf81e2d738b53ead80e6e6194f953e364feb90 Mon Sep 17 00:00:00 2001 From: Roman Shevchenko Date: Fri, 12 Mar 2021 06:41:48 -0500 Subject: [PATCH 20/53] fixed migration --- .../js/background_page/background_page.ts | 3 +- extension/js/background_page/migrations.ts | 69 +++++- .../js/common/platform/store/contact-store.ts | 197 +++++++----------- 3 files changed, 149 insertions(+), 120 deletions(-) diff --git a/extension/js/background_page/background_page.ts b/extension/js/background_page/background_page.ts index c053848fc21..c86446526d7 100644 --- a/extension/js/background_page/background_page.ts +++ b/extension/js/background_page/background_page.ts @@ -9,7 +9,7 @@ import { Catch } from '../common/platform/catch.js'; import { GoogleAuth } from '../common/api/email-provider/gmail/google-auth.js'; import { VERSION } from '../common/core/const.js'; import { injectFcIntoWebmail } from './inject.js'; -import { migrateGlobal } from './migrations.js'; +import { migrateGlobal, moveContactsToEmailsAndPubkeys } from './migrations.js'; import { opgp } from '../common/core/crypto/pgp/openpgpjs-custom.js'; import { GlobalStoreDict, GlobalStore } from '../common/platform/store/global-store.js'; import { ContactStore } from '../common/platform/store/contact-store.js'; @@ -41,6 +41,7 @@ opgp.initWorker({ path: '/lib/openpgp.worker.js' }); try { db = await ContactStore.dbOpen(); // takes 4-10 ms first time + await moveContactsToEmailsAndPubkeys(db); } catch (e) { await BgUtils.handleStoreErr(e); return; diff --git a/extension/js/background_page/migrations.ts b/extension/js/background_page/migrations.ts index 0d2dcc31477..999a6374b2d 100644 --- a/extension/js/background_page/migrations.ts +++ b/extension/js/background_page/migrations.ts @@ -2,10 +2,25 @@ 'use strict'; -import { KeyInfo, KeyUtil } from '../common/core/crypto/key.js'; +import { Key, KeyInfo, KeyUtil } from '../common/core/crypto/key.js'; +import { ContactStore, ContactUpdate } from '../common/platform/store/contact-store.js'; import { GlobalStore } from '../common/platform/store/global-store.js'; import { KeyStore } from '../common/platform/store/key-store.js'; +// contact entity prior to version 4 +type ContactV3 = { + email: string; + name: string | null; + pubkey: Key | string | null; + has_pgp: 0 | 1; + fingerprint: string | null; + pending_lookup: number; + last_use: number | null; + pubkey_last_sig: number | null; + pubkey_last_check: number | null; + expiresOn: number | null; +}; + const addKeyInfoFingerprints = async () => { for (const acctEmail of await GlobalStore.acctEmailsGet()) { const originalKis = await KeyStore.get(acctEmail); @@ -26,3 +41,55 @@ export const migrateGlobal = async () => { console.info('done migrating'); } }; + +export const moveContactsToEmailsAndPubkeys = async (db: IDBDatabase): Promise => { + if (!db.objectStoreNames.contains('contacts')) { + return 0; + } + const entries: ContactV3[] = []; + await new Promise((resolve, reject) => { + const tx = db.transaction(['contacts'], 'readonly'); + ContactStore.setTxHandlers(tx, resolve, reject); + console.info('migrating contacts of ContactStore to emails and pubkeys...'); + const contacts = tx.objectStore('contacts'); + const search = contacts.openCursor(); // todo: simplify with getAll() + search.onsuccess = () => { + const cursor = search.result as IDBCursorWithValue | undefined; + if (cursor) { + entries.push(cursor.value as ContactV3); // tslint:disable-line:no-unsafe-any + cursor.continue(); + } + }; + }); + console.info(`${entries.length} entries found.`); + if (!entries.length) { + return 0; + } + // transform + const updates = await Promise.all(entries.map(async (entry) => { + const armoredPubkey = (entry.pubkey && typeof entry.pubkey === 'object') + ? KeyUtil.armor(entry.pubkey as Key) : entry.pubkey as string; + // parse again to re-calculate expiration-related fields etc. + const pubkey = armoredPubkey ? await KeyUtil.parse(armoredPubkey) : undefined; + return { + email: entry.email, + name: entry.name, + pubkey, + pending_lookup: entry.pending_lookup, + last_use: entry.last_use, + pubkey_last_check: pubkey ? entry.pubkey_last_check : undefined + } as ContactUpdate; + })); + console.info(`transformation complete, saving...`); + // todo: split to batches + await new Promise((resolve, reject) => { + const tx = db.transaction(['contacts', 'emails', 'pubkeys'], 'readwrite'); + ContactStore.setTxHandlers(tx, resolve, reject); + for (const update of updates) { + ContactStore.updateTx(tx, update.email!, update); + tx.objectStore('contacts').delete(update.email!); + } + }); + console.info(`contacts migration finished`); + return updates.length; +} diff --git a/extension/js/common/platform/store/contact-store.ts b/extension/js/common/platform/store/contact-store.ts index a3a8e607256..71df16ac7e1 100644 --- a/extension/js/common/platform/store/contact-store.ts +++ b/extension/js/common/platform/store/contact-store.ts @@ -38,18 +38,10 @@ type Pubkey = { expiresOn: number; }; -// contact entity prior to version 4 -type ContactV3 = { - email: string; - name: string | null; - pubkey: Key | string | null; - has_pgp: 0 | 1; +type PubkeyAttributes = { fingerprint: string | null; - pending_lookup: number; - last_use: number | null; - pubkey_last_sig: number | null; - pubkey_last_check: number | null; expiresOn: number | null; + pubkey_last_sig: number | null }; export type ContactPreview = { @@ -94,7 +86,15 @@ export class ContactStore extends AbstractStore { emails.createIndex('index_fingerprints', 'fingerprints', { multiEntry: true }); // fingerprints of all connected pubkeys pubkeys.createIndex('index_longids', 'longids', { multiEntry: true }); // longids of all public key packets in armored pubkey } - ContactStore.moveContactsToEmailsAndPubkeys(openDbReq); + if (db.objectStoreNames.contains('contacts')) { + const countRequest = openDbReq.transaction!.objectStore('contacts').count(); + countRequest.onsuccess = () => { + if (countRequest.result === 0) { + console.info('contacts store is now empty, deleting it...'); + db.deleteObjectStore('contacts'); + } + } + } }; openDbReq.onsuccess = () => resolve(openDbReq.result as IDBDatabase); openDbReq.onblocked = () => reject(ContactStore.errCategorize(openDbReq.error)); @@ -177,7 +177,11 @@ export class ContactStore extends AbstractStore { Catch.report(`Wrongly updating prv ${update.pubkey.id} as contact - converting to pubkey`); update.pubkey = await KeyUtil.asPublicKey(update.pubkey); } - await ContactStore.updateTx(db.transaction(['emails', 'pubkeys'], 'readwrite'), email, update); + await new Promise((resolve, reject) => { + const tx = db.transaction(['emails', 'pubkeys'], 'readwrite'); + ContactStore.setTxHandlers(tx, resolve, reject); + ContactStore.updateTx(tx, email, update); + }); } public static get = async (db: undefined | IDBDatabase, emailOrLongid: string[]): Promise<(Contact | undefined)[]> => { @@ -209,6 +213,68 @@ export class ContactStore extends AbstractStore { return (await ContactStore.extractPubkeys(db, fingerprints)).map(pubkey => pubkey?.armoredKey).filter(Boolean); } + public static updateTx = (tx: IDBTransaction, email: string, update: ContactUpdate) => { + if (update.pubkey && !update.pubkey_last_check) { // todo: test + const req = tx.objectStore('pubkeys').get(update.pubkey.id); + req.onsuccess = () => ContactStore.updateTxPhase2(tx, email, update, req.result as Pubkey); + } else { + ContactStore.updateTxPhase2(tx, email, update, undefined); // todo: test + } + } + + public static setTxHandlers(tx: IDBTransaction, resolve: (value: unknown) => void, reject: (reason?: any) => void) { + tx.oncomplete = () => resolve(undefined); + this.setReqOnError(tx, reject); + } + + private static updateTxPhase2 = (tx: IDBTransaction, email: string, update: ContactUpdate, existingPubkey: Pubkey | undefined) => { + let pubkeyEntity: Pubkey | undefined; + if (update.pubkey) { + const keyAttrs = ContactStore.getKeyAttributes(update.pubkey); + // todo: will we benefit anything when not saving pubkey if it isn't modified? + pubkeyEntity = { + fingerprint: update.pubkey.id, + lastCheck: update.pubkey_last_check ?? existingPubkey?.lastCheck, + lastSig: keyAttrs.pubkey_last_sig, + expiresOn: keyAttrs.expiresOn, + longids: update.pubkey.allIds.map(id => OpenPGPKey.fingerprintToLongid(id)), + armoredKey: KeyUtil.armor(update.pubkey) + } as Pubkey; + } else if (update.pubkey_last_check) { + Catch.report(`Wrongly updating pubkey_last_check without specifying pubkey for ${email} - ignoring`); + } + const req = tx.objectStore('emails').get(email); + req.onsuccess = () => { + let emailEntity = req.result as Email; + if (!emailEntity) { + const validEmail = Str.parseEmail(email).email; + if (!validEmail) { + throw Error(`Cannot save contact because email is not valid: ${email}`); + } + emailEntity = { email, name: null, searchable: [], fingerprints: [], pendingLookup: 0, lastUse: null }; + } + if (pubkeyEntity) { + if (!emailEntity.fingerprints.includes(pubkeyEntity.fingerprint)) { + emailEntity.fingerprints.push(pubkeyEntity.fingerprint); + } + } + if (Object.keys(update).includes('name')) { + emailEntity.name = update.name ?? null; + } + if (Object.keys(update).includes('pending_lookup')) { + emailEntity.pendingLookup = update.pending_lookup ?? 0; + } + if (Object.keys(update).includes('last_use')) { + emailEntity.lastUse = update.last_use ?? null; + } + ContactStore.updateSearchable(emailEntity); + tx.objectStore('emails').put(emailEntity); + if (pubkeyEntity) { + tx.objectStore('pubkeys').put(pubkeyEntity); + } + } + } + private static extractPubkeys = async (db: IDBDatabase | undefined, fingerprints: string[]): Promise => { if (!db) { // relay op through background process return await BrowserMsg.send.bg.await.db({ f: 'extractPubkeys', args: [fingerprints] }) as Pubkey[]; @@ -282,77 +348,6 @@ export class ContactStore extends AbstractStore { return raw; } - private static getPubkeyTx = async (tx: IDBTransaction, id: string) => { - const existingPubkey: Pubkey = await new Promise((resolve, reject) => { - const req = tx.objectStore('pubkeys').get(id); - ContactStore.setReqPipe(req, - (pubkey: Pubkey) => { - resolve(pubkey); - }, - reject); - }); - return existingPubkey; - } - - private static updateTx = async (tx: IDBTransaction, email: string, update: ContactUpdate): Promise => { - let pubkeyEntity: Pubkey | undefined; - if (update.pubkey) { - const keyAttrs = ContactStore.getKeyAttributes(update.pubkey); - // todo: will we benefit anything when not saving pubkey if it isn't modified? - pubkeyEntity = { - fingerprint: update.pubkey.id, - lastCheck: update.pubkey_last_check ?? (await ContactStore.getPubkeyTx(tx, update.pubkey.id))?.lastCheck, - lastSig: keyAttrs.pubkey_last_sig, - expiresOn: keyAttrs.expiresOn, - longids: update.pubkey.allIds.map(id => OpenPGPKey.fingerprintToLongid(id)), - armoredKey: KeyUtil.armor(update.pubkey) - } as Pubkey; - } else if (update.pubkey_last_check) { - Catch.report(`Wrongly updating pubkey_last_check without specifying pubkey for ${email} - ignoring`); - } - await new Promise((resolve, reject) => { - tx.addEventListener('error', () => reject(ContactStore.errCategorize(tx.error))); - const req = tx.objectStore('emails').get(email); - ContactStore.setReqPipe(req, - (emailEntity: Email) => { - if (!emailEntity) { - const validEmail = Str.parseEmail(email).email; - if (!validEmail) { - reject(`Cannot save contact because email is not valid: ${email}`); - return; - } - emailEntity = { email, name: null, searchable: [], fingerprints: [], pendingLookup: 0, lastUse: null }; - } - if (pubkeyEntity) { - if (!emailEntity.fingerprints.includes(pubkeyEntity.fingerprint)) { - emailEntity.fingerprints.push(pubkeyEntity.fingerprint); - } - } - if (Object.keys(update).includes('name')) { - emailEntity.name = update.name ?? null; - } - if (Object.keys(update).includes('pending_lookup')) { - emailEntity.pendingLookup = update.pending_lookup ?? 0; - } - if (Object.keys(update).includes('last_use')) { - emailEntity.lastUse = update.last_use ?? null; - } - ContactStore.updateSearchable(emailEntity); - const updReq1 = tx.objectStore('emails').put(emailEntity); - ContactStore.setReqPipe(updReq1, () => { - if (pubkeyEntity) { - const updReq2 = tx.objectStore('pubkeys').put(pubkeyEntity); - ContactStore.setReqPipe(updReq2, () => { - resolve(undefined); - }, reject); - } else { - resolve(undefined); - } - }, reject); - }, reject); - }); - } - private static normalizeString = (str: string) => { return str.normalize('NFKD').replace(/[\u0300-\u036F]/g, '').toLowerCase(); } @@ -445,7 +440,7 @@ export class ContactStore extends AbstractStore { }); } - private static getKeyAttributes = (key: Key | undefined): { fingerprint: string | null, expiresOn: number | null, pubkey_last_sig: number | null } => { + private static getKeyAttributes = (key: Key | undefined): PubkeyAttributes => { return { fingerprint: key?.id ?? null, expiresOn: Number(key?.expiration) || null, pubkey_last_sig: Number(key?.lastModified) || null }; } @@ -489,38 +484,4 @@ export class ContactStore extends AbstractStore { } return contacts; } - - private static moveContactsToEmailsAndPubkeys = (openDbReq: IDBOpenDBRequest) => { - const db = openDbReq.result; - if (db.objectStoreNames.contains('contacts')) { - console.info('migrating contacts of ContactStore to emails and pubkeys...'); - const tx = openDbReq.transaction!; - const contacts = tx.objectStore('contacts'); - const search = contacts.openCursor(); - search.onsuccess = async () => { - const cursor = search.result as IDBCursorWithValue | undefined; - if (!cursor) { - console.log('contacts store is now empty, deleting it...'); - db.deleteObjectStore('contacts'); - } else { - const entry = cursor.value as ContactV3; // tslint:disable-line:no-unsafe-any - const armoredPubkey = (entry.pubkey && typeof entry.pubkey === 'object') - ? KeyUtil.armor(entry.pubkey as Key) : entry.pubkey as string; - // parse again to re-calculate expiration-related fields etc. - const pubkey = armoredPubkey ? await KeyUtil.parse(armoredPubkey) : undefined; - console.log(`Migrating ${entry.email} ...`); - await ContactStore.updateTx(tx, entry.email, { - email: entry.email, - name: entry.name, - pubkey, - pending_lookup: entry.pending_lookup, - last_use: entry.last_use, - pubkey_last_check: entry.pubkey_last_check - }); - cursor.continue(); - } - }; - console.info('done migrating'); - } - } } From cd07570f61fa84b4e2334784913165fe0b1e51ca Mon Sep 17 00:00:00 2001 From: Roman Shevchenko Date: Fri, 12 Mar 2021 06:46:03 -0500 Subject: [PATCH 21/53] tslint fix --- extension/js/background_page/migrations.ts | 2 +- extension/js/common/platform/store/contact-store.ts | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/extension/js/background_page/migrations.ts b/extension/js/background_page/migrations.ts index 999a6374b2d..c6d0131ec03 100644 --- a/extension/js/background_page/migrations.ts +++ b/extension/js/background_page/migrations.ts @@ -92,4 +92,4 @@ export const moveContactsToEmailsAndPubkeys = async (db: IDBDatabase): Promise resolve(openDbReq.result as IDBDatabase); @@ -272,7 +272,7 @@ export class ContactStore extends AbstractStore { if (pubkeyEntity) { tx.objectStore('pubkeys').put(pubkeyEntity); } - } + }; } private static extractPubkeys = async (db: IDBDatabase | undefined, fingerprints: string[]): Promise => { From b0e885620c8de8176232e645dcc636497875ca0a Mon Sep 17 00:00:00 2001 From: Roman Shevchenko Date: Fri, 12 Mar 2021 07:07:13 -0500 Subject: [PATCH 22/53] refactor fix --- extension/js/common/platform/store/abstract-store.ts | 5 +++++ extension/js/common/platform/store/contact-store.ts | 5 ----- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/extension/js/common/platform/store/abstract-store.ts b/extension/js/common/platform/store/abstract-store.ts index 37572371708..ffe08ff9260 100644 --- a/extension/js/common/platform/store/abstract-store.ts +++ b/extension/js/common/platform/store/abstract-store.ts @@ -65,6 +65,11 @@ export abstract class AbstractStore { req.onerror = () => reject(AbstractStore.errCategorize(req.error || new Error('Unknown db error'))); } + public static setTxHandlers = (tx: IDBTransaction, resolve: (value: unknown) => void, reject: (reason?: any) => void) => { + tx.oncomplete = () => resolve(undefined); + AbstractStore.setReqOnError(tx, reject); + } + protected static buildSingleAccountStoreFromRawResults = (scope: string, storageObj: RawStore): AcctStoreDict => { const accountStore: AcctStoreDict = {}; for (const k of Object.keys(storageObj)) { diff --git a/extension/js/common/platform/store/contact-store.ts b/extension/js/common/platform/store/contact-store.ts index 526a47cd241..c1a2c236ece 100644 --- a/extension/js/common/platform/store/contact-store.ts +++ b/extension/js/common/platform/store/contact-store.ts @@ -222,11 +222,6 @@ export class ContactStore extends AbstractStore { } } - public static setTxHandlers(tx: IDBTransaction, resolve: (value: unknown) => void, reject: (reason?: any) => void) { - tx.oncomplete = () => resolve(undefined); - this.setReqOnError(tx, reject); - } - private static updateTxPhase2 = (tx: IDBTransaction, email: string, update: ContactUpdate, existingPubkey: Pubkey | undefined) => { let pubkeyEntity: Pubkey | undefined; if (update.pubkey) { From 635946f0f41b0aa3775240203c418c860c8950af Mon Sep 17 00:00:00 2001 From: Roman Shevchenko Date: Fri, 12 Mar 2021 08:49:23 -0500 Subject: [PATCH 23/53] fixed 'searchable' to contain unique entries --- .../js/common/platform/store/contact-store.ts | 11 +++-------- .../browser-unit-tests/unit-ContactStore.js | 19 ++++++++++++++++++- 2 files changed, 21 insertions(+), 9 deletions(-) diff --git a/extension/js/common/platform/store/contact-store.ts b/extension/js/common/platform/store/contact-store.ts index c1a2c236ece..13128c6b23d 100644 --- a/extension/js/common/platform/store/contact-store.ts +++ b/extension/js/common/platform/store/contact-store.ts @@ -365,14 +365,9 @@ export class ContactStore extends AbstractStore { private static updateSearchable = (emailEntity: Email) => { const email = emailEntity.email.toLowerCase(); const name = emailEntity.name ? emailEntity.name.toLowerCase() : ''; - const index: string[] = []; - for (const part of [...email.split(/[^a-z0-9]/), ...name.split(/[^a-z0-9]/)].filter(p => !!p)) { - const normalized = ContactStore.normalizeString(part); - if (!index.includes(normalized)) { - index.push(ContactStore.dbIndex(emailEntity.fingerprints.length > 0, normalized)); - } - } - emailEntity.searchable = index; + emailEntity.searchable = [...email.split(/[^a-z0-9]/), ...name.split(/[^a-z0-9]/)].filter(p => !!p) + .map(ContactStore.normalizeString).filter((value, index, self) => self.indexOf(value) === index) + .map(normalized => ContactStore.dbIndex(emailEntity.fingerprints.length > 0, normalized)); } private static setReqPipe(req: IDBRequest, pipe: (value?: T) => void, reject: (reason?: any) => void) { diff --git a/test/source/tests/browser-unit-tests/unit-ContactStore.js b/test/source/tests/browser-unit-tests/unit-ContactStore.js index b4e206b1569..c6f9a820713 100644 --- a/test/source/tests/browser-unit-tests/unit-ContactStore.js +++ b/test/source/tests/browser-unit-tests/unit-ContactStore.js @@ -24,7 +24,6 @@ BROWSER_UNIT_TEST_NAME(`ContactStore is able to search by partial email address`); (async () => { - const contactABBDEF = await ContactStore.obj({ email: 'abbdef@test.com', pubkey: `-----BEGIN PGP PUBLIC KEY BLOCK----- @@ -157,3 +156,21 @@ WmiyOmaRmLP+ } return 'pass'; })(); + +BROWSER_UNIT_TEST_NAME(`ContactStore doesn't store duplicates in searchable`); +(async () => { + const db = await ContactStore.dbOpen(); + const contact = await ContactStore.obj({ + email: 'this.word.this.word@this.word.this.word', name: 'This Word THIS WORD this word' + }); + await ContactStore.save(db, contact); + // extract the entity from the database to see the actual field + const entity = await new Promise((resolve, reject) => { + const req = db.transaction(['emails'], 'readonly').objectStore('emails').get(contact.email); + ContactStore.setReqPipe(req, () => resolve(req.result), reject); + }); + if (entity?.searchable.length !== 2) { + throw Error(`Expected 2 entries in 'searchable' but got "${entity?.searchable}"`); + } + return 'pass'; +})(); From 2ebcaae79534e3aa6eb584e4fe2cba48b86a4202 Mon Sep 17 00:00:00 2001 From: Roman Shevchenko Date: Thu, 18 Mar 2021 04:25:45 -0400 Subject: [PATCH 24/53] niceties --- .../elements/compose-modules/compose-storage-module.ts | 8 +------- extension/chrome/settings/modules/contacts.ts | 3 +-- 2 files changed, 2 insertions(+), 9 deletions(-) diff --git a/extension/chrome/elements/compose-modules/compose-storage-module.ts b/extension/chrome/elements/compose-modules/compose-storage-module.ts index e036a1c3485..296d3249398 100644 --- a/extension/chrome/elements/compose-modules/compose-storage-module.ts +++ b/extension/chrome/elements/compose-modules/compose-storage-module.ts @@ -110,13 +110,7 @@ export class ComposeStorageModule extends ViewModule { lookupResult.pubkey = null; // tslint:disable-line:no-null-keyword } } - const ksContact = await ContactStore.obj({ - email, - name, - pubkey: lookupResult.pubkey, - // client: // todo: decide whether we need this field - lastCheck: Date.now(), - }); + const ksContact = await ContactStore.obj({ email, name, pubkey: lookupResult.pubkey, lastCheck: Date.now() }); if (ksContact.pubkey) { this.ksLookupsByEmail[email] = ksContact.pubkey; } diff --git a/extension/chrome/settings/modules/contacts.ts b/extension/chrome/settings/modules/contacts.ts index 8db926dc7e8..3e75229c0f4 100644 --- a/extension/chrome/settings/modules/contacts.ts +++ b/extension/chrome/settings/modules/contacts.ts @@ -117,8 +117,7 @@ View.run(class ContactsView extends View { private actionRenderViewPublicKeyHandler = async (viewPubkeyButton: HTMLElement) => { const [contact] = await ContactStore.get(undefined, [$(viewPubkeyButton).closest('tr').attr('email')!]); // defined above $('.hide_when_rendering_subpage').css('display', 'none'); - Xss.sanitizeRender('h1', `${this.backBtn}${this.space}${contact!.email}`); // should exist - from list of contacts - Xss.sanitizeAppend('h1', '      '); // todo: do we need this line? + Xss.sanitizeRender('h1', `${this.backBtn}${this.space}${contact!.email}      `); // should exist - from list of contacts $('#view_contact .key_dump').text(KeyUtil.armor(contact!.pubkey!)); // should exist - from list of contacts && should have pgp - filtered $('#view_contact #container-pubkey-details').text([ `Type: ${contact?.pubkey?.type}`, From a91184c7396524162ed2f69c83f2156813c2db0c Mon Sep 17 00:00:00 2001 From: Roman Shevchenko Date: Thu, 18 Mar 2021 04:28:07 -0400 Subject: [PATCH 25/53] smime-key is either public or private --- extension/js/common/core/crypto/smime/smime-key.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/extension/js/common/core/crypto/smime/smime-key.ts b/extension/js/common/core/crypto/smime/smime-key.ts index 7b8f99fcb00..3a070af8b52 100644 --- a/extension/js/common/core/crypto/smime/smime-key.ts +++ b/extension/js/common/core/crypto/smime/smime-key.ts @@ -29,7 +29,7 @@ export class SmimeKey { expiration: SmimeKey.dateToNumber(certificate.validity.notAfter), fullyDecrypted: false, fullyEncrypted: false, - isPublic: !!certificate.publicKey, + isPublic: certificate.publicKey && !certificate.privateKey, isPrivate: !!certificate.privateKey, } as Key; (key as unknown as { rawArmored: string }).rawArmored = text; From 0611e8262f925036dbba5f7b2f6e1f74407c8f1c Mon Sep 17 00:00:00 2001 From: Roman Shevchenko Date: Thu, 18 Mar 2021 06:59:10 -0400 Subject: [PATCH 26/53] migrating in batches of 50 records --- extension/js/background_page/migrations.ts | 29 +++++++++++----------- 1 file changed, 15 insertions(+), 14 deletions(-) diff --git a/extension/js/background_page/migrations.ts b/extension/js/background_page/migrations.ts index c6d0131ec03..3a0a06b7890 100644 --- a/extension/js/background_page/migrations.ts +++ b/extension/js/background_page/migrations.ts @@ -42,29 +42,33 @@ export const migrateGlobal = async () => { } }; -export const moveContactsToEmailsAndPubkeys = async (db: IDBDatabase): Promise => { +export const moveContactsToEmailsAndPubkeys = async (db: IDBDatabase): Promise => { if (!db.objectStoreNames.contains('contacts')) { - return 0; + return; + } + console.info('migrating contacts of ContactStore to emails and pubkeys...'); + const batchSize = 50; + while (await moveContactsBatchToEmailsAndPubkeys(db, batchSize)) { + console.info('proceeding to the next batch'); } + console.info('migrating contacts of ContactStore is complete'); +} + +const moveContactsBatchToEmailsAndPubkeys = async (db: IDBDatabase, count?: number | undefined): Promise => { const entries: ContactV3[] = []; + const tx = db.transaction(['contacts'], 'readonly'); await new Promise((resolve, reject) => { - const tx = db.transaction(['contacts'], 'readonly'); ContactStore.setTxHandlers(tx, resolve, reject); - console.info('migrating contacts of ContactStore to emails and pubkeys...'); const contacts = tx.objectStore('contacts'); - const search = contacts.openCursor(); // todo: simplify with getAll() + const search = contacts.getAll(undefined, count); search.onsuccess = () => { - const cursor = search.result as IDBCursorWithValue | undefined; - if (cursor) { - entries.push(cursor.value as ContactV3); // tslint:disable-line:no-unsafe-any - cursor.continue(); - } + entries.push(...search.result); }; }); - console.info(`${entries.length} entries found.`); if (!entries.length) { return 0; } + console.info(`Processing a batch of ${entries.length}.`); // transform const updates = await Promise.all(entries.map(async (entry) => { const armoredPubkey = (entry.pubkey && typeof entry.pubkey === 'object') @@ -80,8 +84,6 @@ export const moveContactsToEmailsAndPubkeys = async (db: IDBDatabase): Promise { const tx = db.transaction(['contacts', 'emails', 'pubkeys'], 'readwrite'); ContactStore.setTxHandlers(tx, resolve, reject); @@ -90,6 +92,5 @@ export const moveContactsToEmailsAndPubkeys = async (db: IDBDatabase): Promise Date: Thu, 18 Mar 2021 08:22:40 -0400 Subject: [PATCH 27/53] further decrease the size of 'search' index --- .../js/common/platform/store/contact-store.ts | 7 +++++-- .../browser-unit-tests/unit-ContactStore.js | 19 +++++++++++++++++++ 2 files changed, 24 insertions(+), 2 deletions(-) diff --git a/extension/js/common/platform/store/contact-store.ts b/extension/js/common/platform/store/contact-store.ts index 13128c6b23d..d01f2b5b6bf 100644 --- a/extension/js/common/platform/store/contact-store.ts +++ b/extension/js/common/platform/store/contact-store.ts @@ -365,8 +365,11 @@ export class ContactStore extends AbstractStore { private static updateSearchable = (emailEntity: Email) => { const email = emailEntity.email.toLowerCase(); const name = emailEntity.name ? emailEntity.name.toLowerCase() : ''; - emailEntity.searchable = [...email.split(/[^a-z0-9]/), ...name.split(/[^a-z0-9]/)].filter(p => !!p) - .map(ContactStore.normalizeString).filter((value, index, self) => self.indexOf(value) === index) + // we only need the longest word if it starts with a shorter one, + // e.g. we don't need "flowcrypt" if we have "flowcryptcompatibility" + const sortedNormalized = [...email.split(/[^a-z0-9]/), ...name.split(/[^a-z0-9]/)].filter(p => !!p) + .map(ContactStore.normalizeString).sort((a, b) => b.length - a.length); + emailEntity.searchable = sortedNormalized.filter((value, index, self) => !self.slice(0, index).find((el) => el.startsWith(value))) .map(normalized => ContactStore.dbIndex(emailEntity.fingerprints.length > 0, normalized)); } diff --git a/test/source/tests/browser-unit-tests/unit-ContactStore.js b/test/source/tests/browser-unit-tests/unit-ContactStore.js index c6f9a820713..7f068057ffd 100644 --- a/test/source/tests/browser-unit-tests/unit-ContactStore.js +++ b/test/source/tests/browser-unit-tests/unit-ContactStore.js @@ -174,3 +174,22 @@ BROWSER_UNIT_TEST_NAME(`ContactStore doesn't store duplicates in searchable`); } return 'pass'; })(); + +BROWSER_UNIT_TEST_NAME(`ContactStore doesn't store smaller words in searchable when there is a bigger one that starts with it`); +(async () => { + const db = await ContactStore.dbOpen(); + const contact = await ContactStore.obj({ + email: 'a@big.one', name: 'Bigger' + }); + await ContactStore.save(db, contact); + // extract the entity from the database to see the actual field + const entity = await new Promise((resolve, reject) => { + const req = db.transaction(['emails'], 'readonly').objectStore('emails').get(contact.email); + ContactStore.setReqPipe(req, () => resolve(req.result), reject); + }); + if (entity?.searchable.length !== 3 || !entity.searchable.includes('f:a') + || !entity.searchable.includes('f:bigger') || !entity.searchable.includes('f:one')) { + throw Error(`Expected "a bigger one" entries in 'searchable' but got "${entity?.searchable}"`); + } + return 'pass'; +})(); From 8e7d9952efe4a824454c5e499036f6c9876983eb Mon Sep 17 00:00:00 2001 From: Roman Shevchenko Date: Thu, 18 Mar 2021 08:44:57 -0400 Subject: [PATCH 28/53] tslint fix --- extension/js/background_page/migrations.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/extension/js/background_page/migrations.ts b/extension/js/background_page/migrations.ts index 3a0a06b7890..c77246793e0 100644 --- a/extension/js/background_page/migrations.ts +++ b/extension/js/background_page/migrations.ts @@ -52,7 +52,7 @@ export const moveContactsToEmailsAndPubkeys = async (db: IDBDatabase): Promise => { const entries: ContactV3[] = []; From 128b330ed4d85f28ad104d04ef4b99fc58336bca Mon Sep 17 00:00:00 2001 From: Roman Shevchenko Date: Thu, 18 Mar 2021 08:45:50 -0400 Subject: [PATCH 29/53] dropped ContactStore.recreateDates() --- .../js/common/platform/store/contact-store.ts | 26 +++---------------- 1 file changed, 3 insertions(+), 23 deletions(-) diff --git a/extension/js/common/platform/store/contact-store.ts b/extension/js/common/platform/store/contact-store.ts index d01f2b5b6bf..882b71bf9c6 100644 --- a/extension/js/common/platform/store/contact-store.ts +++ b/extension/js/common/platform/store/contact-store.ts @@ -186,21 +186,21 @@ export class ContactStore extends AbstractStore { public static get = async (db: undefined | IDBDatabase, emailOrLongid: string[]): Promise<(Contact | undefined)[]> => { if (!db) { // relay op through background process - return ContactStore.recreateDates(await BrowserMsg.send.bg.await.db({ f: 'get', args: [emailOrLongid] }) as (Contact | undefined)[]); + return await BrowserMsg.send.bg.await.db({ f: 'get', args: [emailOrLongid] }) as (Contact | undefined)[]; } if (emailOrLongid.length === 1) { const contact = await ContactStore.dbContactInternalGetOne(db, emailOrLongid[0]); if (!contact) { return [contact]; } - return ContactStore.recreateDates([contact]); + return [contact]; } else { const results: (Contact | undefined)[] = []; for (const singleEmailOrLongid of emailOrLongid) { const [contact] = await ContactStore.get(db, [singleEmailOrLongid]); results.push(contact); } - return ContactStore.recreateDates(results); + return results; } } @@ -457,24 +457,4 @@ export class ContactStore extends AbstractStore { private static toContactPreview = (result: Email): ContactPreview => { return { email: result.email, name: result.name, has_pgp: result.fingerprints.length > 0 ? 1 : 0, last_use: result.lastUse }; } - - // todo: migration only - private static recreateDates = (contacts: (Contact | undefined)[]) => { - for (const contact of contacts) { - if (contact) { - // string dates were created by JSON serializing Date objects - // convert any previously saved string-dates into numbers - if (typeof contact?.pubkey?.created === 'string') { - contact.pubkey.created = new Date(contact.pubkey.created).getTime(); - } - if (typeof contact?.pubkey?.expiration === 'string') { - contact.pubkey.expiration = new Date(contact.pubkey.expiration).getTime(); - } - if (typeof contact?.pubkey?.lastModified === 'string') { - contact.pubkey.lastModified = new Date(contact.pubkey.lastModified).getTime(); - } - } - } - return contacts; - } } From 0b26e0d90111509ab56b780bc17fc07e4364da2a Mon Sep 17 00:00:00 2001 From: Roman Shevchenko Date: Fri, 19 Mar 2021 07:17:49 -0400 Subject: [PATCH 30/53] inject testConstants into browser unit tests --- .../mock/attester/attester-endpoints.ts | 4 +- .../mock/key-manager/key-manager-endpoints.ts | 4 +- test/source/mock/wkd/wkd-endpoints.ts | 4 +- test/source/tests/decrypt.ts | 8 +- test/source/tests/settings.ts | 10 +- test/source/tests/tooling/consts.ts | 167 ++++++++++++++---- test/source/tests/unit-browser.ts | 3 + test/source/tests/unit-node.ts | 68 +------ 8 files changed, 161 insertions(+), 107 deletions(-) diff --git a/test/source/mock/attester/attester-endpoints.ts b/test/source/mock/attester/attester-endpoints.ts index 4151459914a..181f7b6e2d0 100644 --- a/test/source/mock/attester/attester-endpoints.ts +++ b/test/source/mock/attester/attester-endpoints.ts @@ -8,7 +8,7 @@ import { oauth } from '../lib/oauth'; import { expect } from 'chai'; import { GoogleData } from '../google/google-data'; import { Buf } from '../../core/buf'; -import { pubkey2864E326A5BE488A } from '../../tests/tooling/consts'; +import { testConstants } from '../../tests/tooling/consts'; // tslint:disable:no-blank-lines-func @@ -62,7 +62,7 @@ export const mockAttesterEndpoints: HandlersDefinition = { return getDC26454AFB71D18EABBAD73D1C7E6D3C5563A941(); } if (['sams50sams50sept@gmail.com', 'president@forged.com', '2864E326A5BE488A'.toLowerCase()].includes(emailOrLongid)) { - return pubkey2864E326A5BE488A; + return testConstants.pubkey2864E326A5BE488A; } if (emailOrLongid.startsWith('martin@p')) { return mpVerificationKey; diff --git a/test/source/mock/key-manager/key-manager-endpoints.ts b/test/source/mock/key-manager/key-manager-endpoints.ts index de95d25818b..c68e4eb34fd 100644 --- a/test/source/mock/key-manager/key-manager-endpoints.ts +++ b/test/source/mock/key-manager/key-manager-endpoints.ts @@ -7,7 +7,7 @@ import { oauth } from '../lib/oauth'; import { Dict } from '../../core/common'; import { expect } from 'chai'; import { KeyUtil } from '../../core/crypto/key'; -import { wkdAtgooglemockflowcryptlocalcom8001Private } from '../../tests/tooling/consts'; +import { testConstants } from '../../tests/tooling/consts'; // tslint:disable:max-line-length /* eslint-disable max-len */ @@ -195,7 +195,7 @@ export const mockKeyManagerEndpoints: HandlersDefinition = { const acctEmail = oauth.checkAuthorizationHeaderWithIdToken(req.headers.authorization); if (isGet(req)) { if (acctEmail === 'wkd@google.mock.flowcryptlocal.com:8001') { - return { privateKeys: [{ decryptedPrivateKey: wkdAtgooglemockflowcryptlocalcom8001Private }] }; + return { privateKeys: [{ decryptedPrivateKey: testConstants.wkdAtgooglemockflowcryptlocalcom8001Private }] }; } if (acctEmail === 'get.key@key-manager-autogen.flowcrypt.com') { return { privateKeys: [{ decryptedPrivateKey: existingPrv }] }; diff --git a/test/source/mock/wkd/wkd-endpoints.ts b/test/source/mock/wkd/wkd-endpoints.ts index 5a264d20dbc..484027ea67a 100644 --- a/test/source/mock/wkd/wkd-endpoints.ts +++ b/test/source/mock/wkd/wkd-endpoints.ts @@ -2,7 +2,7 @@ import { KeyUtil } from '../../core/crypto/key.js'; import { PgpArmor } from '../../core/crypto/pgp/pgp-armor.js'; -import { wkdAtgooglemockflowcryptlocalcom8001Private } from '../../tests/tooling/consts.js'; +import { testConstants } from '../../tests/tooling/consts.js'; import { HandlersDefinition } from '../all-apis-mock'; const alice = `-----BEGIN PGP PUBLIC KEY BLOCK----- @@ -194,7 +194,7 @@ ctnWuBzRDeI0n6XDaPv5TpKpS7uqy/fTlJLGE9vZTFUKzeGkQFomBoXNVWs= export const mockWkdEndpoints: HandlersDefinition = { '/.well-known/openpgpkey/hu/st5or5guodbnsiqbzp6i34xw59h1sgmw?l=wkd': async () => { // direct for wkd@google.mock.flowcryptlocal.com:8001 - const pub = await KeyUtil.asPublicKey(await KeyUtil.parse(wkdAtgooglemockflowcryptlocalcom8001Private)); + const pub = await KeyUtil.asPublicKey(await KeyUtil.parse(testConstants.wkdAtgooglemockflowcryptlocalcom8001Private)); return Buffer.from((await PgpArmor.dearmor(KeyUtil.armor(pub))).data); }, '/.well-known/openpgpkey/hu/ihyath4noz8dsckzjbuyqnh4kbup6h4i?l=john.doe': async () => { diff --git a/test/source/tests/decrypt.ts b/test/source/tests/decrypt.ts index bd0317194f5..efe10ce22d5 100644 --- a/test/source/tests/decrypt.ts +++ b/test/source/tests/decrypt.ts @@ -3,7 +3,7 @@ import * as ava from 'ava'; import { Config, TestVariant, Util } from './../util'; -import { protonCompatPub, expiredPub, unusableKey } from './tooling/consts'; +import { testConstants } from './tooling/consts'; import { BrowserRecipe } from './tooling/browser-recipe'; import { InboxPageRecipe } from './page-recipe/inbox-page-recipe'; import { SettingsPageRecipe } from './page-recipe/settings-page-recipe'; @@ -348,7 +348,7 @@ export const defineDecryptTests = (testVariant: TestVariant, testWithBrowser: Te const textParams = `?frameId=none&message=&msgId=16a9c109bc51687d&` + `senderEmail=mismatch%40mail.com&isOutgoing=___cu_false___&signature=___cu_true___&acctEmail=flowcrypt.compatibility%40gmail.com`; await BrowserRecipe.pgpBlockVerifyDecryptedContent(t, browser, { params: textParams, content: ["1234"], signature: ["Missing pubkey"] }); - const pubFrameUrl = `chrome/elements/pgp_pubkey.htm?frameId=none&armoredPubkey=${encodeURIComponent(protonCompatPub)}&acctEmail=flowcrypt.compatibility%40gmail.com&parentTabId=0`; + const pubFrameUrl = `chrome/elements/pgp_pubkey.htm?frameId=none&armoredPubkey=${encodeURIComponent(testConstants.protonCompatPub)}&acctEmail=flowcrypt.compatibility%40gmail.com&parentTabId=0`; const pubFrame = await browser.newPage(t, pubFrameUrl); await pubFrame.waitAndClick('@action-add-contact'); await Util.sleep(1); @@ -397,7 +397,7 @@ export const defineDecryptTests = (testVariant: TestVariant, testWithBrowser: Te })); ava.default('decrypt - load key - expired key', testWithBrowser('compatibility', async (t, browser) => { - const pubFrameUrl = `chrome/elements/pgp_pubkey.htm?frameId=none&armoredPubkey=${encodeURIComponent(expiredPub)}&acctEmail=flowcrypt.compatibility%40gmail.com&parentTabId=0`; + const pubFrameUrl = `chrome/elements/pgp_pubkey.htm?frameId=none&armoredPubkey=${encodeURIComponent(testConstants.expiredPub)}&acctEmail=flowcrypt.compatibility%40gmail.com&parentTabId=0`; const pubFrame = await browser.newPage(t, pubFrameUrl); await pubFrame.waitAll('@action-add-contact'); expect((await pubFrame.read('@action-add-contact')).toLowerCase()).to.include('expired'); @@ -407,7 +407,7 @@ export const defineDecryptTests = (testVariant: TestVariant, testWithBrowser: Te })); ava.default('decrypt - load key - unusable key', testWithBrowser('compatibility', async (t, browser) => { - const pubFrameUrl = `chrome/elements/pgp_pubkey.htm?frameId=none&armoredPubkey=${encodeURIComponent(unusableKey)}&acctEmail=flowcrypt.compatibility%40gmail.com&parentTabId=0`; + const pubFrameUrl = `chrome/elements/pgp_pubkey.htm?frameId=none&armoredPubkey=${encodeURIComponent(testConstants.unusableKey)}&acctEmail=flowcrypt.compatibility%40gmail.com&parentTabId=0`; const pubFrame = await browser.newPage(t, pubFrameUrl); await Util.sleep(1); await pubFrame.notPresent('@action-add-contact'); diff --git a/test/source/tests/settings.ts b/test/source/tests/settings.ts index 1edeb3fffa1..964896a8742 100644 --- a/test/source/tests/settings.ts +++ b/test/source/tests/settings.ts @@ -14,7 +14,7 @@ import { TestUrls } from './../browser/test-urls'; import { TestVariant } from './../util'; import { expect } from 'chai'; import { SetupPageRecipe } from './page-recipe/setup-page-recipe'; -import { testKeyMultiple1b383d0334e38b28, testKeyMultiple98acfa1eadab5b92, unprotectedPrvKey } from './tooling/consts'; +import { testConstants } from './tooling/consts'; import { PageRecipe } from './page-recipe/abstract-page-recipe'; import { OauthPageRecipe } from './page-recipe/oauth-page-recipe'; @@ -245,7 +245,7 @@ export let defineSettingsTests = (testVariant: TestVariant, testWithBrowser: Tes })); ava.default('settings - add unprotected key', testWithBrowser('ci.tests.gmail', async (t, browser) => { - await SettingsPageRecipe.addKeyTest(t, browser, 'ci.tests.gmail@flowcrypt.dev', unprotectedPrvKey, 'this is a new passphrase to protect previously unprotected key'); + await SettingsPageRecipe.addKeyTest(t, browser, 'ci.tests.gmail@flowcrypt.dev', testConstants.unprotectedPrvKey, 'this is a new passphrase to protect previously unprotected key'); })); ava.default('settings - error modal when page parameter invalid', testWithBrowser('ci.tests.gmail', async (t, browser) => { @@ -262,14 +262,14 @@ export let defineSettingsTests = (testVariant: TestVariant, testWithBrowser: Tes usedPgpBefore: false, key: { title: '?', - armored: testKeyMultiple1b383d0334e38b28, + armored: testConstants.testKeyMultiple1b383d0334e38b28, passphrase: '1234', longid: '1b383d0334e38b28', } }); await settingsPage1.close(); - await SettingsPageRecipe.addKeyTest(t, browser, acctEmail, testKeyMultiple98acfa1eadab5b92, '1234'); + await SettingsPageRecipe.addKeyTest(t, browser, acctEmail, testConstants.testKeyMultiple98acfa1eadab5b92, '1234'); const settingsPage = await browser.newPage(t, TestUrls.extensionSettings(acctEmail)); await SettingsPageRecipe.toggleScreen(settingsPage, 'additional'); @@ -278,7 +278,7 @@ export let defineSettingsTests = (testVariant: TestVariant, testWithBrowser: Tes await Util.sleep(1); await myKeyFrame.waitAll('@content-fingerprint'); await myKeyFrame.waitAndClick('@action-update-prv'); - await myKeyFrame.waitAndType('@input-prv-key', testKeyMultiple98acfa1eadab5b92); + await myKeyFrame.waitAndType('@input-prv-key', testConstants.testKeyMultiple98acfa1eadab5b92); await myKeyFrame.type('@input-passphrase', '1234'); await myKeyFrame.waitAndClick('@action-update-key'); await PageRecipe.waitForModalAndRespond(myKeyFrame, 'confirm', { contentToCheck: 'Public and private key updated locally', clickOn: 'cancel' }); diff --git a/test/source/tests/tooling/consts.ts b/test/source/tests/tooling/consts.ts index d8c25aaf510..dae046d5f3a 100644 --- a/test/source/tests/tooling/consts.ts +++ b/test/source/tests/tooling/consts.ts @@ -1,6 +1,7 @@ /* ©️ 2016 - present FlowCrypt a.s. Limitations apply. Contact human@flowcrypt.com */ -export const protonCompatPub = `-----BEGIN PGP PUBLIC KEY BLOCK----- +export const testConstants = { + protonCompatPub: `-----BEGIN PGP PUBLIC KEY BLOCK----- Version: OpenPGP.js v3.0.5 Comment: https://openpgpjs.org @@ -33,9 +34,8 @@ AdeK17uMFfR6I1Rid3Qftszqg4FNExTOPHFZIc8CiGgWCye8NKcVqeuVlXKw EexnUn1E1mOjFwiYOZavCLvJRtazGCreO0FkWtrrtoa+5F2fbKUIVNGg44fG 7aGdFze6mNyI/fMU =D34s ------END PGP PUBLIC KEY BLOCK-----`; - -export const expiredPub = `-----BEGIN PGP PUBLIC KEY BLOCK----- +-----END PGP PUBLIC KEY BLOCK-----`, + expiredPub: `-----BEGIN PGP PUBLIC KEY BLOCK----- mQGNBF04cLABDADGVUmV8RtjsCIrmg97eO9vmxfc6FeH1cIguCXoFpQxCSk0/Hv8 NA6njdo2EJeZdYaOi7QVJNkfdR5obhxVh5AI4+18ParS4A99grp0riYoJ7w/hFLk @@ -75,9 +75,8 @@ HJCcONA2HUjamUw9DPw3hvTu9HAo6gkjOT5HvLmBy7koJEw+GXXw1LhXUnYx+Ts1 Wl33ecOGuq3bsTUXNujVdtWJ5hDf8l9RaeWfow9Af0OhYgkl8DWQ63V8VRXgcZyX wLiixN34mx9HOoCOwcFxC4+X6VVwVWQ= =4FOH ------END PGP PUBLIC KEY BLOCK-----`; - -export const unusableKey = `-----BEGIN PGP PUBLIC KEY BLOCK----- +-----END PGP PUBLIC KEY BLOCK-----`, + unusableKey: `-----BEGIN PGP PUBLIC KEY BLOCK----- Version: FlowCrypt 6.9.1 Gmail Encryption Comment: Seamlessly send and receive encrypted email @@ -94,9 +93,8 @@ cIYj2v/xPfyV2DfZmRiAQY7sYXS3V/k4XjtLUyzctVZ7YL/oK5xg+MImc5D0 FDC8SBrX2SrNrngcRwoV+sFYX1PGpIVi0wxo8XKFsoSbzJpS+WLwZ+Kp5crh WO7S8BcGKp5KkYMRZgBkO+9SL8tYLooaH3Hj2QARAQAB =qgj+ ------END PGP PUBLIC KEY BLOCK-----`; - -export const pubkey2864E326A5BE488A = `-----BEGIN PGP PUBLIC KEY BLOCK----- +-----END PGP PUBLIC KEY BLOCK-----`, + pubkey2864E326A5BE488A: `-----BEGIN PGP PUBLIC KEY BLOCK----- Version: FlowCrypt Email Encryption 8.0.0 Comment: Seamlessly send and receive encrypted email @@ -129,10 +127,9 @@ LrkuXH7qUgRKmfe58pDQ7aSzWcGD+8Fuft8zWiJ1JL3gict05SO5VzCs3eXP 0NGYe3cl2jXuWmmE6z76bS7oG8ACZNMyQcxGNZjRZExdG+38ptm89ngCiZBy 4HNbNdMwsJ9st4j4/5bwTg== =1JiT ------END PGP PUBLIC KEY BLOCK-----`; - -// ci.tests.gmail@flowcrypt.dev, 95E7 83DC 0289 E2E2 -export const unprotectedPrvKey = `-----BEGIN PGP PRIVATE KEY BLOCK----- +-----END PGP PUBLIC KEY BLOCK-----`, + // ci.tests.gmail@flowcrypt.dev, 95E7 83DC 0289 E2E2 + unprotectedPrvKey: `-----BEGIN PGP PRIVATE KEY BLOCK----- xcLYBF/vGhABCACoP5aISHFQ0fiaWurcVkpNMF/LNCIp0SW1tp5TipKXljwiJY9kK12rYgJRAjJ1 AblBEGhVMBQb1SU794ZzVkdzWikbVgIFk/A4XizMOUi/wfX3CXw4i3gslAjosXeSJlRAICXI8vF8 @@ -181,9 +178,8 @@ BEEpFqFcWbUgy576qtVYYczJSgKyWDIXfDd9LJDcdSBRxVC/YaY+yQLWRULkkk2fVYLxxVb2G8QX dYU0CKNXUkv+q8TEUw== =LKvX -----END PGP PRIVATE KEY BLOCK----- -`; - -export const testKeyMultiple1b383d0334e38b28 = `-----BEGIN PGP PRIVATE KEY BLOCK----- +`, + testKeyMultiple1b383d0334e38b28: `-----BEGIN PGP PRIVATE KEY BLOCK----- xcMGBF/3BiEBCADKE48fkxzYA4YE0zEyg80xIcZA4himkiCwD406Sch5mKb0Gr2nnC6eemootSvn Iz/0IChFqC+c6vCJZvIZZdJgp2qHyzSMTZm4YCzLIjqoUFUt7gPuNrMkOAd/Ete/ret67h/uID7X @@ -232,9 +228,8 @@ WlCuRmaFHlzqmX1O+K04TMri8xNIjxCGP1rMIp0BDO31w0hotx32ZrxQEfnL3V0yCPwqTN4cOgLw KFtLrVgKR+rAw5XDIT4KpEfyU+MNin0VzNHnNkfK+epM58l1HL5BUchVCQZQCeWfcL9LkymD6wY8 4HbMOFvoojvglwNkenaN3QiLfdFBFpZLjZ0nCViPNamvC8OALzB4RFaHfd/9RL5QLE395TTJGzR1 =9S9p ------END PGP PRIVATE KEY BLOCK-----`; - -export const testKeyMultiple98acfa1eadab5b92 = `-----BEGIN PGP PRIVATE KEY BLOCK----- +-----END PGP PRIVATE KEY BLOCK-----`, + testKeyMultiple98acfa1eadab5b92: `-----BEGIN PGP PRIVATE KEY BLOCK----- xcMGBF/3BtsBCADBoXomWXjSi7+Fu7b1eFOIQBNCVCtglgIc9Ko9EzyeNb8UcC8PKFALqgVSCbUD ziRTywdCB2c8yTM+bnAd960qOuL0ABVyE2gn+TVuXpnPG4LYUBbnIkBHCbyCklbWywzC9ir/r/kS @@ -283,9 +278,8 @@ uVEBKbe/SsqxVg5v0/fFSfDsnks7hz4xk4u7M6GIq2qcuh+8qc8AXYsEEQ+E1X5eOwY0T59vjvjq Qhb2hMOaD7jK2qcP3mTFIBuYt0bPEZLhyMssa9ssK0dcY+JWs6LOeMPzCC1+bz7jCHiBCG5DBG8j h6VN43eiceJMKCGcndDbIkmHT/JiAtUj2VdKXAldLPIuECUzXtTGL8LdqgSYvxo0b0RiIv6pBNI+ =Iqrh ------END PGP PRIVATE KEY BLOCK-----`; - -export const rsa1024subkeyOnly = `-----BEGIN PGP PRIVATE KEY BLOCK----- +-----END PGP PRIVATE KEY BLOCK-----`, + rsa1024subkeyOnly: `-----BEGIN PGP PRIVATE KEY BLOCK----- xcLYBGANjIkBCADQU/PgpIj3pe0/A0HZS6r4DbwIb1Rr6sEGVE/juigYZJr8UYKGAA47rHWLiDW7 UTsE0Q9n/+rmSJfH6BR3aJ4+8npKN8+yDQDjGXw0TbVF8D63OsKX8oy8eLrO+WiPYUJisffXwGWM @@ -325,9 +319,8 @@ WaJ8nIk6VSs/KU2P5HOCmrAukSpQ60SjuOIaXGavsoWNT3q7WVfgKjw22Of2IuMqFaisIbrb8TcF q/r9MrIvi5i5Klky5xTsIHL9vlsY0xnJbqnMdxTMnM3glzFZdzRryeRQARUtw/xzT9pF96COx2oe FKL2jRv9kxmeREM= =831c ------END PGP PRIVATE KEY BLOCK-----`; - -export const rsa1024subkeyOnlyEncrypted = `-----BEGIN PGP PRIVATE KEY BLOCK----- +-----END PGP PRIVATE KEY BLOCK-----`, + rsa1024subkeyOnlyEncrypted: `-----BEGIN PGP PRIVATE KEY BLOCK----- xcMGBGAaiRgBCADaQgR5ypbhTtFygHGUb2Vj3Fz/R0yWcE43VuisFL5IuxJODjvswFUWGES5nsxL fLxkQ+TGunzqXz8eM04XjeImokrJraqBNHVNfcsGdEmB6hs5+YOJf7hRh6TvKWgyvlH5wjjY366F @@ -368,9 +361,8 @@ QxptAWCHHIlAK9Q4VRJcvqxiwImhg55jORNB/MpywzNpH9rC4ffG6roCYQvryqmpmTvmNRZjCtso jz0glYBgSVo77aVfLvdtSXWsmsO9qCN/wII7UccKFGcu/wxqA/eWqiVle1oGlvbetswL8LtGtLTv lstiX22h/LfRGVPpuuH0cdxEMmEtTn7LcV3dK5Ynt8eiQdUN8akg3sO2rNbXRA== =WgF7 ------END PGP PRIVATE KEY BLOCK-----`; - -export const wkdAtgooglemockflowcryptlocalcom8001Private = `-----BEGIN PGP PRIVATE KEY BLOCK----- +-----END PGP PRIVATE KEY BLOCK-----`, + wkdAtgooglemockflowcryptlocalcom8001Private: `-----BEGIN PGP PRIVATE KEY BLOCK----- xcLYBGBSOEsBCADjQgL1x/lAADXIEuOcArIO0H+CQEozvC5XiGMawKSRteHFywD425hgYPJVXQpp 0pJhNRY2njNFXdvdLz1bBo0KDk/ClCwkY8y5GFkImYe8yq/gfnAqv/o18IHhKMexw6MJojYOR6dz @@ -419,4 +411,119 @@ efiQy6n0m7dwgoD8iUaKMMahVI2CPm+ivLpSuxGxYKbN7YT/quLaum8ST+umpfO3caxZtxgqesSL ZcbMsfmsjQ2xERPJauMielisgbxBIIQs =idfp -----END PGP PRIVATE KEY BLOCK----- -`; +`, + flowcryptcompatibilityPublicKey7FDE685548AEA788: `-----BEGIN PGP PUBLIC KEY BLOCK----- +Version: FlowCrypt Email Encryption 7.9.9 +Comment: Seamlessly send and receive encrypted email + +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 +eg== +=CvEL +-----END PGP PUBLIC KEY BLOCK-----`, + flowcryptcompatibilityPublicKeyADAC279C95093207: `-----BEGIN PGP PUBLIC KEY BLOCK----- +Version: FlowCrypt Email Encryption 8.0.3 +Comment: Seamlessly send and receive encrypted email + +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 +Eg== +=r2et +-----END PGP PUBLIC KEY BLOCK-----` +}; \ No newline at end of file diff --git a/test/source/tests/unit-browser.ts b/test/source/tests/unit-browser.ts index 8787b35cedf..1b317c3acf7 100644 --- a/test/source/tests/unit-browser.ts +++ b/test/source/tests/unit-browser.ts @@ -7,6 +7,7 @@ import { TestWithBrowser } from '../test'; import { TestUrls } from '../browser/test-urls'; import { readdirSync, readFileSync } from 'fs'; import { Buf } from '../core/buf'; +import { testConstants } from './tooling/consts'; // tslint:disable:no-blank-lines-func /* eslint-disable max-len */ @@ -25,6 +26,8 @@ export let defineUnitBrowserTests = (testVariant: TestVariant, testWithBrowser: const hostPage = await browser.newPage(t, TestUrls.extension(`chrome/dev/ci_unit_test.htm`)); // update host page h1 await hostPage.target.evaluate((title) => { window.document.getElementsByTagName('h1')[0].textContent = title; }, title); + // inject testConstants + await hostPage.target.evaluate((object) => { (window as any).testConstants = object; }, testConstants); // prepare code to run const runThisCodeInBrowser = ` (async () => { diff --git a/test/source/tests/unit-node.ts b/test/source/tests/unit-node.ts index 9c9ae1ed97f..1f6fc708f4e 100644 --- a/test/source/tests/unit-node.ts +++ b/test/source/tests/unit-node.ts @@ -17,7 +17,7 @@ import { opgp } from '../core/crypto/pgp/openpgpjs-custom'; import { Attachment } from '../core/attachment.js'; import { ContactStore } from '../platform/store/contact-store.js'; import { GoogleData, GmailParser, GmailMsg } from '../mock/google/google-data'; -import { pubkey2864E326A5BE488A, rsa1024subkeyOnly, rsa1024subkeyOnlyEncrypted } from './tooling/consts'; +import { testConstants } from './tooling/consts'; import { PgpArmor } from '../core/crypto/pgp/pgp-armor'; import { equals } from '../buf.js'; @@ -558,7 +558,7 @@ vpQiyk4ceuTNkUZ/qmgiMpQLxXZnDDo= const enc = Buf.fromBase64Str(msg!.raw!).toUtfStr() .match(/\-\-\-\-\-BEGIN PGP SIGNED MESSAGE\-\-\-\-\-.*\-\-\-\-\-END PGP SIGNATURE\-\-\-\-\-/s)![0]; const encryptedData = Buf.fromUtfStr(enc); - const pubkey = await KeyUtil.parse(pubkey2864E326A5BE488A); + const pubkey = await KeyUtil.parse(testConstants.pubkey2864E326A5BE488A); await ContactStore.update(undefined, 'president@forged.com', { name: 'President', pubkey }); const decrypted = await MsgUtil.decryptMessage({ kisWithPp: [], encryptedData }); expect(decrypted.success).to.equal(true); @@ -623,63 +623,7 @@ vpQiyk4ceuTNkUZ/qmgiMpQLxXZnDDo= if ((await ContactStore.get(undefined, ['7FDE685548AEA788'])).length === 0) { const contact = await ContactStore.obj({ email: 'flowcrypt.compatibility@gmail.com', - pubkey: `-----BEGIN PGP PUBLIC KEY BLOCK----- -Version: FlowCrypt Email Encryption 7.9.9 -Comment: Seamlessly send and receive encrypted email - -xsFNBFn9GlIBEACiAU8yhymNq2lTxEG1OU0Xka9tUJ4A7wsDhHNnuhxzjVP8 -TDnpWb+kQ7pDgj4SEjXV5NAKLS9ISRsizxEvwo8HWulL0kmmlaESd5oNwc3+ -O4CxX3M9oNDaEHXmsphWyvBvTxdZW3d5I9dT4vjJ/p7AznY995bKhLCK7Kyo -J6Le+H7I8EXUfNBIkK7AUmhtzaH2UlhfBtJl3+VK7mAje6wgvf4bz+xsuZ/s -GlQAhQjrRax/zjTxSHdEjBJ+l2gIvCnkVe6i/BcjqLQUvHJsgzaKr+3Ri2Qs -AjVL3MtsNyUha2QImkWSP62J28AGSgk556vd9COP89dxcmhXlmeTM40A29Gc -xNzoBUDJxbX//gk1VVXhOA9/Bk6JAS4T+m3IftK3QJNC/y+SnqDV9xwAl4KM -8qBweUtFJ0X2C4DbC9EIP9F2Sy2jWbM9cuaTD21mjQdOU5cbWkJV40H2FgEH -cbKB9+GlMntg+tPUFlrIJPSKhDUBCym2zUbkWkz606q5W5vpSUOu+3GiV2XF -eGvv9afnOoo3rLjVW4UimcEDLrxiEdct+oDTI0XRNTLIUFtZskdEUe7pPoqW -4+TPz9GxUlfP9Csi1pylgHHclnE7s/B+Z+tjUOrhIayw6j0dYtl0zBhMe14J -w53fO/AKe4hthVYOH1oj6zSJKeEwJYe9F8ofsQARAQABzTtGbG93Q3J5cHQg -Q29tcGF0aWJpbGl0eSA8Zmxvd2NyeXB0LmNvbXBhdGliaWxpdHlAZ21haWwu -Y29tPsLBfwQQAQgAKQUCWf0aVwYLCQcIAwIJEH/eaFVIrqeIBBUICgIDFgIB -AhkBAhsDAh4BAAoJEH/eaFVIrqeIgzQP+gKonBwvVBAAO1KLVxs2ybRWC/Le -O7XAMnxoq0Gb4viahiCA2B/ZuOFrhbCtnKJ/GUIM60QwYMGYKW7EoQu+Gc3Y -lJ0T8rhhJ3vEmjb1cLeNa0Gca8/H9JsFWau/Qo4wox7l6folS6H9GzQ2LErJ -n8pKz3daxCI2gZF87IPJHV+iH9ij2teCts9eykwGNU06z2OglLToomvgGCq/ -u9Q0qDlsVnmBUT7hRB7hTKrqDeh/W7me7YQIjzpjSxZrKaMfPB15/5p1L3m0 -xrBFSSTiKS1NmgAxwr6fkjss2FRDuOu7k/BeEgeARYHSL2zvJQvPEzKG4DqC -yyGNliYs3jzCgj/3D9XGsAu2f1ZZft700Hyjm9AaA6FkYZQBmHn3KglrSmjk -cIcdHM9le5pz92FjJ42M7ROschsvxrW8kdt8BtF1SFZxPxYD6kB14l43bSju -jynFNQ4yXSVuqPmOtYa2VhngUitMb94+OF4XGpEVCYibWg8rHwTKoqz86OLK -0udau4ikTUzr7CQ9qP0eN5v1QA+xaUaAxx8Xy4/IrqOzliOjYzAz+k70EhrG -STvGWSZ02vdIbx/vHIVUbYLxMHKVH01zSCQTb4L09bdnxu0Tm/M6G/20brlX -ucLuM/8OCbHLviEiUaMCqU8gRe6sQgqnY6g3DvwZiPubJSCDSXNh7tOVzsFN -BFn9GlIBEADf0TowIsGmOh0Tr7Amt3KDVkFxWMzwofOnVA+O3YgYCAsR5WvR -18Twa39jqg3yCe6F6W+FMliv+6m2cwjZzdyEfD5b90zgFbC99qxe4p+BxlKW -q4swu9roURVpq+26qer9VRs8FpSXOcHjfxvMIreAmpEZ1H5uzcL46ql8YOTj -W5f2ehy++f9HxtMMdFMRM+nLAGqgE8jeIuesRbXGc9CvZElYsnWEQcS/mrIC -26GE8282bapOIsZjvQXdVhO7kiQKfLNP4RGRmcZdc5hQGoiEbefhLdZ50DV5 -gfgLwwwl5QphJ2r3LdA0YHzjNykJaYpJ6RczTzZFtp/PwN9IVPOf6qQVyqET -7dT2scgkQ1mljBPkGc4nPrehkLwByidGyXYhuouwFYQOB+P2FlmyvnzyIooU -5eoFHeXddX+breIKJxJyNubj1FC0L2c7C1IqkHGynolputsqwZNiQxdEK0FL -J4LQ2AzIzGA/Tazc/AMpqC0iR3pD7stcQssOG9ERJnUecGgzmnVurlihDCfB -+LAdpyWejN2Ok7GEum88WgFdG4iBpazHjEIsgcYsP7w6u5HGuFts8cvws8HF -eBOGpeC3s2qtRZd9VsdpzcCeI8pajq989MOnlJzoUsA0e4mE5kKy02IMBXNv -m4BKxQmPQHhvHSKAn7SWNBsRTksQciN4kwARAQABwsFpBBgBCAATBQJZ/RpZ -CRB/3mhVSK6niAIbDAAKCRB/3mhVSK6niDTZD/sF8LBOKY4GruWyiqU9SDDD -oTW5udklNWZzKvYy3sgSJDFMrensrHfxlTOkDsB03JHh9Z5pbL2m26EYUftJ -OkxuAW8/4Lgoqeo6Oqu291NSVh0/9gjQOuiPjqP3CArcwwBO+ndp/smvEKzR -1aVV9Oscno2gdPZoQa2Y31GLmpDiOUgbqGGXXj2j7w5Z0w93LdVFewD2/ssL -Q98ozHvdjTCdxO4t+GBzxpkzy1aM4udBesech/D5EUn5YqMJFt1XuY9z3RLV -WQoLq1WUUoIXDpBsUMIyg/t4g7ITPveWNzIRc9y06bGBU8zCE46nAwVav8OD -tZcDRL1Y6RTafIX/c26VF89KXEgbjehPYlQ0vI/Bs3qdSGKwdX/+fZwEwWvL -sLSUyCyfDL9MMwXpc+7GYJUDWcd+ikYwvx8Nmnq1FBUMxYyN2WqP+PRyZRJi -4OyuubVEEjNU8/SMpuOWIy6ZiM4/cukuJGQ2alqWNUak5az6i3ICOhjMMLVd -xLYkBZDN7o+G9aBAZsjaCbGzOXKSNmadIIPXU6nS6EHyOKUJ/eDFGTfnd5Gk -WlK7v7H/kIqy9Ggvz6j/seqokN7X4nuc7xOTub6WI1sNRQePIuw2um+Yp14n -Bk66Izujnvwa9bVz3nuXhI90WDLnu8OQyAe/N4Pv9pXu1IGg4Nx8yYBLuMuc -eg== -=CvEL ------END PGP PUBLIC KEY BLOCK-----` + pubkey: testConstants.flowcryptcompatibilityPublicKey7FDE685548AEA788 }); await ContactStore.save(undefined, contact); } @@ -1343,7 +1287,7 @@ jA== }); ava.default('[KeyUtil.diagnose] decrypts and tests secure PK and insecure SK', async t => { - const result = await KeyUtil.diagnose(await KeyUtil.parse(rsa1024subkeyOnly), ''); + const result = await KeyUtil.diagnose(await KeyUtil.parse(testConstants.rsa1024subkeyOnly), ''); expect(result.get('Is Private?')).to.equal('[-] true'); expect(result.get('User id 0')).to.equal('rsa1024subkey@test'); expect(result.get('Primary User')).to.equal('rsa1024subkey@test'); @@ -1543,7 +1487,7 @@ kBXo }); ava.default(`[unit][OpenPGPKey.parse] sets usableForEncryption to false and usableForSigning to true for 2048/RSA PK and 1024/RSA SK`, async t => { - const key = await KeyUtil.parse(rsa1024subkeyOnly); + const key = await KeyUtil.parse(testConstants.rsa1024subkeyOnly); expect(key.usableForEncryption).to.equal(false); expect(key.usableForSigning).to.equal(true); expect(key.usableForEncryptionButExpired).to.equal(false); @@ -1552,7 +1496,7 @@ kBXo }); ava.default(`[unit][OpenPGPKey.decrypt] sets usableForEncryption to false and usableForSigning to true for 2048/RSA PK and 1024/RSA SK`, async t => { - const key = await KeyUtil.parse(rsa1024subkeyOnlyEncrypted); + const key = await KeyUtil.parse(testConstants.rsa1024subkeyOnlyEncrypted); expect(key.usableForEncryption).to.equal(false); expect(key.usableForSigning).to.equal(true); expect(key.usableForEncryptionButExpired).to.equal(false); From c276b3768a31f638a57db9df27def158484a4843 Mon Sep 17 00:00:00 2001 From: Roman Shevchenko Date: Fri, 19 Mar 2021 07:19:18 -0400 Subject: [PATCH 31/53] added a unit test for update --- .../js/common/platform/store/contact-store.ts | 4 +- .../browser-unit-tests/unit-ContactStore.js | 78 +++++++++++++++++++ 2 files changed, 80 insertions(+), 2 deletions(-) diff --git a/extension/js/common/platform/store/contact-store.ts b/extension/js/common/platform/store/contact-store.ts index 882b71bf9c6..7d65005be93 100644 --- a/extension/js/common/platform/store/contact-store.ts +++ b/extension/js/common/platform/store/contact-store.ts @@ -112,7 +112,7 @@ export class ContactStore extends AbstractStore { public static obj = async ({ email, name, pubkey, pendingLookup, lastUse, lastCheck, lastSig }: DbContactObjArg): Promise => { if (typeof opgp === 'undefined') { - return await BrowserMsg.send.bg.await.db({ f: 'obj', args: [{ email, name, pubkey, pendingLookup, lastUse, lastSig, lastCheck }] }) as Contact; + return await BrowserMsg.send.bg.await.db({ f: 'obj', args: [{ email, name, pubkey, pendingLookup, lastUse, lastCheck, lastSig }] }) as Contact; } else { const validEmail = Str.parseEmail(email).email; if (!validEmail) { @@ -214,7 +214,7 @@ export class ContactStore extends AbstractStore { } public static updateTx = (tx: IDBTransaction, email: string, update: ContactUpdate) => { - if (update.pubkey && !update.pubkey_last_check) { // todo: test + if (update.pubkey && !update.pubkey_last_check) { const req = tx.objectStore('pubkeys').get(update.pubkey.id); req.onsuccess = () => ContactStore.updateTxPhase2(tx, email, update, req.result as Pubkey); } else { diff --git a/test/source/tests/browser-unit-tests/unit-ContactStore.js b/test/source/tests/browser-unit-tests/unit-ContactStore.js index 7f068057ffd..926939436eb 100644 --- a/test/source/tests/browser-unit-tests/unit-ContactStore.js +++ b/test/source/tests/browser-unit-tests/unit-ContactStore.js @@ -193,3 +193,81 @@ BROWSER_UNIT_TEST_NAME(`ContactStore doesn't store smaller words in searchable w } return 'pass'; })(); + +BROWSER_UNIT_TEST_NAME(`ContactStore.update updates correct 'pubkey_last_check'`); +(async () => { + const db = await ContactStore.dbOpen(); + const email = 'flowcrypt.compatibility@gmail.com'; + const date2_0 = Date.now(); + const contacts = [ + await ContactStore.obj({ + email, + pubkey: testConstants.flowcryptcompatibilityPublicKey7FDE685548AEA788 + }), + await ContactStore.obj({ + email, + pubkey: testConstants.flowcryptcompatibilityPublicKeyADAC279C95093207, + lastCheck: date2_0 + })]; + await ContactStore.save(db, contacts); + // extract the entities from the database + const fp1 = '5520CACE2CB61EA713E5B0057FDE685548AEA788'; + const fp2 = 'E8F0517BA6D7DAB6081C96E4ADAC279C95093207'; + const getEntity = async(fp) => { return await new Promise((resolve, reject) => { + const req = db.transaction(['pubkeys'], 'readonly').objectStore('pubkeys').get(fp); + ContactStore.setReqPipe(req, () => resolve(req.result), reject); + }); }; + let entity1 = await getEntity(fp1); + let entity2 = await getEntity(fp2); + if (entity1.fingerprint !== fp1) { + throw Error(`Failed to extract pubkey ${fp1}`); + } + if (entity2.fingerprint !== fp2) { + throw Error(`Failed to extract pubkey ${fp2}`); + } + if (entity1.lastCheck) { + throw Error(`Expected undefined lastCheck for ${fp1} but got ${entity1.lastCheck}`); + } + if (entity2.lastCheck !== date2_0) { + throw Error(`Expected lastCheck=${date2_0} for ${fp2} but got ${entity2.lastCheck}`); + } + const pubkey1 = await KeyUtil.parse(testConstants.flowcryptcompatibilityPublicKey7FDE685548AEA788); + const pubkey2 = await KeyUtil.parse(testConstants.flowcryptcompatibilityPublicKeyADAC279C95093207); + const date1_1 = date2_0 + 1000; + // update entity 1 with pubkey_last_check = date1_1 + await ContactStore.update(db, email, { pubkey_last_check: date1_1, pubkey: pubkey1 }); + // extract the entities from the database + entity1 = await getEntity(fp1); + entity2 = await getEntity(fp2); + if (entity1.lastCheck !== date1_1) { + throw Error(`Expected lastCheck=${date1_1} for ${fp1} but got ${entity1.lastCheck}`); + } + if (entity2.lastCheck !== date2_0) { + throw Error(`Expected lastCheck=${date2_0} for ${fp2} but got ${entity2.lastCheck}`); + } + const date2_2 = date1_1 + 10000; + // updating with undefined value shouldn't modify pubkey_last_check + await ContactStore.update(db, email, { pubkey_last_check: undefined, pubkey: pubkey1 }); + await ContactStore.update(db, email, { pubkey_last_check: date2_2, pubkey: pubkey2 }); + // extract the entities from the database + entity1 = await getEntity(fp1); + entity2 = await getEntity(fp2); + if (entity1.lastCheck !== date1_1) { + throw Error(`Expected lastCheck=${date1_1} for ${fp1} but got ${entity1.lastCheck}`); + } + if (entity2.lastCheck !== date2_2) { + throw Error(`Expected lastCheck=${date2_2} for ${fp2} but got ${entity2.lastCheck}`); + } + // updating contact details without specifying a pubkey shouln't update pubkey_last_check + await ContactStore.update(db, email, { name: 'Some Name' }); + // extract the entities from the database + entity1 = await getEntity(fp1); + entity2 = await getEntity(fp2); + if (entity1.lastCheck !== date1_1) { + throw Error(`Expected lastCheck=${date1_1} for ${fp1} but got ${entity1.lastCheck}`); + } + if (entity2.lastCheck !== date2_2) { + throw Error(`Expected lastCheck=${date2_2} for ${fp2} but got ${entity2.lastCheck}`); + } + return 'pass'; +})(); From 218ba76ae97253b27e8b1664113845412bbe3781 Mon Sep 17 00:00:00 2001 From: Roman Shevchenko Date: Fri, 19 Mar 2021 08:08:17 -0400 Subject: [PATCH 32/53] refactored lastSig parameter out --- .../elements/compose-modules/compose-storage-module.ts | 5 ----- extension/chrome/elements/pgp_pubkey.ts | 3 +-- extension/chrome/settings/setup.ts | 3 +-- extension/js/common/platform/store/contact-store.ts | 5 ++--- test/source/platform/store/contact-store.ts | 4 ++-- 5 files changed, 6 insertions(+), 14 deletions(-) diff --git a/extension/chrome/elements/compose-modules/compose-storage-module.ts b/extension/chrome/elements/compose-modules/compose-storage-module.ts index 296d3249398..4990dcf4ef0 100644 --- a/extension/chrome/elements/compose-modules/compose-storage-module.ts +++ b/extension/chrome/elements/compose-modules/compose-storage-module.ts @@ -132,11 +132,6 @@ export class ComposeStorageModule extends ViewModule { if (!contact.pubkey || !contact.fingerprint) { return; } - if (!contact.pubkey_last_sig) { - const lastSig = Number(contact.pubkey.lastModified); - contact.pubkey_last_sig = lastSig; - await ContactStore.update(undefined, contact.email, { pubkey: contact.pubkey }); - } const lastCheckOverWeekAgoOrNever = !contact.pubkey_last_check || new Date(contact.pubkey_last_check).getTime() < Date.now() - (1000 * 60 * 60 * 24 * 7); const isExpired = contact.expiresOn && contact.expiresOn < Date.now(); if (lastCheckOverWeekAgoOrNever || isExpired) { diff --git a/extension/chrome/elements/pgp_pubkey.ts b/extension/chrome/elements/pgp_pubkey.ts index ab16c4ab5ee..00deae9f030 100644 --- a/extension/chrome/elements/pgp_pubkey.ts +++ b/extension/chrome/elements/pgp_pubkey.ts @@ -140,7 +140,7 @@ View.run(class PgpPubkeyView extends View { for (const pubkey of this.publicKeys!) { const email = pubkey.emails[0]; if (email) { - contacts.push(await ContactStore.obj({ email, pubkey: KeyUtil.armor(pubkey), lastSig: Number(pubkey.lastModified) })); + contacts.push(await ContactStore.obj({ email, pubkey: KeyUtil.armor(pubkey) })); } } await ContactStore.save(undefined, contacts); @@ -155,7 +155,6 @@ View.run(class PgpPubkeyView extends View { const contact = await ContactStore.obj({ email: String($('.input_email').val()), pubkey: KeyUtil.armor(this.publicKeys![0]), - lastSig: Number(this.publicKeys![0].lastModified) }); await ContactStore.save(undefined, contact); BrowserMsg.send.addToContacts(this.parentTabId); diff --git a/extension/chrome/settings/setup.ts b/extension/chrome/settings/setup.ts index f3e6c0786bb..345b3338e22 100644 --- a/extension/chrome/settings/setup.ts +++ b/extension/chrome/settings/setup.ts @@ -214,8 +214,7 @@ export class SetupView extends View { myOwnEmailAddrsAsContacts.push(await ContactStore.obj({ email, name, - pubkey: KeyUtil.armor(await KeyUtil.asPublicKey(prvs[0])), - lastSig: Number(prvs[0].lastModified) + pubkey: KeyUtil.armor(await KeyUtil.asPublicKey(prvs[0])) })); } await ContactStore.save(undefined, myOwnEmailAddrsAsContacts); diff --git a/extension/js/common/platform/store/contact-store.ts b/extension/js/common/platform/store/contact-store.ts index 7d65005be93..99e2946625d 100644 --- a/extension/js/common/platform/store/contact-store.ts +++ b/extension/js/common/platform/store/contact-store.ts @@ -16,7 +16,6 @@ type DbContactObjArg = { pubkey?: string | null, pendingLookup?: boolean | number | null, lastUse?: number | null, // when was this contact last used to send an email - lastSig?: number | null, // last pubkey signature (when was pubkey last updated by owner) lastCheck?: number | null; // when was the local copy of the pubkey last updated (or checked against Attester) }; @@ -110,9 +109,9 @@ export class ContactStore extends AbstractStore { return { email: validEmail, name: name || null, has_pgp: 0, last_use: null }; } - public static obj = async ({ email, name, pubkey, pendingLookup, lastUse, lastCheck, lastSig }: DbContactObjArg): Promise => { + public static obj = async ({ email, name, pubkey, pendingLookup, lastUse, lastCheck }: DbContactObjArg): Promise => { if (typeof opgp === 'undefined') { - return await BrowserMsg.send.bg.await.db({ f: 'obj', args: [{ email, name, pubkey, pendingLookup, lastUse, lastCheck, lastSig }] }) as Contact; + return await BrowserMsg.send.bg.await.db({ f: 'obj', args: [{ email, name, pubkey, pendingLookup, lastUse, lastCheck }] }) as Contact; } else { const validEmail = Str.parseEmail(email).email; if (!validEmail) { diff --git a/test/source/platform/store/contact-store.ts b/test/source/platform/store/contact-store.ts index cf90a77e2fb..7b8fc20da81 100644 --- a/test/source/platform/store/contact-store.ts +++ b/test/source/platform/store/contact-store.ts @@ -50,7 +50,7 @@ export class ContactStore { } } - public static obj = async ({ email, name, pubkey, pendingLookup, lastUse, lastCheck, lastSig }: any): Promise => { + public static obj = async ({ email, name, pubkey, pendingLookup, lastUse, lastCheck }: any): Promise => { if (!pubkey) { return { email, @@ -75,7 +75,7 @@ export class ContactStore { pending_lookup: pendingLookup, last_use: lastUse, pubkey_last_check: lastCheck, - pubkey_last_sig: lastSig + pubkey_last_sig: pk.lastModified } as Contact; return contact; } From 64b7ac80b3add6d46fd68e67b2ca805e75d43067 Mon Sep 17 00:00:00 2001 From: Roman Shevchenko Date: Fri, 19 Mar 2021 08:14:23 -0400 Subject: [PATCH 33/53] tslint fix --- extension/chrome/elements/pgp_pubkey.ts | 3 +-- extension/chrome/settings/setup.ts | 4 +--- 2 files changed, 2 insertions(+), 5 deletions(-) diff --git a/extension/chrome/elements/pgp_pubkey.ts b/extension/chrome/elements/pgp_pubkey.ts index 00deae9f030..2b1887b9cfc 100644 --- a/extension/chrome/elements/pgp_pubkey.ts +++ b/extension/chrome/elements/pgp_pubkey.ts @@ -153,8 +153,7 @@ View.run(class PgpPubkeyView extends View { } else if (this.publicKeys!.length) { if (Str.isEmailValid(String($('.input_email').val()))) { const contact = await ContactStore.obj({ - email: String($('.input_email').val()), - pubkey: KeyUtil.armor(this.publicKeys![0]), + email: String($('.input_email').val()), pubkey: KeyUtil.armor(this.publicKeys![0]), }); await ContactStore.save(undefined, contact); BrowserMsg.send.addToContacts(this.parentTabId); diff --git a/extension/chrome/settings/setup.ts b/extension/chrome/settings/setup.ts index 345b3338e22..cf1e29d9a8c 100644 --- a/extension/chrome/settings/setup.ts +++ b/extension/chrome/settings/setup.ts @@ -212,9 +212,7 @@ export class SetupView extends View { const { full_name: name } = await AcctStore.get(this.acctEmail, ['full_name']); for (const email of this.submitKeyForAddrs) { myOwnEmailAddrsAsContacts.push(await ContactStore.obj({ - email, - name, - pubkey: KeyUtil.armor(await KeyUtil.asPublicKey(prvs[0])) + email, name, pubkey: KeyUtil.armor(await KeyUtil.asPublicKey(prvs[0])) })); } await ContactStore.save(undefined, myOwnEmailAddrsAsContacts); From 73157673789abd4af4ca8dd7f2c362348cef6931 Mon Sep 17 00:00:00 2001 From: Roman Shevchenko Date: Fri, 19 Mar 2021 08:30:42 -0400 Subject: [PATCH 34/53] tslint fix --- extension/chrome/elements/pgp_pubkey.ts | 4 +--- extension/chrome/settings/setup.ts | 4 +--- 2 files changed, 2 insertions(+), 6 deletions(-) diff --git a/extension/chrome/elements/pgp_pubkey.ts b/extension/chrome/elements/pgp_pubkey.ts index 2b1887b9cfc..8cb8d6a1776 100644 --- a/extension/chrome/elements/pgp_pubkey.ts +++ b/extension/chrome/elements/pgp_pubkey.ts @@ -152,9 +152,7 @@ View.run(class PgpPubkeyView extends View { $('.input_email').remove(); } else if (this.publicKeys!.length) { if (Str.isEmailValid(String($('.input_email').val()))) { - const contact = await ContactStore.obj({ - email: String($('.input_email').val()), pubkey: KeyUtil.armor(this.publicKeys![0]), - }); + const contact = await ContactStore.obj({ email: String($('.input_email').val()), pubkey: KeyUtil.armor(this.publicKeys![0]) }); await ContactStore.save(undefined, contact); BrowserMsg.send.addToContacts(this.parentTabId); Xss.sanitizeReplace(addContactBtn, `${Xss.escape(String($('.input_email').val()))} added`); diff --git a/extension/chrome/settings/setup.ts b/extension/chrome/settings/setup.ts index cf1e29d9a8c..45f38f21888 100644 --- a/extension/chrome/settings/setup.ts +++ b/extension/chrome/settings/setup.ts @@ -211,9 +211,7 @@ export class SetupView extends View { const myOwnEmailAddrsAsContacts: Contact[] = []; const { full_name: name } = await AcctStore.get(this.acctEmail, ['full_name']); for (const email of this.submitKeyForAddrs) { - myOwnEmailAddrsAsContacts.push(await ContactStore.obj({ - email, name, pubkey: KeyUtil.armor(await KeyUtil.asPublicKey(prvs[0])) - })); + myOwnEmailAddrsAsContacts.push(await ContactStore.obj({ email, name, pubkey: KeyUtil.armor(await KeyUtil.asPublicKey(prvs[0])) })); } await ContactStore.save(undefined, myOwnEmailAddrsAsContacts); } From 72b78653b903a717b532cd4399518b24cd191b23 Mon Sep 17 00:00:00 2001 From: Roman Shevchenko Date: Fri, 19 Mar 2021 08:32:33 -0400 Subject: [PATCH 35/53] refactored lastSig out --- extension/js/common/platform/store/contact-store.ts | 2 -- 1 file changed, 2 deletions(-) diff --git a/extension/js/common/platform/store/contact-store.ts b/extension/js/common/platform/store/contact-store.ts index 99e2946625d..10cdc701424 100644 --- a/extension/js/common/platform/store/contact-store.ts +++ b/extension/js/common/platform/store/contact-store.ts @@ -33,7 +33,6 @@ type Pubkey = { armoredKey: string; longids: string[]; lastCheck: number | null, - lastSig: number; expiresOn: number; }; @@ -229,7 +228,6 @@ export class ContactStore extends AbstractStore { pubkeyEntity = { fingerprint: update.pubkey.id, lastCheck: update.pubkey_last_check ?? existingPubkey?.lastCheck, - lastSig: keyAttrs.pubkey_last_sig, expiresOn: keyAttrs.expiresOn, longids: update.pubkey.allIds.map(id => OpenPGPKey.fingerprintToLongid(id)), armoredKey: KeyUtil.armor(update.pubkey) From af415f1db480ea78f7f873e886ce034cdebdf642 Mon Sep 17 00:00:00 2001 From: Roman Shevchenko Date: Sat, 27 Mar 2021 06:59:44 -0400 Subject: [PATCH 36/53] added more tests and better date handling --- extension/js/background_page/migrations.ts | 1 - extension/js/common/core/common.ts | 13 +++ extension/js/common/core/crypto/key.ts | 1 - .../js/common/platform/store/contact-store.ts | 16 ++-- test/source/platform/store/contact-store.ts | 3 - .../browser-unit-tests/unit-ContactStore.js | 84 +++++++++++++++++++ 6 files changed, 104 insertions(+), 14 deletions(-) diff --git a/extension/js/background_page/migrations.ts b/extension/js/background_page/migrations.ts index c77246793e0..d628e08b1fd 100644 --- a/extension/js/background_page/migrations.ts +++ b/extension/js/background_page/migrations.ts @@ -16,7 +16,6 @@ type ContactV3 = { fingerprint: string | null; pending_lookup: number; last_use: number | null; - pubkey_last_sig: number | null; pubkey_last_check: number | null; expiresOn: number | null; }; diff --git a/extension/js/common/core/common.ts b/extension/js/common/core/common.ts index f7ac137307c..895c622b728 100644 --- a/extension/js/common/core/common.ts +++ b/extension/js/common/core/common.ts @@ -168,6 +168,19 @@ export class Str { } +export class DateUtility { + // tslint:disable-next-line:no-null-keyword + public static asNumber = (date: number | null | undefined): number | null => { + if (typeof date === 'number') { + return date; + } else if (!date) { + return null; // tslint:disable-line:no-null-keyword + } else { + return new Date(date).getTime(); + } + } +} + export class Value { public static arr = { diff --git a/extension/js/common/core/crypto/key.ts b/extension/js/common/core/crypto/key.ts index 5a873eb024e..bdb0cb2d48b 100644 --- a/extension/js/common/core/crypto/key.ts +++ b/extension/js/common/core/crypto/key.ts @@ -56,7 +56,6 @@ export type Contact = { fingerprint: string | null; pending_lookup: number; last_use: number | null; - pubkey_last_sig: number | null; pubkey_last_check: number | null; expiresOn: number | null; }; diff --git a/extension/js/common/platform/store/contact-store.ts b/extension/js/common/platform/store/contact-store.ts index 10cdc701424..9d7f543f6f3 100644 --- a/extension/js/common/platform/store/contact-store.ts +++ b/extension/js/common/platform/store/contact-store.ts @@ -4,7 +4,7 @@ import { AbstractStore } from './abstract-store.js'; import { Catch } from '../catch.js'; import { opgp } from '../../core/crypto/pgp/openpgpjs-custom.js'; import { BrowserMsg } from '../../browser/browser-msg.js'; -import { Str } from '../../core/common.js'; +import { DateUtility, Str } from '../../core/common.js'; import { Key, Contact, KeyUtil } from '../../core/crypto/key.js'; import { OpenPGPKey } from '../../core/crypto/pgp/openpgp-key.js'; @@ -33,13 +33,12 @@ type Pubkey = { armoredKey: string; longids: string[]; lastCheck: number | null, - expiresOn: number; + expiresOn: number | null; }; type PubkeyAttributes = { fingerprint: string | null; expiresOn: number | null; - pubkey_last_sig: number | null }; export type ContactPreview = { @@ -125,7 +124,6 @@ export class ContactStore extends AbstractStore { has_pgp: 0, // number because we use it for sorting fingerprint: null, last_use: lastUse || null, - pubkey_last_sig: null, pubkey_last_check: null, expiresOn: null }; @@ -216,7 +214,7 @@ export class ContactStore extends AbstractStore { const req = tx.objectStore('pubkeys').get(update.pubkey.id); req.onsuccess = () => ContactStore.updateTxPhase2(tx, email, update, req.result as Pubkey); } else { - ContactStore.updateTxPhase2(tx, email, update, undefined); // todo: test + ContactStore.updateTxPhase2(tx, email, update, undefined); } } @@ -227,7 +225,7 @@ export class ContactStore extends AbstractStore { // todo: will we benefit anything when not saving pubkey if it isn't modified? pubkeyEntity = { fingerprint: update.pubkey.id, - lastCheck: update.pubkey_last_check ?? existingPubkey?.lastCheck, + lastCheck: DateUtility.asNumber(update.pubkey_last_check ?? existingPubkey?.lastCheck), expiresOn: keyAttrs.expiresOn, longids: update.pubkey.allIds.map(id => OpenPGPKey.fingerprintToLongid(id)), armoredKey: KeyUtil.armor(update.pubkey) @@ -254,10 +252,10 @@ export class ContactStore extends AbstractStore { emailEntity.name = update.name ?? null; } if (Object.keys(update).includes('pending_lookup')) { - emailEntity.pendingLookup = update.pending_lookup ?? 0; + emailEntity.pendingLookup = update.pending_lookup ? 1 : 0; } if (Object.keys(update).includes('last_use')) { - emailEntity.lastUse = update.last_use ?? null; + emailEntity.lastUse = DateUtility.asNumber(update.last_use); } ContactStore.updateSearchable(emailEntity); tx.objectStore('emails').put(emailEntity); @@ -431,7 +429,7 @@ export class ContactStore extends AbstractStore { } private static getKeyAttributes = (key: Key | undefined): PubkeyAttributes => { - return { fingerprint: key?.id ?? null, expiresOn: Number(key?.expiration) || null, pubkey_last_sig: Number(key?.lastModified) || null }; + return { fingerprint: key?.id ?? null, expiresOn: DateUtility.asNumber(key?.expiration) }; } private static toContact = async (email: Email, pubkey: Pubkey): Promise => { diff --git a/test/source/platform/store/contact-store.ts b/test/source/platform/store/contact-store.ts index 7b8fc20da81..c3186945b4b 100644 --- a/test/source/platform/store/contact-store.ts +++ b/test/source/platform/store/contact-store.ts @@ -44,7 +44,6 @@ export class ContactStore { const key = typeof update.pubkey === 'string' ? await KeyUtil.parse(update.pubkey) : update.pubkey; updated.pubkey = key; updated.fingerprint = key.id; - updated.pubkey_last_sig = key.lastModified ? Number(key.lastModified) : null; updated.expiresOn = key.expiration ? Number(key.expiration) : null; updated.has_pgp = 1; } @@ -60,7 +59,6 @@ export class ContactStore { has_pgp: 0, // number because we use it for sorting fingerprint: null, last_use: lastUse || null, - pubkey_last_sig: null, pubkey_last_check: null, expiresOn: null }; @@ -75,7 +73,6 @@ export class ContactStore { pending_lookup: pendingLookup, last_use: lastUse, pubkey_last_check: lastCheck, - pubkey_last_sig: pk.lastModified } as Contact; return contact; } diff --git a/test/source/tests/browser-unit-tests/unit-ContactStore.js b/test/source/tests/browser-unit-tests/unit-ContactStore.js index 926939436eb..9e7adf51925 100644 --- a/test/source/tests/browser-unit-tests/unit-ContactStore.js +++ b/test/source/tests/browser-unit-tests/unit-ContactStore.js @@ -271,3 +271,87 @@ BROWSER_UNIT_TEST_NAME(`ContactStore.update updates correct 'pubkey_last_check'` } return 'pass'; })(); + +BROWSER_UNIT_TEST_NAME(`ContactStore.update tests`); +(async () => { + const db = await ContactStore.dbOpen(); + const email1 = 'email1@test.com'; + const email2 = 'email2@test.com'; + const contacts = [ + await ContactStore.obj({ email: email1 }), + await ContactStore.obj({ email: email2 })]; + await ContactStore.save(db, contacts); + const expectedObj1 = { + email: email1, + name: undefined, + pendingLookup: 0, + lastUse: undefined + } + const expectedObj2 = { + email: email2, + name: undefined, + pendingLookup: 0, + lastUse: undefined + } + const getEntity = async(email) => { return await new Promise((resolve, reject) => { + const req = db.transaction(['emails'], 'readonly').objectStore('emails').get(email); + ContactStore.setReqPipe(req, () => resolve(req.result), reject); + }); }; + const compareEntity = async(expectedObj) => { + const loaded = await getEntity(expectedObj.email); + if (loaded.name != expectedObj.name) { + throw Error(`name field mismatch, expected ${expectedObj.name} but got ${loaded.name}`); + } + if (loaded.pendingLookup != expectedObj.pendingLookup) { + throw Error(`pendingLookup field mismatch, expected ${expectedObj.pendingLookup} but got ${loaded.pendingLookup}`); + } + if (loaded.lastUse != expectedObj.lastUse) { + throw Error(`lastUse field mismatch, expected ${expectedObj.lastUse} but got ${loaded.lastUse}`); + } + }; + const compareEntities = async() => { + await compareEntity(expectedObj1); + await compareEntity(expectedObj2); + } + await compareEntities(); + expectedObj1.name = 'New Name for contact 1'; + await ContactStore.update(db, email1, { name: expectedObj1.name }); + await compareEntities(); + expectedObj2.pendingLookup = 1; + // provide any non-zero value + await ContactStore.update(db, email2, { pending_lookup: 4 }); + await compareEntities(); + expectedObj2.pendingLookup = 0; + // provide a "zero" value + await ContactStore.update(db, email2, { pending_lookup: undefined }); + await compareEntities(); + const date = new Date(); + expectedObj1.lastUse = date.getTime(); + await ContactStore.update(db, email1, {last_use: date }); + await compareEntities(); + expectedObj1.lastUse = undefined; + await ContactStore.update(db, email1, {last_use: undefined }); + await compareEntities(); + return 'pass'; +})(); + +BROWSER_UNIT_TEST_NAME(`ContactStore saves and returns dates as numbers`); +(async () => { + // we'll use background operation to make sure the date isn't transformed on its way + const email = 'test@expired.com'; + const lastCheck = Date.now(); + const lastUse = lastCheck + 1000; + const contact = await ContactStore.obj({ email, pubkey:testConstants.expiredPub, lastCheck, lastUse }); + await ContactStore.save(undefined, [contact]); + const [loaded] = await ContactStore.get(undefined, [email]); + if (typeof loaded.last_use !== 'number') { + throw Error(`last_use was expected to be a number, but got ${typeof loaded.last_use}`); + } + if (typeof loaded.pubkey_last_check !== 'number') { + throw Error(`pubkey_last_check was expected to be a number, but got ${typeof loaded.pubkey_last_check}`); + } + if (typeof loaded.last_use !== 'number') { + throw Error(`expiresOn was expected to be a number, but got ${typeof loaded.expiresOn}`); + } + return 'pass'; +})(); From 5c9b97fed235ca8978e49c543bf3027b0c845af6 Mon Sep 17 00:00:00 2001 From: Roman Shevchenko Date: Sat, 27 Mar 2021 12:38:25 -0400 Subject: [PATCH 37/53] pick possibly non-expired key usable for encryption if there are many --- extension/js/background_page/migrations.ts | 40 ++--- .../js/common/platform/store/contact-store.ts | 150 ++++++++++++------ 2 files changed, 121 insertions(+), 69 deletions(-) diff --git a/extension/js/background_page/migrations.ts b/extension/js/background_page/migrations.ts index d628e08b1fd..d41f3daf9ac 100644 --- a/extension/js/background_page/migrations.ts +++ b/extension/js/background_page/migrations.ts @@ -55,17 +55,19 @@ export const moveContactsToEmailsAndPubkeys = async (db: IDBDatabase): Promise => { const entries: ContactV3[] = []; - const tx = db.transaction(['contacts'], 'readonly'); - await new Promise((resolve, reject) => { - ContactStore.setTxHandlers(tx, resolve, reject); - const contacts = tx.objectStore('contacts'); - const search = contacts.getAll(undefined, count); - search.onsuccess = () => { - entries.push(...search.result); - }; - }); - if (!entries.length) { - return 0; + { + const tx = db.transaction(['contacts'], 'readonly'); + await new Promise((resolve, reject) => { + ContactStore.setTxHandlers(tx, resolve, reject); + const contacts = tx.objectStore('contacts'); + const search = contacts.getAll(undefined, count); + search.onsuccess = () => { + entries.push(...search.result); + }; + }); + if (!entries.length) { + return 0; + } } console.info(`Processing a batch of ${entries.length}.`); // transform @@ -83,13 +85,15 @@ const moveContactsBatchToEmailsAndPubkeys = async (db: IDBDatabase, count?: numb pubkey_last_check: pubkey ? entry.pubkey_last_check : undefined } as ContactUpdate; })); - await new Promise((resolve, reject) => { + { const tx = db.transaction(['contacts', 'emails', 'pubkeys'], 'readwrite'); - ContactStore.setTxHandlers(tx, resolve, reject); - for (const update of updates) { - ContactStore.updateTx(tx, update.email!, update); - tx.objectStore('contacts').delete(update.email!); - } - }); + await new Promise((resolve, reject) => { + ContactStore.setTxHandlers(tx, resolve, reject); + for (const update of updates) { + ContactStore.updateTx(tx, update.email!, update); + tx.objectStore('contacts').delete(update.email!); + } + }); + } return updates.length; }; diff --git a/extension/js/common/platform/store/contact-store.ts b/extension/js/common/platform/store/contact-store.ts index 9d7f543f6f3..6a73448c11e 100644 --- a/extension/js/common/platform/store/contact-store.ts +++ b/extension/js/common/platform/store/contact-store.ts @@ -41,6 +41,11 @@ type PubkeyAttributes = { expiresOn: number | null; }; +export type ContactV4 = { + info: Email, + pubkeys: Pubkey[] +} + export type ContactPreview = { email: string; name: string | null; @@ -173,8 +178,8 @@ export class ContactStore extends AbstractStore { Catch.report(`Wrongly updating prv ${update.pubkey.id} as contact - converting to pubkey`); update.pubkey = await KeyUtil.asPublicKey(update.pubkey); } + const tx = db.transaction(['emails', 'pubkeys'], 'readwrite'); await new Promise((resolve, reject) => { - const tx = db.transaction(['emails', 'pubkeys'], 'readwrite'); ContactStore.setTxHandlers(tx, resolve, reject); ContactStore.updateTx(tx, email, update); }); @@ -209,6 +214,42 @@ export class ContactStore extends AbstractStore { return (await ContactStore.extractPubkeys(db, fingerprints)).map(pubkey => pubkey?.armoredKey).filter(Boolean); } + public static getOneWithAllPubkeys = async (db: IDBDatabase, email: string): Promise => { + const tx = db.transaction(['emails', 'pubkeys'], 'readonly'); + const pubkeys: Pubkey[] = []; + const emailEntity: Email | undefined = await new Promise((resolve, reject) => { + const req = tx.objectStore('emails').get(email); + ContactStore.setReqPipe(req, + (email: Email) => { + if (!email) { + resolve(undefined); + return; + } + if (!email.fingerprints || email.fingerprints.length === 0) { + resolve(email); + return; + } + let countdown = email.fingerprints.length; + // request all pubkeys by fingerprints + for (const fp of email.fingerprints) { + const req2 = tx.objectStore('pubkeys').get(fp); + ContactStore.setReqPipe(req2, + (pubkey: Pubkey) => { + if (pubkey) { + pubkeys.push(pubkey); + } + if (!--countdown) { + resolve(email); + } + }, + reject); + } + }, + reject); + }); + return emailEntity ? { info: emailEntity, pubkeys } : undefined; + } + public static updateTx = (tx: IDBTransaction, email: string, update: ContactUpdate) => { if (update.pubkey && !update.pubkey_last_check) { const req = tx.objectStore('pubkeys').get(update.pubkey.id); @@ -269,8 +310,8 @@ export class ContactStore extends AbstractStore { if (!db) { // relay op through background process return await BrowserMsg.send.bg.await.db({ f: 'extractPubkeys', args: [fingerprints] }) as Pubkey[]; } + const tx = db.transaction(['pubkeys'], 'readonly'); const raw: Pubkey[] = await new Promise((resolve, reject) => { - const tx = db.transaction(['pubkeys'], 'readonly'); const search = tx.objectStore('pubkeys').openCursor(fingerprints); const found: Pubkey[] = []; ContactStore.setReqPipe(search, @@ -308,8 +349,8 @@ export class ContactStore extends AbstractStore { return resultsWithPgp.concat(resultsWithoutPgp); } } + const emails = db.transaction(['emails'], 'readonly').objectStore('emails'); const raw: Email[] = await new Promise((resolve, reject) => { - const emails = db.transaction(['emails'], 'readonly').objectStore('emails'); let search: IDBRequest; if (typeof query.has_pgp === 'undefined') { // any query.has_pgp value search = emails.openCursor(); // no substring, already covered in `typeof query.has_pgp === 'undefined' && query.substring` above @@ -381,50 +422,50 @@ export class ContactStore extends AbstractStore { } private static dbContactInternalGetOne = async (db: IDBDatabase, emailOrLongid: string): Promise => { - return await new Promise((resolve, reject) => { - const tx = db.transaction(['emails', 'pubkeys'], 'readonly'); - if (!/^[A-F0-9]{16}$/.test(emailOrLongid)) { // email - const req = tx.objectStore('emails').get(emailOrLongid); - ContactStore.setReqPipe(req, - (email: Email) => { - if (!email) { - resolve(undefined); - return; - } - if (!email.fingerprints || email.fingerprints.length === 0) { - resolve(undefined); - return; - } - // todo: load all fingerprints and pick a valid one? - const req2 = tx.objectStore('pubkeys').get(email.fingerprints[0]); - ContactStore.setReqPipe(req2, - (pubkey: Pubkey) => { - resolve(ContactStore.toContact(email, pubkey)); - }, - reject); - }, - reject); - } else { // search all longids - const req = tx.objectStore('pubkeys').index('index_longids').get(emailOrLongid); - ContactStore.setReqPipe(req, - (pubkey: Pubkey) => { - if (!pubkey) { - resolve(undefined); - return; - } - const req2 = tx.objectStore('emails').index('index_fingerprints').get(pubkey.fingerprint!); - ContactStore.setReqPipe(req2, - (email: Email) => { - if (!email) { - resolve(undefined); - } else { - resolve(ContactStore.toContact(email, pubkey)); - } - }, - reject); - }, - reject); + if (!/^[A-F0-9]{16}$/.test(emailOrLongid)) { // email + const contactWithAllPubkeys = await ContactStore.getOneWithAllPubkeys(db, emailOrLongid); + if (!contactWithAllPubkeys) { + return contactWithAllPubkeys; + } + if (!contactWithAllPubkeys.pubkeys.length) { + return await ContactStore.toContact(contactWithAllPubkeys.info, undefined); + } + // parse the keys + const parsed = await Promise.all(contactWithAllPubkeys.pubkeys.map(async (pubkey) => { return { lastCheck: pubkey.lastCheck, pubkey: await KeyUtil.parse(pubkey.armoredKey) }; })); + // sort non-expired first, pick first usableForEncryption + const sorted = parsed.sort((a, b) => (typeof b.pubkey.expiration === 'undefined') ? Infinity : b.pubkey.expiration! + - ((typeof a.pubkey.expiration === 'undefined') ? Infinity : a.pubkey.expiration!)); + let selected = sorted.find(entry => entry.pubkey.usableForEncryption); + if (!selected) { + selected = sorted.find(entry => entry.pubkey.usableForEncryptionButExpired); + } + if (!selected) { + selected = sorted[0]; } + return ContactStore.toContactFromKey(contactWithAllPubkeys.info, selected.pubkey, selected.lastCheck); + } + // search all longids + const tx = db.transaction(['emails', 'pubkeys'], 'readonly'); + return await new Promise((resolve, reject) => { + const req = tx.objectStore('pubkeys').index('index_longids').get(emailOrLongid); + ContactStore.setReqPipe(req, + (pubkey: Pubkey) => { + if (!pubkey) { + resolve(undefined); + return; + } + const req2 = tx.objectStore('emails').index('index_fingerprints').get(pubkey.fingerprint!); + ContactStore.setReqPipe(req2, + (email: Email) => { + if (!email) { + resolve(undefined); + } else { + resolve(ContactStore.toContact(email, pubkey)); // todo: test + } + }, + reject); + }, + reject); }); } @@ -432,20 +473,27 @@ export class ContactStore extends AbstractStore { return { fingerprint: key?.id ?? null, expiresOn: DateUtility.asNumber(key?.expiration) }; } - private static toContact = async (email: Email, pubkey: Pubkey): Promise => { + private static toContact = async (email: Email, pubkey: Pubkey | undefined): Promise => { if (!email) { return; } const parsed = pubkey ? await KeyUtil.parse(pubkey.armoredKey) : undefined; + return ContactStore.toContactFromKey(email, parsed, parsed ? pubkey!.lastCheck : null) + } + + private static toContactFromKey = (email: Email, key: Key | undefined, lastCheck: number | null): Contact | undefined => { + if (!email) { + return; + } return { email: email.email, name: email.name, - pubkey: parsed, - has_pgp: pubkey ? 1 : 0, + pubkey: key, + has_pgp: key ? 1 : 0, pending_lookup: email.pendingLookup, last_use: email.lastUse, - pubkey_last_check: pubkey?.lastCheck, - ...ContactStore.getKeyAttributes(parsed) + pubkey_last_check: lastCheck, + ...ContactStore.getKeyAttributes(key) }; } From 4d66e2eb7e48cb02e1c8fd28db769cbed74e6222 Mon Sep 17 00:00:00 2001 From: Roman Shevchenko Date: Sat, 27 Mar 2021 12:53:01 -0400 Subject: [PATCH 38/53] tslint fix --- extension/js/common/platform/store/contact-store.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/extension/js/common/platform/store/contact-store.ts b/extension/js/common/platform/store/contact-store.ts index 6a73448c11e..8fe394c2d0f 100644 --- a/extension/js/common/platform/store/contact-store.ts +++ b/extension/js/common/platform/store/contact-store.ts @@ -478,7 +478,7 @@ export class ContactStore extends AbstractStore { return; } const parsed = pubkey ? await KeyUtil.parse(pubkey.armoredKey) : undefined; - return ContactStore.toContactFromKey(email, parsed, parsed ? pubkey!.lastCheck : null) + return ContactStore.toContactFromKey(email, parsed, parsed ? pubkey!.lastCheck : null); } private static toContactFromKey = (email: Email, key: Key | undefined, lastCheck: number | null): Contact | undefined => { From f3c38e0e89ed875c1d7281673a9eb9c158cf23c7 Mon Sep 17 00:00:00 2001 From: Roman Shevchenko Date: Mon, 29 Mar 2021 07:00:39 -0400 Subject: [PATCH 39/53] refactored out pending_lookup (aka pendingLookup) --- extension/js/background_page/migrations.ts | 2 -- extension/js/common/core/crypto/key.ts | 1 - .../js/common/platform/store/contact-store.ts | 16 +++----------- test/source/platform/store/contact-store.ts | 5 +---- .../browser-unit-tests/unit-ContactStore.js | 21 ++++--------------- 5 files changed, 8 insertions(+), 37 deletions(-) diff --git a/extension/js/background_page/migrations.ts b/extension/js/background_page/migrations.ts index d41f3daf9ac..97f11a86aea 100644 --- a/extension/js/background_page/migrations.ts +++ b/extension/js/background_page/migrations.ts @@ -14,7 +14,6 @@ type ContactV3 = { pubkey: Key | string | null; has_pgp: 0 | 1; fingerprint: string | null; - pending_lookup: number; last_use: number | null; pubkey_last_check: number | null; expiresOn: number | null; @@ -80,7 +79,6 @@ const moveContactsBatchToEmailsAndPubkeys = async (db: IDBDatabase, count?: numb email: entry.email, name: entry.name, pubkey, - pending_lookup: entry.pending_lookup, last_use: entry.last_use, pubkey_last_check: pubkey ? entry.pubkey_last_check : undefined } as ContactUpdate; diff --git a/extension/js/common/core/crypto/key.ts b/extension/js/common/core/crypto/key.ts index a7cc3619339..be68cd24a3b 100644 --- a/extension/js/common/core/crypto/key.ts +++ b/extension/js/common/core/crypto/key.ts @@ -53,7 +53,6 @@ export type Contact = { pubkey: Key | undefined; has_pgp: 0 | 1; fingerprint: string | null; - pending_lookup: number; last_use: number | null; pubkey_last_check: number | null; expiresOn: number | null; diff --git a/extension/js/common/platform/store/contact-store.ts b/extension/js/common/platform/store/contact-store.ts index 8fe394c2d0f..4255c46b56f 100644 --- a/extension/js/common/platform/store/contact-store.ts +++ b/extension/js/common/platform/store/contact-store.ts @@ -14,7 +14,6 @@ type DbContactObjArg = { email: string, name?: string | null, pubkey?: string | null, - pendingLookup?: boolean | number | null, lastUse?: number | null, // when was this contact last used to send an email lastCheck?: number | null; // when was the local copy of the pubkey last updated (or checked against Attester) }; @@ -24,7 +23,6 @@ type Email = { name: string | null; searchable: string[]; fingerprints: string[]; - pendingLookup: number; lastUse: number | null; }; @@ -56,7 +54,6 @@ export type ContactPreview = { export type ContactUpdate = { email?: string; name?: string | null; - pending_lookup?: number; last_use?: number | null; pubkey?: Key; pubkey_last_check?: number | null; // when non-null, `pubkey` must be supplied @@ -84,7 +81,6 @@ export class ContactStore extends AbstractStore { const emails = db.createObjectStore('emails', { keyPath: 'email' }); const pubkeys = db.createObjectStore('pubkeys', { keyPath: 'fingerprint' }); emails.createIndex('search', 'searchable', { multiEntry: true }); - emails.createIndex('index_pending_lookup', 'pendingLookup'); emails.createIndex('index_fingerprints', 'fingerprints', { multiEntry: true }); // fingerprints of all connected pubkeys pubkeys.createIndex('index_longids', 'longids', { multiEntry: true }); // longids of all public key packets in armored pubkey } @@ -112,9 +108,9 @@ export class ContactStore extends AbstractStore { return { email: validEmail, name: name || null, has_pgp: 0, last_use: null }; } - public static obj = async ({ email, name, pubkey, pendingLookup, lastUse, lastCheck }: DbContactObjArg): Promise => { + public static obj = async ({ email, name, pubkey, lastUse, lastCheck }: DbContactObjArg): Promise => { if (typeof opgp === 'undefined') { - return await BrowserMsg.send.bg.await.db({ f: 'obj', args: [{ email, name, pubkey, pendingLookup, lastUse, lastCheck }] }) as Contact; + return await BrowserMsg.send.bg.await.db({ f: 'obj', args: [{ email, name, pubkey, lastUse, lastCheck }] }) as Contact; } else { const validEmail = Str.parseEmail(email).email; if (!validEmail) { @@ -124,7 +120,6 @@ export class ContactStore extends AbstractStore { return { email: validEmail, name: name || null, - pending_lookup: (pendingLookup ? 1 : 0), pubkey: undefined, has_pgp: 0, // number because we use it for sorting fingerprint: null, @@ -139,7 +134,6 @@ export class ContactStore extends AbstractStore { name: name || null, pubkey: pk, has_pgp: 1, // number because we use it for sorting - pending_lookup: 0, last_use: lastUse || null, pubkey_last_check: lastCheck || null, ...ContactStore.getKeyAttributes(pk) @@ -282,7 +276,7 @@ export class ContactStore extends AbstractStore { if (!validEmail) { throw Error(`Cannot save contact because email is not valid: ${email}`); } - emailEntity = { email, name: null, searchable: [], fingerprints: [], pendingLookup: 0, lastUse: null }; + emailEntity = { email, name: null, searchable: [], fingerprints: [], lastUse: null }; } if (pubkeyEntity) { if (!emailEntity.fingerprints.includes(pubkeyEntity.fingerprint)) { @@ -292,9 +286,6 @@ export class ContactStore extends AbstractStore { if (Object.keys(update).includes('name')) { emailEntity.name = update.name ?? null; } - if (Object.keys(update).includes('pending_lookup')) { - emailEntity.pendingLookup = update.pending_lookup ? 1 : 0; - } if (Object.keys(update).includes('last_use')) { emailEntity.lastUse = DateUtility.asNumber(update.last_use); } @@ -490,7 +481,6 @@ export class ContactStore extends AbstractStore { name: email.name, pubkey: key, has_pgp: key ? 1 : 0, - pending_lookup: email.pendingLookup, last_use: email.lastUse, pubkey_last_check: lastCheck, ...ContactStore.getKeyAttributes(key) diff --git a/test/source/platform/store/contact-store.ts b/test/source/platform/store/contact-store.ts index c3186945b4b..5e7ac70b132 100644 --- a/test/source/platform/store/contact-store.ts +++ b/test/source/platform/store/contact-store.ts @@ -11,7 +11,6 @@ export type ContactUpdate = { email?: string; name?: string | null; pubkey?: Key; - pending_lookup?: number; last_use?: number | null; }; @@ -49,12 +48,11 @@ export class ContactStore { } } - public static obj = async ({ email, name, pubkey, pendingLookup, lastUse, lastCheck }: any): Promise => { + public static obj = async ({ email, name, pubkey, lastUse, lastCheck }: any): Promise => { if (!pubkey) { return { email, name: name || null, - pending_lookup: (pendingLookup ? 1 : 0), pubkey: undefined, has_pgp: 0, // number because we use it for sorting fingerprint: null, @@ -70,7 +68,6 @@ export class ContactStore { pubkey: pk, has_pgp: 1, // number because we use it for sorting fingerprint: pk.id, - pending_lookup: pendingLookup, last_use: lastUse, pubkey_last_check: lastCheck, } as Contact; diff --git a/test/source/tests/browser-unit-tests/unit-ContactStore.js b/test/source/tests/browser-unit-tests/unit-ContactStore.js index 9e7adf51925..2e98caeafcf 100644 --- a/test/source/tests/browser-unit-tests/unit-ContactStore.js +++ b/test/source/tests/browser-unit-tests/unit-ContactStore.js @@ -284,13 +284,11 @@ BROWSER_UNIT_TEST_NAME(`ContactStore.update tests`); const expectedObj1 = { email: email1, name: undefined, - pendingLookup: 0, lastUse: undefined } const expectedObj2 = { email: email2, name: undefined, - pendingLookup: 0, lastUse: undefined } const getEntity = async(email) => { return await new Promise((resolve, reject) => { @@ -302,9 +300,6 @@ BROWSER_UNIT_TEST_NAME(`ContactStore.update tests`); if (loaded.name != expectedObj.name) { throw Error(`name field mismatch, expected ${expectedObj.name} but got ${loaded.name}`); } - if (loaded.pendingLookup != expectedObj.pendingLookup) { - throw Error(`pendingLookup field mismatch, expected ${expectedObj.pendingLookup} but got ${loaded.pendingLookup}`); - } if (loaded.lastUse != expectedObj.lastUse) { throw Error(`lastUse field mismatch, expected ${expectedObj.lastUse} but got ${loaded.lastUse}`); } @@ -317,20 +312,12 @@ BROWSER_UNIT_TEST_NAME(`ContactStore.update tests`); expectedObj1.name = 'New Name for contact 1'; await ContactStore.update(db, email1, { name: expectedObj1.name }); await compareEntities(); - expectedObj2.pendingLookup = 1; - // provide any non-zero value - await ContactStore.update(db, email2, { pending_lookup: 4 }); - await compareEntities(); - expectedObj2.pendingLookup = 0; - // provide a "zero" value - await ContactStore.update(db, email2, { pending_lookup: undefined }); - await compareEntities(); const date = new Date(); - expectedObj1.lastUse = date.getTime(); - await ContactStore.update(db, email1, {last_use: date }); + expectedObj2.lastUse = date.getTime(); + await ContactStore.update(db, email2, {last_use: date }); await compareEntities(); - expectedObj1.lastUse = undefined; - await ContactStore.update(db, email1, {last_use: undefined }); + expectedObj2.lastUse = undefined; + await ContactStore.update(db, email2, {last_use: undefined }); await compareEntities(); return 'pass'; })(); From 759f1a3bfec8cf8c3062b686ef369f39fbe32ffe Mon Sep 17 00:00:00 2001 From: Roman Shevchenko Date: Mon, 29 Mar 2021 07:33:05 -0400 Subject: [PATCH 40/53] S/mime fixes --- extension/js/common/core/crypto/smime/smime-key.ts | 4 ++-- test/source/tests/unit-node.ts | 3 ++- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/extension/js/common/core/crypto/smime/smime-key.ts b/extension/js/common/core/crypto/smime/smime-key.ts index 1526037547d..330639fb3a2 100644 --- a/extension/js/common/core/crypto/smime/smime-key.ts +++ b/extension/js/common/core/crypto/smime/smime-key.ts @@ -107,8 +107,8 @@ export class SmimeKey { expiration: SmimeKey.dateToNumber(certificate.validity.notAfter), fullyDecrypted: false, fullyEncrypted: false, - isPublic: true, - isPrivate: true, + isPublic: certificate.publicKey && !certificate.privateKey, + isPrivate: !!certificate.privateKey, } as Key; (key as unknown as { rawArmored: string }).rawArmored = text; return key; diff --git a/test/source/tests/unit-node.ts b/test/source/tests/unit-node.ts index 34788de42b1..7bb22a97f47 100644 --- a/test/source/tests/unit-node.ts +++ b/test/source/tests/unit-node.ts @@ -1446,7 +1446,8 @@ jA== expect(parsed[0].type).to.be.equal('x509'); expect(parsed[0].emails.length).to.be.equal(1); expect(parsed[0].emails[0]).to.be.equal('test@example.com'); - expect(parsed[0].isPrivate).to.be.equal(true); + expect(parsed[0].isPrivate).to.be.equal(false); + expect(parsed[0].isPublic).to.be.equal(true); t.pass(); }); From 48d72bac6c36485727d7908eb738b8754bd32783 Mon Sep 17 00:00:00 2001 From: Roman Shevchenko Date: Mon, 29 Mar 2021 08:34:25 -0400 Subject: [PATCH 41/53] fix --- test/source/tests/browser-unit-tests/unit-ContactStore.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/source/tests/browser-unit-tests/unit-ContactStore.js b/test/source/tests/browser-unit-tests/unit-ContactStore.js index 2e98caeafcf..06ee99bcd2e 100644 --- a/test/source/tests/browser-unit-tests/unit-ContactStore.js +++ b/test/source/tests/browser-unit-tests/unit-ContactStore.js @@ -337,7 +337,7 @@ BROWSER_UNIT_TEST_NAME(`ContactStore saves and returns dates as numbers`); if (typeof loaded.pubkey_last_check !== 'number') { throw Error(`pubkey_last_check was expected to be a number, but got ${typeof loaded.pubkey_last_check}`); } - if (typeof loaded.last_use !== 'number') { + if (typeof loaded.expiresOn !== 'number') { throw Error(`expiresOn was expected to be a number, but got ${typeof loaded.expiresOn}`); } return 'pass'; From a4a1d119c195e49b0696bbb085b50ddaccd17dc7 Mon Sep 17 00:00:00 2001 From: Roman Shevchenko Date: Tue, 30 Mar 2021 03:00:43 -0400 Subject: [PATCH 42/53] added tests to retrieve a contaqct by longid --- .../browser-unit-tests/unit-ContactStore.js | 184 ++++++++---------- test/source/tests/tooling/consts.ts | 117 +++++++++++ 2 files changed, 194 insertions(+), 107 deletions(-) diff --git a/test/source/tests/browser-unit-tests/unit-ContactStore.js b/test/source/tests/browser-unit-tests/unit-ContactStore.js index 06ee99bcd2e..ce83d38b90a 100644 --- a/test/source/tests/browser-unit-tests/unit-ContactStore.js +++ b/test/source/tests/browser-unit-tests/unit-ContactStore.js @@ -25,116 +25,13 @@ BROWSER_UNIT_TEST_NAME(`ContactStore is able to search by partial email address`); (async () => { const contactABBDEF = await ContactStore.obj({ - email: 'abbdef@test.com', pubkey: `-----BEGIN PGP PUBLIC KEY BLOCK----- - -xsBNBGBKJpABCAC/EABGGXizvq4j96YsI0olYqS+9wSydO2Wn1AkoCCsyY9d7xrqG8UONylrTv0/ -FpF951TnpQiWK3Z0RZcUhtVvLvmgF9+RwW1G2/KMc5SrjcAEhlIqPlXwd3hJfJgD03XtKT4mr8Y/ -MVKLcIZyfn/45I/kWY88qVIKKkeG6NbCoV0zBczqTUsx+Tfij6eAo9iYb+ml2vyuEgZiNTdfkCxI -CzBo7udOcamziz9x8KINJidjwCv0vGO8vhmTQav1sJP71vd5T/t1jghK3DA6uz5GNFoaGG5F3Pl9 -JrgNWkmufuJVMFyC19GUPxm8EPys9yvo8n4Lf1FugeRuIBZPU8K7ABEBAAHND2FiYmRlZkB0ZXN0 -LmNvbcLAiQQTAQgAMxYhBLeQro9CXcRGM6jAht9jZZw7SoH7BQJgSiaUAhsDBQsJCAcCBhUICQoL -AgUWAgMBAAAKCRDfY2WcO0qB+2ADB/94z3/Y0OQAJqvJSIaPtw7NJdyrsh7guahyGKdMFxVLeGmp -dCbW9wHwBJ4vDd4sSt6ufX3iQfrwZn5RHtX97AAJ0kZOPJBTlhDPClU1sDudbStde4UpJ6EtYWV6 -o2CLiFQA8OT0endU1b6uiGDOGkUz98lzyqvKlP6lT3EwW68xSL3NrewNoYJDQox7N9ATznGSbGaG -Jl2STYYA082bpXbgi2cKKwKo3WkSn4iEBEVdrO5yj8PyoOUwE5RK3mCbNLW5KEhY7hHWLz6IO+NC -7xHj4UZdVxtLjZVtui0Ha9qGO5iTs/S3KwhQ/9uA22RUc438vPBdVJ7kDAD+m2SPobWkzsBNBGBK -JpQBCAC+zvtIhjKvb8tYaDXHJPivBNhWYq2xOuUt/yXZs2DPYLEGbiELz/URPW1ew8aYmrtqHg7z -Q47kXz/P5HxZsrMq3bal4mRW02cC9jZT7FrCNI/IE3og3PbHd43spTR8IAz0PDM1huZ/IU0OOBU5 -xjgTRFTGv7eaA39xY0KU8GKtTCXuzPa/3gYby3em2E6tgrCKoicnMtf4uaWIsd6fJ5i5scSBFD93 -0c44U7QgAKuoB90n6887PtNH0voRfrRpLPGQg55WWCHUsx4WdZvPQOw8UPgNVdu+O4+k2xdJ6mEA -Nz0et1bXDy1b91ywpBTqXzdnwBZ2dFxqsiSTTn11i4XlABEBAAHCwHYEGAEIACAWIQS3kK6PQl3E -RjOowIbfY2WcO0qB+wUCYEommAIbDAAKCRDfY2WcO0qB+6OQB/wInDNjHfjnqiorwbAg7mOg5qUl -a9Lrqz9o6ysw9UUV2aof366sy1B3SaYO6gd5vvLF9TTxpgk4ciAjJ8A0m5Xwywmz0chQ766aye/J -IKcMsL1I47EpMbuxMfZWYEamNxEtpuPVuKpJOwVW//obiqTYBBpsPovi9s1j66aSBO6Ij9h9V8FQ -TIxPYXu77fCgpVkxh4RffsGQ8l7H/aRQp8a9sKdCe5X7uOF/6Amw25Rfzm1RN9Yg04bVPxtjD2L1 -SqxF5hZCC16HX1SL4GLY9MjLv2JUw6vlMqjASXKI5MyAlpKAicSe1P5yd/ysM9YiLQHriF3+hYxX -PsqHPr70z6If -=ack7 ------END PGP PUBLIC KEY BLOCK----- -` }); + email: 'abbdef@test.com', pubkey: testConstants.abbdefTestComPubkey }); const contactABCDEF = await ContactStore.obj({ - email: 'abcdef@test.com', pubkey: `-----BEGIN PGP PUBLIC KEY BLOCK----- - -xsBNBGBKJS4BCADxtPTToUQsjy8G88QBeU3a+B0l2CHy6OhsHJkuUOEzM0S++rZqLJJCGdgVAdAF -o+vk1SyaQRQRMMl3mmFc0Qf3kDWARAT7TCvnYGfiR6PyAn7jcyFsY6y88jF6YrjEs30Tz0I8pItC -rmsYGWtoDyBBfaXhZUAAHlRELaG/KzhIt66zowJkP4UwrBnOYzkw7yu4KUcmsOrG7t8XXqJ3LHuQ -Hi6eyWceuFz7Ybsy9qw7GYWxU60NfSnUssQTzRZL9ZeNDzxIjKlpC02SeAyexF9bHuoRbR5GB4QW -byLLcH15U9FTxlf8oHVgP8pD4AEwBrhc+rqLXX6wwT/3G0nFX+LhABEBAAHND2FiY2RlZkB0ZXN0 -LmNvbcLAiQQTAQgAMxYhBDFV8Ri25zKzY4oc4WCLzXl6I/uRBQJgSiUyAhsDBQsJCAcCBhUICQoL -AgUWAgMBAAAKCRBgi815eiP7kdFlCACf7Qf9NZAHfE/CfiZHTTvw+RoLLYKu/Xg4s1uKfGVIe6+w -1wtdy/NHTtf2wWRU/oPC5PK8+P2GjpvwaJIIGCS7sdkeRrRfICxvhYSEGDfvZ2ojLBAz4IGggVcu -YkUc8ZLq1wOgh02wbjbkvIbDPLtPFoK/3hWswPPs+UoheCg1QfEKEpzvvg0NDxO8YhmFqedLYBBu -TSH/b1tIXAdujO2o8U7yUtOe+HZ5f9DHrXf8jiySJ1rZehb2srOt+H9+g7zUXBojqsqzJupuaj8f -5i7g0Hb0c4Kq2NdvoEU7dzFu/Tqy6Pv+ZpgktlOqiFyAwXH8sDBLqeWe8gGAk68lPe/tzsBNBGBK -JTIBCADI7CylGW0udtxCk2FM4lgcjEp1IAbcsarLd8TrxRRus/OTCm9e4FEmB5+nYX3uWhh5WSm7 -zxX3ufjGyIx3fEPOrvjvdiZEUjTansMz+smuMk9+4sWVZcGT1BC0f7zGNfu3Hd2VbXOA763HdQnd -4S4oycj/aOBCjth1pgVMaEJEJAozC8uzzYBp4guMolreC2Xu1UixpPn2N/+ZjJzHDdIwC4yXjYT4 -xz3IoPwl/XVsXZOofQR+v88AhmsHtXbJy0pGpY9jtd8gncq3DMFcOhACoU0o1wu7FwRDLtjR5W8R -yBAShf78spBzuBCunSfxw2Bv3ak+b43jtN77TrTZrF2xABEBAAHCwHYEGAEIACAWIQQxVfEYtucy -s2OKHOFgi815eiP7kQUCYEolOQIbDAAKCRBgi815eiP7kUMoB/0SvYvjthGGhzrHXHC2WusC6rEN -Szp7FrUbc5upp2dktVmH62jC649K9lsoJUhitcE8E2C+lLToIJMhsNIXgPP7Ai+a6dJn6LKwT95b -RZGNIk/dQehU53g0BNdsWDCBUa92vFmtngQ34nwM40iiYLraioCah9/yZGdANFAEFr4iA2mmfBlt -j3kOljjta/iqbEO0hWSVwUT7D7ljitU4/BOmyT0n10ra7FtUMfMzVHrvJZGjEkrk8DjVLunPkkqj -kM3d32EJ1lZdub6GcDURdWNaOd9FmNGizKYYu1Wgeik0SnrhCy6DGLT+JDfv1/arwK2s1Usi2SOq -X7O4C+D4oKVA -=a/tS ------END PGP PUBLIC KEY BLOCK----- -` }); + email: 'abcdef@test.com', pubkey: testConstants.abcdefTestComPubkey }); const contactABCDDF = await ContactStore.obj({ - email: 'abcddf@test.com', pubkey: `-----BEGIN PGP PUBLIC KEY BLOCK----- - -xsBNBGBKJS4BCADxtPTToUQsjy8G88QBeU3a+B0l2CHy6OhsHJkuUOEzM0S++rZqLJJCGdgVAdAF -o+vk1SyaQRQRMMl3mmFc0Qf3kDWARAT7TCvnYGfiR6PyAn7jcyFsY6y88jF6YrjEs30Tz0I8pItC -rmsYGWtoDyBBfaXhZUAAHlRELaG/KzhIt66zowJkP4UwrBnOYzkw7yu4KUcmsOrG7t8XXqJ3LHuQ -Hi6eyWceuFz7Ybsy9qw7GYWxU60NfSnUssQTzRZL9ZeNDzxIjKlpC02SeAyexF9bHuoRbR5GB4QW -byLLcH15U9FTxlf8oHVgP8pD4AEwBrhc+rqLXX6wwT/3G0nFX+LhABEBAAHND2FiY2RlZkB0ZXN0 -LmNvbcLAiQQTAQgAMxYhBDFV8Ri25zKzY4oc4WCLzXl6I/uRBQJgSiUyAhsDBQsJCAcCBhUICQoL -AgUWAgMBAAAKCRBgi815eiP7kdFlCACf7Qf9NZAHfE/CfiZHTTvw+RoLLYKu/Xg4s1uKfGVIe6+w -1wtdy/NHTtf2wWRU/oPC5PK8+P2GjpvwaJIIGCS7sdkeRrRfICxvhYSEGDfvZ2ojLBAz4IGggVcu -YkUc8ZLq1wOgh02wbjbkvIbDPLtPFoK/3hWswPPs+UoheCg1QfEKEpzvvg0NDxO8YhmFqedLYBBu -TSH/b1tIXAdujO2o8U7yUtOe+HZ5f9DHrXf8jiySJ1rZehb2srOt+H9+g7zUXBojqsqzJupuaj8f -5i7g0Hb0c4Kq2NdvoEU7dzFu/Tqy6Pv+ZpgktlOqiFyAwXH8sDBLqeWe8gGAk68lPe/tzsBNBGBK -JTIBCADI7CylGW0udtxCk2FM4lgcjEp1IAbcsarLd8TrxRRus/OTCm9e4FEmB5+nYX3uWhh5WSm7 -zxX3ufjGyIx3fEPOrvjvdiZEUjTansMz+smuMk9+4sWVZcGT1BC0f7zGNfu3Hd2VbXOA763HdQnd -4S4oycj/aOBCjth1pgVMaEJEJAozC8uzzYBp4guMolreC2Xu1UixpPn2N/+ZjJzHDdIwC4yXjYT4 -xz3IoPwl/XVsXZOofQR+v88AhmsHtXbJy0pGpY9jtd8gncq3DMFcOhACoU0o1wu7FwRDLtjR5W8R -yBAShf78spBzuBCunSfxw2Bv3ak+b43jtN77TrTZrF2xABEBAAHCwHYEGAEIACAWIQQxVfEYtucy -s2OKHOFgi815eiP7kQUCYEolOQIbDAAKCRBgi815eiP7kUMoB/0SvYvjthGGhzrHXHC2WusC6rEN -Szp7FrUbc5upp2dktVmH62jC649K9lsoJUhitcE8E2C+lLToIJMhsNIXgPP7Ai+a6dJn6LKwT95b -RZGNIk/dQehU53g0BNdsWDCBUa92vFmtngQ34nwM40iiYLraioCah9/yZGdANFAEFr4iA2mmfBlt -j3kOljjta/iqbEO0hWSVwUT7D7ljitU4/BOmyT0n10ra7FtUMfMzVHrvJZGjEkrk8DjVLunPkkqj -kM3d32EJ1lZdub6GcDURdWNaOd9FmNGizKYYu1Wgeik0SnrhCy6DGLT+JDfv1/arwK2s1Usi2SOq -X7O4C+D4oKVA -=a/tS ------END PGP PUBLIC KEY BLOCK----- -` }); + email: 'abcddf@test.com', pubkey: testConstants.abcddfTestComPubkey }); const contactABDDEF = await ContactStore.obj({ - email: 'abddef@test.com', pubkey: `-----BEGIN PGP PUBLIC KEY BLOCK----- - -xsBNBGBKJqQBCACzNxbtfJMZdrhzTpV34rEy4t50Q/8jwo4+z7GLPX6vSmHGy/Y4fOBsae5rXMr9 -v02IAdoLgGTbPqSa5fDPWAbiyNL/M/5ojBwAzHBChWyD2543M1XOOOAgUm2dKospBww4RyavkE4t -Ng3HIY/eWtm0sDGuYYkwvrgu5Puc+1kMegdBkE1CkkNd/jC/EJnnYs3WDaVd1h1is/IxKJ8xjTQD -Rc2+YJYxCT5+KxRFlApqXogJDhPQEK+S8Rl/nMunxMVq9ls2ixdsnWdvA3+4xbRN6WLDKy/mx8XD -OqvOpXQ8f9rXiwwjW8EsoCFrTvCdh2JaY0uPqRtfcrQodhidAUMXABEBAAHND2FiZGRlZkB0ZXN0 -LmNvbcLAiQQTAQgAMxYhBJ4CDZt1L9P/8X7Ztl/MFUHPKClRBQJgSiaoAhsDBQsJCAcCBhUICQoL -AgUWAgMBAAAKCRBfzBVBzygpUYZ7CACxwRjeDlaHQCNsV+yG3gwXorKBHmMVZ++pO2fjCWRIwQA6 -DfkQ//tjudLwLIZRNFgdn9T04XEX3p65wkhK8vbyhTk18VS57NPLFpSjOrkhXd0JNgMNI0LVcOp9 -gPkgQZ7qBlRh1rpZiZyO/sccJAb0RfLzbaMl7BOKOKsAvUOGT4eiIjp+37/HsrYvOaJkzt8vI4dx -RPuJ5rWJPrlnJuPO1im0hsi7dj9XrVdWth58AyMvQ5JjbAid9b42VZ4HuB1P+PSiDeUQq4O9ISWA -ZtKsfTusZSQP/Bq9jZf+ucdRIM7eo6NCY3X4jFefjsWdi7mofcFZCowMTc+PkCMCBsmlzsBNBGBK -JqgBCADSGmHgTuMTfCvoRbzJ14i2WtFFODl7BwY4U8NZG6YcNv7QsCWIqJkwBIzX1OquO8PiZd4D -AKuYpuG2KCF/vLNFkkq5BWkiMrGIZ7QYvtQFD+BwbAfREcs6ZUMm22eTrdqgs1o5vsDYGGsN36Qh -ClIDFcUwlpb/35ryrp50GjLFaKjdgBFhksKOY6ZJRJNZcq+i+ii4FizEiJ23vfrPWPByVip1jx4L -+MlYCG102pNPrnaBnU02tj+tXwfHDXVT5QygO7nX2YM96wTIVxH8seatyjDUK668PQYmT5vGQKl2 -Ikr+orTzqJhMWN0gjA/EHRcpuQn2EJrTVi4+4oU6dZBzABEBAAHCwHYEGAEIACAWIQSeAg2bdS/T -//F+2bZfzBVBzygpUQUCYEomrQIbDAAKCRBfzBVBzygpUQFYB/9WfGAfJb3lIWEBIUE8viIH74B1 -/E1lpWbGbyzSjJAUsFiEAwM0gRaYr9pY2iVsRJwr0dmmhSsESwSy0/dD97jCqjD4d/AkiSxmEMlA -F9PCnKC7HizaM33lA1S0pADBBEVtwfLd4t0bAo4TnJWnjb/fd9osyPEZGU1zF/fFsfLAIb9GC9VB -5nRZgXIUeTZDCypk0fCc25kGVO3i8H37eRXonV3TcmNEgYUBvi/3Pk3s/7GUkpp1cKtn4s7MnHzO -wBff8jybIDc7uGSzTW5qc/3qcgbfH0FGCoIy20H7zgnEJ6PnkENlb/WfynSHAXvfMc8r9YLTCrkv -WmiyOmaRmLP+ -=Iusd ------END PGP PUBLIC KEY BLOCK-----` }); + email: 'abddef@test.com', pubkey: testConstants.abddefTestComPubkey }); await ContactStore.save(undefined, contactABBDEF); await ContactStore.save(undefined, contactABCDEF); await ContactStore.save(undefined, contactABCDDF); @@ -342,3 +239,76 @@ BROWSER_UNIT_TEST_NAME(`ContactStore saves and returns dates as numbers`); } return 'pass'; })(); + +BROWSER_UNIT_TEST_NAME(`ContactStore gets a contact by any longid`); +(async () => { + const contactABBDEF = await ContactStore.obj({ + email: 'abbdef@test.com', pubkey: testConstants.abbdefTestComPubkey }); + const contactABCDEF = await ContactStore.obj({ + email: 'abcdef@test.com', pubkey: testConstants.abcdefTestComPubkey }); + const contactABCDDF = await ContactStore.obj({ + email: 'abcddf@test.com', pubkey: testConstants.abcddfTestComPubkey }); + const contactABDDEF = await ContactStore.obj({ + email: 'abddef@test.com', pubkey: testConstants.abddefTestComPubkey }); + await ContactStore.save(undefined, [contactABBDEF, contactABCDEF, contactABCDDF, contactABDDEF]); + const [abbdefByPrimaryLongid] = await ContactStore.get(undefined, ['DF63659C3B4A81FB']); + if (abbdefByPrimaryLongid.email !== 'abbdef@test.com') { + throw Error(`Expected to get the key for abbdef@test.com by primary longid but got ${abbdefByPrimaryLongid.email}`) + } + if (abbdefByPrimaryLongid.pubkey.id !== 'B790AE8F425DC44633A8C086DF63659C3B4A81FB') { + throw Error(`Expected to get the key fingerprint B790AE8F425DC44633A8C086DF63659C3B4A81FB but got ${abbdefByPrimaryLongid.pubkey.id}`) + } + const [abbdefBySubkeyLongid] = await ContactStore.get(undefined, ['621DE1814AD675E0']); + if (abbdefBySubkeyLongid.email !== 'abbdef@test.com') { + throw Error(`Expected to get the key for abbdef@test.com by subkey longid but got ${abbdefBySubkeyLongid.email}`) + } + if (abbdefBySubkeyLongid.pubkey.id !== 'B790AE8F425DC44633A8C086DF63659C3B4A81FB') { + throw Error(`Expected to get the key fingerprint B790AE8F425DC44633A8C086DF63659C3B4A81FB but got ${abbdefBySubkeyLongid.pubkey.id}`) + } + + const [abcdefByPrimaryLongid] = await ContactStore.get(undefined, ['608BCD797A23FB91']); + if (abcdefByPrimaryLongid.email !== 'abcdef@test.com') { + throw Error(`Expected to get the key for abcdef@test.com by primary longid but got ${abcdefByPrimaryLongid.email}`) + } + if (abcdefByPrimaryLongid.pubkey.id !== '3155F118B6E732B3638A1CE1608BCD797A23FB91') { + throw Error(`Expected to get the key fingerprint 3155F118B6E732B3638A1CE1608BCD797A23FB91 but got ${abcdefByPrimaryLongid.pubkey.id}`) + } + const [abcdefBySubkeyLongid] = await ContactStore.get(undefined, ['2D47A41943DFAFCE']); + if (abcdefBySubkeyLongid.email !== 'abcdef@test.com') { + throw Error(`Expected to get the key for abcdef@test.com by subkey longid but got ${abcdefBySubkeyLongid.email}`) + } + if (abcdefBySubkeyLongid.pubkey.id !== '3155F118B6E732B3638A1CE1608BCD797A23FB91') { + throw Error(`Expected to get the key fingerprint 3155F118B6E732B3638A1CE1608BCD797A23FB91 but got ${abcdefBySubkeyLongid.pubkey.id}`) + } + + const [abcddfByPrimaryLongid] = await ContactStore.get(undefined, ['75AA44AB8930F7E9']); + if (abcddfByPrimaryLongid.email !== 'abcddf@test.com') { + throw Error(`Expected to get the key for abcddf@test.com by primary longid but got ${abcddfByPrimaryLongid.email}`) + } + if (abcddfByPrimaryLongid.pubkey.id !== '6CF53D2329C2A80828F499D375AA44AB8930F7E9') { + throw Error(`Expected to get the key fingerprint 6CF53D2329C2A80828F499D375AA44AB8930F7E9 but got ${abcddfByPrimaryLongid.pubkey.id}`) + } + const [abcddfBySubkeyLongid] = await ContactStore.get(undefined, ['92CFDAC7AA3A4253']); + if (abcddfBySubkeyLongid.email !== 'abcddf@test.com') { + throw Error(`Expected to get the key for abcddf@test.com by subkey longid but got ${abcddfBySubkeyLongid.email}`) + } + if (abcddfBySubkeyLongid.pubkey.id !== '6CF53D2329C2A80828F499D375AA44AB8930F7E9') { + throw Error(`Expected to get the key fingerprint 6CF53D2329C2A80828F499D375AA44AB8930F7E9 but got ${abcddfBySubkeyLongid.pubkey.id}`) + } + + const [abddefByPrimaryLongid] = await ContactStore.get(undefined, ['5FCC1541CF282951']); + if (abddefByPrimaryLongid.email !== 'abddef@test.com') { + throw Error(`Expected to get the key for abddef@test.com by primary longid but got ${abddefByPrimaryLongid.email}`) + } + if (abddefByPrimaryLongid.pubkey.id !== '9E020D9B752FD3FFF17ED9B65FCC1541CF282951') { + throw Error(`Expected to get the key fingerprint 9E020D9B752FD3FFF17ED9B65FCC1541CF282951 but got ${abddefByPrimaryLongid.pubkey.id}`) + } + const [abddefBySubkeyLongid] = await ContactStore.get(undefined, ['EAA7A05FE34F3A1A']); + if (abddefBySubkeyLongid.email !== 'abddef@test.com') { + throw Error(`Expected to get the key for abddef@test.com by subkey longid but got ${abddefBySubkeyLongid.email}`) + } + if (abddefBySubkeyLongid.pubkey.id !== '9E020D9B752FD3FFF17ED9B65FCC1541CF282951') { + throw Error(`Expected to get the key fingerprint 9E020D9B752FD3FFF17ED9B65FCC1541CF282951 but got ${abddefBySubkeyLongid.pubkey.id}`) + } + return 'pass'; +})(); \ No newline at end of file diff --git a/test/source/tests/tooling/consts.ts b/test/source/tests/tooling/consts.ts index dae046d5f3a..6b09073279e 100644 --- a/test/source/tests/tooling/consts.ts +++ b/test/source/tests/tooling/consts.ts @@ -525,5 +525,122 @@ wfn9cM5lL3XLo+VR0CN8NLfj8h4yVLxIzVAiUGQseonXy+JA0erD2Jht/nns fm8L3l8EuB2q1535rkqr/uHHyx+th0vWUnK2IvRWAZZLQZUvVxkxTCG++7xv Eg== =r2et +-----END PGP PUBLIC KEY BLOCK-----`, + abbdefTestComPubkey: + // primary fingerprint: B790 AE8F 425D C446 33A8 C086 DF63 659C 3B4A 81FB + // subkey longid: 621DE1814AD675E0 + `-----BEGIN PGP PUBLIC KEY BLOCK----- + +xsBNBGBKJpABCAC/EABGGXizvq4j96YsI0olYqS+9wSydO2Wn1AkoCCsyY9d7xrqG8UONylrTv0/ +FpF951TnpQiWK3Z0RZcUhtVvLvmgF9+RwW1G2/KMc5SrjcAEhlIqPlXwd3hJfJgD03XtKT4mr8Y/ +MVKLcIZyfn/45I/kWY88qVIKKkeG6NbCoV0zBczqTUsx+Tfij6eAo9iYb+ml2vyuEgZiNTdfkCxI +CzBo7udOcamziz9x8KINJidjwCv0vGO8vhmTQav1sJP71vd5T/t1jghK3DA6uz5GNFoaGG5F3Pl9 +JrgNWkmufuJVMFyC19GUPxm8EPys9yvo8n4Lf1FugeRuIBZPU8K7ABEBAAHND2FiYmRlZkB0ZXN0 +LmNvbcLAiQQTAQgAMxYhBLeQro9CXcRGM6jAht9jZZw7SoH7BQJgSiaUAhsDBQsJCAcCBhUICQoL +AgUWAgMBAAAKCRDfY2WcO0qB+2ADB/94z3/Y0OQAJqvJSIaPtw7NJdyrsh7guahyGKdMFxVLeGmp +dCbW9wHwBJ4vDd4sSt6ufX3iQfrwZn5RHtX97AAJ0kZOPJBTlhDPClU1sDudbStde4UpJ6EtYWV6 +o2CLiFQA8OT0endU1b6uiGDOGkUz98lzyqvKlP6lT3EwW68xSL3NrewNoYJDQox7N9ATznGSbGaG +Jl2STYYA082bpXbgi2cKKwKo3WkSn4iEBEVdrO5yj8PyoOUwE5RK3mCbNLW5KEhY7hHWLz6IO+NC +7xHj4UZdVxtLjZVtui0Ha9qGO5iTs/S3KwhQ/9uA22RUc438vPBdVJ7kDAD+m2SPobWkzsBNBGBK +JpQBCAC+zvtIhjKvb8tYaDXHJPivBNhWYq2xOuUt/yXZs2DPYLEGbiELz/URPW1ew8aYmrtqHg7z +Q47kXz/P5HxZsrMq3bal4mRW02cC9jZT7FrCNI/IE3og3PbHd43spTR8IAz0PDM1huZ/IU0OOBU5 +xjgTRFTGv7eaA39xY0KU8GKtTCXuzPa/3gYby3em2E6tgrCKoicnMtf4uaWIsd6fJ5i5scSBFD93 +0c44U7QgAKuoB90n6887PtNH0voRfrRpLPGQg55WWCHUsx4WdZvPQOw8UPgNVdu+O4+k2xdJ6mEA +Nz0et1bXDy1b91ywpBTqXzdnwBZ2dFxqsiSTTn11i4XlABEBAAHCwHYEGAEIACAWIQS3kK6PQl3E +RjOowIbfY2WcO0qB+wUCYEommAIbDAAKCRDfY2WcO0qB+6OQB/wInDNjHfjnqiorwbAg7mOg5qUl +a9Lrqz9o6ysw9UUV2aof366sy1B3SaYO6gd5vvLF9TTxpgk4ciAjJ8A0m5Xwywmz0chQ766aye/J +IKcMsL1I47EpMbuxMfZWYEamNxEtpuPVuKpJOwVW//obiqTYBBpsPovi9s1j66aSBO6Ij9h9V8FQ +TIxPYXu77fCgpVkxh4RffsGQ8l7H/aRQp8a9sKdCe5X7uOF/6Amw25Rfzm1RN9Yg04bVPxtjD2L1 +SqxF5hZCC16HX1SL4GLY9MjLv2JUw6vlMqjASXKI5MyAlpKAicSe1P5yd/ysM9YiLQHriF3+hYxX +PsqHPr70z6If +=ack7 +-----END PGP PUBLIC KEY BLOCK-----`, + abcdefTestComPubkey: + // primary fingerprint: 3155 F118 B6E7 32B3 638A 1CE1 608B CD79 7A23 FB91 + // subkey longid: 2D47A41943DFAFCE + `-----BEGIN PGP PUBLIC KEY BLOCK----- + +xsBNBGBKJS4BCADxtPTToUQsjy8G88QBeU3a+B0l2CHy6OhsHJkuUOEzM0S++rZqLJJCGdgVAdAF +o+vk1SyaQRQRMMl3mmFc0Qf3kDWARAT7TCvnYGfiR6PyAn7jcyFsY6y88jF6YrjEs30Tz0I8pItC +rmsYGWtoDyBBfaXhZUAAHlRELaG/KzhIt66zowJkP4UwrBnOYzkw7yu4KUcmsOrG7t8XXqJ3LHuQ +Hi6eyWceuFz7Ybsy9qw7GYWxU60NfSnUssQTzRZL9ZeNDzxIjKlpC02SeAyexF9bHuoRbR5GB4QW +byLLcH15U9FTxlf8oHVgP8pD4AEwBrhc+rqLXX6wwT/3G0nFX+LhABEBAAHND2FiY2RlZkB0ZXN0 +LmNvbcLAiQQTAQgAMxYhBDFV8Ri25zKzY4oc4WCLzXl6I/uRBQJgSiUyAhsDBQsJCAcCBhUICQoL +AgUWAgMBAAAKCRBgi815eiP7kdFlCACf7Qf9NZAHfE/CfiZHTTvw+RoLLYKu/Xg4s1uKfGVIe6+w +1wtdy/NHTtf2wWRU/oPC5PK8+P2GjpvwaJIIGCS7sdkeRrRfICxvhYSEGDfvZ2ojLBAz4IGggVcu +YkUc8ZLq1wOgh02wbjbkvIbDPLtPFoK/3hWswPPs+UoheCg1QfEKEpzvvg0NDxO8YhmFqedLYBBu +TSH/b1tIXAdujO2o8U7yUtOe+HZ5f9DHrXf8jiySJ1rZehb2srOt+H9+g7zUXBojqsqzJupuaj8f +5i7g0Hb0c4Kq2NdvoEU7dzFu/Tqy6Pv+ZpgktlOqiFyAwXH8sDBLqeWe8gGAk68lPe/tzsBNBGBK +JTIBCADI7CylGW0udtxCk2FM4lgcjEp1IAbcsarLd8TrxRRus/OTCm9e4FEmB5+nYX3uWhh5WSm7 +zxX3ufjGyIx3fEPOrvjvdiZEUjTansMz+smuMk9+4sWVZcGT1BC0f7zGNfu3Hd2VbXOA763HdQnd +4S4oycj/aOBCjth1pgVMaEJEJAozC8uzzYBp4guMolreC2Xu1UixpPn2N/+ZjJzHDdIwC4yXjYT4 +xz3IoPwl/XVsXZOofQR+v88AhmsHtXbJy0pGpY9jtd8gncq3DMFcOhACoU0o1wu7FwRDLtjR5W8R +yBAShf78spBzuBCunSfxw2Bv3ak+b43jtN77TrTZrF2xABEBAAHCwHYEGAEIACAWIQQxVfEYtucy +s2OKHOFgi815eiP7kQUCYEolOQIbDAAKCRBgi815eiP7kUMoB/0SvYvjthGGhzrHXHC2WusC6rEN +Szp7FrUbc5upp2dktVmH62jC649K9lsoJUhitcE8E2C+lLToIJMhsNIXgPP7Ai+a6dJn6LKwT95b +RZGNIk/dQehU53g0BNdsWDCBUa92vFmtngQ34nwM40iiYLraioCah9/yZGdANFAEFr4iA2mmfBlt +j3kOljjta/iqbEO0hWSVwUT7D7ljitU4/BOmyT0n10ra7FtUMfMzVHrvJZGjEkrk8DjVLunPkkqj +kM3d32EJ1lZdub6GcDURdWNaOd9FmNGizKYYu1Wgeik0SnrhCy6DGLT+JDfv1/arwK2s1Usi2SOq +X7O4C+D4oKVA +=a/tS +-----END PGP PUBLIC KEY BLOCK-----`, + abcddfTestComPubkey: + // primary fingerprint: 6CF5 3D23 29C2 A808 28F4 99D3 75AA 44AB 8930 F7E9 + // subkey longid: 92CFDAC7AA3A4253 + `-----BEGIN PGP PUBLIC KEY BLOCK----- + +xsBNBGBh8bEBCACo6CHZfPEUlISpb6fl/J8mqStDIJzwlPcx7i60hc+tXToQOZ/28yDlJF3jiZ42 +PEqysKAN34MtwhhRxfa2LG5i2hmIKjGE8gPPgGNj03e+carenBRFXkdU41xL98ZZYPBaCOvwYAUS +7g5TpUIXnO2RG/eeOzwPrDRONSshK2SaqF4PUzncWARt0XEI4k8Ka9bmf/nZz2b2CV9XLW7py2FJ +5NwGB/ZDSAS1lU7ZwkCSlVCTAR6zVPozmwP3gn/ZScMNbnjsFFpv96EZhNIvnpHIeey1qofwlstr +MKhkWyUMU112smrlZ9PTmpjAkuBaETKX0aRXX22lHJOqDft5SBorABEBAAHND2FiY2RkZkB0ZXN0 +LmNvbcLAiQQTAQgAMxYhBGz1PSMpwqgIKPSZ03WqRKuJMPfpBQJgYfG7AhsDBQsJCAcCBhUICQoL +AgUWAgMBAAAKCRB1qkSriTD36bAUB/44+PErfacT31aqfAHWbz9udYo6UE20LDaD+zU+RM/EAvxg +v7FM7VeMQ7gFLQ8zmQKmPnXP5F5mQ1VtWSuH+RTngWpAUHjTE9GfAPbMS2rgS1n+NaaJwwAqSFZK +/UzHPykNTHoACRLrxw66xth64uhm06UxwG48GDpK2BioRfxFwQhxZKNx20JbNYS5KgBRijhwBBGB +U/U+kMuZPHyvU1fTtKlfDL9PhWGmxeruJ9s2KeeYp0zIwlgatchxynmjzBHFaWQHdgQ8VH5oSGMT +Aa7jvrCh66E8rIuWnMj+Oi2ty0A3S/zUFuP0tHlsUUzhcROv5g68B33SgjsvPIGxPrqnzsBNBGBh +8bsBCAC4+XAJ7OZar43QI2F9J2kvitrhQPh3HZCwsTz0OauZNMA3veeeRMXkHzv34xtWfDY4giNc +Y0UoulkTvlemSAuj6+s7awO1VS25B01okoprjDM4B0BjDPeCyanOht7UN9raM2xIcUxhBTlj1UVf +J8jSSN3CjgOFVpOzz5zkzR1WKnrXHHW2DZ3kPJh9RVRyw3QcOOdyiAkUgyK+Xmt1JIvCKc+wzZuf +fZzTspn/SokW9n2MIowuVMxxTtZK0vA18U20Ile49RTTdiExV+xNnIFFezL+Xi+HtCPlDi6L5SsH +ccElgpamS6UmF88B/ZutVbpB+5bUYPxkU29kR8L0PFhhABEBAAHCwHYEGAEIACAWIQRs9T0jKcKo +CCj0mdN1qkSriTD36QUCYGHxwQIbDAAKCRB1qkSriTD36WEIB/9Zg8isMsuHCvUHt9YfPZX3qOja +xQq2l/lRQ4IbHBrgrxv1bQIev5ki0VO4Jjt5rzcgeg0AVa8pNe5YIZ11iuWayNu9oghp+rNXSeNi +SuwxbPtzfX7ZC0DpYMqWQpIdG0IQBgmbVytyrgpGXG/aYGBntu7sQUrsKFUnqwcNV2QZ0RCGLKlo +yfPvA+3SNboGl5FXRso2sOm5DP+nWiUAaaE97+eU9lLNDr92PE1CAfZqFl+UjNSQVyFikcS6BFll +/eKFk6t5Enhc7tkz5ZefLA51v+G1V8Vb7EPd2hCe7NPySajDlrg4djcch3AaUi+MkCbajYbwD6aX +R4BlrEu7r7Fg +=LG9y +-----END PGP PUBLIC KEY BLOCK----- +`, + abddefTestComPubkey: + // primary fingerprint: 9E02 0D9B 752F D3FF F17E D9B6 5FCC 1541 CF28 2951 + // subkey longid: EAA7A05FE34F3A1A + `-----BEGIN PGP PUBLIC KEY BLOCK----- + +xsBNBGBKJqQBCACzNxbtfJMZdrhzTpV34rEy4t50Q/8jwo4+z7GLPX6vSmHGy/Y4fOBsae5rXMr9 +v02IAdoLgGTbPqSa5fDPWAbiyNL/M/5ojBwAzHBChWyD2543M1XOOOAgUm2dKospBww4RyavkE4t +Ng3HIY/eWtm0sDGuYYkwvrgu5Puc+1kMegdBkE1CkkNd/jC/EJnnYs3WDaVd1h1is/IxKJ8xjTQD +Rc2+YJYxCT5+KxRFlApqXogJDhPQEK+S8Rl/nMunxMVq9ls2ixdsnWdvA3+4xbRN6WLDKy/mx8XD +OqvOpXQ8f9rXiwwjW8EsoCFrTvCdh2JaY0uPqRtfcrQodhidAUMXABEBAAHND2FiZGRlZkB0ZXN0 +LmNvbcLAiQQTAQgAMxYhBJ4CDZt1L9P/8X7Ztl/MFUHPKClRBQJgSiaoAhsDBQsJCAcCBhUICQoL +AgUWAgMBAAAKCRBfzBVBzygpUYZ7CACxwRjeDlaHQCNsV+yG3gwXorKBHmMVZ++pO2fjCWRIwQA6 +DfkQ//tjudLwLIZRNFgdn9T04XEX3p65wkhK8vbyhTk18VS57NPLFpSjOrkhXd0JNgMNI0LVcOp9 +gPkgQZ7qBlRh1rpZiZyO/sccJAb0RfLzbaMl7BOKOKsAvUOGT4eiIjp+37/HsrYvOaJkzt8vI4dx +RPuJ5rWJPrlnJuPO1im0hsi7dj9XrVdWth58AyMvQ5JjbAid9b42VZ4HuB1P+PSiDeUQq4O9ISWA +ZtKsfTusZSQP/Bq9jZf+ucdRIM7eo6NCY3X4jFefjsWdi7mofcFZCowMTc+PkCMCBsmlzsBNBGBK +JqgBCADSGmHgTuMTfCvoRbzJ14i2WtFFODl7BwY4U8NZG6YcNv7QsCWIqJkwBIzX1OquO8PiZd4D +AKuYpuG2KCF/vLNFkkq5BWkiMrGIZ7QYvtQFD+BwbAfREcs6ZUMm22eTrdqgs1o5vsDYGGsN36Qh +ClIDFcUwlpb/35ryrp50GjLFaKjdgBFhksKOY6ZJRJNZcq+i+ii4FizEiJ23vfrPWPByVip1jx4L ++MlYCG102pNPrnaBnU02tj+tXwfHDXVT5QygO7nX2YM96wTIVxH8seatyjDUK668PQYmT5vGQKl2 +Ikr+orTzqJhMWN0gjA/EHRcpuQn2EJrTVi4+4oU6dZBzABEBAAHCwHYEGAEIACAWIQSeAg2bdS/T +//F+2bZfzBVBzygpUQUCYEomrQIbDAAKCRBfzBVBzygpUQFYB/9WfGAfJb3lIWEBIUE8viIH74B1 +/E1lpWbGbyzSjJAUsFiEAwM0gRaYr9pY2iVsRJwr0dmmhSsESwSy0/dD97jCqjD4d/AkiSxmEMlA +F9PCnKC7HizaM33lA1S0pADBBEVtwfLd4t0bAo4TnJWnjb/fd9osyPEZGU1zF/fFsfLAIb9GC9VB +5nRZgXIUeTZDCypk0fCc25kGVO3i8H37eRXonV3TcmNEgYUBvi/3Pk3s/7GUkpp1cKtn4s7MnHzO +wBff8jybIDc7uGSzTW5qc/3qcgbfH0FGCoIy20H7zgnEJ6PnkENlb/WfynSHAXvfMc8r9YLTCrkv +WmiyOmaRmLP+ +=Iusd -----END PGP PUBLIC KEY BLOCK-----` }; \ No newline at end of file From 280ae2fd3e2476e607429063675323fd2c686cc6 Mon Sep 17 00:00:00 2001 From: Roman Shevchenko Date: Tue, 30 Mar 2021 03:23:57 -0400 Subject: [PATCH 43/53] removed todo --- extension/js/common/platform/store/contact-store.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/extension/js/common/platform/store/contact-store.ts b/extension/js/common/platform/store/contact-store.ts index 4255c46b56f..3569ff1c511 100644 --- a/extension/js/common/platform/store/contact-store.ts +++ b/extension/js/common/platform/store/contact-store.ts @@ -451,7 +451,7 @@ export class ContactStore extends AbstractStore { if (!email) { resolve(undefined); } else { - resolve(ContactStore.toContact(email, pubkey)); // todo: test + resolve(ContactStore.toContact(email, pubkey)); } }, reject); From 51edae29dd1276f014b5149b80c698db6137360b Mon Sep 17 00:00:00 2001 From: Roman Shevchenko Date: Tue, 30 Mar 2021 05:01:46 -0400 Subject: [PATCH 44/53] Added test for valid key extraction --- test/source/mock/wkd/wkd-endpoints.ts | 97 +------------------ .../browser-unit-tests/unit-ContactStore.js | 22 ++++- test/source/tests/tooling/consts.ts | 94 ++++++++++++++++++ 3 files changed, 118 insertions(+), 95 deletions(-) diff --git a/test/source/mock/wkd/wkd-endpoints.ts b/test/source/mock/wkd/wkd-endpoints.ts index 484027ea67a..4fda8e8057b 100644 --- a/test/source/mock/wkd/wkd-endpoints.ts +++ b/test/source/mock/wkd/wkd-endpoints.ts @@ -98,97 +98,6 @@ nmusEeYtrrMytL4oUohBVZk= -----END PGP PUBLIC KEY BLOCK----- `; -const validAmongRevokedRevoked1 = ` ------BEGIN PGP PUBLIC KEY BLOCK----- - -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 -=vqJ0 ------END PGP PUBLIC KEY BLOCK-----`; - -const validAmongRevokedValid = `-----BEGIN PGP PUBLIC KEY BLOCK----- - -xsBNBGAeYQ0BCADHMOjbN/X/TH4JpTz7Sj1VTGIeXzWUVZIbsjLgp8U0dFo2zWXMsgLsnNAZuL43 -pUAnIqw+wvUcSpndEO79upVvUzc1qgvp2DTJuDrVGAPx1cqKOi3A/XPO0uIxTyCChcQBQ+YUvwc6 -7ZU69irRC320AQC5aFrL+yP7RmlWQgslJ0qJXPa3On6Cp71GL26iADPXnQOqZtmhv87nYlHhimOv -bKLtC/YMTqGk0h7HqNQPcP8B6bylofS/7Rgy+JKsqWmlng+U/0uQWsnfIua0BPkrZYwJdaF77cs1 -7A2LV2glUiG7XzPkHPTMtG3xV7ZbiAsLSwWN7x1mG3uvpppeXkd1ABEBAAHNFnNvbWUucmV2b2tl -ZEBsb2NhbGhvc3TCwIkEEwEIADMWIQTWZixfub3p2gHzmUqqHvgy2Myk8gUCYB5hFQIbAwULCQgH -AgYVCAkKCwIFFgIDAQAACgkQqh74MtjMpPI7JQf8Dnw4XZLgR8lZV0S4e9JhG/cQqhIzXKVAFcMF -EPWVEHfUYTBCDmTPpi4m9rl5P9T70TXjMbpb6BzvuTS+OZHfyaj8YB39C5FKtqEemoMyO+VO5t7b -I4jUMG3Uu2kuwgN6I2g8jYeA6SYcoUN6NHIpQTkS2BW2IICWqUh09EfcVvdQbZKbMLaoQLfJvTze -gH7LPuNxsvfuhVPtL9WzOIgSFKDmfQnpHluJRKcAhK+aahtUetdsBemBrP7JbNIreIb6+qhmX4q5 -8uGVUFrucSjRwFqqlxSo63ze1jsyzpOvfdzsaDMOG1yIX28cqfOZJpDft5nQjnznjSTJ3I6tGHtL -qs7ATQRgHmEVAQgArN5xkxz80Cbfm9UOT3U5wPkYyn/LA7UAfcdqk+rgLy+3dGItnUs2Lqa87fbT -YMf2Zj2fFnuIJ29DcPxRBF9s8FbeLx04wmzvw5TRE8AKvg4wGFlWm+pTOuik6069k/09rgCb5fOf -xEH6NKApQldaZGLWm8ThNX6jv30PwIjB/NwfCaGug6ehLyXGVSJuPhP5oYWUr/d+ppY5cNuObE83 -ZAcOEYgdXFzERzTz25DnO38vhGlkBZZkBaGpLNfIbT7g9Ur4AVkMzJeOLIRtd7HDjWT8mww3DWly -UbdOhQoFEbQE2oVmYBBYXYMyS5wtRTufpcNYT+UC81W8nsX3rD2J3wARAQABwsB2BBgBCAAgFiEE -1mYsX7m96doB85lKqh74MtjMpPIFAmAeYRoCGwwACgkQqh74MtjMpPJp5QgApZ+Bm8v/EiwhIBnv -yAsXlVeMnKjnX8pjJouYtIwk4MoryZ6Ris/VD0WGG5nmgD5x9CbWNLh+pUj4I41uyMIbt++q5xlc -6qw4GsZVUkcTKIARKpPVvxkcZHlBbtkNj+US31lvkBlLPoIyn0/TB3aw9Sxu+DY0+tORGNI6VkAO -wPK57RZ8W/IQ7x76k7S44m634e6usKnD+reitX1QWi3vel8HC4qxviu/xLbIJyjMR1IgPsUWaMAe -DC024L0txF5zDnbODx9X1LM+/8D1pVizUjOwt1liPq0hh2JKU8iLqzdSkv0dte0UbEUPMyCVp8h6 -scbnq9KEwLGCMJ0IkCSUNA== -=iXGJ ------END PGP PUBLIC KEY BLOCK-----`; -const validAmongRevokedRevoked2 = `-----BEGIN PGP PUBLIC KEY BLOCK----- - -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 -=vKdv ------END PGP PUBLIC KEY BLOCK----- -`; // todo - add a not found test with: throw new HttpClientErr('Pubkey not found', 404); export const mockWkdEndpoints: HandlersDefinition = { @@ -217,9 +126,9 @@ export const mockWkdEndpoints: HandlersDefinition = { }, '/.well-known/openpgpkey/localhost/hu/66iu18j7mk6hod4wqzf6qd37u6wejx4y?l=some.revoked': async () => { return Buffer.from([ - ...(await PgpArmor.dearmor(validAmongRevokedRevoked1)).data, - ...(await PgpArmor.dearmor(validAmongRevokedValid)).data, - ...(await PgpArmor.dearmor(validAmongRevokedRevoked2)).data, + ...(await PgpArmor.dearmor(testConstants.somerevokedRevoked1)).data, + ...(await PgpArmor.dearmor(testConstants.somerevokedValid)).data, + ...(await PgpArmor.dearmor(testConstants.somerevokedRevoked2)).data, ]); }, '/.well-known/openpgpkey/localhost/policy': async () => { diff --git a/test/source/tests/browser-unit-tests/unit-ContactStore.js b/test/source/tests/browser-unit-tests/unit-ContactStore.js index ce83d38b90a..a677318ed06 100644 --- a/test/source/tests/browser-unit-tests/unit-ContactStore.js +++ b/test/source/tests/browser-unit-tests/unit-ContactStore.js @@ -311,4 +311,24 @@ BROWSER_UNIT_TEST_NAME(`ContactStore gets a contact by any longid`); throw Error(`Expected to get the key fingerprint 9E020D9B752FD3FFF17ED9B65FCC1541CF282951 but got ${abddefBySubkeyLongid.pubkey.id}`) } return 'pass'; -})(); \ No newline at end of file +})(); + +BROWSER_UNIT_TEST_NAME(`ContactStore gets a valid pubkey by e-mail, or exact pubkey by longid`); +(async () => { + await ContactStore.update(undefined, 'some.revoked@localhost', { email: 'some.revoked@localhost', pubkey: await KeyUtil.parse(testConstants.somerevokedRevoked1) }); + await ContactStore.update(undefined, 'some.revoked@localhost', { email: 'some.revoked@localhost', pubkey: await KeyUtil.parse(testConstants.somerevokedValid) }); + await ContactStore.update(undefined, 'some.revoked@localhost', { email: 'some.revoked@localhost', pubkey: await KeyUtil.parse(testConstants.somerevokedRevoked2) }); + + const [expectedValid] = await ContactStore.get(undefined, ['some.revoked@localhost']); + if (expectedValid.pubkey.id !== 'D6662C5FB9BDE9DA01F3994AAA1EF832D8CCA4F2') { + throw Error(`Expected to get the key fingerprint D6662C5FB9BDE9DA01F3994AAA1EF832D8CCA4F2 but got ${expectedValid.pubkey.id}`) + } + const [expectedRevoked1] = await ContactStore.get(undefined, ['097EEBF354259A5E']); + if (expectedRevoked1.pubkey.id !== 'A5CFC8E8EA4AE69989FE2631097EEBF354259A5E') { + throw Error(`Expected to get the key fingerprint A5CFC8E8EA4AE69989FE2631097EEBF354259A5E but got ${expectedRevoked1.pubkey.id}`) + } + const [expectedRevoked2] = await ContactStore.get(undefined, ['DE8538DDA1648C76']); + if (expectedRevoked2.pubkey.id !== '3930752556D57C46A1C56B63DE8538DDA1648C76') { + throw Error(`Expected to get the key fingerprint 3930752556D57C46A1C56B63DE8538DDA1648C76 but got ${expectedRevoked2.pubkey.id}`) + } +})(); diff --git a/test/source/tests/tooling/consts.ts b/test/source/tests/tooling/consts.ts index 6b09073279e..f0897caeac2 100644 --- a/test/source/tests/tooling/consts.ts +++ b/test/source/tests/tooling/consts.ts @@ -642,5 +642,99 @@ F9PCnKC7HizaM33lA1S0pADBBEVtwfLd4t0bAo4TnJWnjb/fd9osyPEZGU1zF/fFsfLAIb9GC9VB wBff8jybIDc7uGSzTW5qc/3qcgbfH0FGCoIy20H7zgnEJ6PnkENlb/WfynSHAXvfMc8r9YLTCrkv WmiyOmaRmLP+ =Iusd +-----END PGP PUBLIC KEY BLOCK-----`, + somerevokedRevoked1: // some.revoked@localhost + // revoked key with fingerprint A5CF C8E8 EA4A E699 89FE 2631 097E EBF3 5425 9A5E + `-----BEGIN PGP PUBLIC KEY BLOCK----- + +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 +=vqJ0 +-----END PGP PUBLIC KEY BLOCK-----`, + somerevokedValid: // some.revoked@localhost + // valid key with fingerprint D666 2C5F B9BD E9DA 01F3 994A AA1E F832 D8CC A4F2 + `-----BEGIN PGP PUBLIC KEY BLOCK----- + +xsBNBGAeYQ0BCADHMOjbN/X/TH4JpTz7Sj1VTGIeXzWUVZIbsjLgp8U0dFo2zWXMsgLsnNAZuL43 +pUAnIqw+wvUcSpndEO79upVvUzc1qgvp2DTJuDrVGAPx1cqKOi3A/XPO0uIxTyCChcQBQ+YUvwc6 +7ZU69irRC320AQC5aFrL+yP7RmlWQgslJ0qJXPa3On6Cp71GL26iADPXnQOqZtmhv87nYlHhimOv +bKLtC/YMTqGk0h7HqNQPcP8B6bylofS/7Rgy+JKsqWmlng+U/0uQWsnfIua0BPkrZYwJdaF77cs1 +7A2LV2glUiG7XzPkHPTMtG3xV7ZbiAsLSwWN7x1mG3uvpppeXkd1ABEBAAHNFnNvbWUucmV2b2tl +ZEBsb2NhbGhvc3TCwIkEEwEIADMWIQTWZixfub3p2gHzmUqqHvgy2Myk8gUCYB5hFQIbAwULCQgH +AgYVCAkKCwIFFgIDAQAACgkQqh74MtjMpPI7JQf8Dnw4XZLgR8lZV0S4e9JhG/cQqhIzXKVAFcMF +EPWVEHfUYTBCDmTPpi4m9rl5P9T70TXjMbpb6BzvuTS+OZHfyaj8YB39C5FKtqEemoMyO+VO5t7b +I4jUMG3Uu2kuwgN6I2g8jYeA6SYcoUN6NHIpQTkS2BW2IICWqUh09EfcVvdQbZKbMLaoQLfJvTze +gH7LPuNxsvfuhVPtL9WzOIgSFKDmfQnpHluJRKcAhK+aahtUetdsBemBrP7JbNIreIb6+qhmX4q5 +8uGVUFrucSjRwFqqlxSo63ze1jsyzpOvfdzsaDMOG1yIX28cqfOZJpDft5nQjnznjSTJ3I6tGHtL +qs7ATQRgHmEVAQgArN5xkxz80Cbfm9UOT3U5wPkYyn/LA7UAfcdqk+rgLy+3dGItnUs2Lqa87fbT +YMf2Zj2fFnuIJ29DcPxRBF9s8FbeLx04wmzvw5TRE8AKvg4wGFlWm+pTOuik6069k/09rgCb5fOf +xEH6NKApQldaZGLWm8ThNX6jv30PwIjB/NwfCaGug6ehLyXGVSJuPhP5oYWUr/d+ppY5cNuObE83 +ZAcOEYgdXFzERzTz25DnO38vhGlkBZZkBaGpLNfIbT7g9Ur4AVkMzJeOLIRtd7HDjWT8mww3DWly +UbdOhQoFEbQE2oVmYBBYXYMyS5wtRTufpcNYT+UC81W8nsX3rD2J3wARAQABwsB2BBgBCAAgFiEE +1mYsX7m96doB85lKqh74MtjMpPIFAmAeYRoCGwwACgkQqh74MtjMpPJp5QgApZ+Bm8v/EiwhIBnv +yAsXlVeMnKjnX8pjJouYtIwk4MoryZ6Ris/VD0WGG5nmgD5x9CbWNLh+pUj4I41uyMIbt++q5xlc +6qw4GsZVUkcTKIARKpPVvxkcZHlBbtkNj+US31lvkBlLPoIyn0/TB3aw9Sxu+DY0+tORGNI6VkAO +wPK57RZ8W/IQ7x76k7S44m634e6usKnD+reitX1QWi3vel8HC4qxviu/xLbIJyjMR1IgPsUWaMAe +DC024L0txF5zDnbODx9X1LM+/8D1pVizUjOwt1liPq0hh2JKU8iLqzdSkv0dte0UbEUPMyCVp8h6 +scbnq9KEwLGCMJ0IkCSUNA== +=iXGJ +-----END PGP PUBLIC KEY BLOCK-----`, + somerevokedRevoked2: // some.revoked@localhost + // revoked key with fingerprint 3930 7525 56D5 7C46 A1C5 6B63 DE85 38DD A164 8C76 + `-----BEGIN PGP PUBLIC KEY BLOCK----- + +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 +=vKdv -----END PGP PUBLIC KEY BLOCK-----` }; \ No newline at end of file From 73b603d812a9f8128fe2de62bb35f0ea8055676a Mon Sep 17 00:00:00 2001 From: Roman Shevchenko Date: Tue, 30 Mar 2021 08:32:04 -0400 Subject: [PATCH 45/53] safer request pipe --- extension/js/background_page/migrations.ts | 28 +++--- .../js/common/platform/store/contact-store.ts | 41 ++++----- .../browser-unit-tests/unit-ContactStore.js | 89 +++++++++++-------- 3 files changed, 88 insertions(+), 70 deletions(-) diff --git a/extension/js/background_page/migrations.ts b/extension/js/background_page/migrations.ts index 97f11a86aea..1a421cecf4a 100644 --- a/extension/js/background_page/migrations.ts +++ b/extension/js/background_page/migrations.ts @@ -60,9 +60,9 @@ const moveContactsBatchToEmailsAndPubkeys = async (db: IDBDatabase, count?: numb ContactStore.setTxHandlers(tx, resolve, reject); const contacts = tx.objectStore('contacts'); const search = contacts.getAll(undefined, count); - search.onsuccess = () => { - entries.push(...search.result); - }; + ContactStore.setReqPipe(search, (result: ContactV3[]) => { + entries.push(...result); + }); }); if (!entries.length) { return 0; @@ -70,28 +70,30 @@ const moveContactsBatchToEmailsAndPubkeys = async (db: IDBDatabase, count?: numb } console.info(`Processing a batch of ${entries.length}.`); // transform - const updates = await Promise.all(entries.map(async (entry) => { + const converted = await Promise.all(entries.map(async (entry) => { const armoredPubkey = (entry.pubkey && typeof entry.pubkey === 'object') ? KeyUtil.armor(entry.pubkey as Key) : entry.pubkey as string; // parse again to re-calculate expiration-related fields etc. const pubkey = armoredPubkey ? await KeyUtil.parse(armoredPubkey) : undefined; return { email: entry.email, - name: entry.name, - pubkey, - last_use: entry.last_use, - pubkey_last_check: pubkey ? entry.pubkey_last_check : undefined - } as ContactUpdate; + update: { + name: entry.name, + pubkey, + last_use: entry.last_use, + pubkey_last_check: pubkey ? entry.pubkey_last_check : undefined + } as ContactUpdate + }; })); { const tx = db.transaction(['contacts', 'emails', 'pubkeys'], 'readwrite'); await new Promise((resolve, reject) => { ContactStore.setTxHandlers(tx, resolve, reject); - for (const update of updates) { - ContactStore.updateTx(tx, update.email!, update); - tx.objectStore('contacts').delete(update.email!); + for (const item of converted) { + ContactStore.updateTx(tx, item.email, item.update); + tx.objectStore('contacts').delete(item.email); } }); } - return updates.length; + return converted.length; }; diff --git a/extension/js/common/platform/store/contact-store.ts b/extension/js/common/platform/store/contact-store.ts index 3569ff1c511..6edcbf7e2fc 100644 --- a/extension/js/common/platform/store/contact-store.ts +++ b/extension/js/common/platform/store/contact-store.ts @@ -52,7 +52,6 @@ export type ContactPreview = { }; export type ContactUpdate = { - email?: string; name?: string | null; last_use?: number | null; pubkey?: Key; @@ -86,12 +85,12 @@ export class ContactStore extends AbstractStore { } if (db.objectStoreNames.contains('contacts')) { const countRequest = openDbReq.transaction!.objectStore('contacts').count(); - countRequest.onsuccess = () => { - if (countRequest.result === 0) { + ContactStore.setReqPipe(countRequest, (count: number) => { + if (count === 0) { console.info('contacts store is now empty, deleting it...'); db.deleteObjectStore('contacts'); } - }; + }); } }; openDbReq.onsuccess = () => resolve(openDbReq.result as IDBDatabase); @@ -168,6 +167,10 @@ export class ContactStore extends AbstractStore { await Promise.all(email.map(oneEmail => ContactStore.update(db, oneEmail, update))); return; } + const validEmail = Str.parseEmail(email).email; + if (!validEmail) { + throw Error(`Cannot update contact because email is not valid: ${email}`); + } if (update.pubkey?.isPrivate) { Catch.report(`Wrongly updating prv ${update.pubkey.id} as contact - converting to pubkey`); update.pubkey = await KeyUtil.asPublicKey(update.pubkey); @@ -175,7 +178,7 @@ export class ContactStore extends AbstractStore { const tx = db.transaction(['emails', 'pubkeys'], 'readwrite'); await new Promise((resolve, reject) => { ContactStore.setTxHandlers(tx, resolve, reject); - ContactStore.updateTx(tx, email, update); + ContactStore.updateTx(tx, validEmail, update); }); } @@ -247,7 +250,7 @@ export class ContactStore extends AbstractStore { public static updateTx = (tx: IDBTransaction, email: string, update: ContactUpdate) => { if (update.pubkey && !update.pubkey_last_check) { const req = tx.objectStore('pubkeys').get(update.pubkey.id); - req.onsuccess = () => ContactStore.updateTxPhase2(tx, email, update, req.result as Pubkey); + ContactStore.setReqPipe(req, (pubkey: Pubkey) => ContactStore.updateTxPhase2(tx, email, update, pubkey)); } else { ContactStore.updateTxPhase2(tx, email, update, undefined); } @@ -269,13 +272,8 @@ export class ContactStore extends AbstractStore { Catch.report(`Wrongly updating pubkey_last_check without specifying pubkey for ${email} - ignoring`); } const req = tx.objectStore('emails').get(email); - req.onsuccess = () => { - let emailEntity = req.result as Email; + ContactStore.setReqPipe(req, (emailEntity: Email) => { if (!emailEntity) { - const validEmail = Str.parseEmail(email).email; - if (!validEmail) { - throw Error(`Cannot save contact because email is not valid: ${email}`); - } emailEntity = { email, name: null, searchable: [], fingerprints: [], lastUse: null }; } if (pubkeyEntity) { @@ -294,7 +292,7 @@ export class ContactStore extends AbstractStore { if (pubkeyEntity) { tx.objectStore('pubkeys').put(pubkeyEntity); } - }; + }); } private static extractPubkeys = async (db: IDBDatabase | undefined, fingerprints: string[]): Promise => { @@ -306,8 +304,7 @@ export class ContactStore extends AbstractStore { const search = tx.objectStore('pubkeys').openCursor(fingerprints); const found: Pubkey[] = []; ContactStore.setReqPipe(search, - () => { - const cursor = search.result as IDBCursorWithValue | undefined; + (cursor: IDBCursorWithValue) => { if (!cursor) { resolve(found); } else { @@ -352,8 +349,7 @@ export class ContactStore extends AbstractStore { } const found: Email[] = []; ContactStore.setReqPipe(search, - () => { - const cursor = search.result as IDBCursorWithValue | undefined; + (cursor: IDBCursorWithValue) => { if (!cursor) { resolve(found); } else { @@ -400,16 +396,21 @@ export class ContactStore extends AbstractStore { .map(normalized => ContactStore.dbIndex(emailEntity.fingerprints.length > 0, normalized)); } - private static setReqPipe(req: IDBRequest, pipe: (value?: T) => void, reject: (reason?: any) => void) { + public static setReqPipe(req: IDBRequest, pipe: (value?: T) => void, reject?: ((reason?: any) => void) | undefined) { req.onsuccess = () => { try { pipe(req.result as T); } catch (codeErr) { - reject(codeErr); + req.transaction!.dispatchEvent(new ErrorEvent('error')); + if (reject) { + reject(codeErr); + } Catch.reportErr(codeErr); } }; - this.setReqOnError(req, reject); + if (reject) { + this.setReqOnError(req, reject); + } } private static dbContactInternalGetOne = async (db: IDBDatabase, emailOrLongid: string): Promise => { diff --git a/test/source/tests/browser-unit-tests/unit-ContactStore.js b/test/source/tests/browser-unit-tests/unit-ContactStore.js index a677318ed06..08fe04cc173 100644 --- a/test/source/tests/browser-unit-tests/unit-ContactStore.js +++ b/test/source/tests/browser-unit-tests/unit-ContactStore.js @@ -25,13 +25,17 @@ BROWSER_UNIT_TEST_NAME(`ContactStore is able to search by partial email address`); (async () => { const contactABBDEF = await ContactStore.obj({ - email: 'abbdef@test.com', pubkey: testConstants.abbdefTestComPubkey }); + email: 'abbdef@test.com', pubkey: testConstants.abbdefTestComPubkey + }); const contactABCDEF = await ContactStore.obj({ - email: 'abcdef@test.com', pubkey: testConstants.abcdefTestComPubkey }); + email: 'abcdef@test.com', pubkey: testConstants.abcdefTestComPubkey + }); const contactABCDDF = await ContactStore.obj({ - email: 'abcddf@test.com', pubkey: testConstants.abcddfTestComPubkey }); + email: 'abcddf@test.com', pubkey: testConstants.abcddfTestComPubkey + }); const contactABDDEF = await ContactStore.obj({ - email: 'abddef@test.com', pubkey: testConstants.abddefTestComPubkey }); + email: 'abddef@test.com', pubkey: testConstants.abddefTestComPubkey + }); await ContactStore.save(undefined, contactABBDEF); await ContactStore.save(undefined, contactABCDEF); await ContactStore.save(undefined, contactABCDDF); @@ -64,7 +68,7 @@ BROWSER_UNIT_TEST_NAME(`ContactStore doesn't store duplicates in searchable`); // extract the entity from the database to see the actual field const entity = await new Promise((resolve, reject) => { const req = db.transaction(['emails'], 'readonly').objectStore('emails').get(contact.email); - ContactStore.setReqPipe(req, () => resolve(req.result), reject); + ContactStore.setReqPipe(req, resolve, reject); }); if (entity?.searchable.length !== 2) { throw Error(`Expected 2 entries in 'searchable' but got "${entity?.searchable}"`); @@ -82,7 +86,7 @@ BROWSER_UNIT_TEST_NAME(`ContactStore doesn't store smaller words in searchable w // extract the entity from the database to see the actual field const entity = await new Promise((resolve, reject) => { const req = db.transaction(['emails'], 'readonly').objectStore('emails').get(contact.email); - ContactStore.setReqPipe(req, () => resolve(req.result), reject); + ContactStore.setReqPipe(req, resolve, reject); }); if (entity?.searchable.length !== 3 || !entity.searchable.includes('f:a') || !entity.searchable.includes('f:bigger') || !entity.searchable.includes('f:one')) { @@ -97,23 +101,25 @@ BROWSER_UNIT_TEST_NAME(`ContactStore.update updates correct 'pubkey_last_check'` const email = 'flowcrypt.compatibility@gmail.com'; const date2_0 = Date.now(); const contacts = [ - await ContactStore.obj({ - email, - pubkey: testConstants.flowcryptcompatibilityPublicKey7FDE685548AEA788 - }), - await ContactStore.obj({ - email, - pubkey: testConstants.flowcryptcompatibilityPublicKeyADAC279C95093207, - lastCheck: date2_0 - })]; + await ContactStore.obj({ + email, + pubkey: testConstants.flowcryptcompatibilityPublicKey7FDE685548AEA788 + }), + await ContactStore.obj({ + email, + pubkey: testConstants.flowcryptcompatibilityPublicKeyADAC279C95093207, + lastCheck: date2_0 + })]; await ContactStore.save(db, contacts); // extract the entities from the database const fp1 = '5520CACE2CB61EA713E5B0057FDE685548AEA788'; const fp2 = 'E8F0517BA6D7DAB6081C96E4ADAC279C95093207'; - const getEntity = async(fp) => { return await new Promise((resolve, reject) => { - const req = db.transaction(['pubkeys'], 'readonly').objectStore('pubkeys').get(fp); - ContactStore.setReqPipe(req, () => resolve(req.result), reject); - }); }; + const getEntity = async (fp) => { + return await new Promise((resolve, reject) => { + const req = db.transaction(['pubkeys'], 'readonly').objectStore('pubkeys').get(fp); + ContactStore.setReqPipe(req, resolve, reject); + }); + }; let entity1 = await getEntity(fp1); let entity2 = await getEntity(fp2); if (entity1.fingerprint !== fp1) { @@ -188,11 +194,13 @@ BROWSER_UNIT_TEST_NAME(`ContactStore.update tests`); name: undefined, lastUse: undefined } - const getEntity = async(email) => { return await new Promise((resolve, reject) => { - const req = db.transaction(['emails'], 'readonly').objectStore('emails').get(email); - ContactStore.setReqPipe(req, () => resolve(req.result), reject); - }); }; - const compareEntity = async(expectedObj) => { + const getEntity = async (email) => { + return await new Promise((resolve, reject) => { + const req = db.transaction(['emails'], 'readonly').objectStore('emails').get(email); + ContactStore.setReqPipe(req, () => resolve, reject); + }); + }; + const compareEntity = async (expectedObj) => { const loaded = await getEntity(expectedObj.email); if (loaded.name != expectedObj.name) { throw Error(`name field mismatch, expected ${expectedObj.name} but got ${loaded.name}`); @@ -201,7 +209,7 @@ BROWSER_UNIT_TEST_NAME(`ContactStore.update tests`); throw Error(`lastUse field mismatch, expected ${expectedObj.lastUse} but got ${loaded.lastUse}`); } }; - const compareEntities = async() => { + const compareEntities = async () => { await compareEntity(expectedObj1); await compareEntity(expectedObj2); } @@ -211,10 +219,10 @@ BROWSER_UNIT_TEST_NAME(`ContactStore.update tests`); await compareEntities(); const date = new Date(); expectedObj2.lastUse = date.getTime(); - await ContactStore.update(db, email2, {last_use: date }); + await ContactStore.update(db, email2, { last_use: date }); await compareEntities(); expectedObj2.lastUse = undefined; - await ContactStore.update(db, email2, {last_use: undefined }); + await ContactStore.update(db, email2, { last_use: undefined }); await compareEntities(); return 'pass'; })(); @@ -225,7 +233,7 @@ BROWSER_UNIT_TEST_NAME(`ContactStore saves and returns dates as numbers`); const email = 'test@expired.com'; const lastCheck = Date.now(); const lastUse = lastCheck + 1000; - const contact = await ContactStore.obj({ email, pubkey:testConstants.expiredPub, lastCheck, lastUse }); + const contact = await ContactStore.obj({ email, pubkey: testConstants.expiredPub, lastCheck, lastUse }); await ContactStore.save(undefined, [contact]); const [loaded] = await ContactStore.get(undefined, [email]); if (typeof loaded.last_use !== 'number') { @@ -243,13 +251,17 @@ BROWSER_UNIT_TEST_NAME(`ContactStore saves and returns dates as numbers`); BROWSER_UNIT_TEST_NAME(`ContactStore gets a contact by any longid`); (async () => { const contactABBDEF = await ContactStore.obj({ - email: 'abbdef@test.com', pubkey: testConstants.abbdefTestComPubkey }); + email: 'abbdef@test.com', pubkey: testConstants.abbdefTestComPubkey + }); const contactABCDEF = await ContactStore.obj({ - email: 'abcdef@test.com', pubkey: testConstants.abcdefTestComPubkey }); + email: 'abcdef@test.com', pubkey: testConstants.abcdefTestComPubkey + }); const contactABCDDF = await ContactStore.obj({ - email: 'abcddf@test.com', pubkey: testConstants.abcddfTestComPubkey }); + email: 'abcddf@test.com', pubkey: testConstants.abcddfTestComPubkey + }); const contactABDDEF = await ContactStore.obj({ - email: 'abddef@test.com', pubkey: testConstants.abddefTestComPubkey }); + email: 'abddef@test.com', pubkey: testConstants.abddefTestComPubkey + }); await ContactStore.save(undefined, [contactABBDEF, contactABCDEF, contactABCDDF, contactABDDEF]); const [abbdefByPrimaryLongid] = await ContactStore.get(undefined, ['DF63659C3B4A81FB']); if (abbdefByPrimaryLongid.email !== 'abbdef@test.com') { @@ -272,7 +284,7 @@ BROWSER_UNIT_TEST_NAME(`ContactStore gets a contact by any longid`); } if (abcdefByPrimaryLongid.pubkey.id !== '3155F118B6E732B3638A1CE1608BCD797A23FB91') { throw Error(`Expected to get the key fingerprint 3155F118B6E732B3638A1CE1608BCD797A23FB91 but got ${abcdefByPrimaryLongid.pubkey.id}`) - } + } const [abcdefBySubkeyLongid] = await ContactStore.get(undefined, ['2D47A41943DFAFCE']); if (abcdefBySubkeyLongid.email !== 'abcdef@test.com') { throw Error(`Expected to get the key for abcdef@test.com by subkey longid but got ${abcdefBySubkeyLongid.email}`) @@ -315,11 +327,13 @@ BROWSER_UNIT_TEST_NAME(`ContactStore gets a contact by any longid`); BROWSER_UNIT_TEST_NAME(`ContactStore gets a valid pubkey by e-mail, or exact pubkey by longid`); (async () => { - await ContactStore.update(undefined, 'some.revoked@localhost', { email: 'some.revoked@localhost', pubkey: await KeyUtil.parse(testConstants.somerevokedRevoked1) }); - await ContactStore.update(undefined, 'some.revoked@localhost', { email: 'some.revoked@localhost', pubkey: await KeyUtil.parse(testConstants.somerevokedValid) }); - await ContactStore.update(undefined, 'some.revoked@localhost', { email: 'some.revoked@localhost', pubkey: await KeyUtil.parse(testConstants.somerevokedRevoked2) }); + // Note 1: email differs from pubkey id + // Note 2: not necessary to call ContactStore.save, it's possible to always use ContactStore.update + await ContactStore.update(undefined, 'some.revoked@otherhost.com', { pubkey: await KeyUtil.parse(testConstants.somerevokedRevoked1) }); + await ContactStore.update(undefined, 'some.revoked@otherhost.com', { pubkey: await KeyUtil.parse(testConstants.somerevokedValid) }); + await ContactStore.update(undefined, 'some.revoked@otherhost.com', { pubkey: await KeyUtil.parse(testConstants.somerevokedRevoked2) }); - const [expectedValid] = await ContactStore.get(undefined, ['some.revoked@localhost']); + const [expectedValid] = await ContactStore.get(undefined, ['some.revoked@otherhost.com']); if (expectedValid.pubkey.id !== 'D6662C5FB9BDE9DA01F3994AAA1EF832D8CCA4F2') { throw Error(`Expected to get the key fingerprint D6662C5FB9BDE9DA01F3994AAA1EF832D8CCA4F2 but got ${expectedValid.pubkey.id}`) } @@ -331,4 +345,5 @@ BROWSER_UNIT_TEST_NAME(`ContactStore gets a valid pubkey by e-mail, or exact pub if (expectedRevoked2.pubkey.id !== '3930752556D57C46A1C56B63DE8538DDA1648C76') { throw Error(`Expected to get the key fingerprint 3930752556D57C46A1C56B63DE8538DDA1648C76 but got ${expectedRevoked2.pubkey.id}`) } + return 'pass'; })(); From bdef9074a5eef2ee81650fcefc06dfaacb8a09ae Mon Sep 17 00:00:00 2001 From: Roman Shevchenko Date: Wed, 31 Mar 2021 04:26:01 -0400 Subject: [PATCH 46/53] test fix --- test/source/tests/browser-unit-tests/unit-ContactStore.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/source/tests/browser-unit-tests/unit-ContactStore.js b/test/source/tests/browser-unit-tests/unit-ContactStore.js index 08fe04cc173..0d38ffff161 100644 --- a/test/source/tests/browser-unit-tests/unit-ContactStore.js +++ b/test/source/tests/browser-unit-tests/unit-ContactStore.js @@ -197,7 +197,7 @@ BROWSER_UNIT_TEST_NAME(`ContactStore.update tests`); const getEntity = async (email) => { return await new Promise((resolve, reject) => { const req = db.transaction(['emails'], 'readonly').objectStore('emails').get(email); - ContactStore.setReqPipe(req, () => resolve, reject); + ContactStore.setReqPipe(req, resolve, reject); }); }; const compareEntity = async (expectedObj) => { From 93a91d45e51195eecc456b65491d01c643797c43 Mon Sep 17 00:00:00 2001 From: Roman Shevchenko Date: Wed, 31 Mar 2021 04:27:17 -0400 Subject: [PATCH 47/53] tslint fix --- .../js/common/platform/store/contact-store.ts | 34 +++++++++---------- 1 file changed, 17 insertions(+), 17 deletions(-) diff --git a/extension/js/common/platform/store/contact-store.ts b/extension/js/common/platform/store/contact-store.ts index 6edcbf7e2fc..2e1621c5ed2 100644 --- a/extension/js/common/platform/store/contact-store.ts +++ b/extension/js/common/platform/store/contact-store.ts @@ -256,6 +256,23 @@ export class ContactStore extends AbstractStore { } } + public static setReqPipe(req: IDBRequest, pipe: (value?: T) => void, reject?: ((reason?: any) => void) | undefined) { + req.onsuccess = () => { + try { + pipe(req.result as T); + } catch (codeErr) { + req.transaction!.dispatchEvent(new ErrorEvent('error')); + if (reject) { + reject(codeErr); + } + Catch.reportErr(codeErr); + } + }; + if (reject) { + this.setReqOnError(req, reject); + } + } + private static updateTxPhase2 = (tx: IDBTransaction, email: string, update: ContactUpdate, existingPubkey: Pubkey | undefined) => { let pubkeyEntity: Pubkey | undefined; if (update.pubkey) { @@ -396,23 +413,6 @@ export class ContactStore extends AbstractStore { .map(normalized => ContactStore.dbIndex(emailEntity.fingerprints.length > 0, normalized)); } - public static setReqPipe(req: IDBRequest, pipe: (value?: T) => void, reject?: ((reason?: any) => void) | undefined) { - req.onsuccess = () => { - try { - pipe(req.result as T); - } catch (codeErr) { - req.transaction!.dispatchEvent(new ErrorEvent('error')); - if (reject) { - reject(codeErr); - } - Catch.reportErr(codeErr); - } - }; - if (reject) { - this.setReqOnError(req, reject); - } - } - private static dbContactInternalGetOne = async (db: IDBDatabase, emailOrLongid: string): Promise => { if (!/^[A-F0-9]{16}$/.test(emailOrLongid)) { // email const contactWithAllPubkeys = await ContactStore.getOneWithAllPubkeys(db, emailOrLongid); From 440bd29f08cd045ae612796414dc89984d804306 Mon Sep 17 00:00:00 2001 From: Roman Shevchenko Date: Wed, 31 Mar 2021 05:52:07 -0400 Subject: [PATCH 48/53] wrap migration block in try/catch to make extension usable --- extension/js/background_page/migrations.ts | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/extension/js/background_page/migrations.ts b/extension/js/background_page/migrations.ts index 1a421cecf4a..c9973c240d3 100644 --- a/extension/js/background_page/migrations.ts +++ b/extension/js/background_page/migrations.ts @@ -46,10 +46,14 @@ export const moveContactsToEmailsAndPubkeys = async (db: IDBDatabase): Promise => { From 74f45310f081eb0b420393cc078606e89dbaa1df Mon Sep 17 00:00:00 2001 From: Roman Shevchenko Date: Thu, 1 Apr 2021 01:40:42 -0400 Subject: [PATCH 49/53] simplify --- extension/js/common/platform/store/contact-store.ts | 3 --- 1 file changed, 3 deletions(-) diff --git a/extension/js/common/platform/store/contact-store.ts b/extension/js/common/platform/store/contact-store.ts index 2e1621c5ed2..6014cbae2e0 100644 --- a/extension/js/common/platform/store/contact-store.ts +++ b/extension/js/common/platform/store/contact-store.ts @@ -188,9 +188,6 @@ export class ContactStore extends AbstractStore { } if (emailOrLongid.length === 1) { const contact = await ContactStore.dbContactInternalGetOne(db, emailOrLongid[0]); - if (!contact) { - return [contact]; - } return [contact]; } else { const results: (Contact | undefined)[] = []; From a575c6101e83cdceff700ca8748fc8fc0238ba57 Mon Sep 17 00:00:00 2001 From: Roman Shevchenko Date: Thu, 1 Apr 2021 02:04:14 -0400 Subject: [PATCH 50/53] Added test for abcd.vwxyz@hello.com --- .../browser-unit-tests/unit-ContactStore.js | 25 +++++++++++++------ 1 file changed, 17 insertions(+), 8 deletions(-) diff --git a/test/source/tests/browser-unit-tests/unit-ContactStore.js b/test/source/tests/browser-unit-tests/unit-ContactStore.js index 0d38ffff161..13923036d25 100644 --- a/test/source/tests/browser-unit-tests/unit-ContactStore.js +++ b/test/source/tests/browser-unit-tests/unit-ContactStore.js @@ -36,17 +36,18 @@ BROWSER_UNIT_TEST_NAME(`ContactStore is able to search by partial email address` const contactABDDEF = await ContactStore.obj({ email: 'abddef@test.com', pubkey: testConstants.abddefTestComPubkey }); - await ContactStore.save(undefined, contactABBDEF); - await ContactStore.save(undefined, contactABCDEF); - await ContactStore.save(undefined, contactABCDDF); - await ContactStore.save(undefined, contactABDDEF); + const contactABCDVWXYZHELLOCOM = await ContactStore.obj({ + email: 'abcd.vwxyz@hello.com', pubkey: testConstants.abcdVwxyzHelloComPubkey + }); + await ContactStore.save(undefined, [contactABBDEF, contactABCDEF, contactABCDDF, contactABDDEF, + contactABCDVWXYZHELLOCOM]); const contactsABC = await ContactStore.search(undefined, { has_pgp: true, substring: 'abc' }); - if (contactsABC.length !== 2) { - throw Error(`Expected 2 contacts to match "abc" but got "${contactsABC.length}"`); + if (contactsABC.length !== 3) { + throw Error(`Expected 3 contacts to match "abc" but got "${contactsABC.length}"`); } const contactsABCD = await ContactStore.search(undefined, { has_pgp: true, substring: 'abcd' }); - if (contactsABCD.length !== 2) { - throw Error(`Expected 2 contacts to match "abcd" but got "${contactsABCD.length}"`); + if (contactsABCD.length !== 3) { + throw Error(`Expected 3 contacts to match "abcd" but got "${contactsABCD.length}"`); } const contactsABCDE = await ContactStore.search(undefined, { has_pgp: true, substring: 'abcde' }); if (contactsABCDE.length !== 1) { @@ -55,6 +56,14 @@ BROWSER_UNIT_TEST_NAME(`ContactStore is able to search by partial email address` if (contactsABCDE[0].email !== 'abcdef@test.com') { throw Error(`Expected "abcdef@test.com" but got "${contactsABCDE[0].email}"`); } + const contactsVWX = await ContactStore.search(undefined, { has_pgp: true, substring: 'vwx' }); + if (contactsVWX.length !== 1) { + throw Error(`Expected 1 contact to match "vwx" but got "${contactsVWX.length}"`); + } + const contactsHEL = await ContactStore.search(undefined, { has_pgp: true, substring: 'hel' }); + if (contactsHEL.length !== 1) { + throw Error(`Expected 1 contact to match "hel" but got "${contactsHEL.length}"`); + } return 'pass'; })(); From 288b1d3964f5548864573ec4c4d664ec06c3a145 Mon Sep 17 00:00:00 2001 From: Roman Shevchenko Date: Thu, 1 Apr 2021 03:50:07 -0400 Subject: [PATCH 51/53] Added pubkey for abcd.vwxyz@hello.com --- test/source/tests/tooling/consts.ts | 26 ++++++++++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/test/source/tests/tooling/consts.ts b/test/source/tests/tooling/consts.ts index f0897caeac2..112b2f74060 100644 --- a/test/source/tests/tooling/consts.ts +++ b/test/source/tests/tooling/consts.ts @@ -736,5 +736,31 @@ cq4w9llj06CRUTKo4LwPK0ESP2OfNQtWaz5sceUI5parHn4n8aV1nQ3pTAaIhTOkzhbm+3aH8wby hgT9Z+4pYT+erCXPq+wd0CBm5J3631frN+OPtftYLl/ESRkaX7c/ULkn7xePo8Uwd3JgpIgJuN8p ctnWuBzRDeI0n6XDaPv5TpKpS7uqy/fTlJLGE9vZTFUKzeGkQFomBoXNVWs= =vKdv +-----END PGP PUBLIC KEY BLOCK-----`, + abcdVwxyzHelloComPubkey: `-----BEGIN PGP PUBLIC KEY BLOCK----- + +xsBNBGBlXdMBCACl6VOisUuXhaolA2nnbdCZOcv2kENmf20DjNfKuPSp/L1FfuAErorzl1HrER+i +8Gs7y7bsgIgeoe/LRlkxB/OlFAtzWeynKsHj+mekA/kl+6piZre7ln+36okAou+C+6u1dTKtimue +7LrYER9HX/X+7wsXg6umPcwtp9eW65jvkVrsIBuTcst7Xwky2otkzmgt0zH9f+q/US+c6DGOBrdD +tsHm4UfpAzD/IGIMZgaBpUibFncc0xYeGiTLS2ez63HTUH8DZIzuyzg9gmqsF5FtxtzhPCafCszT +HlxeC2UkXEuGx8ralZ1ytH6zdslt9/VlWTopTf0uDVZKVHlGabrFABEBAAHNFGFiY2Qudnd4eXpA +aGVsbG8uY29twsCJBBMBCAAzFiEEjkOwy7qndz5oJeowXStcQJYcfNkFAmBlXdgCGwMFCwkIBwIG +FQgJCgsCBRYCAwEAAAoJEF0rXECWHHzZQ1YH/0/T48WqfHUbB32UM1wLu9DsXe+aSJjkP2cCXnlL +QYaqE2HSuSBlFauaNSQkJ0OCLnKV/wne/X4unsOsp9lam9sLlAJJDZU/KHrO2hF2qYSToUYGsaZa +eOTFNrJNdh4AYvNugY/UseXImH+fPwFEaY4df9MVZhMCQd9Stp+UuAf3rFlyeXwk3vB7JzUh3GkN +wGtZ7VCWVZdlMaOU9uC+rhYbW86jihfCTomctGbEJUBXq1w4C/wu8jz/GPn+xlP+q6hyoQOr1d4s +31UPkkTqSK/XsBf26Pu9NBy8aG3Ry1g228iZQ71+potDdStjAw8BeEVQp/wG+pSHil/5ovGcceTO +wE0EYGVd2AEIAMK5heTL27MOtvmXIL4pths6mpzP3C0Shu5zqU5aRXlmtACMolj+TRSBWmbnvsFe +bXk9lX8dsJd99JEVixsHRpAn0ECK6lmVCeKL4p3zbVMHahnhmc4EAtTwBk95xWAwiaHhalkT134K +xZyJJNSVC+lj8aQnXQCvwbnLr7oUnKXqWgcjlQJNzSboeroiCqXKpUSTsH3kL11NVrEZ3CzgR6/e +lVfqCS7zqOYhRhT3blgfzX2b/pNAzVKiv87p4Uft05X83hhN7f9owgsuWhf+23ZQynfwx0gTbXAh +DKRyps1MRWMo+2s+7thV7EnMcitnsxeVMZFrnqIp+z+nZSZUaqEAEQEAAcLAdgQYAQgAIBYhBI5D +sMu6p3c+aCXqMF0rXECWHHzZBQJgZV3cAhsMAAoJEF0rXECWHHzZpGEH/14yBVvtOxM9FJLJWn6F +74XcY2zrw8cdFGr4LxMF8W8U1KjSezObUnxS8ebSbkOz12Bz3oKV1ul2CuzzcwDjk2/Yd9KFKzmX +MiMYq0gS2AGiBSjPBCXHjbarEe3iuTKeA+a/gt86Anlrq3fNXbagsu/07dUn+m+D3XhctO8eGcnR +UAYXUheuOOBJrHSWc9+sfhJVxG9+JY2xSPoHWfa0q6QZoKQSPXK/Z63cNDK6vDnLbv2VvTFkBlPH +wHlKRDVNpiNVKyevQ8PI9iQGMEkKOv23GdDuC0F0rCCnqp6gEVvezjMMk5bMCEiY4sn2+wwVDpC3 +5adfA360UnwZSMt5qrk= +=EDBf -----END PGP PUBLIC KEY BLOCK-----` }; \ No newline at end of file From 35c32e1245fe2f7fcb4a08e507a2a2e1b9bdf643 Mon Sep 17 00:00:00 2001 From: Roman Shevchenko Date: Thu, 1 Apr 2021 14:28:10 -0400 Subject: [PATCH 52/53] Added explanation of key range search --- extension/js/common/platform/store/contact-store.ts | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/extension/js/common/platform/store/contact-store.ts b/extension/js/common/platform/store/contact-store.ts index 6014cbae2e0..f8378024196 100644 --- a/extension/js/common/platform/store/contact-store.ts +++ b/extension/js/common/platform/store/contact-store.ts @@ -358,6 +358,11 @@ export class ContactStore extends AbstractStore { search = emails.openCursor(); // no substring, already covered in `typeof query.has_pgp === 'undefined' && query.substring` above } else { // specific query.has_pgp value const indexRange = ContactStore.dbIndexRange(query.has_pgp, query.substring ?? ''); + // To find all the index keys starting with a certain sequence of characters (e.g. 'abc') + // we use a range with inclusive lower boundary and exclusive upper boundary + // ['t:abc', 't:abd) or ['f:abc', 'f:abd'), so that any key having an arbitrary tail of + // characters beyond 'abc' falls into this range, and none of the non-matching keys do. + // Thus we only have to keep complete keywords in the 'search' index. const range = IDBKeyRange.bound(indexRange.lowerBound, indexRange.upperBound, false, true); search = emails.index('search').openCursor(range); } @@ -389,6 +394,13 @@ export class ContactStore extends AbstractStore { } private static dbIndexRange = (hasPgp: boolean, substring: string): { lowerBound: string, upperBound: string } => { + // to find all the keys starting with 'abc', we need to use a range search with exlcusive upper boundary + // ['t:abc', 't:abd'), that is, we "replace" the last char ('c') with the char having subsequent code ('d') + // The edge case is when the search string terminates with a certain char X having the max allowed code (65535) + // or with a sequence of these, e.g. 'abcXXXXX'. In this case, we have to remove the tail of X characters + // and increase the preceding non-X char, hence, the range would be ['t:abcXXXXX', 't:abd') + // If the search sequence consists entirely of such symbols, the search range will have + // the upper boundary of 'f;' or 't;', so this algorithm always works const lowerBound = ContactStore.dbIndex(hasPgp, substring); let copyLength = lowerBound.length - 1; let lastChar = lowerBound.charCodeAt(copyLength); From bc51f5b309ed0fa751b13c38ea1e08d4114b1b04 Mon Sep 17 00:00:00 2001 From: Roman Shevchenko Date: Sat, 3 Apr 2021 04:45:46 -0400 Subject: [PATCH 53/53] tslint fix --- extension/js/common/platform/store/contact-store.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/extension/js/common/platform/store/contact-store.ts b/extension/js/common/platform/store/contact-store.ts index f8378024196..00bf2690c84 100644 --- a/extension/js/common/platform/store/contact-store.ts +++ b/extension/js/common/platform/store/contact-store.ts @@ -359,7 +359,7 @@ export class ContactStore extends AbstractStore { } else { // specific query.has_pgp value const indexRange = ContactStore.dbIndexRange(query.has_pgp, query.substring ?? ''); // To find all the index keys starting with a certain sequence of characters (e.g. 'abc') - // we use a range with inclusive lower boundary and exclusive upper boundary + // we use a range with inclusive lower boundary and exclusive upper boundary // ['t:abc', 't:abd) or ['f:abc', 'f:abd'), so that any key having an arbitrary tail of // characters beyond 'abc' falls into this range, and none of the non-matching keys do. // Thus we only have to keep complete keywords in the 'search' index.