From 95b2c78e024a5e91bf9de50c630de545732fd8fd Mon Sep 17 00:00:00 2001 From: Ioan Moldovan Date: Fri, 6 Sep 2024 11:10:17 +0300 Subject: [PATCH 1/7] feat: added ability to generate keys --- .../elements/shared/create_key.template.htm | 71 +++++++ extension/chrome/settings/index.htm | 13 +- .../chrome/settings/modules/generate_key.htm | 46 ++++ .../chrome/settings/modules/generate_key.ts | 200 ++++++++++++++++++ extension/chrome/settings/setup.htm | 72 +------ extension/chrome/settings/setup.ts | 139 +++--------- .../chrome/settings/setup/setup-create-key.ts | 4 +- .../chrome/settings/setup/setup-import-key.ts | 4 +- extension/css/mobile-menu-styling.css | 8 +- extension/css/settings.css | 5 +- extension/js/common/key-helper.ts | 91 ++++++++ extension/js/common/ui/key-import-ui.ts | 22 ++ 12 files changed, 480 insertions(+), 195 deletions(-) create mode 100644 extension/chrome/elements/shared/create_key.template.htm create mode 100644 extension/chrome/settings/modules/generate_key.htm create mode 100644 extension/chrome/settings/modules/generate_key.ts create mode 100644 extension/js/common/key-helper.ts diff --git a/extension/chrome/elements/shared/create_key.template.htm b/extension/chrome/elements/shared/create_key.template.htm new file mode 100644 index 00000000000..246d887d400 --- /dev/null +++ b/extension/chrome/elements/shared/create_key.template.htm @@ -0,0 +1,71 @@ +
+
+ Choose pass phrase to protect your encrypted emails. choosing secure pass phrases +
+
+ +
+
+ +
+ +
+ +
+
+ +
+ +
+ +
+
+ +
+ +
+ +
+ +
+ +
+
diff --git a/extension/chrome/settings/index.htm b/extension/chrome/settings/index.htm index 6c493435c02..4ca20d14187 100644 --- a/extension/chrome/settings/index.htm +++ b/extension/chrome/settings/index.htm @@ -196,11 +196,20 @@

FlowCrypt Settings

-
+
key icon My Keys - Add Key + Add Key + Generate Key
diff --git a/extension/chrome/settings/modules/generate_key.htm b/extension/chrome/settings/modules/generate_key.htm new file mode 100644 index 00000000000..9e266fc8b4f --- /dev/null +++ b/extension/chrome/settings/modules/generate_key.htm @@ -0,0 +1,46 @@ + + + + + + + FlowCrypt + + + + + + + + + + +
+
+
+

Generate Private Key

