From e73150b3d69f5f30b1eca76ae6dd5a5fe3bd0cba Mon Sep 17 00:00:00 2001 From: martgil Date: Thu, 13 Mar 2025 16:04:57 +0800 Subject: [PATCH 1/7] feat: add info modal for key with newer expiry than existing key --- extension/js/common/ui/key-import-ui.ts | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/extension/js/common/ui/key-import-ui.ts b/extension/js/common/ui/key-import-ui.ts index ce13b731712..b1382d21b75 100644 --- a/extension/js/common/ui/key-import-ui.ts +++ b/extension/js/common/ui/key-import-ui.ts @@ -356,7 +356,19 @@ export class KeyImportUi { const keyinfos = await KeyStore.get(acctEmail); const privateKeysIds = keyinfos.map(ki => ki.fingerprints[0]); if (privateKeysIds.includes(k.id)) { - throw new UserAlert('This is one of your current keys, try another one.'); + const key = keyinfos.find(ki => ki.id === k.id); + /* eslint-disable @typescript-eslint/no-non-null-assertion */ + const existingKey = await KeyUtil.parse(key!.public); + const hasNewerExpiration = !!(k.expiration && existingKey.expiration && k.expiration > existingKey.expiration); + if (hasNewerExpiration) { + const updateKeyMessage = + "The key you're trying to import is a newer version of one you already have, based on its expiry date.\n" + + 'Follow the guide \n' + + 'to update it or contact your FlowCrypt admin.'; + await Ui.modal.info(updateKeyMessage, true); + } else { + throw new UserAlert('This is one of your current keys, try another one.'); + } } } }; From e8230cdd18b31cbb8dbd66405b6e0297ac707615 Mon Sep 17 00:00:00 2001 From: martgil Date: Mon, 17 Mar 2025 14:43:56 +0800 Subject: [PATCH 2/7] test: add test --- extension/js/common/ui/passphrase-ui.ts | 1 + test/source/tests/settings.ts | 30 +++++++++++++++++++++ test/source/tests/tooling/consts.ts | 36 +++++++++++++++++++++++++ 3 files changed, 67 insertions(+) diff --git a/extension/js/common/ui/passphrase-ui.ts b/extension/js/common/ui/passphrase-ui.ts index 7620d4be690..6adfd7e4b3c 100644 --- a/extension/js/common/ui/passphrase-ui.ts +++ b/extension/js/common/ui/passphrase-ui.ts @@ -26,6 +26,7 @@ export const initPassphraseToggle = async (passphraseInputIds: string[], forceIn for (const id of passphraseInputIds) { const passphraseInput = $(`#${id}`); passphraseInput.addClass('toggled_passphrase'); + passphraseInput.attr('data-test', 'input-passphrase'); if (show) { passphraseInput.after(``); // xss-direct passphraseInput.attr('type', 'text'); diff --git a/test/source/tests/settings.ts b/test/source/tests/settings.ts index 0addfe2bb5a..9fd1720c9e2 100644 --- a/test/source/tests/settings.ts +++ b/test/source/tests/settings.ts @@ -32,6 +32,36 @@ import { flowcryptCompatibilityAliasList } from '../mock/google/google-endpoints export const defineSettingsTests = (testVariant: TestVariant, testWithBrowser: TestWithBrowser) => { if (testVariant !== 'CONSUMER-LIVE-GMAIL') { + test( + 'settings - inform user when importing newer key version', + testWithBrowser(async (t, browser) => { + t.context.mockApi!.configProvider = new ConfigurationProvider({ + attester: { + pubkeyLookup: {}, + }, + }); + const acct = 'flowcrypt.compatibility@gmail.com'; + const settingsPage = await BrowserRecipe.openSettingsLoginApprove(t, browser, acct); + const key50yearExpiry = testConstants.keyWith50yearsExpiry; + await SetupPageRecipe.manualEnter(settingsPage, '', { + fixKey: true, + key: { + title: '', + armored: key50yearExpiry, + passphrase: 'passphrase', + longid: '6B673BAB02EC3E43', + }, + }); + await SettingsPageRecipe.toggleScreen(settingsPage, 'additional'); + const addKeyPopup = await SettingsPageRecipe.awaitNewPageFrame(settingsPage, '@action-open-add-key-page', ['add_key.htm']); + await addKeyPopup.waitAndClick('@source-paste'); + const key80yearExpiry = testConstants.keyWith80yearsExpiry; + await addKeyPopup.waitAndType('@input-armored-key', key80yearExpiry); + await addKeyPopup.waitAndType('@input-passphrase', 'passphrase'); + const expectedInfoMsg = "The key you're trying to import is a newer version of one you already have, based on its expiry date"; + await addKeyPopup.waitAndRespondToModal('info', 'confirm', expectedInfoMsg); + }) + ); test( 'settings - my own emails show as contacts', testWithBrowser(async (t, browser) => { diff --git a/test/source/tests/tooling/consts.ts b/test/source/tests/tooling/consts.ts index ad4302abe70..0b8bf037f0f 100644 --- a/test/source/tests/tooling/consts.ts +++ b/test/source/tests/tooling/consts.ts @@ -2050,6 +2050,42 @@ UR98qQ186mnyAcpbCVs7suK/0v+OIDHbF4gxMpRZmljstuT7mK4o6obfYwbOAq0Z LE0zUUI5Yvk= =XL9F -----END PGP PUBLIC KEY BLOCK-----`, + keyWith50yearsExpiry: `-----BEGIN PGP PRIVATE KEY BLOCK----- +Comment: passphrase is passphrase + +lIYEZ9etehYJKwYBBAHaRw8BAQdAS3w39Z7POxljA2Pw82iHSxdw/LXR+FzOpNMA +v6Pzjuf+BwMCnms3a0yOh9L/bSD0R3dfzQAnyKtlY5wuT9GY30ne9Wi5OrodVPDL +mm9lwOoq4CZhT76FCZH+VqQ1/W4ag5eXGus53teE9f+DvFQuBdUDFLQkdGVzdCBr +ZXkgPGxvbmcudGltZXNwYW5AZXhhbXBsZS5jb20+iJkEExYKAEECGwMFCwkIBwIC +IgIGFQoJCAsCBBYCAwECHgcCF4AWIQS6AopPWvdAXlKLtolrZzurAuw+QwUCZ9et +qQUJXfwPLwAKCRBrZzurAuw+Q0mZAP4g+96pBZQK+x0GWeTPbctN95Iutex0RS+f +vVsMhTPvwQD/Ytlt25KLL5hUdw4RMDckK6l9G+RoQH6FyWPWpZmZuw+ciwRn1616 +EgorBgEEAZdVAQUBAQdA9TVaPoFGzRGv3zmNNEDQDGONRo7Up3baDpux3rgvmnMD +AQgH/gcDAiP/VHZnfkp4/2ux2bb4OZ9GHtIWZ4ijNqBggR3HBX/T9y3JSCgxs1uc +LM0JL5XrwaGGDDokpEr+GBuFFuGw6LfV+bw4No2Xuv84SxAF1RCIfgQYFgoAJhYh +BLoCik9a90BeUou2iWtnO6sC7D5DBQJn1616AhsMBQmWYBgAAAoJEGtnO6sC7D5D +OkYA/1W6mbl/dAqt1j3kwFptypOiK48yLcXjIYpptpqi6Jy+AQCofaj8LPWXUZmK +dm7daVbBUy6YHWaePL/Si2pfPR1WDg== +=5V6v +-----END PGP PRIVATE KEY BLOCK-----`, + keyWith80yearsExpiry: `-----BEGIN PGP PRIVATE KEY BLOCK----- +Comment: passphrase is passphrase + +lIYEZ9etehYJKwYBBAHaRw8BAQdAS3w39Z7POxljA2Pw82iHSxdw/LXR+FzOpNMA +v6Pzjuf+BwMCNigCZ9Hr0Ab/cwmxlbbT1PEXSJyu7oVhZlTFjKu0SAmKp9vxjv44 +Ry5So0rGJ5WjChH6SAHGoU7embN4zO8gf7oezsYcS3l4pkx/SAdworQkdGVzdCBr +ZXkgPGxvbmcudGltZXNwYW5AZXhhbXBsZS5jb20+iJkEExYKAEEWIQS6AopPWvdA +XlKLtolrZzurAuw+QwUCZ9etegIbAwUJlmAYAAULCQgHAgIiAgYVCgkICwIEFgID +AQIeBwIXgAAKCRBrZzurAuw+Q61pAP9UJMO8zjug77uEtepzSTEe42DXD6DPiGzT +2zGfIsuZ3QEA2MfWfxxSylNOcenjDiyJdZXbQ9XBIOd6gnSYpPFi0g+ciwRn1616 +EgorBgEEAZdVAQUBAQdA9TVaPoFGzRGv3zmNNEDQDGONRo7Up3baDpux3rgvmnMD +AQgH/gcDAuFWQ5cWpFMO/wxxJn+r1CbUruerHu0m/lNAI9zq5PQ++i5vfrufR34r +ENWg+7hKHItP0eN4dC+63pfKOHw9ugcCQNPOtudaLTC1s9gMu9iIfgQYFgoAJhYh +BLoCik9a90BeUou2iWtnO6sC7D5DBQJn1616AhsMBQmWYBgAAAoJEGtnO6sC7D5D +OkYA/1W6mbl/dAqt1j3kwFptypOiK48yLcXjIYpptpqi6Jy+AQCofaj8LPWXUZmK +dm7daVbBUy6YHWaePL/Si2pfPR1WDg== +=Tujf +-----END PGP PRIVATE KEY BLOCK-----`, }; export const testKeyConstants = { From d5ba555c7ddbd345f1500c07299aaca8f5c625bd Mon Sep 17 00:00:00 2001 From: martgil Date: Tue, 18 Mar 2025 16:32:14 +0800 Subject: [PATCH 3/7] refactor: remove data-test --- extension/js/common/ui/passphrase-ui.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/extension/js/common/ui/passphrase-ui.ts b/extension/js/common/ui/passphrase-ui.ts index 6adfd7e4b3c..7620d4be690 100644 --- a/extension/js/common/ui/passphrase-ui.ts +++ b/extension/js/common/ui/passphrase-ui.ts @@ -26,7 +26,6 @@ export const initPassphraseToggle = async (passphraseInputIds: string[], forceIn for (const id of passphraseInputIds) { const passphraseInput = $(`#${id}`); passphraseInput.addClass('toggled_passphrase'); - passphraseInput.attr('data-test', 'input-passphrase'); if (show) { passphraseInput.after(``); // xss-direct passphraseInput.attr('type', 'text'); From cadf3d0d9183d367e8035f1988298689b521e989 Mon Sep 17 00:00:00 2001 From: martgil Date: Fri, 21 Mar 2025 18:35:39 +0800 Subject: [PATCH 4/7] test: fix failing test --- extension/chrome/settings/modules/add_key.htm | 2 +- extension/js/common/ui/passphrase-ui.ts | 3 + test/source/tests/settings.ts | 59 +++++++++---------- 3 files changed, 33 insertions(+), 31 deletions(-) diff --git a/extension/chrome/settings/modules/add_key.htm b/extension/chrome/settings/modules/add_key.htm index 2abbcbe4039..db65def88ab 100644 --- a/extension/chrome/settings/modules/add_key.htm +++ b/extension/chrome/settings/modules/add_key.htm @@ -69,7 +69,7 @@

Add Private Key


- +
diff --git a/extension/js/common/ui/passphrase-ui.ts b/extension/js/common/ui/passphrase-ui.ts index 7620d4be690..c8eae1f7ce7 100644 --- a/extension/js/common/ui/passphrase-ui.ts +++ b/extension/js/common/ui/passphrase-ui.ts @@ -26,6 +26,9 @@ export const initPassphraseToggle = async (passphraseInputIds: string[], forceIn for (const id of passphraseInputIds) { const passphraseInput = $(`#${id}`); passphraseInput.addClass('toggled_passphrase'); + if (!passphraseInput.attr('data-test')) { + passphraseInput.attr('data-test', 'input-passphrase'); + } if (show) { passphraseInput.after(``); // xss-direct passphraseInput.attr('type', 'text'); diff --git a/test/source/tests/settings.ts b/test/source/tests/settings.ts index 9fd1720c9e2..c91fea0c9e9 100644 --- a/test/source/tests/settings.ts +++ b/test/source/tests/settings.ts @@ -32,36 +32,6 @@ import { flowcryptCompatibilityAliasList } from '../mock/google/google-endpoints export const defineSettingsTests = (testVariant: TestVariant, testWithBrowser: TestWithBrowser) => { if (testVariant !== 'CONSUMER-LIVE-GMAIL') { - test( - 'settings - inform user when importing newer key version', - testWithBrowser(async (t, browser) => { - t.context.mockApi!.configProvider = new ConfigurationProvider({ - attester: { - pubkeyLookup: {}, - }, - }); - const acct = 'flowcrypt.compatibility@gmail.com'; - const settingsPage = await BrowserRecipe.openSettingsLoginApprove(t, browser, acct); - const key50yearExpiry = testConstants.keyWith50yearsExpiry; - await SetupPageRecipe.manualEnter(settingsPage, '', { - fixKey: true, - key: { - title: '', - armored: key50yearExpiry, - passphrase: 'passphrase', - longid: '6B673BAB02EC3E43', - }, - }); - await SettingsPageRecipe.toggleScreen(settingsPage, 'additional'); - const addKeyPopup = await SettingsPageRecipe.awaitNewPageFrame(settingsPage, '@action-open-add-key-page', ['add_key.htm']); - await addKeyPopup.waitAndClick('@source-paste'); - const key80yearExpiry = testConstants.keyWith80yearsExpiry; - await addKeyPopup.waitAndType('@input-armored-key', key80yearExpiry); - await addKeyPopup.waitAndType('@input-passphrase', 'passphrase'); - const expectedInfoMsg = "The key you're trying to import is a newer version of one you already have, based on its expiry date"; - await addKeyPopup.waitAndRespondToModal('info', 'confirm', expectedInfoMsg); - }) - ); test( 'settings - my own emails show as contacts', testWithBrowser(async (t, browser) => { @@ -843,6 +813,35 @@ export const defineSettingsTests = (testVariant: TestVariant, testWithBrowser: T expect(pubkeyExpiration).to.equal(expectedExpiration); }) ); + test( + 'settings - inform user when importing newer key version', + testWithBrowser(async (t, browser) => { + t.context.mockApi!.configProvider = new ConfigurationProvider({ + attester: { + pubkeyLookup: {}, + }, + }); + const acct = 'flowcrypt.compatibility@gmail.com'; + const settingsPage = await BrowserRecipe.openSettingsLoginApprove(t, browser, acct); + const key50yearExpiry = testConstants.keyWith50yearsExpiry; + await SetupPageRecipe.manualEnter(settingsPage, '', { + key: { + title: '?', + armored: key50yearExpiry, + passphrase: 'passphrase', + longid: '6B673BAB02EC3E43', + }, + }); + await SettingsPageRecipe.toggleScreen(settingsPage, 'additional'); + const addKeyPopup = await SettingsPageRecipe.awaitNewPageFrame(settingsPage, '@action-open-add-key-page', ['add_key.htm']); + await addKeyPopup.waitAndClick('@source-paste'); + await addKeyPopup.waitAndType('@input-armored-key', testConstants.keyWith80yearsExpiry); + await addKeyPopup.waitAndType('@input-passphrase', 'passphrase'); + await addKeyPopup.waitAndClick('@action-add-private-key-btn'); + const expectedInfoMsg = "The key you're trying to import is a newer version of one you already have, based on its expiry date"; + await addKeyPopup.waitAndRespondToModal('info', 'confirm', expectedInfoMsg); + }) + ); test( 'settings - attachment previews are rendered according to their types', testWithBrowser(async (t, browser) => { From 4b3eda4d79f534fa3f4daa7c791db23ebc0ed53a Mon Sep 17 00:00:00 2001 From: martgil <46025304+martgil@users.noreply.github.com> Date: Wed, 26 Mar 2025 14:11:14 +0800 Subject: [PATCH 5/7] refactor: add user redirection going to my_key_update.htm --- extension/js/common/ui/key-import-ui.ts | 24 +++++++++++++----------- 1 file changed, 13 insertions(+), 11 deletions(-) diff --git a/extension/js/common/ui/key-import-ui.ts b/extension/js/common/ui/key-import-ui.ts index b1382d21b75..7678661af2b 100644 --- a/extension/js/common/ui/key-import-ui.ts +++ b/extension/js/common/ui/key-import-ui.ts @@ -357,17 +357,19 @@ export class KeyImportUi { const privateKeysIds = keyinfos.map(ki => ki.fingerprints[0]); if (privateKeysIds.includes(k.id)) { const key = keyinfos.find(ki => ki.id === k.id); - /* eslint-disable @typescript-eslint/no-non-null-assertion */ - const existingKey = await KeyUtil.parse(key!.public); - const hasNewerExpiration = !!(k.expiration && existingKey.expiration && k.expiration > existingKey.expiration); - if (hasNewerExpiration) { - const updateKeyMessage = - "The key you're trying to import is a newer version of one you already have, based on its expiry date.\n" + - 'Follow the guide \n' + - 'to update it or contact your FlowCrypt admin.'; - await Ui.modal.info(updateKeyMessage, true); - } else { - throw new UserAlert('This is one of your current keys, try another one.'); + if (key) { + const existingKey = await KeyUtil.parse(key.public); + const hasNewerExpiration = !!(k.expiration && existingKey.expiration && k.expiration > existingKey.expiration); + if (hasNewerExpiration) { + const updateKeyMessage = + "The key you're trying to import is a newer version of one you already have, based on its expiry date.\n\n" + + "We'll redirect you to the key update page to manage your key."; + if (await Ui.modal.confirm(updateKeyMessage, true)) { + window.location.href = Url.create('/chrome/settings/modules/my_key_update.htm', { acctEmail, fingerprint: k.id, parentTabId: '' }); + } + } else { + throw new UserAlert('This is one of your current keys, try another one.'); + } } } } From cc795a0281a4895ca48913f53927bf076d36b7d8 Mon Sep 17 00:00:00 2001 From: martgil <46025304+martgil@users.noreply.github.com> Date: Wed, 26 Mar 2025 14:22:47 +0800 Subject: [PATCH 6/7] test: update test --- test/source/tests/settings.ts | 22 +++++++++++++++------- 1 file changed, 15 insertions(+), 7 deletions(-) diff --git a/test/source/tests/settings.ts b/test/source/tests/settings.ts index c91fea0c9e9..81a833b1ff1 100644 --- a/test/source/tests/settings.ts +++ b/test/source/tests/settings.ts @@ -833,13 +833,21 @@ export const defineSettingsTests = (testVariant: TestVariant, testWithBrowser: T }, }); await SettingsPageRecipe.toggleScreen(settingsPage, 'additional'); - const addKeyPopup = await SettingsPageRecipe.awaitNewPageFrame(settingsPage, '@action-open-add-key-page', ['add_key.htm']); - await addKeyPopup.waitAndClick('@source-paste'); - await addKeyPopup.waitAndType('@input-armored-key', testConstants.keyWith80yearsExpiry); - await addKeyPopup.waitAndType('@input-passphrase', 'passphrase'); - await addKeyPopup.waitAndClick('@action-add-private-key-btn'); - const expectedInfoMsg = "The key you're trying to import is a newer version of one you already have, based on its expiry date"; - await addKeyPopup.waitAndRespondToModal('info', 'confirm', expectedInfoMsg); + const addKeyPage = await SettingsPageRecipe.awaitNewPageFrame(settingsPage, '@action-open-add-key-page', ['add_key.htm']); + await addKeyPage.waitAndClick('@source-paste'); + await addKeyPage.waitAndType('@input-armored-key', testConstants.keyWith80yearsExpiry); + await addKeyPage.waitAndType('@input-passphrase', 'passphrase'); + await addKeyPage.waitAndClick('@action-add-private-key-btn'); + const expectedInfoMsg = + "The key you're trying to import is a newer version of one you already have, based on its expiry date.\n\n" + + "We'll redirect you to the key update page to manage your key."; + await addKeyPage.waitAndRespondToModal('info', 'confirm', expectedInfoMsg); + const myKeyUpdatePage = await settingsPage.getFrame(['my_key_update.htm']); + await myKeyUpdatePage.waitAndClick('@source-paste'); + await myKeyUpdatePage.waitAndType('@input-prv-key', testConstants.keyWith80yearsExpiry); + await myKeyUpdatePage.waitAndType('@input-passphrase', 'passphrase'); + await myKeyUpdatePage.waitAndClick('@action-update-key'); + await myKeyUpdatePage.waitAndRespondToModal('confirm', 'confirm', 'Public and private key updated locally.'); }) ); test( From 7155fd29007e87641ed12d41f2cf14d5749ad21d Mon Sep 17 00:00:00 2001 From: martgil <46025304+martgil@users.noreply.github.com> Date: Wed, 26 Mar 2025 15:20:06 +0800 Subject: [PATCH 7/7] test: fix failing test --- extension/js/common/ui/key-import-ui.ts | 2 +- test/source/tests/settings.ts | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/extension/js/common/ui/key-import-ui.ts b/extension/js/common/ui/key-import-ui.ts index 7678661af2b..f2342abacff 100644 --- a/extension/js/common/ui/key-import-ui.ts +++ b/extension/js/common/ui/key-import-ui.ts @@ -362,7 +362,7 @@ export class KeyImportUi { const hasNewerExpiration = !!(k.expiration && existingKey.expiration && k.expiration > existingKey.expiration); if (hasNewerExpiration) { const updateKeyMessage = - "The key you're trying to import is a newer version of one you already have, based on its expiry date.\n\n" + + "The key you're trying to import is a newer version of one you already have, based on its expiry date. " + "We'll redirect you to the key update page to manage your key."; if (await Ui.modal.confirm(updateKeyMessage, true)) { window.location.href = Url.create('/chrome/settings/modules/my_key_update.htm', { acctEmail, fingerprint: k.id, parentTabId: '' }); diff --git a/test/source/tests/settings.ts b/test/source/tests/settings.ts index 81a833b1ff1..20e75521b1a 100644 --- a/test/source/tests/settings.ts +++ b/test/source/tests/settings.ts @@ -839,9 +839,9 @@ export const defineSettingsTests = (testVariant: TestVariant, testWithBrowser: T await addKeyPage.waitAndType('@input-passphrase', 'passphrase'); await addKeyPage.waitAndClick('@action-add-private-key-btn'); const expectedInfoMsg = - "The key you're trying to import is a newer version of one you already have, based on its expiry date.\n\n" + + "The key you're trying to import is a newer version of one you already have, based on its expiry date. " + "We'll redirect you to the key update page to manage your key."; - await addKeyPage.waitAndRespondToModal('info', 'confirm', expectedInfoMsg); + await addKeyPage.waitAndRespondToModal('confirm', 'confirm', expectedInfoMsg); const myKeyUpdatePage = await settingsPage.getFrame(['my_key_update.htm']); await myKeyUpdatePage.waitAndClick('@source-paste'); await myKeyUpdatePage.waitAndType('@input-prv-key', testConstants.keyWith80yearsExpiry);