diff --git a/extension/chrome/dev/ci_unit_test.ts b/extension/chrome/dev/ci_unit_test.ts index 74ca379cf56..2b59d5aa442 100644 --- a/extension/chrome/dev/ci_unit_test.ts +++ b/extension/chrome/dev/ci_unit_test.ts @@ -12,6 +12,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 { AcctStore } from '../../js/common/platform/store/acct-store.js'; import { ContactStore } from '../../js/common/platform/store/contact-store.js'; import { Debug } from '../../js/common/platform/debug.js'; import { Catch } from '../../js/common/platform/catch.js'; @@ -33,6 +34,7 @@ const libs: unknown[] = [ Sks, MsgUtil, Ui, + AcctStore, ContactStore, Debug, Catch, diff --git a/extension/chrome/elements/passphrase.ts b/extension/chrome/elements/passphrase.ts index 05e00198fc4..0ec7ad37017 100644 --- a/extension/chrome/elements/passphrase.ts +++ b/extension/chrome/elements/passphrase.ts @@ -34,8 +34,10 @@ View.run(class PassphraseView extends View { const uncheckedUrlParams = Url.parse(['acctEmail', 'parentTabId', 'longids', 'type', 'initiatorFrameId']); this.acctEmail = Assert.urlParamRequire.string(uncheckedUrlParams, 'acctEmail'); this.parentTabId = Assert.urlParamRequire.string(uncheckedUrlParams, 'parentTabId'); - this.longids = Assert.urlParamRequire.string(uncheckedUrlParams, 'longids').split(','); - this.type = Assert.urlParamRequire.oneof(uncheckedUrlParams, 'type', ['embedded', 'sign', 'message', 'draft', 'attachment', 'quote', 'backup']); + const longidsParam = Assert.urlParamRequire.string(uncheckedUrlParams, 'longids'); + this.longids = longidsParam ? longidsParam.split(',') : []; + this.type = Assert.urlParamRequire.oneof(uncheckedUrlParams, 'type', + ['embedded', 'sign', 'message', 'draft', 'attachment', 'quote', 'backup', 'update_key']); this.initiatorFrameId = Assert.urlParamRequire.optionalString(uncheckedUrlParams, 'initiatorFrameId'); } @@ -55,6 +57,9 @@ View.run(class PassphraseView extends View { } await initPassphraseToggle(['passphrase']); const allPrivateKeys = await KeyStore.get(this.acctEmail); + if (this.longids.length === 0) { + this.longids.push(...allPrivateKeys.map(ki => ki.longid)); + } this.keysWeNeedPassPhraseFor = allPrivateKeys.filter(ki => this.longids.includes(ki.longid)); if (this.type === 'embedded') { $('h1').parent().css('display', 'none'); @@ -71,6 +76,8 @@ View.run(class PassphraseView extends View { $('h1').text('Enter FlowCrypt pass phrase to load quoted content'); } else if (this.type === 'backup') { $('h1').text('Enter FlowCrypt pass phrase to back up'); + } else if (this.type === 'update_key') { + $('h1').text('Enter FlowCrypt pass phrase to keep your account keys up to date'); } $('#passphrase').focus(); if (allPrivateKeys.length > 1) { diff --git a/extension/chrome/settings/setup.ts b/extension/chrome/settings/setup.ts index 31d3db655a1..d9e5d6989a1 100644 --- a/extension/chrome/settings/setup.ts +++ b/extension/chrome/settings/setup.ts @@ -7,7 +7,7 @@ import { Url } from '../../js/common/core/common.js'; import { ApiErr } from '../../js/common/api/shared/api-error.js'; import { Assert } from '../../js/common/assert.js'; import { Catch } from '../../js/common/platform/catch.js'; -import { Key, KeyInfoWithIdentity, KeyUtil } from '../../js/common/core/crypto/key.js'; +import { KeyInfoWithIdentity, KeyUtil } from '../../js/common/core/crypto/key.js'; import { Gmail } from '../../js/common/api/email-provider/gmail/gmail.js'; import { Google } from '../../js/common/api/email-provider/gmail/google.js'; import { KeyImportUi } from '../../js/common/ui/key-import-ui.js'; @@ -27,17 +27,18 @@ import { PubLookup } from '../../js/common/api/pub-lookup.js'; import { Scopes, AcctStoreDict, AcctStore } from '../../js/common/platform/store/acct-store.js'; import { KeyStore } from '../../js/common/platform/store/key-store.js'; import { KeyStoreUtil } from "../../js/common/core/crypto/key-store-util.js"; -import { PassphraseStore } from '../../js/common/platform/store/passphrase-store.js'; -import { ContactStore } from '../../js/common/platform/store/contact-store.js'; import { KeyManager } from '../../js/common/api/key-server/key-manager.js'; import { SetupWithEmailKeyManagerModule } from './setup/setup-key-manager-autogen.js'; import { shouldPassPhraseBeHidden } from '../../js/common/ui/passphrase-ui.js'; import Swal from 'sweetalert2'; import { BackupUi } from '../../js/common/ui/backup-ui/backup-ui.js'; -export interface SetupOptions { +export interface PassphraseOptions { passphrase: string; passphrase_save: boolean; +} + +export interface SetupOptions extends PassphraseOptions { submit_main: boolean; submit_all: boolean; recovered?: boolean; @@ -246,20 +247,6 @@ export class SetupView extends View { await AcctStore.set(this.acctEmail, { setup_date: Date.now(), setup_done: true, cryptup_enabled: true }); }; - public saveKeysAndPassPhrase = async (prvs: Key[], options: SetupOptions) => { - for (const prv of prvs) { - await KeyStore.add(this.acctEmail, prv); - await PassphraseStore.set((options.passphrase_save && !this.clientConfiguration.forbidStoringPassPhrase()) ? 'local' : 'session', - this.acctEmail, { longid: KeyUtil.getPrimaryLongid(prv) }, options.passphrase); - } - const { sendAs } = await AcctStore.get(this.acctEmail, ['sendAs']); - const myOwnEmailsAddrs: string[] = [this.acctEmail].concat(Object.keys(sendAs!)); - const { full_name: name } = await AcctStore.get(this.acctEmail, ['full_name']); - for (const email of myOwnEmailsAddrs) { - await ContactStore.update(undefined, email, { name, pubkey: KeyUtil.armor(await KeyUtil.asPublicKey(prvs[0])) }); - } - }; - public shouldSubmitPubkey = (checkboxSelector: string) => { if (this.clientConfiguration.mustSubmitToAttester() && !this.clientConfiguration.canSubmitPubToAttester()) { throw new Error('Organisation rules are misconfigured: ENFORCE_ATTESTER_SUBMIT not compatible with NO_ATTESTER_SUBMIT'); diff --git a/extension/chrome/settings/setup/setup-create-key.ts b/extension/chrome/settings/setup/setup-create-key.ts index c5574a27948..60df2cb991d 100644 --- a/extension/chrome/settings/setup/setup-create-key.ts +++ b/extension/chrome/settings/setup/setup-create-key.ts @@ -10,6 +10,7 @@ import { Ui } from '../../../js/common/browser/ui.js'; import { Xss } from '../../../js/common/platform/xss.js'; import { AcctStore } from '../../../js/common/platform/store/acct-store.js'; import { OpenPGPKey } from '../../../js/common/core/crypto/pgp/openpgp-key.js'; +import { saveKeysAndPassPhrase } from '../../../js/common/helpers.js'; export class SetupCreateKeyModule { @@ -81,7 +82,7 @@ export class SetupCreateKeyModule { const expireMonths = this.view.clientConfiguration.getEnforcedKeygenExpirationMonths(); const key = await OpenPGPKey.create(pgpUids, keyAlgo, options.passphrase, expireMonths); const prv = await KeyUtil.parse(key.private); - await this.view.saveKeysAndPassPhrase([prv], options); + await saveKeysAndPassPhrase(this.view.acctEmail, [prv], options); return { id: prv.id, family: prv.family }; }; } diff --git a/extension/chrome/settings/setup/setup-import-key.ts b/extension/chrome/settings/setup/setup-import-key.ts index b94bc87b99c..0681c20ef8c 100644 --- a/extension/chrome/settings/setup/setup-import-key.ts +++ b/extension/chrome/settings/setup/setup-import-key.ts @@ -11,6 +11,7 @@ import { Ui } from '../../../js/common/browser/ui.js'; import { Xss } from '../../../js/common/platform/xss.js'; import { Key, UnexpectedKeyTypeError } from '../../../js/common/core/crypto/key.js'; import { Lang } from '../../../js/common/lang.js'; +import { saveKeysAndPassPhrase } from '../../../js/common/helpers.js'; export class SetupImportKeyModule { @@ -39,7 +40,7 @@ export class SetupImportKeyModule { } } Xss.sanitizeRender('#step_2b_manual_enter .action_add_private_key', Ui.spinner('white')); - await this.view.saveKeysAndPassPhrase([checked.encrypted], options); + await saveKeysAndPassPhrase(this.view.acctEmail, [checked.encrypted], options); await this.view.submitPublicKeys(options); await this.view.finalizeSetup(); await this.view.setupRender.renderSetupDone(); @@ -70,7 +71,7 @@ export class SetupImportKeyModule { this.view.setupRender.displayBlock('step_2b_manual_enter'); return; } - await this.view.saveKeysAndPassPhrase([fixedPrv], options); + await saveKeysAndPassPhrase(this.view.acctEmail, [fixedPrv], options); await this.view.submitPublicKeys(options); await this.view.finalizeSetup(); await this.view.setupRender.renderSetupDone(); diff --git a/extension/chrome/settings/setup/setup-key-manager-autogen.ts b/extension/chrome/settings/setup/setup-key-manager-autogen.ts index 1b6153d216b..16cc72c5857 100644 --- a/extension/chrome/settings/setup/setup-key-manager-autogen.ts +++ b/extension/chrome/settings/setup/setup-key-manager-autogen.ts @@ -6,13 +6,13 @@ import { SetupOptions, SetupView } from '../setup.js'; import { Ui } from '../../../js/common/browser/ui.js'; import { Url } from '../../../js/common/core/common.js'; import { AcctStore } from '../../../js/common/platform/store/acct-store.js'; -import { Buf } from '../../../js/common/core/buf.js'; import { ApiErr } from '../../../js/common/api/shared/api-error.js'; import { Api } from '../../../js/common/api/shared/api.js'; import { Settings } from '../../../js/common/settings.js'; import { KeyUtil } from '../../../js/common/core/crypto/key.js'; import { OpenPGPKey } from '../../../js/common/core/crypto/pgp/openpgp-key.js'; import { Lang } from '../../../js/common/lang.js'; +import { processAndStoreKeysFromEkmLocally, saveKeysAndPassPhrase } from '../../../js/common/helpers.js'; import { Xss } from '../../../js/common/platform/xss.js'; export class SetupWithEmailKeyManagerModule { @@ -55,7 +55,15 @@ export class SetupWithEmailKeyManagerModule { const { privateKeys } = await this.view.keyManager!.getPrivateKeys(this.view.idToken!); if (privateKeys.length) { // keys already exist on keyserver, auto-import - await this.processAndStoreKeysFromEkmLocally(privateKeys, setupOptions); + try { + await processAndStoreKeysFromEkmLocally({ + acctEmail: this.view.acctEmail, + decryptedPrivateKeys: privateKeys.map(entry => entry.decryptedPrivateKey), + ppOptions: setupOptions + }); + } catch (e) { + throw new Error(`Could not store keys from EKM due to error: ${e instanceof Error ? e.message : String(e)}`); + } } else if (this.view.clientConfiguration.canCreateKeys()) { // generate keys on client and store them on key manager await this.autoGenerateKeyAndStoreBothLocallyAndToEkm(setupOptions); @@ -76,23 +84,6 @@ export class SetupWithEmailKeyManagerModule { } }; - private processAndStoreKeysFromEkmLocally = async (privateKeys: { decryptedPrivateKey: string }[], setupOptions: SetupOptions) => { - const { keys } = await KeyUtil.readMany(Buf.fromUtfStr(privateKeys.map(pk => pk.decryptedPrivateKey).join('\n'))); - if (!keys.length) { - throw new Error(`Could not parse any valid keys from Key Manager response for user ${this.view.acctEmail}`); - } - for (const prv of keys) { - if (!prv.isPrivate) { - throw new Error(`Key ${prv.id} for user ${this.view.acctEmail} is not a private key`); - } - if (!prv.fullyDecrypted) { - throw new Error(`Key ${prv.id} for user ${this.view.acctEmail} from FlowCrypt Email Key Manager is not fully decrypted`); - } - await KeyUtil.encrypt(prv, setupOptions.passphrase); - } - await this.view.saveKeysAndPassPhrase(keys, setupOptions); - }; - private autoGenerateKeyAndStoreBothLocallyAndToEkm = async (setupOptions: SetupOptions) => { const keygenAlgo = this.view.clientConfiguration.getEnforcedKeygenAlgo(); if (!keygenAlgo) { @@ -111,7 +102,7 @@ export class SetupWithEmailKeyManagerModule { } const storePrvOnKm = async () => this.view.keyManager!.storePrivateKey(this.view.idToken!, KeyUtil.armor(decryptablePrv)); await Settings.retryUntilSuccessful(storePrvOnKm, 'Failed to store newly generated key on FlowCrypt Email Key Manager', Lang.general.contactIfNeedAssistance(this.view.isFesUsed())); - await this.view.saveKeysAndPassPhrase([await KeyUtil.parse(generated.private)], setupOptions); // store encrypted key + pass phrase locally + await saveKeysAndPassPhrase(this.view.acctEmail, [await KeyUtil.parse(generated.private)], setupOptions); // store encrypted key + pass phrase locally }; } diff --git a/extension/chrome/settings/setup/setup-recover-key.ts b/extension/chrome/settings/setup/setup-recover-key.ts index 4be82f1f2ed..0480cd550ae 100644 --- a/extension/chrome/settings/setup/setup-recover-key.ts +++ b/extension/chrome/settings/setup/setup-recover-key.ts @@ -12,6 +12,7 @@ import { Url } from '../../../js/common/core/common.js'; import { Xss } from '../../../js/common/platform/xss.js'; import { AcctStore } from '../../../js/common/platform/store/acct-store.js'; import { KeyStore } from '../../../js/common/platform/store/key-store.js'; +import { saveKeysAndPassPhrase } from '../../../js/common/helpers.js'; export class SetupRecoverKeyModule { @@ -63,7 +64,7 @@ export class SetupRecoverKeyModule { passphrase_save: true, // todo - reevaluate saving passphrase when recovering recovered: true, }; - await this.view.saveKeysAndPassPhrase(newlyMatchingKeys, options); + await saveKeysAndPassPhrase(this.view.acctEmail, newlyMatchingKeys, options); const { setup_done } = await AcctStore.get(this.view.acctEmail, ['setup_done']); if (!setup_done) { // normal situation - fresh setup await this.view.submitPublicKeys(options); diff --git a/extension/js/background_page/background_page.ts b/extension/js/background_page/background_page.ts index e2f552366f1..9930d238f6f 100644 --- a/extension/js/background_page/background_page.ts +++ b/extension/js/background_page/background_page.ts @@ -16,6 +16,7 @@ import { ContactStore } from '../common/platform/store/contact-store.js'; import { AcctStore } from '../common/platform/store/acct-store.js'; import { ExpirationCache } from '../common/core/expiration-cache.js'; import { emailKeyIndex } from '../common/core/common.js'; +import { processAndStoreKeysFromEkmLocally } from '../common/helpers.js'; console.info('background_process.js starting'); @@ -61,6 +62,7 @@ opgp.initWorker({ path: '/lib/openpgp.worker.js' }); BrowserMsg.bgAddListener('storeGlobalSet', (r: Bm.StoreGlobalSet) => GlobalStore.set(r.values)); BrowserMsg.bgAddListener('storeAcctGet', (r: Bm.StoreAcctGet) => AcctStore.get(r.acctEmail, r.keys)); BrowserMsg.bgAddListener('storeAcctSet', (r: Bm.StoreAcctSet) => AcctStore.set(r.acctEmail, r.values)); + BrowserMsg.bgAddListener('processAndStoreKeysFromEkmLocally', processAndStoreKeysFromEkmLocally); BrowserMsg.addPgpListeners(); // todo - remove https://github.com/FlowCrypt/flowcrypt-browser/issues/2560 fixed diff --git a/extension/js/common/browser/browser-msg.ts b/extension/js/common/browser/browser-msg.ts index 2de6a82760f..5a9a31e202f 100644 --- a/extension/js/common/browser/browser-msg.ts +++ b/extension/js/common/browser/browser-msg.ts @@ -65,6 +65,7 @@ export namespace Bm { export type ShowAttachmentPreview = { iframeUrl: string }; export type ReRenderRecipient = { email: string }; export type SaveFetchedPubkeys = { email: string, pubkeys: string[] }; + export type ProcessAndStoreKeysFromEkmLocally = { acctEmail: string, decryptedPrivateKeys: string[] }; export namespace Res { export type GetActiveTabInfo = { provider: 'gmail' | undefined, acctEmail: string | undefined, sameWorld: boolean | undefined }; @@ -83,13 +84,14 @@ export namespace Bm { export type AjaxGmailAttachmentGetChunk = { chunk: Buf }; export type _tab_ = { tabId: string | null | undefined }; export type SaveFetchedPubkeys = boolean; + export type ProcessAndStoreKeysFromEkmLocally = { needPassphrase: boolean, updateCount: number }; export type Db = any; // not included in Any below export type Ajax = any; // not included in Any below export type Any = GetActiveTabInfo | _tab_ | ReconnectAcctAuthPopup | PgpMsgDecrypt | PgpMsgDiagnoseMsgPubkeys | PgpMsgVerify | PgpHashChallengeAnswer | PgpMsgType | InMemoryStoreGet | InMemoryStoreSet | StoreAcctGet | StoreAcctSet | StoreGlobalGet | StoreGlobalSet - | AjaxGmailAttachmentGetChunk | SaveFetchedPubkeys; + | AjaxGmailAttachmentGetChunk | SaveFetchedPubkeys | ProcessAndStoreKeysFromEkmLocally; } export type AnyRequest = PassphraseEntry | OpenPage | OpenGoogleAuthDialog | Redirect | Reload | @@ -98,7 +100,7 @@ export namespace Bm { NotificationShow | PassphraseDialog | PassphraseDialog | Settings | SetCss | AddOrRemoveClass | ReconnectAcctAuthPopup | Db | InMemoryStoreSet | InMemoryStoreGet | StoreGlobalGet | StoreGlobalSet | StoreAcctGet | StoreAcctSet | PgpMsgDecrypt | PgpMsgDiagnoseMsgPubkeys | PgpMsgVerifyDetached | PgpHashChallengeAnswer | PgpMsgType | Ajax | - ShowAttachmentPreview | ReRenderRecipient | SaveFetchedPubkeys; + ShowAttachmentPreview | ReRenderRecipient | SaveFetchedPubkeys | ProcessAndStoreKeysFromEkmLocally; // export type RawResponselessHandler = (req: AnyRequest) => Promise; // export type RawRespoHandler = (req: AnyRequest) => Promise; @@ -146,6 +148,8 @@ export class BrowserMsg { pgpMsgVerifyDetached: (bm: Bm.PgpMsgVerifyDetached) => BrowserMsg.sendAwait(undefined, 'pgpMsgVerifyDetached', bm, true) as Promise, pgpMsgType: (bm: Bm.PgpMsgType) => BrowserMsg.sendAwait(undefined, 'pgpMsgType', bm, true) as Promise, saveFetchedPubkeys: (bm: Bm.SaveFetchedPubkeys) => BrowserMsg.sendAwait(undefined, 'saveFetchedPubkeys', bm, true) as Promise, + processAndStoreKeysFromEkmLocally: + (bm: Bm.ProcessAndStoreKeysFromEkmLocally) => BrowserMsg.sendAwait(undefined, 'processAndStoreKeysFromEkmLocally', bm, true) as Promise, }, }, passphraseEntry: (dest: Bm.Dest, bm: Bm.PassphraseEntry) => BrowserMsg.sendCatch(dest, 'passphrase_entry', bm), diff --git a/extension/js/common/core/crypto/key.ts b/extension/js/common/core/crypto/key.ts index a250437d4be..b4ebc013fdd 100644 --- a/extension/js/common/core/crypto/key.ts +++ b/extension/js/common/core/crypto/key.ts @@ -363,7 +363,7 @@ export class KeyUtil { } }; - public static reformatKey = async (privateKey: Key, passphrase: string, userIds: { email: string | undefined; name: string }[], expireSeconds: number) => { + public static reformatKey = async (privateKey: Key, passphrase: string | undefined, userIds: { email: string | undefined; name: string }[], expireSeconds: number) => { if (privateKey.family === 'openpgp') { return await OpenPGPKey.reformatKey(privateKey, passphrase, userIds, expireSeconds); } else { diff --git a/extension/js/common/core/crypto/pgp/openpgp-key.ts b/extension/js/common/core/crypto/pgp/openpgp-key.ts index 99cf1aa3cb1..3dce1414b50 100644 --- a/extension/js/common/core/crypto/pgp/openpgp-key.ts +++ b/extension/js/common/core/crypto/pgp/openpgp-key.ts @@ -151,7 +151,7 @@ export class OpenPGPKey { return await Catch.doesReject(opgpPrv.verifyPrimaryKey(), ['No self-certifications']); }; - public static reformatKey = async (privateKey: Key, passphrase: string, userIds: { email: string | undefined; name: string }[], expireSeconds: number) => { + public static reformatKey = async (privateKey: Key, passphrase: string | undefined, userIds: { email: string | undefined; name: string }[], expireSeconds: number) => { const opgpPrv = OpenPGPKey.extractExternalLibraryObjFromKey(privateKey); const keyPair = await opgp.reformatKey({ privateKey: opgpPrv, passphrase, userIds, keyExpirationTime: expireSeconds }); return await OpenPGPKey.convertExternalLibraryObjToKey(keyPair.key); diff --git a/extension/js/common/helpers.ts b/extension/js/common/helpers.ts index 066962223ec..b67fc651d50 100644 --- a/extension/js/common/helpers.ts +++ b/extension/js/common/helpers.ts @@ -3,8 +3,118 @@ 'use strict'; import { AcctStore } from './platform/store/acct-store.js'; - +import { PassphraseOptions } from '../../chrome/settings/setup.js'; +import { Buf } from './core/buf.js'; +import { Key, KeyInfoWithIdentity, KeyUtil } from './core/crypto/key.js'; +import { ClientConfiguration } from './client-configuration.js'; +import { ContactStore } from './platform/store/contact-store.js'; +import { KeyStore } from './platform/store/key-store.js'; +import { PassphraseStore } from './platform/store/passphrase-store.js'; +import { Bm } from './browser/browser-msg.js'; export const isFesUsed = async (acctEmail: string) => { const { fesUrl } = await AcctStore.get(acctEmail, ['fesUrl']); return Boolean(fesUrl); }; + +export const saveKeysAndPassPhrase = async (acctEmail: string, prvs: Key[], ppOptions?: PassphraseOptions) => { + const clientConfiguration = await ClientConfiguration.newInstance(acctEmail); + for (const prv of prvs) { + await KeyStore.add(acctEmail, prv); + if (ppOptions !== undefined) { + await PassphraseStore.set((ppOptions.passphrase_save && !clientConfiguration.forbidStoringPassPhrase()) ? 'local' : 'session', + acctEmail, { longid: KeyUtil.getPrimaryLongid(prv) }, ppOptions.passphrase); + } + } + const { sendAs, full_name: name } = await AcctStore.get(acctEmail, ['sendAs', 'full_name']); + const myOwnEmailsAddrs: string[] = [acctEmail].concat(Object.keys(sendAs!)); + for (const email of myOwnEmailsAddrs) { + if (ppOptions !== undefined) { + // first run, update `name`, todo: refactor in #4545 + await ContactStore.update(undefined, email, { name }); + } + for (const prv of prvs) { + await ContactStore.update(undefined, email, { pubkey: KeyUtil.armor(await KeyUtil.asPublicKey(prv)) }); + } + } +}; + +const parseAndCheckPrivateKeys = async (decryptedPrivateKeys: string[]) => { + const unencryptedPrvs: Key[] = []; + // parse and check that all the keys are valid + for (const entry of decryptedPrivateKeys) { + const { keys, errs } = await KeyUtil.readMany(Buf.fromUtfStr(entry)); + if (errs.length) { + throw new Error(`Some keys could not be parsed`); + } + if (!keys.length) { + throw new Error(`Could not parse any valid keys`); + } + for (const prv of keys) { + if (!prv.isPrivate) { + throw new Error(`Key ${prv.id} is not a private key`); + } + if (!prv.fullyDecrypted) { + throw new Error(`Key ${prv.id} is not fully decrypted`); + } + } + unencryptedPrvs.push(...keys); + } + return { unencryptedPrvs }; +}; + +const filterKeysToSave = async (candidateKeys: Key[], existingKeys: KeyInfoWithIdentity[]) => { + if (!existingKeys.length) { + return candidateKeys; + } + const result: Key[] = []; + for (const candidate of candidateKeys) { + const longid = KeyUtil.getPrimaryLongid(candidate); + const keyToUpdate = existingKeys.filter(ki => ki.longid === longid && ki.family === candidate.family); + if (keyToUpdate.length === 1) { + const oldKey = await KeyUtil.parse(keyToUpdate[0].private); + if (!candidate.lastModified || (oldKey.lastModified && oldKey.lastModified >= candidate.lastModified)) { + continue; + } + } else if (keyToUpdate.length > 1) { + throw new Error(`Unexpected error: key search by longid=${longid} yielded ${keyToUpdate.length} results`); + } + result.push(candidate); + } + return result; +}; + +export const processAndStoreKeysFromEkmLocally = async ( + { acctEmail, decryptedPrivateKeys, ppOptions }: { acctEmail: string, decryptedPrivateKeys: string[], ppOptions?: PassphraseOptions } +): Promise => { + const { unencryptedPrvs } = await parseAndCheckPrivateKeys(decryptedPrivateKeys); + const existingKeys = await KeyStore.get(acctEmail); + let passphrase = ppOptions?.passphrase; + if (passphrase === undefined && !existingKeys.length) { + return { needPassphrase: false, updateCount: 0 }; // return success as we can't possibly validate a passphrase + // this can only happen on misconfiguration + // todo: or should we throw? + } + let unencryptedKeysToSave = await filterKeysToSave(unencryptedPrvs, existingKeys); + let encryptedKeys: Key[] = []; + if (unencryptedKeysToSave.length) { + if (passphrase === undefined) { + // trying to find a passphrase that unlocks at least one key + const passphrases = await PassphraseStore.getMany(acctEmail, existingKeys); + passphrase = passphrases.find(pp => pp !== undefined); + } + if (passphrase !== undefined) { + const pp = passphrase; + // todo: some more fancy conversion, preserving a passphrase for a particual longid? + await Promise.all(unencryptedKeysToSave.map(prv => KeyUtil.encrypt(prv, pp))); + encryptedKeys = unencryptedKeysToSave; + unencryptedKeysToSave = []; + } + } + if (encryptedKeys.length) { + // also updates `name`, todo: refactor in #4545 + await saveKeysAndPassPhrase(acctEmail, encryptedKeys, ppOptions); + return { needPassphrase: false, updateCount: encryptedKeys.length }; + } else { + return { needPassphrase: unencryptedKeysToSave.length > 0, updateCount: 0 }; + } +}; diff --git a/extension/js/common/platform/store/passphrase-store.ts b/extension/js/common/platform/store/passphrase-store.ts index 19e658ca245..0dd56386feb 100644 --- a/extension/js/common/platform/store/passphrase-store.ts +++ b/extension/js/common/platform/store/passphrase-store.ts @@ -13,8 +13,13 @@ export class PassphraseStore extends AbstractStore { // if we implement (and migrate) password storage to use KeyIdentity instead of longid, we'll have `keyInfo: KeyIdentity` here public static get = async (acctEmail: string, keyInfo: { longid: string }, ignoreSession: boolean = false): Promise => { - const storageIndex = PassphraseStore.getIndex(keyInfo.longid); - return await PassphraseStore.getByIndex(acctEmail, storageIndex, ignoreSession); + return (await PassphraseStore.getMany(acctEmail, [keyInfo], ignoreSession))[0]; + }; + + // if we implement (and migrate) password storage to use KeyIdentity instead of longid, we'll have `keyInfo: KeyIdentity` here + public static getMany = async (acctEmail: string, keyInfos: { longid: string }[], ignoreSession: boolean = false): Promise<(string | undefined)[]> => { + const storageIndexes = keyInfos.map(keyInfo => PassphraseStore.getIndex(keyInfo.longid)); + return await PassphraseStore.getByIndexes(acctEmail, storageIndexes, ignoreSession); }; // if we implement (and migrate) password storage to use KeyIdentity instead of longid, we'll have `keyInfo: KeyIdentity` here @@ -50,17 +55,19 @@ export class PassphraseStore extends AbstractStore { return `passphrase_${longid}` as unknown as AccountIndex; }; - private static getByIndex = async (acctEmail: string, storageIndex: AccountIndex, ignoreSession: boolean = false): Promise => { - const storage = await AcctStore.get(acctEmail, [storageIndex]); - const found = storage[storageIndex]; - if (typeof found === 'string') { - return found; - } - if (ignoreSession) { - return undefined; - } - const res = await InMemoryStore.get(acctEmail, storageIndex) ?? undefined; - return res; + private static getByIndexes = async (acctEmail: string, storageIndexes: AccountIndex[], ignoreSession: boolean = false): Promise<(string | undefined)[]> => { + const storage = await AcctStore.get(acctEmail, storageIndexes); + const results = await Promise.all(storageIndexes.map(async storageIndex => { + const found = storage[storageIndex]; + if (typeof found === 'string') { + return found; + } + if (ignoreSession) { + return undefined; + } + return await InMemoryStore.get(acctEmail, storageIndex) ?? undefined; + })); + return results; }; private static setByIndex = async (storageType: StorageType, acctEmail: string, storageIndex: AccountIndex, passphrase: string | undefined): Promise => { diff --git a/extension/js/common/xss-safe-factory.ts b/extension/js/common/xss-safe-factory.ts index 1bf63e6665d..681cb9be34d 100644 --- a/extension/js/common/xss-safe-factory.ts +++ b/extension/js/common/xss-safe-factory.ts @@ -19,7 +19,7 @@ import { SendAsAlias } from './platform/store/acct-store.js'; type Placement = 'settings' | 'settings_compose' | 'default' | 'dialog' | 'gmail' | 'embedded' | 'compose'; export type WebmailVariantString = undefined | 'html' | 'standard' | 'new'; -export type PassphraseDialogType = 'embedded' | 'message' | 'attachment' | 'draft' | 'sign' | `quote` | `backup`; +export type PassphraseDialogType = 'embedded' | 'message' | 'attachment' | 'draft' | 'sign' | `quote` | `backup` | 'update_key'; export type FactoryReplyParams = { replyMsgId?: string, draftId?: string, diff --git a/extension/js/content_scripts/webmail/setup-webmail-content-script.ts b/extension/js/content_scripts/webmail/setup-webmail-content-script.ts index 107042cdfe4..802c5e33207 100644 --- a/extension/js/content_scripts/webmail/setup-webmail-content-script.ts +++ b/extension/js/content_scripts/webmail/setup-webmail-content-script.ts @@ -12,9 +12,12 @@ import { Injector } from '../../common/inject.js'; import { Notifications } from '../../common/notifications.js'; import Swal from 'sweetalert2'; import { Ui } from '../../common/browser/ui.js'; -import { VERSION } from '../../common/core/const.js'; +import { InMemoryStoreKeys, VERSION } from '../../common/core/const.js'; import { AcctStore } from '../../common/platform/store/acct-store.js'; import { GlobalStore } from '../../common/platform/store/global-store.js'; +import { ClientConfiguration } from '../../common/client-configuration.js'; +import { KeyManager } from '../../common/api/key-server/key-manager.js'; +import { InMemoryStore } from '../../common/platform/store/in-memory-store.js'; export type WebmailVariantObject = { newDataLayer: undefined | boolean, newUi: undefined | boolean, email: undefined | string, gmailVariant: WebmailVariantString }; export type IntervalFunction = { interval: number, handler: () => void }; @@ -24,7 +27,7 @@ type WebmailSpecificInfo = { getUserAccountEmail: () => string | undefined; getUserFullName: () => string | undefined; getReplacer: () => WebmailElementReplacer; - start: (acctEmail: string, inject: Injector, notifications: Notifications, factory: XssSafeFactory, notifyMurdered: () => void) => Promise; + start: (acctEmail: string, clientConfiguration: ClientConfiguration, inject: Injector, notifications: Notifications, factory: XssSafeFactory, notifyMurdered: () => void) => Promise; }; export interface WebmailElementReplacer { getIntervalFunctions: () => Array; @@ -116,7 +119,7 @@ export const contentScriptSetupIfVacant = async (webmailSpecific: WebmailSpecifi } }; - const browserMsgListen = (acctEmail: string, tabId: string, inject: Injector, factory: XssSafeFactory, notifications: Notifications) => { + const browserMsgListen = (acctEmail: string, tabId: string, inject: Injector, factory: XssSafeFactory, notifications: Notifications, ppEvent: { entered?: boolean }) => { BrowserMsg.addListener('set_active_window', async ({ frameId }: Bm.ComposeWindow) => { if ($(`.secure_compose_window.active[data-frame-id="${frameId}"]`).length) { return; // already active @@ -174,8 +177,11 @@ export const contentScriptSetupIfVacant = async (webmailSpecific: WebmailSpecifi BrowserMsg.addListener('scroll_to_cursor_in_reply_box', async ({ replyMsgId, cursorOffsetTop }: Bm.ScrollToCursorInReplyBox) => { webmailSpecific.getReplacer().scrollToCursorInReplyBox(replyMsgId, cursorOffsetTop); }); - BrowserMsg.addListener('passphrase_dialog', async ({ longids, type, initiatorFrameId }: Bm.PassphraseDialog) => { - await factory.showPassphraseDialog(longids, type, initiatorFrameId); + BrowserMsg.addListener('passphrase_dialog', async (args: Bm.PassphraseDialog) => { + await showPassphraseDialog(factory, args); + }); + BrowserMsg.addListener('passphrase_entry', async ({ entered }: Bm.PassphraseEntry) => { + ppEvent.entered = entered; }); BrowserMsg.addListener('add_pubkey_dialog', async ({ emails }: Bm.AddPubkeyDialog) => { await factory.showAddPubkeyDialog(emails); @@ -232,13 +238,58 @@ export const contentScriptSetupIfVacant = async (webmailSpecific: WebmailSpecifi notifEl.appendChild(div); }; + const showPassphraseDialog = async (factory: XssSafeFactory, { longids, type, initiatorFrameId }: Bm.PassphraseDialog) => { + await factory.showPassphraseDialog(longids, type, initiatorFrameId); + }; + + const processKeysFromEkm = async (acctEmail: string, decryptedPrivateKeys: string[], factory: XssSafeFactory, ppEvent: { entered?: boolean }) => { + try { + const { needPassphrase, updateCount } = await BrowserMsg.send.bg.await.processAndStoreKeysFromEkmLocally({ acctEmail, decryptedPrivateKeys }); + if (needPassphrase) { + ppEvent.entered = undefined; + await showPassphraseDialog(factory, { longids: [], type: 'update_key' }); + while (ppEvent.entered === undefined) { + await Ui.time.sleep(100); + } + if (ppEvent.entered) { + await processKeysFromEkm(acctEmail, decryptedPrivateKeys, factory, ppEvent); + } else { + return; + } + } else if (updateCount > 0) { + Ui.toast('Account keys updated'); + } + } catch (e) { + Catch.reportErr(e); + Ui.toast(`Could not update keys from EKM due to error: ${e instanceof Error ? e.message : String(e)}`); + } + }; + + const startPullingKeysFromEkm = async (acctEmail: string, clientConfiguration: ClientConfiguration, factory: XssSafeFactory, ppEvent: { entered?: boolean }) => { + if (clientConfiguration.usesKeyManager()) { + const idToken = await InMemoryStore.get(acctEmail, InMemoryStoreKeys.ID_TOKEN); + if (idToken) { + const keyManager = new KeyManager(clientConfiguration.getKeyManagerUrlForPrivateKeys()!); + Catch.setHandledTimeout(async () => { + const { privateKeys } = await keyManager.getPrivateKeys(idToken); + if (privateKeys.length) { + await processKeysFromEkm(acctEmail, privateKeys.map(entry => entry.decryptedPrivateKey), factory, ppEvent); + } + }, 0); + } + } + }; + const entrypoint = async () => { try { const acctEmail = await waitForAcctEmail(); const { tabId, notifications, factory, inject } = await initInternalVars(acctEmail); await showNotificationsAndWaitTilAcctSetUp(acctEmail, notifications); - browserMsgListen(acctEmail, tabId, inject, factory, notifications); - await webmailSpecific.start(acctEmail, inject, notifications, factory, notifyMurdered); + const ppEvent: { entered?: boolean } = {}; + browserMsgListen(acctEmail, tabId, inject, factory, notifications, ppEvent); + const clientConfiguration = await ClientConfiguration.newInstance(acctEmail); + await startPullingKeysFromEkm(acctEmail, clientConfiguration, factory, ppEvent); + await webmailSpecific.start(acctEmail, clientConfiguration, inject, notifications, factory, notifyMurdered); } catch (e) { if (e instanceof TabIdRequiredError) { console.error(`FlowCrypt cannot start: ${String(e)}`); diff --git a/extension/js/content_scripts/webmail/webmail.ts b/extension/js/content_scripts/webmail/webmail.ts index 187ad913c73..3398a2b6535 100644 --- a/extension/js/content_scripts/webmail/webmail.ts +++ b/extension/js/content_scripts/webmail/webmail.ts @@ -81,10 +81,15 @@ Catch.try(async () => { return insights; }; - const start = async (acctEmail: string, injector: Injector, notifications: Notifications, factory: XssSafeFactory, notifyMurdered: () => void) => { + const start = async (acctEmail: string, + clientConfiguration: ClientConfiguration, + injector: Injector, + notifications: Notifications, + factory: XssSafeFactory, + notifyMurdered: () => void + ) => { hijackGmailHotkeys(); const storage = await AcctStore.get(acctEmail, ['sendAs', 'full_name']); - const clientConfiguration = await ClientConfiguration.newInstance(acctEmail); if (!storage.sendAs) { storage.sendAs = {}; storage.sendAs[acctEmail] = { name: storage.full_name, isPrimary: true }; diff --git a/package-lock.json b/package-lock.json index 683d9131681..f8ddf9cd166 100644 --- a/package-lock.json +++ b/package-lock.json @@ -33,8 +33,7 @@ "@types/mkdirp": "1.0.2", "@types/request": "2.48.8", "@typescript-eslint/eslint-plugin": "5.33.0", - "@typescript-eslint/parser": "5.33.0" - + "@typescript-eslint/parser": "5.33.0", "ava": "4.3.1", "chai": "4.3.6", "chai-as-promised": "7.1.1", @@ -586,62 +585,6 @@ } } }, - "node_modules/@typescript-eslint/eslint-plugin/node_modules/@typescript-eslint/scope-manager": { - "version": "5.33.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-5.33.0.tgz", - "integrity": "sha512-/Jta8yMNpXYpRDl8EwF/M8It2A9sFJTubDo0ATZefGXmOqlaBffEw0ZbkbQ7TNDK6q55NPHFshGBPAZvZkE8Pw==", - "dev": true, - "dependencies": { - "@typescript-eslint/types": "5.33.0", - "@typescript-eslint/visitor-keys": "5.33.0" - }, - "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - } - }, - "node_modules/@typescript-eslint/eslint-plugin/node_modules/@typescript-eslint/types": { - "version": "5.33.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-5.33.0.tgz", - "integrity": "sha512-nIMt96JngB4MYFYXpZ/3ZNU4GWPNdBbcB5w2rDOCpXOVUkhtNlG2mmm8uXhubhidRZdwMaMBap7Uk8SZMU/ppw==", - "dev": true, - "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - } - }, - "node_modules/@typescript-eslint/eslint-plugin/node_modules/@typescript-eslint/visitor-keys": { - "version": "5.33.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-5.33.0.tgz", - "integrity": "sha512-/XsqCzD4t+Y9p5wd9HZiptuGKBlaZO5showwqODii5C0nZawxWLF+Q6k5wYHBrQv96h6GYKyqqMHCSTqta8Kiw==", - "dev": true, - "dependencies": { - "@typescript-eslint/types": "5.33.0", - "eslint-visitor-keys": "^3.3.0" - }, - "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - } - }, - "node_modules/@typescript-eslint/eslint-plugin/node_modules/eslint-visitor-keys": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.3.0.tgz", - "integrity": "sha512-mQ+suqKJVyeuwGYHAdjMFqjCyfl8+Ldnxuyp3ldiMBFKkvytrXUZWaiPCEav8qDHKty44bD+qV1IP4T+w+xXRA==", - "dev": true, - "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - } - }, "node_modules/@typescript-eslint/parser": { "version": "5.33.0", "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-5.33.0.tgz", @@ -776,89 +719,6 @@ "eslint": "^6.0.0 || ^7.0.0 || ^8.0.0" } }, - "node_modules/@typescript-eslint/utils/node_modules/@typescript-eslint/scope-manager": { - "version": "5.33.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-5.33.0.tgz", - "integrity": "sha512-/Jta8yMNpXYpRDl8EwF/M8It2A9sFJTubDo0ATZefGXmOqlaBffEw0ZbkbQ7TNDK6q55NPHFshGBPAZvZkE8Pw==", - "dev": true, - "dependencies": { - "@typescript-eslint/types": "5.33.0", - "@typescript-eslint/visitor-keys": "5.33.0" - }, - "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - } - }, - "node_modules/@typescript-eslint/utils/node_modules/@typescript-eslint/types": { - "version": "5.33.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-5.33.0.tgz", - "integrity": "sha512-nIMt96JngB4MYFYXpZ/3ZNU4GWPNdBbcB5w2rDOCpXOVUkhtNlG2mmm8uXhubhidRZdwMaMBap7Uk8SZMU/ppw==", - "dev": true, - "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - } - }, - "node_modules/@typescript-eslint/utils/node_modules/@typescript-eslint/typescript-estree": { - "version": "5.33.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-5.33.0.tgz", - "integrity": "sha512-tqq3MRLlggkJKJUrzM6wltk8NckKyyorCSGMq4eVkyL5sDYzJJcMgZATqmF8fLdsWrW7OjjIZ1m9v81vKcaqwQ==", - "dev": true, - "dependencies": { - "@typescript-eslint/types": "5.33.0", - "@typescript-eslint/visitor-keys": "5.33.0", - "debug": "^4.3.4", - "globby": "^11.1.0", - "is-glob": "^4.0.3", - "semver": "^7.3.7", - "tsutils": "^3.21.0" - }, - "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - }, - "peerDependenciesMeta": { - "typescript": { - "optional": true - } - } - }, - "node_modules/@typescript-eslint/utils/node_modules/@typescript-eslint/visitor-keys": { - "version": "5.33.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-5.33.0.tgz", - "integrity": "sha512-/XsqCzD4t+Y9p5wd9HZiptuGKBlaZO5showwqODii5C0nZawxWLF+Q6k5wYHBrQv96h6GYKyqqMHCSTqta8Kiw==", - "dev": true, - "dependencies": { - "@typescript-eslint/types": "5.33.0", - "eslint-visitor-keys": "^3.3.0" - }, - "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - } - }, - "node_modules/@typescript-eslint/utils/node_modules/eslint-visitor-keys": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.3.0.tgz", - "integrity": "sha512-mQ+suqKJVyeuwGYHAdjMFqjCyfl8+Ldnxuyp3ldiMBFKkvytrXUZWaiPCEav8qDHKty44bD+qV1IP4T+w+xXRA==", - "dev": true, - "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - } - }, "node_modules/@typescript-eslint/visitor-keys": { "version": "5.33.0", "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-5.33.0.tgz", @@ -986,28 +846,6 @@ "node": ">=10.10.0" } }, - "node_modules/addons-linter/node_modules/@humanwhocodes/config-array/node_modules/brace-expansion": { - "version": "1.1.11", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", - "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", - "dev": true, - "dependencies": { - "balanced-match": "^1.0.0", - "concat-map": "0.0.1" - } - }, - "node_modules/addons-linter/node_modules/@humanwhocodes/config-array/node_modules/minimatch": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", - "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", - "dev": true, - "dependencies": { - "brace-expansion": "^1.1.7" - }, - "engines": { - "node": "*" - } - }, "node_modules/addons-linter/node_modules/ajv": { "version": "8.11.0", "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.11.0.tgz", @@ -1203,32 +1041,24 @@ "url": "https://github.com/sponsors/epoberezkin" } }, - "node_modules/addons-linter/node_modules/eslint/node_modules/brace-expansion": { - "version": "1.1.11", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", - "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", - "dev": true, - "dependencies": { - "balanced-match": "^1.0.0", - "concat-map": "0.0.1" - } - }, "node_modules/addons-linter/node_modules/eslint/node_modules/json-schema-traverse": { "version": "0.4.1", "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", "dev": true }, - "node_modules/addons-linter/node_modules/eslint/node_modules/minimatch": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", - "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "node_modules/addons-linter/node_modules/espree": { + "version": "9.3.2", + "resolved": "https://registry.npmjs.org/espree/-/espree-9.3.2.tgz", + "integrity": "sha512-D211tC7ZwouTIuY5x9XnS0E9sWNChB7IYKX/Xp5eQj3nFXhqmiUDB9q27y76oFl8jTg3pXcQx/bpxMfs3CIZbA==", "dev": true, "dependencies": { - "brace-expansion": "^1.1.7" + "acorn": "^8.7.1", + "acorn-jsx": "^5.3.2", + "eslint-visitor-keys": "^3.3.0" }, "engines": { - "node": "*" + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" } }, "node_modules/addons-linter/node_modules/estraverse": { @@ -1271,6 +1101,18 @@ "node": ">=10.13.0" } }, + "node_modules/addons-linter/node_modules/glob/node_modules/minimatch": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.0.tgz", + "integrity": "sha512-9TPBGGak4nHfGZsPBohm9AWg6NoT7QTCehS3BIJABslyZbzxfV78QM2Y6+i741OPZIafFAaiiEMh5OyIrJPgtg==", + "dev": true, + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=10" + } + }, "node_modules/addons-linter/node_modules/js-yaml": { "version": "4.1.0", "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", @@ -1289,18 +1131,6 @@ "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", "dev": true }, - "node_modules/addons-linter/node_modules/minimatch": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.0.tgz", - "integrity": "sha512-9TPBGGak4nHfGZsPBohm9AWg6NoT7QTCehS3BIJABslyZbzxfV78QM2Y6+i741OPZIafFAaiiEMh5OyIrJPgtg==", - "dev": true, - "dependencies": { - "brace-expansion": "^2.0.1" - }, - "engines": { - "node": ">=10" - } - }, "node_modules/addons-linter/node_modules/strip-ansi": { "version": "6.0.1", "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", @@ -1845,6 +1675,15 @@ "node": ">=6" } }, + "node_modules/ava/node_modules/is-path-inside": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/is-path-inside/-/is-path-inside-3.0.3.tgz", + "integrity": "sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, "node_modules/ava/node_modules/mem": { "version": "9.0.2", "resolved": "https://registry.npmjs.org/mem/-/mem-9.0.2.tgz", @@ -2199,9 +2038,9 @@ } }, "node_modules/cacheable-lookup": { - "version": "6.0.4", - "resolved": "https://registry.npmjs.org/cacheable-lookup/-/cacheable-lookup-6.0.4.tgz", - "integrity": "sha512-mbcDEZCkv2CZF4G01kr8eBd/5agkt9oCqz75tJMSIsquvRZ2sL6Hi5zGVKi/0OSC9oO1GHfJ2AV0ZIOY9vye0A==", + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/cacheable-lookup/-/cacheable-lookup-6.1.0.tgz", + "integrity": "sha512-KJ/Dmo1lDDhmW2XDPMo+9oiy/CeqosPguPCrgcVzKyZrL6pM1gU2GmPY/xo6OQPTUaA/c0kwHuywB4E6nmT9ww==", "dev": true, "engines": { "node": ">=10.6.0" @@ -3346,18 +3185,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/del/node_modules/is-path-inside": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/is-path-inside/-/is-path-inside-4.0.0.tgz", - "integrity": "sha512-lJJV/5dYS+RcL8uQdBDW9c9uWFLLBNRyFhnAKXw5tVqLlKZ4RMGZKv+YQ/IA3OhD+RpbJa1LLFM1FQPGyIXvOA==", - "dev": true, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/del/node_modules/slash": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/slash/-/slash-4.0.0.tgz", @@ -3870,23 +3697,6 @@ "node": "^12.22.0 || ^14.17.0 || >=16.0.0" } }, - "node_modules/eslint/node_modules/espree": { - "version": "9.3.3", - "resolved": "https://registry.npmjs.org/espree/-/espree-9.3.3.tgz", - "integrity": "sha512-ORs1Rt/uQTqUKjDdGCyrtYxbazf5umATSf/K4qxjmZHORR6HJk+2s/2Pqe+Kk49HHINC/xNIrGfgh8sZcll0ng==", - "dev": true, - "dependencies": { - "acorn": "^8.8.0", - "acorn-jsx": "^5.3.2", - "eslint-visitor-keys": "^3.3.0" - }, - "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - }, - "funding": { - "url": "https://opencollective.com/eslint" - } - }, "node_modules/eslint/node_modules/estraverse": { "version": "5.3.0", "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", @@ -4006,17 +3816,20 @@ } }, "node_modules/espree": { - "version": "9.3.2", - "resolved": "https://registry.npmjs.org/espree/-/espree-9.3.2.tgz", - "integrity": "sha512-D211tC7ZwouTIuY5x9XnS0E9sWNChB7IYKX/Xp5eQj3nFXhqmiUDB9q27y76oFl8jTg3pXcQx/bpxMfs3CIZbA==", + "version": "9.3.3", + "resolved": "https://registry.npmjs.org/espree/-/espree-9.3.3.tgz", + "integrity": "sha512-ORs1Rt/uQTqUKjDdGCyrtYxbazf5umATSf/K4qxjmZHORR6HJk+2s/2Pqe+Kk49HHINC/xNIrGfgh8sZcll0ng==", "dev": true, "dependencies": { - "acorn": "^8.7.1", + "acorn": "^8.8.0", "acorn-jsx": "^5.3.2", "eslint-visitor-keys": "^3.3.0" }, "engines": { "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" } }, "node_modules/espree/node_modules/eslint-visitor-keys": { @@ -4504,20 +4317,6 @@ "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=", "dev": true }, - "node_modules/fsevents": { - "version": "2.3.2", - "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz", - "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==", - "dev": true, - "hasInstallScript": true, - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": "^8.16.0 || ^10.6.0 || >=11.0.0" - } - }, "node_modules/function-bind": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz", @@ -4951,9 +4750,9 @@ } }, "node_modules/got": { - "version": "12.3.0", - "resolved": "https://registry.npmjs.org/got/-/got-12.3.0.tgz", - "integrity": "sha512-7uK06aluHF0UibYFBX3lFUZ2FG/W0KS4O4EqAIrbWIdbPxIT33r6ZJy7Zy+pdh0CP/ZbF3zBa7Fd9dCn7vGPBg==", + "version": "12.3.1", + "resolved": "https://registry.npmjs.org/got/-/got-12.3.1.tgz", + "integrity": "sha512-tS6+JMhBh4iXMSXF6KkIsRxmloPln31QHDlcb6Ec3bzxjjFJFr/8aXdpyuLmVc9I4i2HyBHYw1QU5K1ruUdpkw==", "dev": true, "dependencies": { "@sindresorhus/is": "^5.2.0", @@ -5650,6 +5449,15 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/is-installed-globally/node_modules/is-path-inside": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/is-path-inside/-/is-path-inside-3.0.3.tgz", + "integrity": "sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, "node_modules/is-mergeable-object": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/is-mergeable-object/-/is-mergeable-object-1.1.1.tgz", @@ -5699,12 +5507,15 @@ } }, "node_modules/is-path-inside": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/is-path-inside/-/is-path-inside-3.0.3.tgz", - "integrity": "sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ==", + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/is-path-inside/-/is-path-inside-4.0.0.tgz", + "integrity": "sha512-lJJV/5dYS+RcL8uQdBDW9c9uWFLLBNRyFhnAKXw5tVqLlKZ4RMGZKv+YQ/IA3OhD+RpbJa1LLFM1FQPGyIXvOA==", "dev": true, "engines": { - "node": ">=8" + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, "node_modules/is-plain-obj": { @@ -11021,40 +10832,6 @@ "regexpp": "^3.2.0", "semver": "^7.3.7", "tsutils": "^3.21.0" - }, - "dependencies": { - "@typescript-eslint/scope-manager": { - "version": "5.33.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-5.33.0.tgz", - "integrity": "sha512-/Jta8yMNpXYpRDl8EwF/M8It2A9sFJTubDo0ATZefGXmOqlaBffEw0ZbkbQ7TNDK6q55NPHFshGBPAZvZkE8Pw==", - "dev": true, - "requires": { - "@typescript-eslint/types": "5.33.0", - "@typescript-eslint/visitor-keys": "5.33.0" - } - }, - "@typescript-eslint/types": { - "version": "5.33.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-5.33.0.tgz", - "integrity": "sha512-nIMt96JngB4MYFYXpZ/3ZNU4GWPNdBbcB5w2rDOCpXOVUkhtNlG2mmm8uXhubhidRZdwMaMBap7Uk8SZMU/ppw==", - "dev": true - }, - "@typescript-eslint/visitor-keys": { - "version": "5.33.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-5.33.0.tgz", - "integrity": "sha512-/XsqCzD4t+Y9p5wd9HZiptuGKBlaZO5showwqODii5C0nZawxWLF+Q6k5wYHBrQv96h6GYKyqqMHCSTqta8Kiw==", - "dev": true, - "requires": { - "@typescript-eslint/types": "5.33.0", - "eslint-visitor-keys": "^3.3.0" - } - }, - "eslint-visitor-keys": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.3.0.tgz", - "integrity": "sha512-mQ+suqKJVyeuwGYHAdjMFqjCyfl8+Ldnxuyp3ldiMBFKkvytrXUZWaiPCEav8qDHKty44bD+qV1IP4T+w+xXRA==", - "dev": true - } } }, "@typescript-eslint/parser": { @@ -11123,55 +10900,6 @@ "@typescript-eslint/typescript-estree": "5.33.0", "eslint-scope": "^5.1.1", "eslint-utils": "^3.0.0" - }, - "dependencies": { - "@typescript-eslint/scope-manager": { - "version": "5.33.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-5.33.0.tgz", - "integrity": "sha512-/Jta8yMNpXYpRDl8EwF/M8It2A9sFJTubDo0ATZefGXmOqlaBffEw0ZbkbQ7TNDK6q55NPHFshGBPAZvZkE8Pw==", - "dev": true, - "requires": { - "@typescript-eslint/types": "5.33.0", - "@typescript-eslint/visitor-keys": "5.33.0" - } - }, - "@typescript-eslint/types": { - "version": "5.33.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-5.33.0.tgz", - "integrity": "sha512-nIMt96JngB4MYFYXpZ/3ZNU4GWPNdBbcB5w2rDOCpXOVUkhtNlG2mmm8uXhubhidRZdwMaMBap7Uk8SZMU/ppw==", - "dev": true - }, - "@typescript-eslint/typescript-estree": { - "version": "5.33.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-5.33.0.tgz", - "integrity": "sha512-tqq3MRLlggkJKJUrzM6wltk8NckKyyorCSGMq4eVkyL5sDYzJJcMgZATqmF8fLdsWrW7OjjIZ1m9v81vKcaqwQ==", - "dev": true, - "requires": { - "@typescript-eslint/types": "5.33.0", - "@typescript-eslint/visitor-keys": "5.33.0", - "debug": "^4.3.4", - "globby": "^11.1.0", - "is-glob": "^4.0.3", - "semver": "^7.3.7", - "tsutils": "^3.21.0" - } - }, - "@typescript-eslint/visitor-keys": { - "version": "5.33.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-5.33.0.tgz", - "integrity": "sha512-/XsqCzD4t+Y9p5wd9HZiptuGKBlaZO5showwqODii5C0nZawxWLF+Q6k5wYHBrQv96h6GYKyqqMHCSTqta8Kiw==", - "dev": true, - "requires": { - "@typescript-eslint/types": "5.33.0", - "eslint-visitor-keys": "^3.3.0" - } - }, - "eslint-visitor-keys": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.3.0.tgz", - "integrity": "sha512-mQ+suqKJVyeuwGYHAdjMFqjCyfl8+Ldnxuyp3ldiMBFKkvytrXUZWaiPCEav8qDHKty44bD+qV1IP4T+w+xXRA==", - "dev": true - } } }, "@typescript-eslint/visitor-keys": { @@ -11268,27 +10996,6 @@ "@humanwhocodes/object-schema": "^1.2.1", "debug": "^4.1.1", "minimatch": "^3.0.4" - }, - "dependencies": { - "brace-expansion": { - "version": "1.1.11", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", - "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", - "dev": true, - "requires": { - "balanced-match": "^1.0.0", - "concat-map": "0.0.1" - } - }, - "minimatch": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", - "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", - "dev": true, - "requires": { - "brace-expansion": "^1.1.7" - } - } } }, "ajv": { @@ -11423,30 +11130,11 @@ "uri-js": "^4.2.2" } }, - "brace-expansion": { - "version": "1.1.11", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", - "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", - "dev": true, - "requires": { - "balanced-match": "^1.0.0", - "concat-map": "0.0.1" - } - }, "json-schema-traverse": { "version": "0.4.1", "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", "dev": true - }, - "minimatch": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", - "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", - "dev": true, - "requires": { - "brace-expansion": "^1.1.7" - } } } }, @@ -11466,6 +11154,17 @@ "integrity": "sha512-mQ+suqKJVyeuwGYHAdjMFqjCyfl8+Ldnxuyp3ldiMBFKkvytrXUZWaiPCEav8qDHKty44bD+qV1IP4T+w+xXRA==", "dev": true }, + "espree": { + "version": "9.3.2", + "resolved": "https://registry.npmjs.org/espree/-/espree-9.3.2.tgz", + "integrity": "sha512-D211tC7ZwouTIuY5x9XnS0E9sWNChB7IYKX/Xp5eQj3nFXhqmiUDB9q27y76oFl8jTg3pXcQx/bpxMfs3CIZbA==", + "dev": true, + "requires": { + "acorn": "^8.7.1", + "acorn-jsx": "^5.3.2", + "eslint-visitor-keys": "^3.3.0" + } + }, "estraverse": { "version": "5.3.0", "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", @@ -11483,6 +11182,17 @@ "inherits": "2", "minimatch": "^5.0.1", "once": "^1.3.0" + }, + "dependencies": { + "minimatch": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.0.tgz", + "integrity": "sha512-9TPBGGak4nHfGZsPBohm9AWg6NoT7QTCehS3BIJABslyZbzxfV78QM2Y6+i741OPZIafFAaiiEMh5OyIrJPgtg==", + "dev": true, + "requires": { + "brace-expansion": "^2.0.1" + } + } } }, "glob-parent": { @@ -11509,15 +11219,6 @@ "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", "dev": true }, - "minimatch": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.0.tgz", - "integrity": "sha512-9TPBGGak4nHfGZsPBohm9AWg6NoT7QTCehS3BIJABslyZbzxfV78QM2Y6+i741OPZIafFAaiiEMh5OyIrJPgtg==", - "dev": true, - "requires": { - "brace-expansion": "^2.0.1" - } - }, "strip-ansi": { "version": "6.0.1", "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", @@ -11898,6 +11599,12 @@ "integrity": "sha512-w942bTcih8fdJPJmQHFzkS76NEP8Kzzvmw92cXsazb8intwLqPibPPdXf4ANdKV3rYMuuQYGIWtvz9JilB3NFQ==", "dev": true }, + "is-path-inside": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/is-path-inside/-/is-path-inside-3.0.3.tgz", + "integrity": "sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ==", + "dev": true + }, "mem": { "version": "9.0.2", "resolved": "https://registry.npmjs.org/mem/-/mem-9.0.2.tgz", @@ -12148,9 +11855,9 @@ } }, "cacheable-lookup": { - "version": "6.0.4", - "resolved": "https://registry.npmjs.org/cacheable-lookup/-/cacheable-lookup-6.0.4.tgz", - "integrity": "sha512-mbcDEZCkv2CZF4G01kr8eBd/5agkt9oCqz75tJMSIsquvRZ2sL6Hi5zGVKi/0OSC9oO1GHfJ2AV0ZIOY9vye0A==", + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/cacheable-lookup/-/cacheable-lookup-6.1.0.tgz", + "integrity": "sha512-KJ/Dmo1lDDhmW2XDPMo+9oiy/CeqosPguPCrgcVzKyZrL6pM1gU2GmPY/xo6OQPTUaA/c0kwHuywB4E6nmT9ww==", "dev": true }, "cacheable-request": { @@ -13031,12 +12738,6 @@ "slash": "^4.0.0" } }, - "is-path-inside": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/is-path-inside/-/is-path-inside-4.0.0.tgz", - "integrity": "sha512-lJJV/5dYS+RcL8uQdBDW9c9uWFLLBNRyFhnAKXw5tVqLlKZ4RMGZKv+YQ/IA3OhD+RpbJa1LLFM1FQPGyIXvOA==", - "dev": true - }, "slash": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/slash/-/slash-4.0.0.tgz", @@ -13371,17 +13072,6 @@ "integrity": "sha512-mQ+suqKJVyeuwGYHAdjMFqjCyfl8+Ldnxuyp3ldiMBFKkvytrXUZWaiPCEav8qDHKty44bD+qV1IP4T+w+xXRA==", "dev": true }, - "espree": { - "version": "9.3.3", - "resolved": "https://registry.npmjs.org/espree/-/espree-9.3.3.tgz", - "integrity": "sha512-ORs1Rt/uQTqUKjDdGCyrtYxbazf5umATSf/K4qxjmZHORR6HJk+2s/2Pqe+Kk49HHINC/xNIrGfgh8sZcll0ng==", - "dev": true, - "requires": { - "acorn": "^8.8.0", - "acorn-jsx": "^5.3.2", - "eslint-visitor-keys": "^3.3.0" - } - }, "estraverse": { "version": "5.3.0", "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", @@ -13506,12 +13196,12 @@ "dev": true }, "espree": { - "version": "9.3.2", - "resolved": "https://registry.npmjs.org/espree/-/espree-9.3.2.tgz", - "integrity": "sha512-D211tC7ZwouTIuY5x9XnS0E9sWNChB7IYKX/Xp5eQj3nFXhqmiUDB9q27y76oFl8jTg3pXcQx/bpxMfs3CIZbA==", + "version": "9.3.3", + "resolved": "https://registry.npmjs.org/espree/-/espree-9.3.3.tgz", + "integrity": "sha512-ORs1Rt/uQTqUKjDdGCyrtYxbazf5umATSf/K4qxjmZHORR6HJk+2s/2Pqe+Kk49HHINC/xNIrGfgh8sZcll0ng==", "dev": true, "requires": { - "acorn": "^8.7.1", + "acorn": "^8.8.0", "acorn-jsx": "^5.3.2", "eslint-visitor-keys": "^3.3.0" }, @@ -13897,13 +13587,6 @@ "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=", "dev": true }, - "fsevents": { - "version": "2.3.2", - "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz", - "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==", - "dev": true, - "optional": true - }, "function-bind": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz", @@ -14242,9 +13925,9 @@ } }, "got": { - "version": "12.3.0", - "resolved": "https://registry.npmjs.org/got/-/got-12.3.0.tgz", - "integrity": "sha512-7uK06aluHF0UibYFBX3lFUZ2FG/W0KS4O4EqAIrbWIdbPxIT33r6ZJy7Zy+pdh0CP/ZbF3zBa7Fd9dCn7vGPBg==", + "version": "12.3.1", + "resolved": "https://registry.npmjs.org/got/-/got-12.3.1.tgz", + "integrity": "sha512-tS6+JMhBh4iXMSXF6KkIsRxmloPln31QHDlcb6Ec3bzxjjFJFr/8aXdpyuLmVc9I4i2HyBHYw1QU5K1ruUdpkw==", "dev": true, "requires": { "@sindresorhus/is": "^5.2.0", @@ -14738,6 +14421,14 @@ "requires": { "global-dirs": "^3.0.0", "is-path-inside": "^3.0.2" + }, + "dependencies": { + "is-path-inside": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/is-path-inside/-/is-path-inside-3.0.3.tgz", + "integrity": "sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ==", + "dev": true + } } }, "is-mergeable-object": { @@ -14771,9 +14462,9 @@ "dev": true }, "is-path-inside": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/is-path-inside/-/is-path-inside-3.0.3.tgz", - "integrity": "sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ==", + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/is-path-inside/-/is-path-inside-4.0.0.tgz", + "integrity": "sha512-lJJV/5dYS+RcL8uQdBDW9c9uWFLLBNRyFhnAKXw5tVqLlKZ4RMGZKv+YQ/IA3OhD+RpbJa1LLFM1FQPGyIXvOA==", "dev": true }, "is-plain-obj": { diff --git a/test/source/browser/browser-handle.ts b/test/source/browser/browser-handle.ts index 911dd3235ef..92187659208 100644 --- a/test/source/browser/browser-handle.ts +++ b/test/source/browser/browser-handle.ts @@ -20,8 +20,16 @@ export class BrowserHandle { this.viewport = { height, width }; } - public newPage = async (t: AvaContext, url?: string, initialScript?: EvaluateFunc): Promise => { + public newPage = async ( + t: AvaContext, + url?: string, + initialScript?: EvaluateFunc, + extraHeaders?: Record + ): Promise => { const page = await this.browser.newPage(); + if (extraHeaders !== undefined) { + await page.setExtraHTTPHeaders(extraHeaders); + } await page.setViewport(this.viewport); const controllablePage = new ControllablePage(t, page); if (url) { diff --git a/test/source/mock/google/google-data.ts b/test/source/mock/google/google-data.ts index 84726f2db4a..e39da5cf061 100644 --- a/test/source/mock/google/google-data.ts +++ b/test/source/mock/google/google-data.ts @@ -163,6 +163,17 @@ export class GoogleData { return msgCopy; }; + public static getMockGmailPage = (acct: string) => ` + +
+
${acct}
+
+
+
Full Name
+
+ + `; + private static msgSubject = (m: GmailMsg): string => { const subjectHeader = m.payload && m.payload.headers && m.payload.headers.find(h => h.name === 'Subject'); return (subjectHeader && subjectHeader.value) || ''; diff --git a/test/source/mock/google/google-endpoints.ts b/test/source/mock/google/google-endpoints.ts index 41eab9ad714..881725b0588 100644 --- a/test/source/mock/google/google-endpoints.ts +++ b/test/source/mock/google/google-endpoints.ts @@ -94,6 +94,13 @@ export const mockGoogleEndpoints: HandlersDefinition = { return empty; } }, + '/gmail': async (_parsedReq, req) => { + if (isGet(req)) { + const acct = oauth.checkAuthorizationHeaderWithAccessToken(req.headers.authorization); + return GoogleData.getMockGmailPage(acct); + } + throw new HttpClientErr(`Method not implemented for ${req.url}: ${req.method}`); + }, '/gmail/v1/users/me/settings/sendAs': async (parsedReq, req) => { const acct = oauth.checkAuthorizationHeaderWithAccessToken(req.headers.authorization); if (isGet(req)) { diff --git a/test/source/mock/key-manager/key-manager-endpoints.ts b/test/source/mock/key-manager/key-manager-endpoints.ts index dab7952479b..2c4713e5ba1 100644 --- a/test/source/mock/key-manager/key-manager-endpoints.ts +++ b/test/source/mock/key-manager/key-manager-endpoints.ts @@ -1,6 +1,6 @@ /* ©️ 2016 - present FlowCrypt a.s. Limitations apply. Contact human@flowcrypt.com */ -import { HttpClientErr } from '../lib/api'; +import { HttpClientErr, Status } from '../lib/api'; import { HandlersDefinition } from '../all-apis-mock'; import { isPut, isGet } from '../lib/mock-util'; import { oauth } from '../lib/oauth'; @@ -14,9 +14,6 @@ import { testConstants } from '../../tests/tooling/consts'; // tslint:disable:no-unused-expression /* eslint-disable no-unused-expressions */ -// longid: 00B0115807969D75 -const existingPrv = '-----BEGIN PGP PRIVATE KEY BLOCK-----\r\nVersion: FlowCrypt 7.6.2 Gmail Encryption\r\nComment: Seamlessly send and receive encrypted email\r\n\r\n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\r\n=tqvP\r\n-----END PGP PRIVATE KEY BLOCK-----\r\n'; - const twoKeys1 = `-----BEGIN PGP PRIVATE KEY BLOCK----- xcLYBF+RzZUBCADHT42w0/fMBIEjNZhIgl3bVDXPoX9FYmrROXN2nOy+mEhB @@ -209,6 +206,8 @@ yeSm0uVPwODhwX7ezB9jW6uVt0R8S8iM3rQdEMsA/jDep5LNn47K6o8VrDt0zYo6 export const MOCK_KM_LAST_INSERTED_KEY: { [acct: string]: { privateKey: string } } = {}; // accessed from test runners +export const MOCK_KM_UPDATING_KEY: { response?: { privateKeys: { decryptedPrivateKey: string }[] }, badRequestError?: string } = {}; + export const mockKeyManagerEndpoints: HandlersDefinition = { '/flowcrypt-email-key-manager/v1/keys/private': async ({ body }, req) => { const acctEmail = oauth.checkAuthorizationHeaderWithIdToken(req.headers.authorization); @@ -217,7 +216,13 @@ export const mockKeyManagerEndpoints: HandlersDefinition = { return { privateKeys: [{ decryptedPrivateKey: testConstants.wkdAtgooglemockflowcryptlocalcom8001Private }] }; } if (acctEmail === 'get.key@key-manager-autogen.flowcrypt.test') { - return { privateKeys: [{ decryptedPrivateKey: existingPrv }] }; + return { privateKeys: [{ decryptedPrivateKey: testConstants.existingPrv }] }; + } + if (acctEmail === 'get.updating.key@key-manager-choose-passphrase-forbid-storing.flowcrypt.test') { + if (MOCK_KM_UPDATING_KEY.response !== undefined && MOCK_KM_UPDATING_KEY.badRequestError === undefined) { + return MOCK_KM_UPDATING_KEY.response; + } + throw new HttpClientErr(MOCK_KM_UPDATING_KEY.badRequestError || 'Vague error', Status.BAD_REQUEST); } if (acctEmail === 'get.key@no-submit-client-configuration.key-manager-autogen.flowcrypt.test') { return { privateKeys: [{ decryptedPrivateKey: prvNoSubmit }] }; @@ -226,13 +231,13 @@ export const mockKeyManagerEndpoints: HandlersDefinition = { return { privateKeys: [{ decryptedPrivateKey: twoKeys1 }, { decryptedPrivateKey: twoKeys2 }] }; } if (acctEmail === 'user@key-manager-no-pub-lookup.flowcrypt.test') { - return { privateKeys: [{ decryptedPrivateKey: existingPrv }] }; + return { privateKeys: [{ decryptedPrivateKey: testConstants.existingPrv }] }; } if (acctEmail === 'get.key@key-manager-choose-passphrase-forbid-storing.flowcrypt.test') { - return { privateKeys: [{ decryptedPrivateKey: existingPrv }] }; + return { privateKeys: [{ decryptedPrivateKey: testConstants.existingPrv }] }; } if (acctEmail === 'get.key@key-manager-choose-passphrase.flowcrypt.test') { - return { privateKeys: [{ decryptedPrivateKey: existingPrv }] }; + return { privateKeys: [{ decryptedPrivateKey: testConstants.existingPrv }] }; } if (acctEmail === 'get.key@key-manager-autoimport-no-prv-create.flowcrypt.test') { return { privateKeys: [] }; diff --git a/test/source/test.ts b/test/source/test.ts index c9683e7a090..60192d77faf 100644 --- a/test/source/test.ts +++ b/test/source/test.ts @@ -122,6 +122,11 @@ ava.default.after.always('evaluate Catch.reportErr errors', async t => { // our S/MIME implementation is still early so it throws "reportable" errors like this during tests const usefulErrors = mockBackendData.reportedErrors .filter(e => e.message !== 'Too few bytes to read ASN.1 value.') + // below for test "get.updating.key@key-manager-choose-passphrase-forbid-storing.flowcrypt.test - automatic update of key found on key manager" + .filter(e => ![ + 'BrowserMsg(processAndStoreKeysFromEkmLocally) sendRawResponse::Error: Some keys could not be parsed', + 'BrowserMsg(ajax) Bad Request: 400 when GET-ing https://localhost:8001/flowcrypt-email-key-manager/v1/keys/private (no body): -> RequestTimeout' + ].includes(e.message)) // below for test "user4@standardsubdomainfes.test:8001 - PWD encrypted message with FES web portal - a send fails with gateway update error" .filter(e => !e.message.includes('Test error')) // below for test "no.fes@example.com - skip FES on consumer, show friendly message on enterprise" diff --git a/test/source/tests/page-recipe/compose-page-recipe.ts b/test/source/tests/page-recipe/compose-page-recipe.ts index 3537c5c8ec0..aadc021d17e 100644 --- a/test/source/tests/page-recipe/compose-page-recipe.ts +++ b/test/source/tests/page-recipe/compose-page-recipe.ts @@ -183,7 +183,7 @@ export class ComposePageRecipe extends PageRecipe { } else if (inputMethod === 'keyboard') { await page.press('Escape'); } - await page.waitTillGone('@dialog'); + await page.waitTillGone('@dialog-passphrase'); expect(passPhraseFrame.frame.isDetached()).to.equal(true); }; } diff --git a/test/source/tests/setup.ts b/test/source/tests/setup.ts index 892c1505289..6d143cb3df5 100644 --- a/test/source/tests/setup.ts +++ b/test/source/tests/setup.ts @@ -9,11 +9,13 @@ import { expect } from 'chai'; import { SettingsPageRecipe } from './page-recipe/settings-page-recipe'; import { ComposePageRecipe } from './page-recipe/compose-page-recipe'; import { Str } from './../core/common'; -import { MOCK_KM_LAST_INSERTED_KEY } from './../mock/key-manager/key-manager-endpoints'; +import { MOCK_KM_LAST_INSERTED_KEY, MOCK_KM_UPDATING_KEY } from './../mock/key-manager/key-manager-endpoints'; import { MOCK_ATTESTER_LAST_INSERTED_PUB } from './../mock/attester/attester-endpoints'; import { BrowserRecipe } from './tooling/browser-recipe'; -import { KeyInfoWithIdentity, KeyUtil } from '../core/crypto/key'; +import { Key, KeyInfoWithIdentity, KeyUtil } from '../core/crypto/key'; import { testConstants } from './tooling/consts'; +import { InboxPageRecipe } from './page-recipe/inbox-page-recipe'; +import { PageRecipe } from './page-recipe/abstract-page-recipe'; // tslint:disable:no-blank-lines-func // tslint:disable:no-unused-expression @@ -554,6 +556,191 @@ AN8G3r5Htj8olot+jm9mIa5XLXWzMNUZgg== await securityFrame.notPresent(['@action-change-passphrase-begin', '@action-test-passphrase-begin', '@action-forget-pp']); })); + ava.default('get.updating.key@key-manager-choose-passphrase-forbid-storing.flowcrypt.test - automatic update of key found on key manager', testWithBrowser(undefined, async (t, browser) => { + MOCK_KM_UPDATING_KEY.response = { privateKeys: [{ decryptedPrivateKey: testConstants.updatingPrv }] }; + const acct = 'get.updating.key@key-manager-choose-passphrase-forbid-storing.flowcrypt.test'; + const settingsPage = await BrowserRecipe.openSettingsLoginApprove(t, browser, acct); + const passphrase = 'long enough to suit requirements'; + await SetupPageRecipe.autoSetupWithEKM(settingsPage, { + enterPp: { passphrase, checks: { isSavePassphraseChecked: false, isSavePassphraseHidden: true } } + }); + const accessToken = await BrowserRecipe.getGoogleAccessToken(settingsPage, acct); + const dummyGmailUrl = 'https://gmail.localhost:8001/gmail'; + const extraAuthHeaders = { Authorization: `Bearer ${accessToken}` }; + const retrieveAndCheckKeys = async (expectedKeyCount: number) => { + const { cryptup_getupdatingkeykeymanagerchoosepassphraseforbidstoringflowcrypttest_keys: keyset } + = await settingsPage.getFromLocalStorage(['cryptup_getupdatingkeykeymanagerchoosepassphraseforbidstoringflowcrypttest_keys']); + const kis = keyset as KeyInfoWithIdentity[]; + expect(kis.length).to.equal(expectedKeyCount); + return await Promise.all(kis.map(async ki => { + const prv = await KeyUtil.parse(ki.private); + expect(prv.fullyEncrypted).to.be.true; + expect(await KeyUtil.decrypt(prv, passphrase as string, undefined, undefined)).to.be.true; + expect(prv.lastModified).to.not.be.an.undefined; + return prv; + })); + }; + const updateAndArmorKey = async (prv: Key) => { + return KeyUtil.armor(await KeyUtil.reformatKey(prv, undefined, [{ name: 'Full Name', email: acct }], 6000)); + }; + const set1 = await retrieveAndCheckKeys(1); + // 1. EKM returns the same key, no update, no toast + let gmailPage = await browser.newPage(t, dummyGmailUrl, undefined, extraAuthHeaders); + await PageRecipe.noToastAppears(gmailPage); + await gmailPage.notPresent('@dialog-passphrase'); + const set2 = await retrieveAndCheckKeys(1); + expect(set2[0].lastModified).to.equal(set1[0].lastModified); // no update + await gmailPage.close(); + // 2. EKM returns a newer version of the existing key + const someOlderVersion = await updateAndArmorKey(set2[0]); + MOCK_KM_UPDATING_KEY.response = { privateKeys: [{ decryptedPrivateKey: someOlderVersion }] }; + gmailPage = await browser.newPage(t, dummyGmailUrl, undefined, extraAuthHeaders); + await PageRecipe.waitForToastToAppearAndDisappear(gmailPage, 'Account keys updated'); + const set3 = await retrieveAndCheckKeys(1); + expect(set3[0].lastModified!).to.be.greaterThan(set2[0].lastModified!); // an update happened + await gmailPage.close(); + // 3. EKM returns the same version of the existing key, no toast, no update + gmailPage = await browser.newPage(t, dummyGmailUrl, undefined, extraAuthHeaders); + await PageRecipe.noToastAppears(gmailPage); + await gmailPage.notPresent('@dialog-passphrase'); + const set4 = await retrieveAndCheckKeys(1); + expect(set4[0].lastModified).to.equal(set3[0].lastModified); // no update + // 4. Forget the passphrase, EKM the same version of the existing key, no prompt + await InboxPageRecipe.finishSessionOnInboxPage(gmailPage); + await gmailPage.close(); + gmailPage = await browser.newPage(t, dummyGmailUrl, undefined, extraAuthHeaders); + await PageRecipe.noToastAppears(gmailPage); + await gmailPage.notPresent('@dialog-passphrase'); + const set5 = await retrieveAndCheckKeys(1); + expect(set5[0].lastModified).to.equal(set4[0].lastModified); // no update + await gmailPage.close(); + // 5. EKM returns a newer version of the existing key, canceling passphrase prompt, no update + MOCK_KM_UPDATING_KEY.response = { privateKeys: [{ decryptedPrivateKey: await updateAndArmorKey(set5[0]) }] }; + gmailPage = await browser.newPage(t, dummyGmailUrl, undefined, extraAuthHeaders); + await gmailPage.waitAll('@dialog-passphrase'); + await ComposePageRecipe.cancelPassphraseDialog(gmailPage, 'keyboard'); + await PageRecipe.noToastAppears(gmailPage); + const set6 = await retrieveAndCheckKeys(1); + expect(set6[0].lastModified).to.equal(set5[0].lastModified); // no update + await gmailPage.close(); + // 6. EKM returns a newer version of the existing key, entering the passphrase, update toast + gmailPage = await browser.newPage(t, dummyGmailUrl, undefined, extraAuthHeaders); + await gmailPage.waitAll('@dialog-passphrase'); + { + const passphraseDialog = await gmailPage.getFrame(['passphrase.htm']); + await passphraseDialog.waitForContent('@passphrase-text', 'Enter FlowCrypt pass phrase to keep your account keys up to date'); + await passphraseDialog.waitAndType('@input-pass-phrase', passphrase); + await passphraseDialog.waitAndClick('@action-confirm-pass-phrase-entry'); + } + await gmailPage.waitTillGone('@dialog-passphrase'); + await PageRecipe.waitForToastToAppearAndDisappear(gmailPage, 'Account keys updated'); + const set7 = await retrieveAndCheckKeys(1); + expect(set7[0].lastModified!).to.be.greaterThan(set6[0].lastModified!); // an update happened + await gmailPage.close(); + // 7. EKM returns an older version of the existing key, no toast, no update + MOCK_KM_UPDATING_KEY.response = { privateKeys: [{ decryptedPrivateKey: someOlderVersion }] }; + gmailPage = await browser.newPage(t, dummyGmailUrl, undefined, extraAuthHeaders); + await PageRecipe.noToastAppears(gmailPage); + await gmailPage.notPresent('@dialog-passphrase'); + const set8 = await retrieveAndCheckKeys(1); + expect(set8[0].lastModified).to.equal(set7[0].lastModified); // no update + await gmailPage.close(); + // 8. EKM returns an older version of the existing key, and a new key, toast, new key gets added encrypted with the same passphrase + MOCK_KM_UPDATING_KEY.response = { privateKeys: [{ decryptedPrivateKey: someOlderVersion }, { decryptedPrivateKey: testConstants.existingPrv }] }; + gmailPage = await browser.newPage(t, dummyGmailUrl, undefined, extraAuthHeaders); + await PageRecipe.waitForToastToAppearAndDisappear(gmailPage, 'Account keys updated'); + await gmailPage.notPresent('@dialog-passphrase'); + const set9 = await retrieveAndCheckKeys(2); + const mainKey9 = KeyUtil.filterKeysByIdentity(set9, [{ family: 'openpgp', id: '392FB1E9FF4184659AB6A246835C0141B9ECF536' }]); + expect(mainKey9.length).to.equal(1); + expect(KeyUtil.filterKeysByIdentity(set9, [{ family: 'openpgp', id: 'FAFB7D675AC74E87F84D169F00B0115807969D75' }]).length).to.equal(1); + expect(mainKey9[0].lastModified).to.equal(set8[0].lastModified); // no update + await gmailPage.close(); + // 9. EKM returns a newer version of one key, fully omitting the other one, a toast, and update, no removal + MOCK_KM_UPDATING_KEY.response = { privateKeys: [{ decryptedPrivateKey: await updateAndArmorKey(mainKey9[0]) }] }; + gmailPage = await browser.newPage(t, dummyGmailUrl, undefined, extraAuthHeaders); + await PageRecipe.waitForToastToAppearAndDisappear(gmailPage, 'Account keys updated'); + await gmailPage.notPresent('@dialog-passphrase'); + const set10 = await retrieveAndCheckKeys(2); + const mainKey10 = KeyUtil.filterKeysByIdentity(set10, [{ family: 'openpgp', id: '392FB1E9FF4184659AB6A246835C0141B9ECF536' }]); + expect(mainKey10.length).to.equal(1); + expect(KeyUtil.filterKeysByIdentity(set10, [{ family: 'openpgp', id: 'FAFB7D675AC74E87F84D169F00B0115807969D75' }]).length).to.equal(1); + expect(mainKey10[0].lastModified!).to.be.greaterThan(mainKey9[0].lastModified!); // updated this key + // 10. Forget the passphrase, EKM returns a third key, we enter a passphrase that doesn't match any of the existing keys, no update + await InboxPageRecipe.finishSessionOnInboxPage(gmailPage); + await gmailPage.close(); + MOCK_KM_UPDATING_KEY.response = { privateKeys: [{ decryptedPrivateKey: testConstants.unprotectedPrvKey }] }; + gmailPage = await browser.newPage(t, dummyGmailUrl, undefined, extraAuthHeaders); + await gmailPage.waitAll('@dialog-passphrase'); + { + const passphraseDialog = await gmailPage.getFrame(['passphrase.htm']); + await passphraseDialog.waitAndType('@input-pass-phrase', 'g00D_pa$$worD-But_Different'); + await passphraseDialog.waitAndClick('@action-confirm-pass-phrase-entry'); + // todo: how to wait properly + await passphraseDialog.waitForContent('@input-pass-phrase', /^$/); + expect(await passphraseDialog.attr('@input-pass-phrase', 'placeholder')).to.eq('Please try again'); + } + await ComposePageRecipe.cancelPassphraseDialog(gmailPage, 'keyboard'); + await PageRecipe.noToastAppears(gmailPage); + const set11 = await retrieveAndCheckKeys(2); + expect(set11.map(entry => entry.id)).to.eql(['392FB1E9FF4184659AB6A246835C0141B9ECF536', 'FAFB7D675AC74E87F84D169F00B0115807969D75']); + await gmailPage.close(); + // 11. EKM returns a new third key, we enter a passphrase matching an existing key, update happens + gmailPage = await browser.newPage(t, dummyGmailUrl, undefined, extraAuthHeaders); + await gmailPage.waitAll('@dialog-passphrase'); + { + const passphraseDialog = await gmailPage.getFrame(['passphrase.htm']); + await passphraseDialog.waitForContent('@passphrase-text', 'Enter FlowCrypt pass phrase to keep your account keys up to date'); + await passphraseDialog.waitAndType('@input-pass-phrase', passphrase); + await passphraseDialog.waitAndClick('@action-confirm-pass-phrase-entry'); + } + await gmailPage.waitTillGone('@dialog-passphrase'); + await PageRecipe.waitForToastToAppearAndDisappear(gmailPage, 'Account keys updated'); + const set12 = await retrieveAndCheckKeys(3); + expect(set12.map(entry => entry.id)).to.eql([ + '392FB1E9FF4184659AB6A246835C0141B9ECF536', + 'FAFB7D675AC74E87F84D169F00B0115807969D75', + '277D1ADA213881F4ABE0415395E783DC0289E2E2' + ]); + // 12. Forget the passphrase, EKM sends a broken key, no passphrase dialog, no updates + await InboxPageRecipe.finishSessionOnInboxPage(gmailPage); + await gmailPage.close(); + MOCK_KM_UPDATING_KEY.response.privateKeys = [ + { decryptedPrivateKey: await updateAndArmorKey(set2[0]) }, // update the main key + // only include a half of another armored key + { decryptedPrivateKey: testConstants.unprotectedPrvKey.substring(0, testConstants.unprotectedPrvKey.length / 2) } + ]; + gmailPage = await browser.newPage(t, dummyGmailUrl, undefined, extraAuthHeaders); + await PageRecipe.waitForToastToAppearAndDisappear(gmailPage, + 'Could not update keys from EKM due to error: BrowserMsg(processAndStoreKeysFromEkmLocally) sendRawResponse::Error: Some keys could not be parsed'); + await gmailPage.notPresent('@dialog-passphrase'); + const set13 = await retrieveAndCheckKeys(3); + expect(set13.map(entry => entry.id)).to.eql([ + '392FB1E9FF4184659AB6A246835C0141B9ECF536', + 'FAFB7D675AC74E87F84D169F00B0115807969D75', + '277D1ADA213881F4ABE0415395E783DC0289E2E2' + ]); + const mainKey13 = KeyUtil.filterKeysByIdentity(set13, [{ family: 'openpgp', id: '392FB1E9FF4184659AB6A246835C0141B9ECF536' }]); + expect(mainKey13.length).to.equal(1); + expect(mainKey13[0].lastModified).to.equal(mainKey10[0].lastModified); // no update + await gmailPage.close(); + // 13. EKM down, no toast, no passphrase dialog, no updates + MOCK_KM_UPDATING_KEY.badRequestError = 'RequestTimeout'; + gmailPage = await browser.newPage(t, dummyGmailUrl, undefined, extraAuthHeaders); + await PageRecipe.noToastAppears(gmailPage); + await gmailPage.notPresent('@dialog-passphrase'); + const set14 = await retrieveAndCheckKeys(3); + expect(set14.map(entry => entry.id)).to.eql([ + '392FB1E9FF4184659AB6A246835C0141B9ECF536', + 'FAFB7D675AC74E87F84D169F00B0115807969D75', + '277D1ADA213881F4ABE0415395E783DC0289E2E2' + ]); + const mainKey14 = KeyUtil.filterKeysByIdentity(set14.map(ki => ki), [{ family: 'openpgp', id: '392FB1E9FF4184659AB6A246835C0141B9ECF536' }]); + expect(mainKey14.length).to.equal(1); + expect(mainKey14[0].lastModified).to.equal(mainKey13[0].lastModified); // no update + await gmailPage.close(); + })); + ava.default('get.key@key-manager-choose-passphrase.flowcrypt.test - passphrase chosen by user with key found on key manager', testWithBrowser(undefined, async (t, browser) => { const acct = 'get.key@key-manager-choose-passphrase.flowcrypt.test'; const passphrase = 'Long and complicated pass PHRASE'; diff --git a/test/source/tests/tooling/consts.ts b/test/source/tests/tooling/consts.ts index 234f09ba851..6a35676d96e 100644 --- a/test/source/tests/tooling/consts.ts +++ b/test/source/tests/tooling/consts.ts @@ -1436,6 +1436,133 @@ aXvUekkHcZ2P6wjh6mYZgFKJxwUW6Y3lCBPAHSWLAFKw5xp8EPq1Q3h84NsNE6Te 4SNUmgtls3XYfGQ9ZKUUAQTDqLeLYTIadYq8sD9bthjUIIo0Hz2R1F6kCgDN5NnK 0+tURVqcqBi8Azs3HACQsbhb32RlotA= =GE+P +-----END PGP PRIVATE KEY BLOCK-----`, + existingPrv: // longid: 00B0115807969D75 + `-----BEGIN PGP PRIVATE KEY BLOCK----- +Version: FlowCrypt 7.6.2 Gmail Encryption +Comment: Seamlessly send and receive encrypted email + +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 +=tqvP +-----END PGP PRIVATE KEY BLOCK-----`, + updatingPrv: // get.updating.key@key-manager-choose-passphrase-forbid-storing.flowcrypt.test + `-----BEGIN PGP PRIVATE KEY BLOCK----- +Version: FlowCrypt Email Encryption [BUILD_REPLACEABLE_VERSION] +Comment: Seamlessly send and receive encrypted email + +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 +=3FoO -----END PGP PRIVATE KEY BLOCK-----` }; @@ -1446,7 +1573,26 @@ export const testKeyConstants = { { "title": "ci.tests.gmail", "passphrase": "citstonfcdevdomain", - "armored": "-----BEGIN PGP PRIVATE KEY BLOCK-----\nVersion: FlowCrypt Email Encryption 7.8.9\nComment: Seamlessly send and receive encrypted email\n\nxYYEXzq9RBYJKwYBBAHaRw8BAQdAYB2/hjjJaVZbDboWislVE88A5Bi7CEHV\n1PeNfGEM3dj+CQMIqZRTqDEC2Z7gOBJuZ5sRHi6hGUCAFLCGpVKZO9mCv/7g\nv8BTSC59djP9ezSiVT7JremTjf3fmNKoPeE4y+tobSb0rqUyecvT4PASY1iC\nDM0sR21haWwgQ0kgVGVzdCA8Y2kudGVzdHMuZ21haWxAZmxvd2NyeXB0LmRl\ndj7CjwQQFgoAIAUCXzq9RAYLCQcIAwIEFQgKAgQWAgEAAhkBAhsDAh4BACEJ\nEAdIHIrPnUn+FiEEm6Oc4HXwg5swNA/IB0gcis+dSf42VgD+LuUQu/B0g+ll\nqtzegLKUGX/CDLijJm3fOPFKW7l3T3ABALQGMqXca1jpuMERQdq+dE5yxhqS\nHIqX4yYCamljOaUNx4sEXzq9RBIKKwYBBAGXVQEFAQEHQILCd69DwnEpYMCg\n7rqcZvFbOzdVDo/V7hSape+EVPwRAwEIB/4JAwiOx5ib9VjVguB1bR/LEg9K\nMZVpoJek5xRwgJYUrP0R+FSCq3qQgu0DDJMTepmA+Ks/pVSe8bjLXp6OzhLD\ni9wrQsIltClLzgn5IqoSVHGAwngEGBYIAAkFAl86vUQCGwwAIQkQB0gcis+d\nSf4WIQSbo5zgdfCDmzA0D8gHSByKz51J/jLcAP0dmYzcO3JGCvFRpXDeX7Bs\neB0Dxje8Q1w52uHm4BYZgwD/fFYiASnRQzCHwTpwyk110W+jPt+rNZ6OIRZH\n0d++GQ0=\n=rX/o\n-----END PGP PRIVATE KEY BLOCK-----\n", /// + "armored": `-----BEGIN PGP PRIVATE KEY BLOCK----- +Version: FlowCrypt Email Encryption 7.8.9 +Comment: Seamlessly send and receive encrypted email + +xYYEXzq9RBYJKwYBBAHaRw8BAQdAYB2/hjjJaVZbDboWislVE88A5Bi7CEHV +1PeNfGEM3dj+CQMIqZRTqDEC2Z7gOBJuZ5sRHi6hGUCAFLCGpVKZO9mCv/7g +v8BTSC59djP9ezSiVT7JremTjf3fmNKoPeE4y+tobSb0rqUyecvT4PASY1iC +DM0sR21haWwgQ0kgVGVzdCA8Y2kudGVzdHMuZ21haWxAZmxvd2NyeXB0LmRl +dj7CjwQQFgoAIAUCXzq9RAYLCQcIAwIEFQgKAgQWAgEAAhkBAhsDAh4BACEJ +EAdIHIrPnUn+FiEEm6Oc4HXwg5swNA/IB0gcis+dSf42VgD+LuUQu/B0g+ll +qtzegLKUGX/CDLijJm3fOPFKW7l3T3ABALQGMqXca1jpuMERQdq+dE5yxhqS +HIqX4yYCamljOaUNx4sEXzq9RBIKKwYBBAGXVQEFAQEHQILCd69DwnEpYMCg +7rqcZvFbOzdVDo/V7hSape+EVPwRAwEIB/4JAwiOx5ib9VjVguB1bR/LEg9K +MZVpoJek5xRwgJYUrP0R+FSCq3qQgu0DDJMTepmA+Ks/pVSe8bjLXp6OzhLD +i9wrQsIltClLzgn5IqoSVHGAwngEGBYIAAkFAl86vUQCGwwAIQkQB0gcis+d +Sf4WIQSbo5zgdfCDmzA0D8gHSByKz51J/jLcAP0dmYzcO3JGCvFRpXDeX7Bs +eB0Dxje8Q1w52uHm4BYZgwD/fFYiASnRQzCHwTpwyk110W+jPt+rNZ6OIRZH +0d++GQ0= +=rX/o +-----END PGP PRIVATE KEY BLOCK-----`, "longid": null // tslint:disable-line:no-null-keyword }, { diff --git a/tooling/build-types-and-manifests.ts b/tooling/build-types-and-manifests.ts index 7007f01f585..22e934499b1 100644 --- a/tooling/build-types-and-manifests.ts +++ b/tooling/build-types-and-manifests.ts @@ -118,11 +118,14 @@ const makeMockBuild = (sourceBuildType: string) => { .replace(/const (OAUTH_GOOGLE_API_HOST|GMAIL_GOOGLE_API_HOST|PEOPLE_GOOGLE_API_HOST|GOOGLE_OAUTH_SCREEN_HOST) = [^;]+;/g, `const $1 = '${MOCK_HOST[sourceBuildType]}';`) .replace(/const (BACKEND_API_HOST) = [^;]+;/g, `const $1 = 'https://localhost:8001/api/';`) .replace(/const (ATTESTER_API_HOST) = [^;]+;/g, `const $1 = 'https://localhost:8001/attester/';`) - .replace(/https:\/\/flowcrypt.com\/api\/help\/error/g, 'https://localhost:8001/api/help/error'); + .replace(/https:\/\/flowcrypt\.com\/api\/help\/error/g, 'https://localhost:8001/api/help/error'); }; edit(`${buildDir(mockBuildType)}/js/common/core/const.js`, editor); edit(`${buildDir(mockBuildType)}/js/common/platform/catch.js`, editor); edit(`${buildDir(mockBuildType)}/js/content_scripts/webmail_bundle.js`, editor); + edit(`${buildDir(mockBuildType)}/manifest.json`, (code) => + code.replace(/https:\/\/mail\.google\.com/g, 'https://gmail.localhost:8001') + ); }; const makeLocalFesBuild = (sourceBuildType: string) => { diff --git a/tooling/bundle-content-scripts.ts b/tooling/bundle-content-scripts.ts index 9f7bd76b06a..d0457fb244a 100644 --- a/tooling/bundle-content-scripts.ts +++ b/tooling/bundle-content-scripts.ts @@ -32,19 +32,19 @@ mkdirSync(OUT_DIR); // webmail buildContentScript(([] as string[]).concat( - getFilesInDir(`${sourceDir}/common/platform`, /\.js$/, false), - getFilesInDir(`${sourceDir}/common/platform/store`, /\.js$/, false), - getFilesInDir(`${sourceDir}/common/core`, /\.js$/, false), - getFilesInDir(`${sourceDir}/common/core/crypto`, /\.js$/, false), - getFilesInDir(`${sourceDir}/common/core/crypto/pgp`, /\.js$/, false), - getFilesInDir(`${sourceDir}/common/core/crypto/smime`, /\.js$/, false), - getFilesInDir(`${sourceDir}/common/api/shared`, /\.js$/, false), - getFilesInDir(`${sourceDir}/common/api/key-server`, /\.js$/, false), + getFilesInDir(`${sourceDir}/js/common/platform`, /\.js$/, false), + getFilesInDir(`${sourceDir}/js/common/platform/store`, /\.js$/, false), + getFilesInDir(`${sourceDir}/js/common/core`, /\.js$/, false), + getFilesInDir(`${sourceDir}/js/common/core/crypto`, /\.js$/, false), + getFilesInDir(`${sourceDir}/js/common/core/crypto/pgp`, /\.js$/, false), + getFilesInDir(`${sourceDir}/js/common/core/crypto/smime`, /\.js$/, false), + getFilesInDir(`${sourceDir}/js/common/api/shared`, /\.js$/, false), + getFilesInDir(`${sourceDir}/js/common/api/key-server`, /\.js$/, false), // getFilesInDir(`${sourceDir}/common/api/account-server`, /\.js$/, false), // not used by content scripts yet - getFilesInDir(`${sourceDir}/common/api/email-provider`, /\.js$/, false), - getFilesInDir(`${sourceDir}/common/api/email-provider/gmail`, /\.js$/, false), - getFilesInDir(`${sourceDir}/common/api`, /\.js$/, false), - getFilesInDir(`${sourceDir}/common/browser`, /\.js$/, false), - getFilesInDir(`${sourceDir}/common`, /\.js$/, false), - getFilesInDir(`${sourceDir}/content_scripts/webmail`, /\.js$/), + getFilesInDir(`${sourceDir}/js/common/api/email-provider`, /\.js$/, false), + getFilesInDir(`${sourceDir}/js/common/api/email-provider/gmail`, /\.js$/, false), + getFilesInDir(`${sourceDir}/js/common/api`, /\.js$/, false), + getFilesInDir(`${sourceDir}/js/common/browser`, /\.js$/, false), + getFilesInDir(`${sourceDir}/js/common`, /\.js$/, false), + getFilesInDir(`${sourceDir}/js/content_scripts/webmail`, /\.js$/), ), 'webmail_bundle.js'); diff --git a/tooling/fill-values.ts b/tooling/fill-values.ts index b99014cf1c0..06c469bf112 100644 --- a/tooling/fill-values.ts +++ b/tooling/fill-values.ts @@ -25,7 +25,7 @@ const replaceables: { needle: RegExp, val: string }[] = [ const paths = [ `${targetDirExtension}/js/common/core/const.js`, - `./build/${targetDirContentScripts}/common/core/const.js`, + `./build/${targetDirContentScripts}/js/common/core/const.js`, ]; for (const path of paths) {