+
+
+
+
+
+
+ + + + + + + + + + + + + + + + + + + diff --git a/extension/chrome/settings/modules/generate_key.ts b/extension/chrome/settings/modules/generate_key.ts new file mode 100644 index 00000000000..20af1d0c24c --- /dev/null +++ b/extension/chrome/settings/modules/generate_key.ts @@ -0,0 +1,200 @@ +/* ©️ 2016 - present FlowCrypt a.s. Limitations apply. Contact human@flowcrypt.com */ + +'use strict'; + +import { PubLookup } from '../../../js/common/api/pub-lookup.js'; +import { ApiErr } from '../../../js/common/api/shared/api-error.js'; +import { Assert } from '../../../js/common/assert.js'; +import { BrowserMsg } from '../../../js/common/browser/browser-msg.js'; +import { Ui } from '../../../js/common/browser/ui.js'; +import { ClientConfiguration } from '../../../js/common/client-configuration.js'; +import { Attachment } from '../../../js/common/core/attachment.js'; +import { Buf } from '../../../js/common/core/buf.js'; +import { Url } from '../../../js/common/core/common.js'; +import { KeyStoreUtil } from '../../../js/common/core/crypto/key-store-util.js'; +import { KeyAlgo, KeyIdentity, KeyUtil } from '../../../js/common/core/crypto/key.js'; +import { MsgUtil } from '../../../js/common/core/crypto/pgp/msg-util.js'; +import { OpenPGPKey } from '../../../js/common/core/crypto/pgp/openpgp-key.js'; +import { saveKeysAndPassPhrase } from '../../../js/common/helpers.js'; +import { submitPublicKeyIfNeeded } from '../../../js/common/key-helper.js'; +import { Lang } from '../../../js/common/lang.js'; +import { Catch, CompanyLdapKeyMismatchError } from '../../../js/common/platform/catch.js'; +import { AcctStore } from '../../../js/common/platform/store/acct-store.js'; +import { KeyStore } from '../../../js/common/platform/store/key-store.js'; +import { Xss } from '../../../js/common/platform/xss.js'; +import { Settings } from '../../../js/common/settings.js'; +import { BackupUi } from '../../../js/common/ui/backup-ui/backup-ui.js'; +import { KeyImportUi } from '../../../js/common/ui/key-import-ui.js'; +import { initPassphraseToggle } from '../../../js/common/ui/passphrase-ui.js'; +import { View } from '../../../js/common/view.js'; +import { SetupOptions } from '../setup.js'; + +View.run( + class GenerateKeyView extends View { + public readonly backupUi: BackupUi; + public readonly parentTabId: string; + protected fesUrl?: string; + private readonly acctEmail: string; + private readonly keyImportUi = new KeyImportUi({ rejectKnown: true }); + private clientConfiguration!: ClientConfiguration; + private pubLookup!: PubLookup; + + public constructor() { + super(); + const uncheckedUrlParams = Url.parse(['acctEmail', 'parentTabId']); + this.acctEmail = Assert.urlParamRequire.string(uncheckedUrlParams, 'acctEmail'); + this.parentTabId = Assert.urlParamRequire.string(uncheckedUrlParams, 'parentTabId'); + this.backupUi = new BackupUi(); + } + + public render = async () => { + await this.keyImportUi.renderKeyManualCreateView('#generate-key-container'); + $('#step_2a_manual_create').css('display', 'block'); + const storage = await AcctStore.get(this.acctEmail, ['fesUrl']); + this.fesUrl = storage.fesUrl; + this.clientConfiguration = await ClientConfiguration.newInstance(this.acctEmail); + this.pubLookup = new PubLookup(this.clientConfiguration); + if (!this.clientConfiguration.forbidStoringPassPhrase()) { + $('.input_passphrase_save_label').removeClass('hidden'); + $('.input_passphrase_save').prop('checked', true); + } + if (this.clientConfiguration.usesKeyManager()) { + Xss.sanitizeRender( + 'body', + ` +

+
Please contact your IT staff if you wish to update your keys.
+

+ ` + ); + } else { + $('#content').show(); + if (!this.clientConfiguration.forbidStoringPassPhrase()) { + $('.input_passphrase_save').prop('checked', true).prop('disabled', false); + } + await initPassphraseToggle(['step_2a_manual_create_input_password', 'step_2a_manual_create_input_password2']); + } + }; + + public setHandlers = () => { + $('#step_2a_manual_create .action_proceed_private').on( + 'click', + this.setHandlerPrevent('double', () => this.actionCreateKeyHandler()) + ); + $('#step_2a_manual_create .input_password').on('keydown', this.setEnterHandlerThatClicks('#step_2a_manual_create .action_proceed_private')); + $('#step_2a_manual_create.input_password2').on('keydown', this.setEnterHandlerThatClicks('#step_2a_manual_create .action_proceed_private')); + }; + + public createSaveKeyPair = async (options: SetupOptions, keyAlgo: KeyAlgo): Promise => { + // eslint-disable-next-line @typescript-eslint/naming-convention + const { full_name } = await AcctStore.get(this.acctEmail, ['full_name']); + const pgpUids = [{ name: full_name || '', email: this.acctEmail }]; // todo - add all addresses? + const expireMonths = this.clientConfiguration.getEnforcedKeygenExpirationMonths(); + const key = await OpenPGPKey.create(pgpUids, keyAlgo, options.passphrase, expireMonths); + const prv = await KeyUtil.parse(key.private); + await saveKeysAndPassPhrase(this.acctEmail, [prv], options, []); + return { id: prv.id, family: prv.family }; + }; + + public actionCreateKeyHandler = async () => { + try { + $('#step_2a_manual_create input').prop('disabled', true); + Xss.sanitizeRender('#step_2a_manual_create .action_proceed_private', Ui.spinner('white') + 'just a minute'); + /* eslint-disable @typescript-eslint/naming-convention */ + const opts: SetupOptions = { + passphrase: String($('#step_2a_manual_create .input_password').val()), + passphrase_save: Boolean($('#step_2a_manual_create .input_passphrase_save').prop('checked')), + passphrase_ensure_single_copy: false, // there can't be any saved passphrases for the new key + submit_main: this.keyImportUi.shouldSubmitPubkey(this.clientConfiguration, '#step_2a_manual_create .input_submit_key'), + submit_all: this.keyImportUi.shouldSubmitPubkey(this.clientConfiguration, '#step_2a_manual_create .input_submit_all'), + recovered: false, + }; + /* eslint-enable @typescript-eslint/naming-convention */ + const keyAlgo = this.clientConfiguration.getEnforcedKeygenAlgo() || ($('#step_2a_manual_create .key_type').val() as KeyAlgo); + const keyIdentity = await this.createSaveKeyPair(opts, keyAlgo); + if (this.clientConfiguration.getPublicKeyForPrivateKeyBackupToDesignatedMailbox()) { + const adminPubkey = this.clientConfiguration.getPublicKeyForPrivateKeyBackupToDesignatedMailbox(); + if (adminPubkey) { + const msgEncryptionKey = await KeyUtil.parse(adminPubkey); + const destinationEmail = msgEncryptionKey.emails[0]; + try { + const privateKey = await KeyStore.get(this.acctEmail); + const primaryKeyId = privateKey[0].id; + await this.backupUi.initialize({ + acctEmail: this.acctEmail, + action: 'setup_automatic', + keyIdentity, + onBackedUpFinished: async () => { + this.closeDialog(); + }, + }); + const parsedPrivateKey = await KeyUtil.parse(privateKey[0].private); + await OpenPGPKey.decryptKey(parsedPrivateKey, opts.passphrase); + const armoredPrivateKey = KeyUtil.armor(parsedPrivateKey); + const encryptedPrivateKey = await MsgUtil.encryptMessage({ + pubkeys: [msgEncryptionKey], + data: Buf.fromUtfStr(armoredPrivateKey), + armor: false, + }); + const privateKeyAttachment = new Attachment({ + name: `0x${primaryKeyId}.asc.pgp`, + type: 'application/pgp-encrypted', + data: encryptedPrivateKey.data, + }); + await this.backupUi.manualModule.doBackupOnDesignatedMailbox(msgEncryptionKey, privateKeyAttachment, destinationEmail, primaryKeyId); + } catch (e) { + if (ApiErr.isNetErr(e)) { + await Ui.modal.warning('Need internet connection to finish. Please click the button again to retry.'); + } else { + Catch.reportErr(e); + await Ui.modal.error(`Error happened: ${String(e)}`); + } + } + } + } else if (this.clientConfiguration.canBackupKeys()) { + await this.submitPublicKeys(opts); + const action = $('#step_2a_manual_create .input_backup_inbox').prop('checked') ? 'setup_automatic' : 'setup_manual'; + // only finalize after backup is done. + $('#step_2a_manual_create').hide(); + await this.backupUi.initialize({ + acctEmail: this.acctEmail, + action, + keyIdentity, + onBackedUpFinished: async () => { + this.closeDialog(); + }, + }); + } else { + this.closeDialog(); + } + } catch (e) { + Catch.reportErr(e); + await Ui.modal.error(`There was an error, please try again.\n\n(${String(e)})`); + $('#step_2a_manual_create .action_proceed_private').text('CREATE AND SAVE'); + } + }; + + /* eslint-disable @typescript-eslint/naming-convention */ + private submitPublicKeys = async ({ submit_main, submit_all }: { submit_main: boolean; submit_all: boolean }): Promise => { + const mostUsefulPrv = KeyStoreUtil.chooseMostUseful(await KeyStoreUtil.parse(await KeyStore.getRequired(this.acctEmail)), 'ONLY-FULLY-USABLE'); + try { + await submitPublicKeyIfNeeded(this.clientConfiguration, this.acctEmail, [], this.pubLookup.attester, mostUsefulPrv?.keyInfo.public, { + submit_main, + submit_all, + }); + } catch (e) { + return await Settings.promptToRetry( + e, + e instanceof CompanyLdapKeyMismatchError ? Lang.setup.failedToImportUnknownKey : Lang.setup.failedToSubmitToAttester, + () => this.submitPublicKeys({ submit_main, submit_all }), + Lang.general.contactIfNeedAssistance(Boolean(this.fesUrl)) + ); + } + }; + /* eslint-enable @typescript-eslint/naming-convention */ + + private closeDialog() { + BrowserMsg.send.reload(this.parentTabId, { advanced: true }); + } + } +); diff --git a/extension/chrome/settings/setup.htm b/extension/chrome/settings/setup.htm index 8d1fddf2e90..06dc67602e7 100644 --- a/extension/chrome/settings/setup.htm +++ b/extension/chrome/settings/setup.htm @@ -114,77 +114,7 @@

Set Up FlowCrypt

-
-
- Choose pass phrase to protect your encrypted emails. choosing secure pass phrases -
-
- -
-
- -
- -
- -
-
- -
- -
- -
-
- -
- -
- -
- -
- -
-
+
diff --git a/extension/chrome/settings/setup.ts b/extension/chrome/settings/setup.ts index bfa51ad06ef..74b60fc4c4a 100644 --- a/extension/chrome/settings/setup.ts +++ b/extension/chrome/settings/setup.ts @@ -2,39 +2,36 @@ 'use strict'; -import { Bm, BrowserMsg } from '../../js/common/browser/browser-msg.js'; -import { asyncSome, 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, CompanyLdapKeyMismatchError } from '../../js/common/platform/catch.js'; -import { Key, KeyInfoWithIdentity, KeyUtil } from '../../js/common/core/crypto/key.js'; +import Swal from 'sweetalert2'; 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'; -import { Lang } from '../../js/common/lang.js'; -import { opgp } from '../../js/common/core/crypto/pgp/openpgpjs-custom.js'; +import { KeyManager } from '../../js/common/api/key-server/key-manager.js'; +import { PubLookup } from '../../js/common/api/pub-lookup.js'; +import { Assert } from '../../js/common/assert.js'; +import { Bm, BrowserMsg } from '../../js/common/browser/browser-msg.js'; +import { Ui } from '../../js/common/browser/ui.js'; import { ClientConfiguration } from '../../js/common/client-configuration.js'; +import { Url } from '../../js/common/core/common.js'; +import { KeyStoreUtil } from '../../js/common/core/crypto/key-store-util.js'; +import { KeyInfoWithIdentity } from '../../js/common/core/crypto/key.js'; +import { opgp } from '../../js/common/core/crypto/pgp/openpgpjs-custom.js'; +import { Lang } from '../../js/common/lang.js'; +import { Catch, CompanyLdapKeyMismatchError } from '../../js/common/platform/catch.js'; +import { AcctStore, AcctStoreDict } from '../../js/common/platform/store/acct-store.js'; +import { KeyStore } from '../../js/common/platform/store/key-store.js'; +import { Xss } from '../../js/common/platform/xss.js'; import { Settings } from '../../js/common/settings.js'; +import { BackupUi } from '../../js/common/ui/backup-ui/backup-ui.js'; +import { KeyImportUi } from '../../js/common/ui/key-import-ui.js'; +import { initPassphraseToggle, shouldPassPhraseBeHidden } from '../../js/common/ui/passphrase-ui.js'; +import { View } from '../../js/common/view.js'; +import { BgUtils } from '../../js/service_worker/bgutils.js'; import { SetupCreateKeyModule } from './setup/setup-create-key.js'; import { SetupImportKeyModule } from './setup/setup-import-key.js'; +import { SetupWithEmailKeyManagerModule } from './setup/setup-key-manager-autogen.js'; import { SetupRecoverKeyModule } from './setup/setup-recover-key.js'; import { SetupRenderModule } from './setup/setup-render.js'; -import { Ui } from '../../js/common/browser/ui.js'; -import { View } from '../../js/common/view.js'; -import { Xss } from '../../js/common/platform/xss.js'; -import { initPassphraseToggle } from '../../js/common/ui/passphrase-ui.js'; -import { PubLookup } from '../../js/common/api/pub-lookup.js'; -import { 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 { 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'; -import { InMemoryStoreKeys } from '../../js/common/core/const.js'; -import { InMemoryStore } from '../../js/common/platform/store/in-memory-store.js'; -import { BgUtils } from '../../js/service_worker/bgutils.js'; +import { submitPublicKeyIfNeeded } from '../../js/common/key-helper.js'; /* eslint-disable @typescript-eslint/naming-convention */ export interface PassphraseOptions { @@ -95,7 +92,6 @@ export class SetupView extends View { this.submitKeyForAddrs = []; this.keyImportUi.initPrvImportSrcForm(this.acctEmail, this.parentTabId, this.submitKeyForAddrs); // for step_2b_manual_enter, if user chooses so this.keyImportUi.onBadPassphrase = () => $('#step_2b_manual_enter .input_passphrase').val('').trigger('focus'); - this.keyImportUi.renderPassPhraseStrengthValidationInput($('#step_2a_manual_create .input_password'), $('#step_2a_manual_create .action_proceed_private')); this.keyImportUi.renderPassPhraseStrengthValidationInput( $('#step_2_ekm_choose_pass_phrase .input_password'), $('#step_2_ekm_choose_pass_phrase .action_proceed_private') @@ -113,6 +109,7 @@ export class SetupView extends View { public isCustomerUrlFesUsed = () => Boolean(this.storage.fesUrl); public render = async () => { + await this.keyImportUi.renderKeyManualCreateView('#step_2a_manual_create_container'); await initPassphraseToggle(['step_2b_manual_enter_passphrase'], 'hide'); await initPassphraseToggle([ 'step_2a_manual_create_input_password', @@ -315,7 +312,10 @@ export class SetupView extends View { public submitPublicKeys = async ({ submit_main, submit_all }: { submit_main: boolean; submit_all: boolean }): Promise => { const mostUsefulPrv = KeyStoreUtil.chooseMostUseful(await KeyStoreUtil.parse(await KeyStore.getRequired(this.acctEmail)), 'ONLY-FULLY-USABLE'); try { - await this.submitPublicKeyIfNeeded(mostUsefulPrv?.keyInfo.public, { submit_main, submit_all }); + await submitPublicKeyIfNeeded(this.clientConfiguration, this.acctEmail, this.submitKeyForAddrs, this.pubLookup.attester, mostUsefulPrv?.keyInfo.public, { + submit_main, + submit_all, + }); } catch (e) { return await Settings.promptToRetry( e, @@ -331,19 +331,6 @@ export class SetupView extends View { }; /* eslint-enable @typescript-eslint/naming-convention */ - 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'); - } - if (!this.clientConfiguration.canSubmitPubToAttester()) { - return false; - } - if (this.clientConfiguration.mustSubmitToAttester()) { - return true; - } - return Boolean($(checkboxSelector).prop('checked')); - }; - public isCreatePrivateFormInputCorrect = async (section: string): Promise => { const password1 = $(`#${section} .input_password`); const password2 = $(`#${section} .input_password2`); @@ -384,78 +371,6 @@ export class SetupView extends View { const redirectUrl = Catch.isThunderbirdMail() ? inboxUrl : Google.webmailUrl(this.acctEmail); await BgUtils.openExtensionTab(redirectUrl, true); }; - - /** - * empty pubkey means key not usable - */ - private submitPublicKeyIfNeeded = async ( - armoredPubkey: string | undefined, - options: { submit_main: boolean; submit_all: boolean } // eslint-disable-line @typescript-eslint/naming-convention - ) => { - if (!options.submit_main) { - return; - } - if (!this.clientConfiguration.canSubmitPubToAttester()) { - if (!this.clientConfiguration.usesKeyManager) { - // users who use EKM get their setup automated - no need to inform them of this - // other users chose this manually - let them know it's not allowed - await Ui.modal.error('Not submitting public key to Attester - disabled for your org'); - } - return; - } - if (!armoredPubkey) { - await Ui.modal.warning('Public key not usable - not submitting to Attester'); - return; - } - const pub = await KeyUtil.parse(armoredPubkey); - if (pub.usableForEncryption) { - const idToken = await InMemoryStore.get(this.acctEmail, InMemoryStoreKeys.ID_TOKEN); - this.pubLookup.attester.welcomeMessage(this.acctEmail, armoredPubkey, idToken).catch(ApiErr.reportIfSignificant); - } - let addresses; - if (this.submitKeyForAddrs.length && options.submit_all) { - addresses = [...this.submitKeyForAddrs]; - } else { - addresses = [this.acctEmail]; - } - await this.submitPubkeys(addresses, armoredPubkey); - }; - - private submitPubkeys = async (addresses: string[], pubkey: string) => { - if (this.clientConfiguration.setupEnsureImportedPrvMatchLdapPub()) { - // this will generally ignore errors if conflicting key already exists, except for certain orgs - const result = await this.pubLookup.attester.doLookupLdap(this.acctEmail); - if (result.pubkeys.length) { - const prvs = await KeyStoreUtil.parse(await KeyStore.getRequired(this.acctEmail)); - const parsedPubKeys: Key[] = []; - for (const pubKey of result.pubkeys) { - parsedPubKeys.push(...(await KeyUtil.parseMany(pubKey))); - } - const hasMatchingKey = await asyncSome(prvs, async privateKey => { - return parsedPubKeys.some(parsedPubKey => privateKey.key.id === parsedPubKey.id); - }); - if (!hasMatchingKey) { - const keyIds = prvs.map(prv => prv.key.id).join(', '); - const pubKeyIds = parsedPubKeys.map(pub => pub.id).join(', '); - throw new CompanyLdapKeyMismatchError( - `Imported private key with ids ${keyIds} does not match public keys on company LDAP server with ids ${pubKeyIds} for ${this.acctEmail}. Please ask your help desk.` - ); - } - } else { - throw new CompanyLdapKeyMismatchError( - `Your organization requires public keys to be present on company LDAP server, but no public key was found for ${this.acctEmail}. Please ask your internal help desk.` - ); - } - } else { - // this will actually replace the submitted public key if there was a conflict, better ux - // eslint-disable-next-line @typescript-eslint/no-non-null-assertion - await this.pubLookup.attester.submitPrimaryEmailPubkey(this.acctEmail, pubkey, this.idToken!); - } - const aliases = addresses.filter(a => a !== this.acctEmail); - if (aliases.length) { - await Promise.all(aliases.map(a => this.pubLookup.attester.submitPubkeyWithConditionalEmailVerification(a, pubkey))); - } - }; } View.run(SetupView); diff --git a/extension/chrome/settings/setup/setup-create-key.ts b/extension/chrome/settings/setup/setup-create-key.ts index 95bccd5fe9d..e4ed55e9306 100644 --- a/extension/chrome/settings/setup/setup-create-key.ts +++ b/extension/chrome/settings/setup/setup-create-key.ts @@ -33,8 +33,8 @@ export class SetupCreateKeyModule { passphrase: String($('#step_2a_manual_create .input_password').val()), passphrase_save: Boolean($('#step_2a_manual_create .input_passphrase_save').prop('checked')), passphrase_ensure_single_copy: false, // there can't be any saved passphrases for the new key - submit_main: this.view.shouldSubmitPubkey('#step_2a_manual_create .input_submit_key'), - submit_all: this.view.shouldSubmitPubkey('#step_2a_manual_create .input_submit_all'), + submit_main: this.view.keyImportUi.shouldSubmitPubkey(this.view.clientConfiguration, '#step_2a_manual_create .input_submit_key'), + submit_all: this.view.keyImportUi.shouldSubmitPubkey(this.view.clientConfiguration, '#step_2a_manual_create .input_submit_all'), recovered: false, }; /* eslint-enable @typescript-eslint/naming-convention */ diff --git a/extension/chrome/settings/setup/setup-import-key.ts b/extension/chrome/settings/setup/setup-import-key.ts index 937aab1b146..bef93b506cb 100644 --- a/extension/chrome/settings/setup/setup-import-key.ts +++ b/extension/chrome/settings/setup/setup-import-key.ts @@ -22,8 +22,8 @@ export class SetupImportKeyModule { /* eslint-disable @typescript-eslint/naming-convention */ const options: SetupOptions = { passphrase: String($('#step_2b_manual_enter .input_passphrase').val()), - submit_main: this.view.shouldSubmitPubkey('#step_2b_manual_enter .input_submit_key'), - submit_all: this.view.shouldSubmitPubkey('#step_2b_manual_enter .input_submit_all'), + submit_main: this.view.keyImportUi.shouldSubmitPubkey(this.view.clientConfiguration, '#step_2b_manual_enter .input_submit_key'), + submit_all: this.view.keyImportUi.shouldSubmitPubkey(this.view.clientConfiguration, '#step_2b_manual_enter .input_submit_all'), passphrase_save: Boolean($('#step_2b_manual_enter .input_passphrase_save').prop('checked')), passphrase_ensure_single_copy: true, recovered: false, diff --git a/extension/css/mobile-menu-styling.css b/extension/css/mobile-menu-styling.css index 2521addc85b..75bccc2e2a5 100644 --- a/extension/css/mobile-menu-styling.css +++ b/extension/css/mobile-menu-styling.css @@ -1,5 +1,5 @@ -@media (max-width: 992px) { - .show_settings_page { +@media (width <= 992px) { + .show_settings_page:not(.my_key_link) { display: block; margin-bottom: 20px; text-align: center; @@ -12,7 +12,7 @@ } } -@media (max-width: 767px) { +@media (width <= 767px) { body { overflow-x: hidden; } @@ -76,7 +76,7 @@ } } -@media (max-width: 450px) { +@media (width <= 450px) { .logo-row { margin-top: 0; } diff --git a/extension/css/settings.css b/extension/css/settings.css index 122fb731673..ee56e7e7f19 100644 --- a/extension/css/settings.css +++ b/extension/css/settings.css @@ -320,6 +320,7 @@ a.gray_link { text-indent: 5px; font-weight: 600; display: inline-block; + flex: 1; } #settings-row h1 { @@ -405,9 +406,9 @@ a.gray_link { } .abs-row span.keys-link-top-right-container { - display: inline-block; - position: absolute; + display: flex; right: 10px; + gap: 15px; } .abs-row .key-icon { diff --git a/extension/js/common/key-helper.ts b/extension/js/common/key-helper.ts new file mode 100644 index 00000000000..d71661e2ccf --- /dev/null +++ b/extension/js/common/key-helper.ts @@ -0,0 +1,91 @@ +/* ©️ 2016 - present FlowCrypt a.s. Limitations apply. Contact human@flowcrypt.com */ + +'use strict'; + +import { Attester } from './api/key-server/attester.js'; +import { ApiErr } from './api/shared/api-error.js'; +import { Ui } from './browser/ui.js'; +import { ClientConfiguration } from './client-configuration.js'; +import { asyncSome } from './core/common.js'; +import { InMemoryStoreKeys } from './core/const.js'; +import { KeyUtil, Key } from './core/crypto/key.js'; +import { KeyStoreUtil } from './core/crypto/key-store-util.js'; +import { CompanyLdapKeyMismatchError } from './platform/catch.js'; +import { InMemoryStore } from './platform/store/in-memory-store.js'; +import { KeyStore } from './platform/store/key-store.js'; + +/** + * empty pubkey means key not usable + */ +export const submitPublicKeyIfNeeded = async ( + clientConfiguration: ClientConfiguration, + acctEmail: string, + submitKeyForAddrs: string[], + attester: Attester, + armoredPubkey: string | undefined, + options: { submit_main: boolean; submit_all: boolean } // eslint-disable-line @typescript-eslint/naming-convention +) => { + if (!options.submit_main) { + return; + } + if (!clientConfiguration.canSubmitPubToAttester()) { + if (!clientConfiguration.usesKeyManager) { + // users who use EKM get their setup automated - no need to inform them of this + // other users chose this manually - let them know it's not allowed + await Ui.modal.error('Not submitting public key to Attester - disabled for your org'); + } + return; + } + if (!armoredPubkey) { + await Ui.modal.warning('Public key not usable - not submitting to Attester'); + return; + } + const pub = await KeyUtil.parse(armoredPubkey); + if (pub.usableForEncryption) { + const idToken = await InMemoryStore.get(acctEmail, InMemoryStoreKeys.ID_TOKEN); + attester.welcomeMessage(acctEmail, armoredPubkey, idToken).catch(ApiErr.reportIfSignificant); + } + let addresses; + if (submitKeyForAddrs.length && options.submit_all) { + addresses = [...submitKeyForAddrs]; + } else { + addresses = [acctEmail]; + } + await submitPubkeys(clientConfiguration, acctEmail, attester, addresses, armoredPubkey); +}; + +export const submitPubkeys = async (clientConfiguration: ClientConfiguration, acctEmail: string, attester: Attester, addresses: string[], pubkey: string) => { + if (clientConfiguration.setupEnsureImportedPrvMatchLdapPub()) { + // this will generally ignore errors if conflicting key already exists, except for certain orgs + const result = await attester.doLookupLdap(acctEmail); + if (result.pubkeys.length) { + const prvs = await KeyStoreUtil.parse(await KeyStore.getRequired(acctEmail)); + const parsedPubKeys: Key[] = []; + for (const pubKey of result.pubkeys) { + parsedPubKeys.push(...(await KeyUtil.parseMany(pubKey))); + } + const hasMatchingKey = await asyncSome(prvs, async privateKey => { + return parsedPubKeys.some(parsedPubKey => privateKey.key.id === parsedPubKey.id); + }); + if (!hasMatchingKey) { + const keyIds = prvs.map(prv => prv.key.id).join(', '); + const pubKeyIds = parsedPubKeys.map(pub => pub.id).join(', '); + throw new CompanyLdapKeyMismatchError( + `Imported private key with ids ${keyIds} does not match public keys on company LDAP server with ids ${pubKeyIds} for ${acctEmail}. Please ask your help desk.` + ); + } + } else { + throw new CompanyLdapKeyMismatchError( + `Your organization requires public keys to be present on company LDAP server, but no public key was found for ${acctEmail}. Please ask your internal help desk.` + ); + } + } else { + // this will actually replace the submitted public key if there was a conflict, better ux + const idToken = await InMemoryStore.get(acctEmail, InMemoryStoreKeys.ID_TOKEN); + await attester.submitPrimaryEmailPubkey(acctEmail, pubkey, idToken ?? ''); + } + const aliases = addresses.filter(a => a !== acctEmail); + if (aliases.length) { + await Promise.all(aliases.map(a => attester.submitPubkeyWithConditionalEmailVerification(a, pubkey))); + } +}; diff --git a/extension/js/common/ui/key-import-ui.ts b/extension/js/common/ui/key-import-ui.ts index 1ac06c7f644..eb3ae99b72c 100644 --- a/extension/js/common/ui/key-import-ui.ts +++ b/extension/js/common/ui/key-import-ui.ts @@ -17,6 +17,8 @@ import { opgp } from '../core/crypto/pgp/openpgpjs-custom.js'; import { OpenPGPKey } from '../core/crypto/pgp/openpgp-key.js'; import { KeyStore } from '../platform/store/key-store.js'; import { isCustomerUrlFesUsed } from '../helpers.js'; +import { Xss } from '../platform/xss.js'; +import { ClientConfiguration } from '../client-configuration.js'; type KeyImportUiCheckResult = { normalized: string; @@ -83,6 +85,19 @@ export class KeyImportUi { public onBadPassphrase: VoidCallback = () => undefined; + public shouldSubmitPubkey = (clientConfiguration: ClientConfiguration, checkboxSelector: string) => { + if (clientConfiguration.mustSubmitToAttester() && !clientConfiguration.canSubmitPubToAttester()) { + throw new Error('Organisation rules are misconfigured: ENFORCE_ATTESTER_SUBMIT not compatible with NO_ATTESTER_SUBMIT'); + } + if (!clientConfiguration.canSubmitPubToAttester()) { + return false; + } + if (clientConfiguration.mustSubmitToAttester()) { + return true; + } + return Boolean($(checkboxSelector).prop('checked')); + }; + public initPrvImportSrcForm = (acctEmail: string, parentTabId: string | undefined, submitKeyForAddrs?: string[] | undefined) => { $('input[type=radio][name=source]') .off() @@ -231,6 +246,13 @@ export class KeyImportUi { return normalized; }; + public renderKeyManualCreateView = async (selector: string) => { + const htmlUrl = '/chrome/elements/shared/create_key.template.htm'; + const sanitized = Xss.htmlSanitize(await (await fetch(htmlUrl)).text()); + Xss.setElementContentDANGEROUSLY($(selector).get(0) as Element, sanitized); // xss-sanitized + this.renderPassPhraseStrengthValidationInput($('#step_2a_manual_create .input_password'), $('#step_2a_manual_create .action_proceed_private')); + }; + public renderPassPhraseStrengthValidationInput = (input: JQuery, submitButton?: JQuery, type: 'passphrase' | 'pwd' = 'passphrase') => { const validationElements = this.getPPValidationElements(); const setBtnColor = (type: 'gray' | 'green') => { From 9f14a20855fc9dd19ac200ffa4aa6cfb629e45b3 Mon Sep 17 00:00:00 2001 From: Ioan Moldovan Date: Tue, 10 Sep 2024 11:58:53 +0300 Subject: [PATCH 2/7] feat: add generate key button in add key popup --- extension/chrome/settings/index.htm | 7 - extension/chrome/settings/modules/add_key.htm | 17 +- extension/chrome/settings/modules/add_key.ts | 187 ++++++++-------- .../modules/add_key_generate_module.ts | 165 +++++++++++++++ .../chrome/settings/modules/generate_key.htm | 46 ---- .../chrome/settings/modules/generate_key.ts | 200 ------------------ extension/js/common/ui/backup-ui/backup-ui.ts | 1 + extension/js/common/ui/key-import-ui.ts | 45 ++-- 8 files changed, 307 insertions(+), 361 deletions(-) create mode 100644 extension/chrome/settings/modules/add_key_generate_module.ts delete mode 100644 extension/chrome/settings/modules/generate_key.htm delete mode 100644 extension/chrome/settings/modules/generate_key.ts diff --git a/extension/chrome/settings/index.htm b/extension/chrome/settings/index.htm index 4ca20d14187..d584d09a658 100644 --- a/extension/chrome/settings/index.htm +++ b/extension/chrome/settings/index.htm @@ -203,13 +203,6 @@

FlowCrypt Settings

Add Key - Generate Key
diff --git a/extension/chrome/settings/modules/add_key.htm b/extension/chrome/settings/modules/add_key.htm index 2aca46c2d1d..25068d1e7bb 100644 --- a/extension/chrome/settings/modules/add_key.htm +++ b/extension/chrome/settings/modules/add_key.htm @@ -21,8 +21,12 @@

Add Private Key

-
+
    +
  • + + +
  • @@ -37,6 +41,10 @@

    Add Private Key

+
+
+
+
file
@@ -76,6 +84,13 @@

Add Private Key

+ + + + + + + diff --git a/extension/chrome/settings/modules/add_key.ts b/extension/chrome/settings/modules/add_key.ts index 67601ba5355..10602b0cf3f 100644 --- a/extension/chrome/settings/modules/add_key.ts +++ b/extension/chrome/settings/modules/add_key.ts @@ -17,109 +17,114 @@ import { ClientConfiguration } from '../../../js/common/client-configuration.js' import { AcctStore } from '../../../js/common/platform/store/acct-store.js'; import { saveKeysAndPassPhrase, setPassphraseForPrvs } from '../../../js/common/helpers.js'; import { KeyErrors } from '../../elements/shared/key_errors.js'; +import { AddKeyGenerateModule } from './add_key_generate_module.js'; -View.run( - class AddKeyView extends View { - protected fesUrl?: string; - private readonly acctEmail: string; - private readonly parentTabId: string; - private readonly keyImportUi = new KeyImportUi({ rejectKnown: true }); - private readonly gmail: Gmail; - private keyErrors: KeyErrors | undefined; - private clientConfiguration!: ClientConfiguration; +export class AddKeyView extends View { + public readonly acctEmail: string; + public readonly parentTabId: string; + public clientConfiguration!: ClientConfiguration; + public fesUrl?: string; + private readonly keyImportUi = new KeyImportUi({ rejectKnown: true }); + private readonly gmail: Gmail; + private keyErrors: KeyErrors | undefined; + private readonly addKeyGenerateModule: AddKeyGenerateModule; - public constructor() { - super(); - const uncheckedUrlParams = Url.parse(['acctEmail', 'parentTabId']); - this.acctEmail = Assert.urlParamRequire.string(uncheckedUrlParams, 'acctEmail'); - this.parentTabId = Assert.urlParamRequire.string(uncheckedUrlParams, 'parentTabId'); - this.gmail = new Gmail(this.acctEmail); - } + public constructor() { + super(); + const uncheckedUrlParams = Url.parse(['acctEmail', 'parentTabId']); + this.acctEmail = Assert.urlParamRequire.string(uncheckedUrlParams, 'acctEmail'); + this.parentTabId = Assert.urlParamRequire.string(uncheckedUrlParams, 'parentTabId'); + this.gmail = new Gmail(this.acctEmail); + this.addKeyGenerateModule = new AddKeyGenerateModule(this); + } - public render = async () => { - const storage = await AcctStore.get(this.acctEmail, ['fesUrl']); - this.fesUrl = storage.fesUrl; - this.clientConfiguration = await ClientConfiguration.newInstance(this.acctEmail); - if (!this.clientConfiguration.forbidStoringPassPhrase()) { - $('.input_passphrase_save_label').removeClass('hidden'); - $('.input_passphrase_save').prop('checked', true); - } - if (this.clientConfiguration.usesKeyManager()) { - Xss.sanitizeRender( - 'body', - ` + public render = async () => { + const storage = await AcctStore.get(this.acctEmail, ['fesUrl']); + this.fesUrl = storage.fesUrl; + this.clientConfiguration = await ClientConfiguration.newInstance(this.acctEmail); + await this.addKeyGenerateModule.initGenerateKeyView(); + if (!this.clientConfiguration.forbidStoringPassPhrase()) { + $('.input_passphrase_save_label').removeClass('hidden'); + $('.input_passphrase_save').prop('checked', true); + } + if (this.clientConfiguration.usesKeyManager()) { + Xss.sanitizeRender( + 'body', + `

Please contact your IT staff if you wish to update your keys.


` - ); - } else { - $('#content').show(); - if (!this.clientConfiguration.forbidStoringPassPhrase()) { - $('.input_passphrase_save').prop('checked', true).prop('disabled', false); - } - await initPassphraseToggle(['input_passphrase']); - this.keyImportUi.initPrvImportSrcForm(this.acctEmail, this.parentTabId); - Xss.sanitizeRender('#spinner_container', Ui.spinner('green') + ' loading..'); - await this.loadAndRenderKeyBackupsOrRenderError(); - $('.source_selector').css('display', 'block'); - $('#spinner_container').text(''); + ); + } else { + $('#content').show(); + if (!this.clientConfiguration.forbidStoringPassPhrase()) { + $('.input_passphrase_save').prop('checked', true).prop('disabled', false); } - }; + await initPassphraseToggle(['input_passphrase']); + this.keyImportUi.initPrvImportSrcForm(this.acctEmail, this.parentTabId); + Xss.sanitizeRender('#spinner_container', Ui.spinner('green') + ' loading..'); + await this.loadAndRenderKeyBackupsOrRenderError(); + $('.source_selector').css('display', 'block'); + $('#spinner_container').text(''); + } + }; - public setHandlers = () => { - $('.action_add_private_key').on('click', this.setHandlerPrevent('double', this.addPrivateKeyHandler)); - $('#input_passphrase').keydown(this.setEnterHandlerThatClicks('.action_add_private_key')); - }; + public setHandlers = () => { + $('.action_add_private_key').on('click', this.setHandlerPrevent('double', this.addPrivateKeyHandler)); + $('#input_passphrase').keydown(this.setEnterHandlerThatClicks('.action_add_private_key')); + this.addKeyGenerateModule.setHandlers(); + }; - private loadAndRenderKeyBackupsOrRenderError = async () => { - try { - const backups = await this.gmail.fetchKeyBackups(); - if (!backups.longids.backups.length) { - $('label[for=source_backup]').text('Load from backup (no backups found)').css('color', '#AAA'); - $('#source_backup').prop('disabled', true); - } else if (backups.longids.backupsNotImported.length) { - $('label[for=source_backup]').text(`Load from backup (${backups.longids.backupsNotImported.length} new to import)`); - } else { - $('label[for=source_backup]').text(`Load from backup (${backups.longids.backups.length} already loaded)`).css('color', '#AAA'); - $('#source_backup').prop('disabled', true); - } - } catch (e) { - if (ApiErr.isAuthErr(e)) { - BrowserMsg.send.notificationShowAuthPopupNeeded(this.parentTabId, { acctEmail: this.acctEmail }); - } - $('label[for=source_backup]').text('Load from backup (error checking backups)').css('color', '#AAA'); + private loadAndRenderKeyBackupsOrRenderError = async () => { + try { + const backups = await this.gmail.fetchKeyBackups(); + if (!backups.longids.backups.length) { + $('label[for=source_backup]').text('Load from backup (no backups found)').css('color', '#AAA'); $('#source_backup').prop('disabled', true); + } else if (backups.longids.backupsNotImported.length) { + $('label[for=source_backup]').text(`Load from backup (${backups.longids.backupsNotImported.length} new to import)`); + } else { + $('label[for=source_backup]').text(`Load from backup (${backups.longids.backups.length} already loaded)`).css('color', '#AAA'); + $('#source_backup').prop('disabled', true); + } + } catch (e) { + if (ApiErr.isAuthErr(e)) { + BrowserMsg.send.notificationShowAuthPopupNeeded(this.parentTabId, { acctEmail: this.acctEmail }); } - }; + $('label[for=source_backup]').text('Load from backup (error checking backups)').css('color', '#AAA'); + $('#source_backup').prop('disabled', true); + } + }; - private saveKeyAndContinue = async (key: Key) => { - await saveKeysAndPassPhrase(this.acctEmail, [key]); // resulting new_key checked above - /* eslint-disable @typescript-eslint/naming-convention */ - await setPassphraseForPrvs(this.clientConfiguration, this.acctEmail, [key], { - passphrase: String($('.input_passphrase').val()), - passphrase_save: !!$('.input_passphrase_save').prop('checked'), - passphrase_ensure_single_copy: false, // we require KeyImportUi to rejectKnown keys - }); - /* eslint-enable @typescript-eslint/naming-convention */ - BrowserMsg.send.reload(this.parentTabId, { advanced: true }); - }; + private saveKeyAndContinue = async (key: Key) => { + await saveKeysAndPassPhrase(this.acctEmail, [key]); // resulting new_key checked above + /* eslint-disable @typescript-eslint/naming-convention */ + await setPassphraseForPrvs(this.clientConfiguration, this.acctEmail, [key], { + passphrase: String($('.input_passphrase').val()), + passphrase_save: !!$('.input_passphrase_save').prop('checked'), + passphrase_ensure_single_copy: false, // we require KeyImportUi to rejectKnown keys + }); + /* eslint-enable @typescript-eslint/naming-convention */ + BrowserMsg.send.reload(this.parentTabId, { advanced: true }); + }; - private addPrivateKeyHandler = async (submitBtn: HTMLElement) => { - if (submitBtn.className.includes('gray')) { - await Ui.modal.warning('Please double check the pass phrase input field for any issues.'); - return; - } - try { - const checked = await this.keyImportUi.checkPrv(this.acctEmail, String($('.input_private_key').val()), String($('.input_passphrase').val())); - if (checked) { - await this.saveKeyAndContinue(checked.encrypted); - } - } catch (e) { - this.keyErrors = new KeyErrors(this.fesUrl || '', this.acctEmail, this.parentTabId, this.clientConfiguration); - // eslint-disable-next-line @typescript-eslint/no-unsafe-argument, @typescript-eslint/no-unsafe-member-access - await this.keyErrors.handlePrivateKeyError(e, e.encrypted, undefined); + private addPrivateKeyHandler = async (submitBtn: HTMLElement) => { + if (submitBtn.className.includes('gray')) { + await Ui.modal.warning('Please double check the pass phrase input field for any issues.'); + return; + } + try { + const checked = await this.keyImportUi.checkPrv(this.acctEmail, String($('.input_private_key').val()), String($('.input_passphrase').val())); + if (checked) { + await this.saveKeyAndContinue(checked.encrypted); } - }; - } -); + } catch (e) { + this.keyErrors = new KeyErrors(this.fesUrl || '', this.acctEmail, this.parentTabId, this.clientConfiguration); + // eslint-disable-next-line @typescript-eslint/no-unsafe-argument, @typescript-eslint/no-unsafe-member-access + await this.keyErrors.handlePrivateKeyError(e, e.encrypted, undefined); + } + }; +} + +View.run(AddKeyView); diff --git a/extension/chrome/settings/modules/add_key_generate_module.ts b/extension/chrome/settings/modules/add_key_generate_module.ts new file mode 100644 index 00000000000..097efdd25c1 --- /dev/null +++ b/extension/chrome/settings/modules/add_key_generate_module.ts @@ -0,0 +1,165 @@ +/* ©️ 2016 - present FlowCrypt a.s. Limitations apply. Contact human@flowcrypt.com */ + +import { PubLookup } from '../../../js/common/api/pub-lookup.js'; +import { ApiErr } from '../../../js/common/api/shared/api-error.js'; +import { BrowserMsg } from '../../../js/common/browser/browser-msg.js'; +import { Ui } from '../../../js/common/browser/ui.js'; +import { Attachment } from '../../../js/common/core/attachment.js'; +import { Buf } from '../../../js/common/core/buf.js'; +import { KeyAlgo, KeyIdentity, KeyUtil } from '../../../js/common/core/crypto/key.js'; +import { KeyStoreUtil } from '../../../js/common/core/crypto/key-store-util.js'; +import { MsgUtil } from '../../../js/common/core/crypto/pgp/msg-util.js'; +import { OpenPGPKey } from '../../../js/common/core/crypto/pgp/openpgp-key.js'; +import { saveKeysAndPassPhrase } from '../../../js/common/helpers.js'; +import { submitPublicKeyIfNeeded } from '../../../js/common/key-helper.js'; +import { Lang } from '../../../js/common/lang.js'; +import { Catch, CompanyLdapKeyMismatchError } from '../../../js/common/platform/catch.js'; +import { AcctStore } from '../../../js/common/platform/store/acct-store.js'; +import { KeyStore } from '../../../js/common/platform/store/key-store.js'; +import { Xss } from '../../../js/common/platform/xss.js'; +import { Settings } from '../../../js/common/settings.js'; +import { BackupUi } from '../../../js/common/ui/backup-ui/backup-ui.js'; +import { KeyImportUi } from '../../../js/common/ui/key-import-ui.js'; +import { initPassphraseToggle } from '../../../js/common/ui/passphrase-ui.js'; +import { ViewModule } from '../../../js/common/view-module.js'; +import { SetupOptions } from '../setup'; +import { AddKeyView } from './add_key'; + +export class AddKeyGenerateModule extends ViewModule { + public readonly backupUi: BackupUi; + private readonly keyImportUi = new KeyImportUi({ rejectKnown: true }); + private pubLookup!: PubLookup; + + public constructor(composer: AddKeyView) { + super(composer); + this.backupUi = new BackupUi(); + } + + public initGenerateKeyView = async () => { + this.pubLookup = new PubLookup(this.view.clientConfiguration); + await this.keyImportUi.renderKeyManualCreateView('#generate-key-container'); + $('#step_2a_manual_create').css('display', 'block'); + await initPassphraseToggle(['step_2a_manual_create_input_password', 'step_2a_manual_create_input_password2']); + }; + + public setHandlers = () => { + $('#step_2a_manual_create .action_proceed_private').on( + 'click', + this.view.setHandlerPrevent('double', () => this.actionCreateKeyHandler()) + ); + $('#step_2a_manual_create .input_password').on('keydown', this.view.setEnterHandlerThatClicks('#step_2a_manual_create .action_proceed_private')); + $('#step_2a_manual_create.input_password2').on('keydown', this.view.setEnterHandlerThatClicks('#step_2a_manual_create .action_proceed_private')); + }; + + public createSaveKeyPair = async (options: SetupOptions, keyAlgo: KeyAlgo): Promise => { + // eslint-disable-next-line @typescript-eslint/naming-convention + const { full_name } = await AcctStore.get(this.view.acctEmail, ['full_name']); + const pgpUids = [{ name: full_name || '', email: this.view.acctEmail }]; // todo - add all addresses? + const expireMonths = this.view.clientConfiguration.getEnforcedKeygenExpirationMonths(); + const key = await OpenPGPKey.create(pgpUids, keyAlgo, options.passphrase, expireMonths); + const prv = await KeyUtil.parse(key.private); + await saveKeysAndPassPhrase(this.view.acctEmail, [prv], options, []); + return { id: prv.id, family: prv.family }; + }; + + public actionCreateKeyHandler = async () => { + try { + $('#step_2a_manual_create input').prop('disabled', true); + Xss.sanitizeRender('#step_2a_manual_create .action_proceed_private', Ui.spinner('white') + 'just a minute'); + /* eslint-disable @typescript-eslint/naming-convention */ + const opts: SetupOptions = { + passphrase: String($('#step_2a_manual_create .input_password').val()), + passphrase_save: Boolean($('#step_2a_manual_create .input_passphrase_save').prop('checked')), + passphrase_ensure_single_copy: false, // there can't be any saved passphrases for the new key + submit_main: this.keyImportUi.shouldSubmitPubkey(this.view.clientConfiguration, '#step_2a_manual_create .input_submit_key'), + submit_all: this.keyImportUi.shouldSubmitPubkey(this.view.clientConfiguration, '#step_2a_manual_create .input_submit_all'), + recovered: false, + }; + /* eslint-enable @typescript-eslint/naming-convention */ + const keyAlgo = this.view.clientConfiguration.getEnforcedKeygenAlgo() || ($('#step_2a_manual_create .key_type').val() as KeyAlgo); + const keyIdentity = await this.createSaveKeyPair(opts, keyAlgo); + if (this.view.clientConfiguration.getPublicKeyForPrivateKeyBackupToDesignatedMailbox()) { + const adminPubkey = this.view.clientConfiguration.getPublicKeyForPrivateKeyBackupToDesignatedMailbox(); + if (adminPubkey) { + const msgEncryptionKey = await KeyUtil.parse(adminPubkey); + const destinationEmail = msgEncryptionKey.emails[0]; + try { + const privateKey = await KeyStore.get(this.view.acctEmail); + const primaryKeyId = privateKey[0].id; + await this.backupUi.initialize({ + acctEmail: this.view.acctEmail, + action: 'setup_automatic', + keyIdentity, + onBackedUpFinished: async () => { + this.closeDialog(); + }, + }); + const parsedPrivateKey = await KeyUtil.parse(privateKey[0].private); + await OpenPGPKey.decryptKey(parsedPrivateKey, opts.passphrase); + const armoredPrivateKey = KeyUtil.armor(parsedPrivateKey); + const encryptedPrivateKey = await MsgUtil.encryptMessage({ + pubkeys: [msgEncryptionKey], + data: Buf.fromUtfStr(armoredPrivateKey), + armor: false, + }); + const privateKeyAttachment = new Attachment({ + name: `0x${primaryKeyId}.asc.pgp`, + type: 'application/pgp-encrypted', + data: encryptedPrivateKey.data, + }); + await this.backupUi.manualModule.doBackupOnDesignatedMailbox(msgEncryptionKey, privateKeyAttachment, destinationEmail, primaryKeyId); + } catch (e) { + if (ApiErr.isNetErr(e)) { + await Ui.modal.warning('Need internet connection to finish. Please click the button again to retry.'); + } else { + Catch.reportErr(e); + await Ui.modal.error(`Error happened: ${String(e)}`); + } + } + } + } else if (this.view.clientConfiguration.canBackupKeys()) { + await this.submitPublicKeys(opts); + const action = $('#step_2a_manual_create .input_backup_inbox').prop('checked') ? 'setup_automatic' : 'setup_manual'; + // only finalize after backup is done. + $('#step_2a_manual_create').hide(); + await this.backupUi.initialize({ + acctEmail: this.view.acctEmail, + action, + keyIdentity, + onBackedUpFinished: async () => { + this.closeDialog(); + }, + }); + } else { + this.closeDialog(); + } + } catch (e) { + Catch.reportErr(e); + await Ui.modal.error(`There was an error, please try again.\n\n(${String(e)})`); + $('#step_2a_manual_create .action_proceed_private').text('CREATE AND SAVE'); + } + }; + + /* eslint-disable @typescript-eslint/naming-convention */ + private submitPublicKeys = async ({ submit_main, submit_all }: { submit_main: boolean; submit_all: boolean }): Promise => { + const mostUsefulPrv = KeyStoreUtil.chooseMostUseful(await KeyStoreUtil.parse(await KeyStore.getRequired(this.view.acctEmail)), 'ONLY-FULLY-USABLE'); + try { + await submitPublicKeyIfNeeded(this.view.clientConfiguration, this.view.acctEmail, [], this.pubLookup.attester, mostUsefulPrv?.keyInfo.public, { + submit_main, + submit_all, + }); + } catch (e) { + return await Settings.promptToRetry( + e, + e instanceof CompanyLdapKeyMismatchError ? Lang.setup.failedToImportUnknownKey : Lang.setup.failedToSubmitToAttester, + () => this.submitPublicKeys({ submit_main, submit_all }), + Lang.general.contactIfNeedAssistance(Boolean(this.view.fesUrl)) + ); + } + }; + /* eslint-enable @typescript-eslint/naming-convention */ + + private closeDialog() { + BrowserMsg.send.reload(this.view.parentTabId, { advanced: true }); + } +} diff --git a/extension/chrome/settings/modules/generate_key.htm b/extension/chrome/settings/modules/generate_key.htm deleted file mode 100644 index 9e266fc8b4f..00000000000 --- a/extension/chrome/settings/modules/generate_key.htm +++ /dev/null @@ -1,46 +0,0 @@ - - - - - - - FlowCrypt - - - - - - - - - - -
-
-
-

Generate Private Key

-
-
-
-
-
-
- - - - - - - - - - - - - - - - - - - diff --git a/extension/chrome/settings/modules/generate_key.ts b/extension/chrome/settings/modules/generate_key.ts deleted file mode 100644 index 20af1d0c24c..00000000000 --- a/extension/chrome/settings/modules/generate_key.ts +++ /dev/null @@ -1,200 +0,0 @@ -/* ©️ 2016 - present FlowCrypt a.s. Limitations apply. Contact human@flowcrypt.com */ - -'use strict'; - -import { PubLookup } from '../../../js/common/api/pub-lookup.js'; -import { ApiErr } from '../../../js/common/api/shared/api-error.js'; -import { Assert } from '../../../js/common/assert.js'; -import { BrowserMsg } from '../../../js/common/browser/browser-msg.js'; -import { Ui } from '../../../js/common/browser/ui.js'; -import { ClientConfiguration } from '../../../js/common/client-configuration.js'; -import { Attachment } from '../../../js/common/core/attachment.js'; -import { Buf } from '../../../js/common/core/buf.js'; -import { Url } from '../../../js/common/core/common.js'; -import { KeyStoreUtil } from '../../../js/common/core/crypto/key-store-util.js'; -import { KeyAlgo, KeyIdentity, KeyUtil } from '../../../js/common/core/crypto/key.js'; -import { MsgUtil } from '../../../js/common/core/crypto/pgp/msg-util.js'; -import { OpenPGPKey } from '../../../js/common/core/crypto/pgp/openpgp-key.js'; -import { saveKeysAndPassPhrase } from '../../../js/common/helpers.js'; -import { submitPublicKeyIfNeeded } from '../../../js/common/key-helper.js'; -import { Lang } from '../../../js/common/lang.js'; -import { Catch, CompanyLdapKeyMismatchError } from '../../../js/common/platform/catch.js'; -import { AcctStore } from '../../../js/common/platform/store/acct-store.js'; -import { KeyStore } from '../../../js/common/platform/store/key-store.js'; -import { Xss } from '../../../js/common/platform/xss.js'; -import { Settings } from '../../../js/common/settings.js'; -import { BackupUi } from '../../../js/common/ui/backup-ui/backup-ui.js'; -import { KeyImportUi } from '../../../js/common/ui/key-import-ui.js'; -import { initPassphraseToggle } from '../../../js/common/ui/passphrase-ui.js'; -import { View } from '../../../js/common/view.js'; -import { SetupOptions } from '../setup.js'; - -View.run( - class GenerateKeyView extends View { - public readonly backupUi: BackupUi; - public readonly parentTabId: string; - protected fesUrl?: string; - private readonly acctEmail: string; - private readonly keyImportUi = new KeyImportUi({ rejectKnown: true }); - private clientConfiguration!: ClientConfiguration; - private pubLookup!: PubLookup; - - public constructor() { - super(); - const uncheckedUrlParams = Url.parse(['acctEmail', 'parentTabId']); - this.acctEmail = Assert.urlParamRequire.string(uncheckedUrlParams, 'acctEmail'); - this.parentTabId = Assert.urlParamRequire.string(uncheckedUrlParams, 'parentTabId'); - this.backupUi = new BackupUi(); - } - - public render = async () => { - await this.keyImportUi.renderKeyManualCreateView('#generate-key-container'); - $('#step_2a_manual_create').css('display', 'block'); - const storage = await AcctStore.get(this.acctEmail, ['fesUrl']); - this.fesUrl = storage.fesUrl; - this.clientConfiguration = await ClientConfiguration.newInstance(this.acctEmail); - this.pubLookup = new PubLookup(this.clientConfiguration); - if (!this.clientConfiguration.forbidStoringPassPhrase()) { - $('.input_passphrase_save_label').removeClass('hidden'); - $('.input_passphrase_save').prop('checked', true); - } - if (this.clientConfiguration.usesKeyManager()) { - Xss.sanitizeRender( - 'body', - ` -

-
Please contact your IT staff if you wish to update your keys.
-

- ` - ); - } else { - $('#content').show(); - if (!this.clientConfiguration.forbidStoringPassPhrase()) { - $('.input_passphrase_save').prop('checked', true).prop('disabled', false); - } - await initPassphraseToggle(['step_2a_manual_create_input_password', 'step_2a_manual_create_input_password2']); - } - }; - - public setHandlers = () => { - $('#step_2a_manual_create .action_proceed_private').on( - 'click', - this.setHandlerPrevent('double', () => this.actionCreateKeyHandler()) - ); - $('#step_2a_manual_create .input_password').on('keydown', this.setEnterHandlerThatClicks('#step_2a_manual_create .action_proceed_private')); - $('#step_2a_manual_create.input_password2').on('keydown', this.setEnterHandlerThatClicks('#step_2a_manual_create .action_proceed_private')); - }; - - public createSaveKeyPair = async (options: SetupOptions, keyAlgo: KeyAlgo): Promise => { - // eslint-disable-next-line @typescript-eslint/naming-convention - const { full_name } = await AcctStore.get(this.acctEmail, ['full_name']); - const pgpUids = [{ name: full_name || '', email: this.acctEmail }]; // todo - add all addresses? - const expireMonths = this.clientConfiguration.getEnforcedKeygenExpirationMonths(); - const key = await OpenPGPKey.create(pgpUids, keyAlgo, options.passphrase, expireMonths); - const prv = await KeyUtil.parse(key.private); - await saveKeysAndPassPhrase(this.acctEmail, [prv], options, []); - return { id: prv.id, family: prv.family }; - }; - - public actionCreateKeyHandler = async () => { - try { - $('#step_2a_manual_create input').prop('disabled', true); - Xss.sanitizeRender('#step_2a_manual_create .action_proceed_private', Ui.spinner('white') + 'just a minute'); - /* eslint-disable @typescript-eslint/naming-convention */ - const opts: SetupOptions = { - passphrase: String($('#step_2a_manual_create .input_password').val()), - passphrase_save: Boolean($('#step_2a_manual_create .input_passphrase_save').prop('checked')), - passphrase_ensure_single_copy: false, // there can't be any saved passphrases for the new key - submit_main: this.keyImportUi.shouldSubmitPubkey(this.clientConfiguration, '#step_2a_manual_create .input_submit_key'), - submit_all: this.keyImportUi.shouldSubmitPubkey(this.clientConfiguration, '#step_2a_manual_create .input_submit_all'), - recovered: false, - }; - /* eslint-enable @typescript-eslint/naming-convention */ - const keyAlgo = this.clientConfiguration.getEnforcedKeygenAlgo() || ($('#step_2a_manual_create .key_type').val() as KeyAlgo); - const keyIdentity = await this.createSaveKeyPair(opts, keyAlgo); - if (this.clientConfiguration.getPublicKeyForPrivateKeyBackupToDesignatedMailbox()) { - const adminPubkey = this.clientConfiguration.getPublicKeyForPrivateKeyBackupToDesignatedMailbox(); - if (adminPubkey) { - const msgEncryptionKey = await KeyUtil.parse(adminPubkey); - const destinationEmail = msgEncryptionKey.emails[0]; - try { - const privateKey = await KeyStore.get(this.acctEmail); - const primaryKeyId = privateKey[0].id; - await this.backupUi.initialize({ - acctEmail: this.acctEmail, - action: 'setup_automatic', - keyIdentity, - onBackedUpFinished: async () => { - this.closeDialog(); - }, - }); - const parsedPrivateKey = await KeyUtil.parse(privateKey[0].private); - await OpenPGPKey.decryptKey(parsedPrivateKey, opts.passphrase); - const armoredPrivateKey = KeyUtil.armor(parsedPrivateKey); - const encryptedPrivateKey = await MsgUtil.encryptMessage({ - pubkeys: [msgEncryptionKey], - data: Buf.fromUtfStr(armoredPrivateKey), - armor: false, - }); - const privateKeyAttachment = new Attachment({ - name: `0x${primaryKeyId}.asc.pgp`, - type: 'application/pgp-encrypted', - data: encryptedPrivateKey.data, - }); - await this.backupUi.manualModule.doBackupOnDesignatedMailbox(msgEncryptionKey, privateKeyAttachment, destinationEmail, primaryKeyId); - } catch (e) { - if (ApiErr.isNetErr(e)) { - await Ui.modal.warning('Need internet connection to finish. Please click the button again to retry.'); - } else { - Catch.reportErr(e); - await Ui.modal.error(`Error happened: ${String(e)}`); - } - } - } - } else if (this.clientConfiguration.canBackupKeys()) { - await this.submitPublicKeys(opts); - const action = $('#step_2a_manual_create .input_backup_inbox').prop('checked') ? 'setup_automatic' : 'setup_manual'; - // only finalize after backup is done. - $('#step_2a_manual_create').hide(); - await this.backupUi.initialize({ - acctEmail: this.acctEmail, - action, - keyIdentity, - onBackedUpFinished: async () => { - this.closeDialog(); - }, - }); - } else { - this.closeDialog(); - } - } catch (e) { - Catch.reportErr(e); - await Ui.modal.error(`There was an error, please try again.\n\n(${String(e)})`); - $('#step_2a_manual_create .action_proceed_private').text('CREATE AND SAVE'); - } - }; - - /* eslint-disable @typescript-eslint/naming-convention */ - private submitPublicKeys = async ({ submit_main, submit_all }: { submit_main: boolean; submit_all: boolean }): Promise => { - const mostUsefulPrv = KeyStoreUtil.chooseMostUseful(await KeyStoreUtil.parse(await KeyStore.getRequired(this.acctEmail)), 'ONLY-FULLY-USABLE'); - try { - await submitPublicKeyIfNeeded(this.clientConfiguration, this.acctEmail, [], this.pubLookup.attester, mostUsefulPrv?.keyInfo.public, { - submit_main, - submit_all, - }); - } catch (e) { - return await Settings.promptToRetry( - e, - e instanceof CompanyLdapKeyMismatchError ? Lang.setup.failedToImportUnknownKey : Lang.setup.failedToSubmitToAttester, - () => this.submitPublicKeys({ submit_main, submit_all }), - Lang.general.contactIfNeedAssistance(Boolean(this.fesUrl)) - ); - } - }; - /* eslint-enable @typescript-eslint/naming-convention */ - - private closeDialog() { - BrowserMsg.send.reload(this.parentTabId, { advanced: true }); - } - } -); diff --git a/extension/js/common/ui/backup-ui/backup-ui.ts b/extension/js/common/ui/backup-ui/backup-ui.ts index 50341ddbcd8..4ef82244542 100644 --- a/extension/js/common/ui/backup-ui/backup-ui.ts +++ b/extension/js/common/ui/backup-ui/backup-ui.ts @@ -79,6 +79,7 @@ export class BackupUi { Xss.sanitizeRender('body', `
${Lang.setup.keyBackupsNotAllowed}
`); return; } + $('#add_key_option_container').hide(); if (this.action === 'setup_automatic') { $('#button-go-back').css('display', 'none'); await this.automaticModule.simpleSetupAutoBackupRetryUntilSuccessful(); diff --git a/extension/js/common/ui/key-import-ui.ts b/extension/js/common/ui/key-import-ui.ts index eb3ae99b72c..b1327db780e 100644 --- a/extension/js/common/ui/key-import-ui.ts +++ b/extension/js/common/ui/key-import-ui.ts @@ -101,22 +101,35 @@ export class KeyImportUi { public initPrvImportSrcForm = (acctEmail: string, parentTabId: string | undefined, submitKeyForAddrs?: string[] | undefined) => { $('input[type=radio][name=source]') .off() - .change(function () { - if ((this as HTMLInputElement).value === 'file') { - $('.input_private_key').val('').change().prop('disabled', true); - $('.source_paste_container').css('display', 'none'); - $('.source_paste_container .unprotected_key_create_pass_phrase').hide(); - $('#fineuploader_button > input').trigger('click'); - } else if ((this as HTMLInputElement).value === 'paste') { - $('.input_private_key').val('').change().prop('disabled', false); - $('.source_paste_container').css('display', 'block'); - $('.source_paste_container .unprotected_key_create_pass_phrase').hide(); - } else if ((this as HTMLInputElement).value === 'backup') { - window.location.href = Url.create('/chrome/settings/setup.htm', { - acctEmail, - parentTabId, - action: 'add_key', - }); + .on('change', function () { + const selectedValue = (this as HTMLInputElement).value; + switch (selectedValue) { + case 'file': + $('.input_private_key').val('').change().prop('disabled', true); + $('.source_paste_container').css('display', 'none'); + $('.source_generate_container').hide(); + $('.source_paste_container .unprotected_key_create_pass_phrase').hide(); + $('#fineuploader_button > input').trigger('click'); + break; + case 'paste': + $('.input_private_key').val('').change().prop('disabled', false); + $('.source_generate_container').hide(); + $('.source_paste_container').css('display', 'block'); + $('.source_paste_container .unprotected_key_create_pass_phrase').hide(); + break; + case 'backup': + window.location.href = Url.create('/chrome/settings/setup.htm', { + acctEmail, + parentTabId, + action: 'add_key', + }); + break; + case 'generate': + $('.source_paste_container').hide(); + $('.source_generate_container').show(); + break; + default: + break; } }); $('.line.unprotected_key_create_pass_phrase .action_use_random_pass_phrase').on( From e0c900fa2d4fa143e5e5bab23cf636af389e66bf Mon Sep 17 00:00:00 2001 From: Ioan Moldovan Date: Thu, 12 Sep 2024 03:52:18 +0300 Subject: [PATCH 3/7] feat: ui test --- extension/chrome/settings/modules/add_key.htm | 4 ++-- extension/chrome/settings/modules/add_key.ts | 6 +++--- .../modules/add_key_generate_module.ts | 4 ++-- test/source/tests/settings.ts | 20 +++++++++++++++++++ 4 files changed, 27 insertions(+), 7 deletions(-) diff --git a/extension/chrome/settings/modules/add_key.htm b/extension/chrome/settings/modules/add_key.htm index 25068d1e7bb..2abbcbe4039 100644 --- a/extension/chrome/settings/modules/add_key.htm +++ b/extension/chrome/settings/modules/add_key.htm @@ -24,7 +24,7 @@

Add Private Key

  • - +
  • @@ -63,7 +63,7 @@

    Add Private Key

diff --git a/extension/chrome/settings/modules/add_key.ts b/extension/chrome/settings/modules/add_key.ts index 10602b0cf3f..d419c09a038 100644 --- a/extension/chrome/settings/modules/add_key.ts +++ b/extension/chrome/settings/modules/add_key.ts @@ -45,7 +45,7 @@ export class AddKeyView extends View { await this.addKeyGenerateModule.initGenerateKeyView(); if (!this.clientConfiguration.forbidStoringPassPhrase()) { $('.input_passphrase_save_label').removeClass('hidden'); - $('.input_passphrase_save').prop('checked', true); + $('.import_input_passphrase_save').prop('checked', true); } if (this.clientConfiguration.usesKeyManager()) { Xss.sanitizeRender( @@ -59,7 +59,7 @@ export class AddKeyView extends View { } else { $('#content').show(); if (!this.clientConfiguration.forbidStoringPassPhrase()) { - $('.input_passphrase_save').prop('checked', true).prop('disabled', false); + $('.import_input_passphrase_save').prop('checked', true).prop('disabled', false); } await initPassphraseToggle(['input_passphrase']); this.keyImportUi.initPrvImportSrcForm(this.acctEmail, this.parentTabId); @@ -102,7 +102,7 @@ export class AddKeyView extends View { /* eslint-disable @typescript-eslint/naming-convention */ await setPassphraseForPrvs(this.clientConfiguration, this.acctEmail, [key], { passphrase: String($('.input_passphrase').val()), - passphrase_save: !!$('.input_passphrase_save').prop('checked'), + passphrase_save: !!$('.import_input_passphrase_save').prop('checked'), passphrase_ensure_single_copy: false, // we require KeyImportUi to rejectKnown keys }); /* eslint-enable @typescript-eslint/naming-convention */ diff --git a/extension/chrome/settings/modules/add_key_generate_module.ts b/extension/chrome/settings/modules/add_key_generate_module.ts index 097efdd25c1..06af4491318 100644 --- a/extension/chrome/settings/modules/add_key_generate_module.ts +++ b/extension/chrome/settings/modules/add_key_generate_module.ts @@ -159,7 +159,7 @@ export class AddKeyGenerateModule extends ViewModule { }; /* eslint-enable @typescript-eslint/naming-convention */ - private closeDialog() { + private closeDialog = () => { BrowserMsg.send.reload(this.view.parentTabId, { advanced: true }); - } + }; } diff --git a/test/source/tests/settings.ts b/test/source/tests/settings.ts index 04804eee026..c2af78bdd57 100644 --- a/test/source/tests/settings.ts +++ b/test/source/tests/settings.ts @@ -536,6 +536,26 @@ export const defineSettingsTests = (testVariant: TestVariant, testWithBrowser: T await settingsPage.notPresent('@action-remove-key-0'); }) ); + test( + 'settings - my key page - generate key', + testWithBrowser(async (t, browser) => { + const acct = 'flowcrypt.compatibility@gmail.com'; + await BrowserRecipe.setupCommonAcctWithAttester(t, browser, 'compatibility'); + const settingsPage = await browser.newExtensionSettingsPage(t, acct); + await SettingsPageRecipe.ready(settingsPage); + await SettingsPageRecipe.toggleScreen(settingsPage, 'additional'); + const addKeyPopup = await SettingsPageRecipe.awaitNewPageFrame(settingsPage, '@action-open-add-key-page', ['add_key.htm']); + await addKeyPopup.waitAndClick('@source-generate'); + const passphrase = 'long enough to suit requirements'; + await addKeyPopup.waitAndType('@input-step2bmanualcreate-passphrase-1', passphrase); + await addKeyPopup.waitAndType('@input-step2bmanualcreate-passphrase-2', passphrase); + // Uncheck backup_inbox to check if backup view correctly displayed + await addKeyPopup.waitAndClick('@input-step2bmanualcreate-backup-inbox'); + await addKeyPopup.waitAndClick('@input-step2bmanualcreate-create-and-save'); + await addKeyPopup.waitAndClick('@action-backup-step3manual-continue'); + await SettingsPageRecipe.ready(settingsPage); + }) + ); test( 'settings - my key page - privileged frames and action buttons should be hidden when using key manager test', testWithBrowser(async (t, browser) => { From 9b62ef4d7eae22e7d9bc426a881e167b8f157dd5 Mon Sep 17 00:00:00 2001 From: Ioan Moldovan Date: Thu, 12 Sep 2024 12:02:12 +0300 Subject: [PATCH 4/7] temp: disable tap --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 4e7b0641f38..4938326e953 100644 --- a/package.json +++ b/package.json @@ -81,7 +81,7 @@ "test_async_stack": "node build/test/test/source/async-stack.js", "test_buf": "npx ava --timeout=3m --verbose --concurrency=10 build/test/test/source/buf.js", "test_ci_chrome_consumer_live_gmail": "npx ava --timeout=45m --verbose --tap --concurrency=1 build/test/test/source/test.js -- CONSUMER-LIVE-GMAIL STANDARD-GROUP | npx tap-xunit > report.xml", - "test_ci_chrome_consumer": "npx ava --timeout=30m --verbose --tap --concurrency=10 build/test/test/source/test.js -- CONSUMER-MOCK STANDARD-GROUP | npx tap-xunit > report.xml", + "test_ci_chrome_consumer": "npx ava --timeout=30m --verbose --concurrency=10 build/test/test/source/test.js -- CONSUMER-MOCK STANDARD-GROUP", "test_ci_chrome_enterprise": "npx ava --timeout=30m --verbose --tap --concurrency=10 build/test/test/source/test.js -- ENTERPRISE-MOCK STANDARD-GROUP | npx tap-xunit > report.xml", "test_ci_chrome_consumer_flaky": "npx ava --timeout=30m --verbose --tap --concurrency=10 build/test/test/source/test.js -- CONSUMER-MOCK FLAKY-GROUP | npx tap-xunit > report.xml", "test_ci_chrome_content_scripts": "npx ava --timeout=3m --verbose --tap build/test/test/source/test.js -- CONTENT-SCRIPT-TESTS > report.xml", From 819b8fa5e87e4dbdae133af05e7d2dee507a1ffd Mon Sep 17 00:00:00 2001 From: Ioan Moldovan Date: Thu, 12 Sep 2024 12:36:59 +0300 Subject: [PATCH 5/7] Revert "temp: disable tap" This reverts commit 9b62ef4d7eae22e7d9bc426a881e167b8f157dd5. --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 4938326e953..4e7b0641f38 100644 --- a/package.json +++ b/package.json @@ -81,7 +81,7 @@ "test_async_stack": "node build/test/test/source/async-stack.js", "test_buf": "npx ava --timeout=3m --verbose --concurrency=10 build/test/test/source/buf.js", "test_ci_chrome_consumer_live_gmail": "npx ava --timeout=45m --verbose --tap --concurrency=1 build/test/test/source/test.js -- CONSUMER-LIVE-GMAIL STANDARD-GROUP | npx tap-xunit > report.xml", - "test_ci_chrome_consumer": "npx ava --timeout=30m --verbose --concurrency=10 build/test/test/source/test.js -- CONSUMER-MOCK STANDARD-GROUP", + "test_ci_chrome_consumer": "npx ava --timeout=30m --verbose --tap --concurrency=10 build/test/test/source/test.js -- CONSUMER-MOCK STANDARD-GROUP | npx tap-xunit > report.xml", "test_ci_chrome_enterprise": "npx ava --timeout=30m --verbose --tap --concurrency=10 build/test/test/source/test.js -- ENTERPRISE-MOCK STANDARD-GROUP | npx tap-xunit > report.xml", "test_ci_chrome_consumer_flaky": "npx ava --timeout=30m --verbose --tap --concurrency=10 build/test/test/source/test.js -- CONSUMER-MOCK FLAKY-GROUP | npx tap-xunit > report.xml", "test_ci_chrome_content_scripts": "npx ava --timeout=3m --verbose --tap build/test/test/source/test.js -- CONTENT-SCRIPT-TESTS > report.xml", From bbfb24220e162b38498fba7e0667e98a9957f926 Mon Sep 17 00:00:00 2001 From: Ioan Moldovan Date: Thu, 12 Sep 2024 16:15:31 +0300 Subject: [PATCH 6/7] fix: ui test --- test/source/tests/settings.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/test/source/tests/settings.ts b/test/source/tests/settings.ts index c2af78bdd57..03f89d8abbb 100644 --- a/test/source/tests/settings.ts +++ b/test/source/tests/settings.ts @@ -554,6 +554,8 @@ export const defineSettingsTests = (testVariant: TestVariant, testWithBrowser: T await addKeyPopup.waitAndClick('@input-step2bmanualcreate-create-and-save'); await addKeyPopup.waitAndClick('@action-backup-step3manual-continue'); await SettingsPageRecipe.ready(settingsPage); + await settingsPage.waitAndClick('@action-remove-key-2'); // Delete newly generated key + await SettingsPageRecipe.ready(settingsPage); }) ); test( From 7e4a2b2f4b128d2ed228b305055c6a23c2328a52 Mon Sep 17 00:00:00 2001 From: Ioan Moldovan Date: Fri, 13 Sep 2024 03:36:43 +0300 Subject: [PATCH 7/7] fix: ui test --- 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 03f89d8abbb..68592f17be4 100644 --- a/test/source/tests/settings.ts +++ b/test/source/tests/settings.ts @@ -552,6 +552,7 @@ export const defineSettingsTests = (testVariant: TestVariant, testWithBrowser: T // Uncheck backup_inbox to check if backup view correctly displayed await addKeyPopup.waitAndClick('@input-step2bmanualcreate-backup-inbox'); await addKeyPopup.waitAndClick('@input-step2bmanualcreate-create-and-save'); + await addKeyPopup.waitAndClick('@input-backup-step3manual-no-backup'); // choose no_backup so that it doesn't affect other tests. await addKeyPopup.waitAndClick('@action-backup-step3manual-continue'); await SettingsPageRecipe.ready(settingsPage); await settingsPage.waitAndClick('@action-remove-key-2'); // Delete newly generated key