diff --git a/extension/chrome/elements/passphrase.ts b/extension/chrome/elements/passphrase.ts index b088dae36b7..ec41fc03aa9 100644 --- a/extension/chrome/elements/passphrase.ts +++ b/extension/chrome/elements/passphrase.ts @@ -200,6 +200,18 @@ View.run( BrowserMsg.send.passphraseEntry({ entered, initiatorFrameId }); }; + private closeDialogPageOpenedExternally = async () => { + if (Catch.isThunderbirdMail() && window.top === window.self) { + const currentTab = await messenger.tabs.query({ active: true, currentWindow: true }); + if (currentTab.length > 0) { + const tabId = currentTab[0].id; + if (tabId) { + await messenger.tabs.remove(tabId); + } + } + } + }; + private submitHandler = async () => { if (await this.bruteForceProtection.shouldDisablePassphraseCheck()) { return; @@ -237,10 +249,12 @@ View.run( } if (unlockCount && allPrivateKeys.length > 1) { Ui.toast(`${unlockCount} of ${allPrivateKeys.length} keys ${unlockCount > 1 ? 'were' : 'was'} unlocked by this pass phrase`); + await this.closeDialogPageOpenedExternally(); } if (atLeastOneMatched) { await this.bruteForceProtection.passphraseCheckSucceed(); this.closeDialog(true, this.initiatorFrameId); + await this.closeDialogPageOpenedExternally(); } else { await this.bruteForceProtection.passphraseCheckFailed(); this.renderFailedEntryPpPrompt(); diff --git a/extension/css/cryptup.css b/extension/css/cryptup.css index 7ed07ddc8a1..cea22762c56 100644 --- a/extension/css/cryptup.css +++ b/extension/css/cryptup.css @@ -1428,7 +1428,7 @@ td { .backup_neutral { border: none; border-left: 4px solid #989898; - padding-left: 10px; + padding: 6px 10px; } .pgp_neutral .error_container { diff --git a/extension/js/common/browser/browser-msg.ts b/extension/js/common/browser/browser-msg.ts index 7cb49982024..f67ee9e8175 100644 --- a/extension/js/common/browser/browser-msg.ts +++ b/extension/js/common/browser/browser-msg.ts @@ -96,6 +96,7 @@ export namespace Bm { export type ReRenderRecipient = { email: string }; export type PgpBlockRetry = { frameId: string; messageSender: Dest }; export type PgpBlockReady = { frameId: string; messageSender: Dest }; + export type ThunderbirdOpenPassphraseDialog = { acctEmail: string; longids: string }; export namespace Res { export type GetActiveTabInfo = { @@ -112,6 +113,9 @@ export namespace Bm { export type ExpirationCacheGet = Promise; export type ExpirationCacheSet = Promise; export type ExpirationCacheDeleteExpired = Promise; + export type ThunderbirdGetCurrentUser = string | undefined; + export type ThunderbirdMsgGet = messenger.messages.MessagePart | undefined; + export type ThunderbirdOpenPassphraseDialog = Promise; // eslint-disable-next-line @typescript-eslint/no-explicit-any export type Db = any; // not included in Any below // eslint-disable-next-line @typescript-eslint/no-explicit-any @@ -128,7 +132,8 @@ export namespace Bm { | ExpirationCacheSet | ExpirationCacheDeleteExpired | AjaxGmailAttachmentGetChunk - | ConfirmationResult; + | ConfirmationResult + | ThunderbirdMsgGet; } export type AnyRequest = @@ -169,6 +174,7 @@ export namespace Bm { | PgpBlockReady | PgpBlockRetry | ConfirmationResult + | ThunderbirdOpenPassphraseDialog | Ajax; export type AsyncRespondingHandler = (req: AnyRequest) => Promise; @@ -232,6 +238,11 @@ export class BrowserMsg { BrowserMsg.sendAwait(undefined, 'expirationCacheSet', bm, true) as Promise, expirationCacheDeleteExpired: (bm: Bm.ExpirationCacheDeleteExpired) => BrowserMsg.sendAwait(undefined, 'expirationCacheDeleteExpired', bm, true) as Promise, + thunderbirdGetCurrentUser: () => + BrowserMsg.sendAwait(undefined, 'thunderbirdGetCurrentUser', undefined, true) as Promise, + thunderbirdMsgGet: () => BrowserMsg.sendAwait(undefined, 'thunderbirdMsgGet', undefined, true) as Promise, + thunderbirdOpenPassphraseDiaglog: (bm: Bm.ThunderbirdOpenPassphraseDialog) => + BrowserMsg.sendAwait(undefined, 'thunderbirdOpenPassphraseDialog', bm, true) as Promise, }, }, passphraseEntry: (bm: Bm.PassphraseEntry) => { diff --git a/extension/js/common/browser/env.ts b/extension/js/common/browser/env.ts index ca80d116a7f..74eea7a2442 100644 --- a/extension/js/common/browser/env.ts +++ b/extension/js/common/browser/env.ts @@ -61,7 +61,7 @@ export class Env { } public static async webmails(): Promise { - return ['gmail']; // async because storage may be involved in the future + return ['gmail', 'thunderbird']; // async because storage may be involved in the future } public static getBaseUrl() { diff --git a/extension/js/common/inject.ts b/extension/js/common/inject.ts index e49d00970bd..bac79af33ad 100644 --- a/extension/js/common/inject.ts +++ b/extension/js/common/inject.ts @@ -57,9 +57,11 @@ export class Injector { } public meta = () => { - this.S.cached('body') - .addClass(`cryptup_${this.webmailName} cryptup_${this.webmailName}_${this.webmailVariant} ${Catch.browser().name}`) - .append(this.factory.metaStylesheet('webmail') + this.factory.metaNotificationContainer()); // xss-safe-factory + if (this.webmailName === 'gmail') { + this.S.cached('body') + .addClass(`cryptup_${this.webmailName} cryptup_${this.webmailName}_${this.webmailVariant} ${Catch.browser().name}`) + .append(this.factory.metaStylesheet('webmail') + this.factory.metaNotificationContainer()); // xss-safe-factory + } }; public openComposeWin = (draftId?: string, openInFullScreen?: boolean, thunderbirdMsgId?: number, replyOption?: string, replyMsgId?: string): boolean => { diff --git a/extension/js/content_scripts/webmail/generic/setup-webmail-content-script.ts b/extension/js/content_scripts/webmail/generic/setup-webmail-content-script.ts index e192bd1f8c6..0c41eb72201 100644 --- a/extension/js/content_scripts/webmail/generic/setup-webmail-content-script.ts +++ b/extension/js/content_scripts/webmail/generic/setup-webmail-content-script.ts @@ -37,7 +37,7 @@ export type WebmailVariantObject = { type WebmailSpecificInfo = { name: WebMailName; variant: WebmailVariantString; - getUserAccountEmail: () => string | undefined; + getUserAccountEmail: () => Promise | string | undefined; getUserFullName: () => string | undefined; getReplacer: () => WebmailElementReplacer; start: ( @@ -74,7 +74,7 @@ export const contentScriptSetupIfVacant = async (webmailSpecific: WebmailSpecifi let acctEmailInterval = 1000; const webmails = await Env.webmails(); while (true) { - const acctEmail = webmailSpecific.getUserAccountEmail(); + const acctEmail = await webmailSpecific.getUserAccountEmail(); if (typeof acctEmail !== 'undefined') { win.account_email_global = acctEmail; if (webmails.includes(webmailSpecific.name)) { @@ -444,20 +444,22 @@ export const contentScriptSetupIfVacant = async (webmailSpecific: WebmailSpecifi } const acctEmail = await waitForAcctEmail(); const { tabId, notifications, factory, inject } = await initInternalVars(acctEmail); - await showNotificationsAndWaitTilAcctSetUp(acctEmail, notifications); - Catch.setHandledTimeout(() => updateClientConfiguration(acctEmail), 0); const ppEvent: { entered?: boolean } = {}; const relayManager = new RelayManager(); - browserMsgListen(acctEmail, tabId, inject, factory, notifications, relayManager, ppEvent); const clientConfiguration = await ClientConfiguration.newInstance(acctEmail); - await startPullingKeysFromEkm( - acctEmail, - clientConfiguration, - factory, - ppEvent, - notifications, - Catch.try(() => notifyExpiringKeys(acctEmail, clientConfiguration, notifications)) - ); + if (webmailSpecific.name === 'gmail') { + Catch.setHandledTimeout(() => updateClientConfiguration(acctEmail), 0); + await showNotificationsAndWaitTilAcctSetUp(acctEmail, notifications); + browserMsgListen(acctEmail, tabId, inject, factory, notifications, relayManager, ppEvent); + await startPullingKeysFromEkm( + acctEmail, + clientConfiguration, + factory, + ppEvent, + notifications, + Catch.try(() => notifyExpiringKeys(acctEmail, clientConfiguration, notifications)) + ); + } await webmailSpecific.start(acctEmail, clientConfiguration, inject, notifications, factory, relayManager); } catch (e) { if (e instanceof TabIdRequiredError) { @@ -509,7 +511,11 @@ export const contentScriptSetupIfVacant = async (webmailSpecific: WebmailSpecifi }; win.vacant = () => { - return !$('.' + win.destroyable_class).length; + if (Catch.isThunderbirdMail()) { + return true; + } else { + return !$('.' + win.destroyable_class).length; + } }; win.TrySetDestroyableInterval = (code, ms) => { diff --git a/extension/js/content_scripts/webmail/thunderbird/thunderbird-element-replacer.ts b/extension/js/content_scripts/webmail/thunderbird/thunderbird-element-replacer.ts index fc286d6e341..039f78d5c62 100644 --- a/extension/js/content_scripts/webmail/thunderbird/thunderbird-element-replacer.ts +++ b/extension/js/content_scripts/webmail/thunderbird/thunderbird-element-replacer.ts @@ -2,12 +2,144 @@ 'use strict'; -import { IntervalFunction, WebmailElementReplacer } from '../generic/webmail-element-replacer'; +import { BrowserMsg } from '../../../common/browser/browser-msg.js'; +import { KeyUtil } from '../../../common/core/crypto/key.js'; +import { DecryptError, DecryptErrTypes, MsgUtil } from '../../../common/core/crypto/pgp/msg-util.js'; +import { OpenPGPKey } from '../../../common/core/crypto/pgp/openpgp-key.js'; +import { PgpArmor } from '../../../common/core/crypto/pgp/pgp-armor'; +import { Catch } from '../../../common/platform/catch'; +import { ContactStore } from '../../../common/platform/store/contact-store.js'; +import { KeyStore } from '../../../common/platform/store/key-store.js'; +import { Xss } from '../../../common/platform/xss.js'; +import { IntervalFunction, WebmailElementReplacer } from '../generic/webmail-element-replacer.js'; +import * as openpgp from 'openpgp'; export class ThunderbirdElementReplacer extends WebmailElementReplacer { - public getIntervalFunctions: () => IntervalFunction[]; public setReplyBoxEditable: () => Promise; public reinsertReplyBox: (replyMsgId: string) => void; public scrollToReplyBox: (replyMsgId: string) => void; public scrollToCursorInReplyBox: (replyMsgId: string, cursorOffsetTop: number) => void; + private emailBodyFromThunderbirdMail: string; + + public getIntervalFunctions = (): IntervalFunction[] => { + return [{ interval: 2000, handler: () => this.replaceThunderbirdMsgPane() }]; + }; + + public replaceThunderbirdMsgPane = async () => { + if (Catch.isThunderbirdMail()) { + const fullMsg = await BrowserMsg.send.bg.await.thunderbirdMsgGet(); + if (!fullMsg) { + return; + } else { + const acctEmail = await BrowserMsg.send.bg.await.thunderbirdGetCurrentUser(); + const parsedPubs = (await ContactStore.getOneWithAllPubkeys(undefined, String(acctEmail)))?.sortedPubkeys ?? []; + const signerKeys = parsedPubs.map(key => KeyUtil.armor(key.pubkey)); + if (this.isPublicKeyEncryptedMsg(fullMsg)) { + const result = await MsgUtil.decryptMessage({ + kisWithPp: await KeyStore.getAllWithOptionalPassPhrase(String(acctEmail)), + encryptedData: this.emailBodyFromThunderbirdMail, + verificationPubs: signerKeys, + }); + if (result.success && result.content) { + const decryptedMsg = result.content.toUtfStr(); + const encryptionStatus = result.isEncrypted ? 'encrypted' : 'not encrypted'; + let verificationStatus = ''; + if (result?.signature) { + if (result.signature.match) { + verificationStatus = 'signed'; + } else if (result.signature.error) { + verificationStatus = `could not verify signature: ${result.signature.error}`; + } else { + verificationStatus = 'not signed'; + } + } + const pgpBlock = this.generatePgpBlockTemplate(encryptionStatus, verificationStatus, decryptedMsg); + $('body').html(pgpBlock); // xss-sanitized + } else { + const decryptErr = result as DecryptError; + let decryptionErrorMsg = ''; + if (decryptErr.error && decryptErr.error.type === DecryptErrTypes.needPassphrase) { + const acctEmail = String(await BrowserMsg.send.bg.await.thunderbirdGetCurrentUser()); + const longids = decryptErr.longids.needPassphrase.join(','); + decryptionErrorMsg = `decrypt error: private key needs to be unlocked by your passphrase.`; + await BrowserMsg.send.bg.await.thunderbirdOpenPassphraseDiaglog({ acctEmail, longids }); + } else { + decryptionErrorMsg = `decrypt error: ${(result as DecryptError).error.message}`; + } + const pgpBlock = this.generatePgpBlockTemplate(decryptionErrorMsg, 'not signed', this.emailBodyFromThunderbirdMail); + $('body').html(pgpBlock); // xss-sanitized + } + } else if (this.isCleartextMsg(fullMsg)) { + const message = await openpgp.readCleartextMessage({ cleartextMessage: this.emailBodyFromThunderbirdMail }); + const result = await OpenPGPKey.verify(message, await ContactStore.getPubkeyInfos(undefined, signerKeys)); + let verificationStatus = ''; + let signedMessage = ''; + if (result.match && result.content) { + verificationStatus = 'signed'; + signedMessage = result.content.toUtfStr(); + } else if (result.error) { + verificationStatus = `could not verify signature: ${result.error}`; + } + const pgpBlock = this.generatePgpBlockTemplate('not encrypted', verificationStatus, signedMessage); + $('body').html(pgpBlock); // xss-sanitized + } + // todo: detached signed message via https://github.com/FlowCrypt/flowcrypt-browser/issues/5668 + } + } + }; + + private generatePgpBlockTemplate = (encryptionStatus: string, verificationStatus: string, messageToRender: string): string => { + return ` +
+
+
${encryptionStatus}
+
${verificationStatus}
+
+
+
${Xss.escape(messageToRender)}
+
+
`; + }; + + private isCleartextMsg = (fullMsg: messenger.messages.MessagePart): boolean => { + return ( + (fullMsg.headers && + 'openpgp' in fullMsg.headers && + fullMsg.parts && + fullMsg.parts[0]?.parts?.length === 1 && + fullMsg.parts[0].parts[0].contentType === 'text/plain' && + this.resemblesCleartextMsg(fullMsg.parts[0].parts[0].body?.trim() || '')) || + false + ); + }; + + private resemblesCleartextMsg = (body: string) => { + this.emailBodyFromThunderbirdMail = body; + return ( + body.startsWith(PgpArmor.ARMOR_HEADER_DICT.signedMsg.begin) && + body.includes(String(PgpArmor.ARMOR_HEADER_DICT.signedMsg.middle)) && + body.endsWith(String(PgpArmor.ARMOR_HEADER_DICT.signedMsg.end)) + ); + }; + + private isPublicKeyEncryptedMsg = (fullMsg: messenger.messages.MessagePart): boolean => { + if (fullMsg.headers && 'openpgp' in fullMsg.headers && fullMsg.parts) { + return ( + (fullMsg.parts[0]?.parts?.length === 2 && + fullMsg.parts[0]?.parts[1].contentType === 'application/pgp-encrypted' && + this.resemblesAsciiArmoredMsg(fullMsg.parts[0]?.parts[0].body?.trim() || '')) || + (fullMsg.parts[0]?.parts?.length === 1 && + fullMsg.parts[0]?.contentType === 'multipart/mixed' && + this.resemblesAsciiArmoredMsg(fullMsg.parts[0]?.parts[0].body?.trim() || '')) || + (fullMsg.parts.length === 1 && fullMsg.parts[0]?.contentType === 'text/plain' && this.resemblesAsciiArmoredMsg(fullMsg.parts[0]?.body?.trim() || '')) || + false + ); + } + return false; + }; + + private resemblesAsciiArmoredMsg = (body: string): boolean => { + this.emailBodyFromThunderbirdMail = body; + return body.startsWith(PgpArmor.ARMOR_HEADER_DICT.encryptedMsg.begin) && body.endsWith(PgpArmor.ARMOR_HEADER_DICT.encryptedMsg.end as string); + }; } diff --git a/extension/js/content_scripts/webmail/thunderbird/thunderbird-webmail-startup.ts b/extension/js/content_scripts/webmail/thunderbird/thunderbird-webmail-startup.ts index 2e0b16a4d84..ae9f803a6db 100644 --- a/extension/js/content_scripts/webmail/thunderbird/thunderbird-webmail-startup.ts +++ b/extension/js/content_scripts/webmail/thunderbird/thunderbird-webmail-startup.ts @@ -1,38 +1,29 @@ /* ©️ 2016 - present FlowCrypt a.s. Limitations apply. Contact human@flowcrypt.com */ -import { ClientConfiguration } from '../../../common/client-configuration'; -import { Injector } from '../../../common/inject'; -import { Notifications } from '../../../common/notifications'; +import { BrowserMsg } from '../../../common/browser/browser-msg'; import { contentScriptSetupIfVacant } from '../generic/setup-webmail-content-script'; -import { GmailElementReplacer } from '../gmail/gmail-element-replacer'; import { ThunderbirdElementReplacer } from './thunderbird-element-replacer'; export class ThunderbirdWebmailStartup { - private replacer: GmailElementReplacer; + private replacer: ThunderbirdElementReplacer; public asyncConstructor = async () => { await contentScriptSetupIfVacant({ name: 'thunderbird', variant: undefined, - getUserAccountEmail: () => undefined, // todo, but can start with undefined + getUserAccountEmail: async () => String(await BrowserMsg.send.bg.await.thunderbirdGetCurrentUser()), getUserFullName: () => undefined, // todo, but can start with undefined - getReplacer: () => new ThunderbirdElementReplacer(), // todo - add this class empty, methods do nothing + getReplacer: () => this.replacer, start: this.start, }); }; - private start = async ( - acctEmail: string, - clientConfiguration: ClientConfiguration, - injector: Injector, - notifications: Notifications - // factory: XssSafeFactory, // todo in another issue - // relayManager: RelayManager // todo in another issue - ) => { - // injector.btns(); // todo in another issue - add compose button + private start = async () => { + this.replacer = new ThunderbirdElementReplacer(); this.replacer.runIntervalFunctionsPeriodically(); - await notifications.showInitial(acctEmail); - notifications.show( - 'FlowCrypt Thunderbird support is still in early development, and not expected to function properly yet. Support will be gradually added in upcoming versions.' - ); + // todo: show notification using Thunderbird Notification as contentscript notification or such does not work. + // await notifications.showInitial(acctEmail); + // notifications.show( + // 'FlowCrypt Thunderbird support is still in early development, and not expected to function properly yet. Support will be gradually added in upcoming versions.' + // ); }; } diff --git a/extension/js/content_scripts/webmail/webmail.ts b/extension/js/content_scripts/webmail/webmail.ts index beadcc1047f..38fe276588a 100644 --- a/extension/js/content_scripts/webmail/webmail.ts +++ b/extension/js/content_scripts/webmail/webmail.ts @@ -17,8 +17,7 @@ declare global { Catch.try(async () => { // when we support more webmails, there will be if/else here to figure out which one to run - const browserName = Catch.browser().name; - if (browserName === 'thunderbird') { + if (Catch.isThunderbirdMail()) { await new ThunderbirdWebmailStartup().asyncConstructor(); } else { await new GmailWebmailStartup().asyncConstructor(); diff --git a/extension/js/service_worker/background.ts b/extension/js/service_worker/background.ts index 217c0197d5d..bda3603c0a7 100644 --- a/extension/js/service_worker/background.ts +++ b/extension/js/service_worker/background.ts @@ -70,8 +70,11 @@ console.info('background.js service worker starting'); await BgHandlers.updateUninstallUrl({}); injectFcIntoWebmail(); - // Thunderbird event handlers if (Catch.isThunderbirdMail()) { - BrowserMsg.thunderbirdSecureComposeHandler(); + BgHandlers.thunderbirdSecureComposeHandler(); + await BgHandlers.thunderbirdContentScriptRegistration(); + BrowserMsg.bgAddListener('thunderbirdGetCurrentUser', BgHandlers.thunderbirdGetCurrentUserHandler); + BrowserMsg.bgAddListener('thunderbirdMsgGet', BgHandlers.thunderbirdMsgGetHandler); + BrowserMsg.bgAddListener('thunderbirdOpenPassphraseDialog', BgHandlers.thunderbirdOpenPassphraseDialog); } })().catch(Catch.reportErr); diff --git a/extension/js/service_worker/bg-handlers.ts b/extension/js/service_worker/bg-handlers.ts index b8ce531465f..d33378ea5a6 100644 --- a/extension/js/service_worker/bg-handlers.ts +++ b/extension/js/service_worker/bg-handlers.ts @@ -12,6 +12,7 @@ import { ExpirationCache } from '../common/core/expiration-cache.js'; import { GoogleOAuth } from '../common/api/authentication/google/google-oauth.js'; import { AcctStore } from '../common/platform/store/acct-store.js'; import { ConfiguredIdpOAuth } from '../common/api/authentication/configured-idp-oauth.js'; +import { Url, Str } from '../common/core/common.js'; export class BgHandlers { public static openSettingsPageHandler: Bm.AsyncResponselessHandler = async ({ page, path, pageUrlParams, addNewAcct, acctEmail }: Bm.Settings) => { @@ -112,4 +113,72 @@ export class BgHandlers { } }); }); + + public static thunderbirdSecureComposeHandler = () => { + const handleClickEvent = async (tabId: number, acctEmail: string, thunderbirdMsgId: number, composeMethod?: messenger.compose._ComposeDetailsType) => { + const accountEmails = await GlobalStore.acctEmailsGet(); + const useFullScreenSecureCompose = (await messenger.windows.getCurrent()).type === 'messageCompose'; + composeMethod = composeMethod === 'reply' || composeMethod === 'forward' ? composeMethod : undefined; + if (accountEmails.length !== 0) { + await BgUtils.openExtensionTab( + Url.create('/chrome/settings/inbox/inbox.htm', { acctEmail, useFullScreenSecureCompose, thunderbirdMsgId, composeMethod }) + ); + await messenger.tabs.remove(tabId); + } else { + await BgUtils.openExtensionTab(Url.create('/chrome/settings/initial.htm', {})); + } + }; + messenger.composeAction.onClicked.addListener(async tab => { + const messageDetails = await messenger.compose.getComposeDetails(Number(tab.id)); + const composeMethod = messageDetails.type; + const msgId = Number(messageDetails.relatedMessageId); + const acctEmail = Str.parseEmail(messageDetails.from as string).email; + if (acctEmail) await handleClickEvent(Number(tab.id), acctEmail, msgId, composeMethod); + }); + messenger.messageDisplayAction.onClicked.addListener(async tab => { + const tabId = Number(tab.id); + const messageDetails = await messenger.messageDisplay.getDisplayedMessage(tabId); + if (messageDetails) { + const msgId = messageDetails.id; + const accountId = messageDetails?.folder?.accountId || ''; + const acctEmail = (await messenger.accounts.get(accountId))?.name || ''; + await handleClickEvent(tabId, acctEmail, msgId); + } + }); + }; + + public static thunderbirdContentScriptRegistration = async () => { + const contentScriptGroups = chrome.runtime.getManifest().content_scripts ?? []; // we know it's in the manifest + // sweetalert2.js throws error in Thunderbird environment + const files = contentScriptGroups[0].js?.filter(url => !url.includes('sweetalert2')).map(url => url.replace(/moz-extension:\/\/[^/]+\//, './')) ?? []; + await messenger.messageDisplayScripts.register({ + js: files.map(file => ({ file })), + css: [{ file: './css/cryptup.css' }], + }); + }; + + public static thunderbirdGetCurrentUserHandler = async (): Promise => { + const [tab] = await messenger.tabs.query({ active: true, currentWindow: true }); + if (tab.id) { + const messageDetails = await messenger.messageDisplay.getDisplayedMessage(tab.id); + const accountId = messageDetails?.folder?.accountId || ''; + return (await messenger.accounts.get(accountId))?.name; + } + return; + }; + + public static thunderbirdMsgGetHandler = async (): Promise => { + const [tab] = await messenger.tabs.query({ active: true, currentWindow: true }); + if (tab.id) { + const message = await messenger.messageDisplay.getDisplayedMessage(tab.id); + if (message?.id) { + return await messenger.messages.getFull(Number(message.id)); + } + } + return; + }; + + public static thunderbirdOpenPassphraseDialog = async (r: Bm.ThunderbirdOpenPassphraseDialog): Promise => { + await BgUtils.openExtensionTab(`chrome/elements/passphrase.htm?type=message&parentTabId=0&acctEmail=${r.acctEmail}&longids=${r.longids}`, true); + }; } diff --git a/tooling/build-types-and-manifests.ts b/tooling/build-types-and-manifests.ts index 29969f3f034..0d52a9bd4fb 100644 --- a/tooling/build-types-and-manifests.ts +++ b/tooling/build-types-and-manifests.ts @@ -62,7 +62,7 @@ addManifest( (manifest.browser_action as messenger._manifest._WebExtensionManifestAction).default_title = 'FlowCrypt'; manifest.name = 'FlowCrypt Encryption for Thunderbird'; manifest.description = 'Simple end-to-end encryption to secure email and attachments on Thunderbird'; - manifest.permissions = [...(manifestV3.permissions ?? []), 'compose', 'messagesRead', 'accountsRead']; + manifest.permissions = [...(manifestV3.permissions ?? []), 'compose', 'messagesRead', 'messagesUpdate', 'messagesModify', 'accountsRead']; manifest.compose_action = { default_title: 'Secure Compose', // eslint-disable-line @typescript-eslint/naming-convention default_icon: '/img/logo/flowcrypt-logo-64-64.png', // eslint-disable-line @typescript-eslint/naming-convention