diff --git a/.gitmodules b/.gitmodules index b9baa1e59..8f5340bb9 100644 --- a/.gitmodules +++ b/.gitmodules @@ -1,6 +1,3 @@ [submodule "src/assets/theme"] path = src/assets/theme - url = https://github.com/CTemplar/webclient-themes.git -[submodule "assets/theme"] - path = assets/theme - url = https://github.com/CTemplar/webclient-themes.git + url = https://github.com/CTemplar/webclient-themes.git \ No newline at end of file diff --git a/electron-main.ts b/electron-main.ts index 0aa689f86..bf8cf08c7 100644 --- a/electron-main.ts +++ b/electron-main.ts @@ -51,6 +51,7 @@ function createWindow() { y: mainWindowState.y, width: mainWindowState.width, height: mainWindowState.height, + autoHideMenuBar: true, webPreferences: { nodeIntegration: true, allowRunningInsecureContent: serve ? true : false, diff --git a/package-lock.json b/package-lock.json index 6fa7eb459..807a4355b 100644 --- a/package-lock.json +++ b/package-lock.json @@ -11637,9 +11637,33 @@ "optional": true }, "node-fetch": { - "version": "2.6.1", - "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.1.tgz", - "integrity": "sha512-V4aYg89jEoVRxRb2fJdAg8FHvI7cEyYdVAh94HH0UIK8oJxUfkjlDQN9RbMx+bEjP7+ggMiFRprSti032Oipxw==" + "version": "2.6.7", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.7.tgz", + "integrity": "sha512-ZjMPFEfVx5j+y2yF35Kzx5sF7kDzxuDj6ziH4FFbOp87zKDZNx8yExJIb05OGF4Nlt9IHFIMBkRl41VdvcNdbQ==", + "requires": { + "whatwg-url": "^5.0.0" + }, + "dependencies": { + "tr46": { + "version": "0.0.3", + "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", + "integrity": "sha1-gYT9NH2snNwYWZLzpmIuFLnZq2o=" + }, + "webidl-conversions": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz", + "integrity": "sha1-JFNCdeKnvGvnvIZhHMFq4KVlSHE=" + }, + "whatwg-url": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz", + "integrity": "sha1-lmRU6HZUYuN2RNNib2dCzotwll0=", + "requires": { + "tr46": "~0.0.3", + "webidl-conversions": "^3.0.0" + } + } + } }, "node-fetch-npm": { "version": "2.0.4", diff --git a/package.json b/package.json index 10fabcf3d..8ff776c08 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "ctemplar", - "version": "2.5.63", + "version": "2.5.64", "license": "Apache", "main": "electron-main.js", "description": "Angular webclient (with Linux, macOS and Windows desktop clients) for CTemplar's encrypted email service.", diff --git a/src/app/mail/mail-detail/mail-detail.component.ts b/src/app/mail/mail-detail/mail-detail.component.ts index 76029d7b8..ae702542c 100644 --- a/src/app/mail/mail-detail/mail-detail.component.ts +++ b/src/app/mail/mail-detail/mail-detail.component.ts @@ -868,12 +868,17 @@ export class MailDetailComponent implements OnInit, OnDestroy { newMail.parent = parentId; newMail.content = this.getMessageHistory(previousMails); newMail.htmlQuotedMailContent = newMail.content; - newMail.mailbox = this.mailboxes.find(mailbox => allRecipients.has(mailbox.email))?.id; + // Set drafts's mailbox from the list of available mailboxes + // use the mailbox that is part of the current drafts' receivers giving priority to the current mailbox + const selectedMailbox = allRecipients.has(this.currentMailbox.email) + ? this.currentMailbox + : this.mailboxes.find(mailbox => allRecipients.has(mailbox.email)); + newMail.mailbox = selectedMailbox?.id; newMail.is_html = mail.is_html; if (mail.reply_to && mail.reply_to.length > 0) { newMail.receiver = mail.reply_to; } else { - let newReceivers; + let newReceivers: Set; if (mainReply && mail.children?.length > 0) { // set reciever with it with the reciever and sender of latest child that is not on Trash if (this.isShowTrashRelatedChildren) { @@ -886,10 +891,10 @@ export class MailDetailComponent implements OnInit, OnDestroy { ...mail.children[mail.children.length - 1].cc, ...mail.children[mail.children.length - 1].bcc, ]); - newReceivers.delete(this.currentMailbox?.email); + newReceivers.delete(selectedMailbox?.email); } else { // If it is received email from the other, only sender would be set as receiver - newReceivers = [mail.children[mail.children.length - 1].sender]; + newReceivers = new Set([mail.children[mail.children.length - 1].sender]); } } else { for (let childIndex = mail.children.length; childIndex > 0; childIndex -= 1) { @@ -914,10 +919,10 @@ export class MailDetailComponent implements OnInit, OnDestroy { ...mail.children[childIndex - 1].cc, ...mail.children[childIndex - 1].bcc, ]); - newReceivers.delete(this.currentMailbox?.email); + newReceivers.delete(selectedMailbox?.email); break; } else { - newReceivers = [mail.children[childIndex - 1].sender]; + newReceivers = new Set([mail.children[childIndex - 1].sender]); break; } } @@ -925,10 +930,11 @@ export class MailDetailComponent implements OnInit, OnDestroy { } } else if (this.mailboxes.some(mailbox => mail.sender === mailbox.email)) { newReceivers = new Set([...mail.receiver, mail.sender, ...mail.cc, ...mail.bcc]); - newReceivers.delete(this.currentMailbox?.email); + newReceivers.delete(selectedMailbox?.email); } else { - newReceivers = [mail.sender]; + newReceivers = new Set([mail.sender]); } + newReceivers.delete(selectedMailbox?.email); newMail.receiver = newReceivers ? [...newReceivers] : []; } this.selectedMailToInclude = mail; @@ -952,7 +958,6 @@ export class MailDetailComponent implements OnInit, OnDestroy { parentId: this.mail.id, content: this.getMessageHistory(previousMails), htmlQuotedMailContent: this.getMessageHistory(previousMails), - selectedMailbox: this.mailboxes.find(mailbox => mail.receiver.includes(mailbox.email)), }; let parentId = this.mail.id; if (!this.isConversationView && this.mail.parent) { @@ -962,10 +967,9 @@ export class MailDetailComponent implements OnInit, OnDestroy { newMail.parent = parentId; newMail.content = this.getMessageHistory(previousMails); newMail.htmlQuotedMailContent = newMail.content; - newMail.mailbox = this.mailboxes.find(mailbox => mail.receiver.includes(mailbox.email))?.id; newMail.is_html = mail.is_html; - let newReceivers; + let newReceivers: Set; if (mainReply && mail.children?.length > 0) { // set reciever with it with the reciever and sender of latest child that is not on Trash if (this.isShowTrashRelatedChildren) { @@ -975,7 +979,6 @@ export class MailDetailComponent implements OnInit, OnDestroy { ...mail.children[mail.children.length - 1].cc, ...mail.children[mail.children.length - 1].bcc, ]); - newReceivers.delete(this.currentMailbox?.email); } else { for (let childIndex = mail.children.length; childIndex > 0; childIndex -= 1) { if ( @@ -989,16 +992,23 @@ export class MailDetailComponent implements OnInit, OnDestroy { ...mail.children[childIndex - 1].cc, ...mail.children[childIndex - 1].bcc, ]); - newReceivers.delete(this.currentMailbox?.email); break; } } } } else { newReceivers = new Set([...mail.receiver, mail.sender, ...mail.cc, ...mail.bcc]); - newReceivers.delete(this.currentMailbox?.email); } + + // Set drafts's mailbox from the list of available mailboxes + // use the mailbox that is part of the current drafts' receivers giving priority to the current mailbox + const selectedMailbox = newReceivers.has(this.currentMailbox.email) + ? this.currentMailbox + : this.mailboxes.find(mailbox => newReceivers.has(mailbox.email)); + this.composeMailData[mail.id].selectedMailbox = selectedMailbox; + newReceivers.delete(selectedMailbox?.email); newMail.receiver = newReceivers ? [...newReceivers] : []; + newMail.mailbox = selectedMailbox?.id; this.selectedMailToInclude = mail; newMail.last_action = MailAction.REPLY_ALL; diff --git a/src/app/mail/mail-sidebar/compose-mail/compose-mail.component.scss b/src/app/mail/mail-sidebar/compose-mail/compose-mail.component.scss index a7ae769fe..6e0a91aa7 100644 --- a/src/app/mail/mail-sidebar/compose-mail/compose-mail.component.scss +++ b/src/app/mail/mail-sidebar/compose-mail/compose-mail.component.scss @@ -133,3 +133,7 @@ textarea:disabled { ::ng-deep pre { overflow: hidden !important; } + +.mail-label { + white-space: nowrap; +} diff --git a/src/app/mail/mail-sidebar/compose-mail/compose-mail.component.ts b/src/app/mail/mail-sidebar/compose-mail/compose-mail.component.ts index cc92d5566..8cd9cab54 100644 --- a/src/app/mail/mail-sidebar/compose-mail/compose-mail.component.ts +++ b/src/app/mail/mail-sidebar/compose-mail/compose-mail.component.ts @@ -15,7 +15,7 @@ import { NgbDateStruct, NgbModal, NgbModalRef } from '@ng-bootstrap/ng-bootstrap import { Store } from '@ngrx/store'; import * as parseEmail from 'email-addresses'; import { of, Subject, Subscription } from 'rxjs'; -import { debounceTime, filter, finalize, pairwise } from 'rxjs/operators'; +import { debounceTime, filter, finalize, pairwise, withLatestFrom } from 'rxjs/operators'; import { UntilDestroy, untilDestroyed } from '@ngneat/until-destroy'; import * as xss from 'xss'; @@ -59,7 +59,7 @@ import { BlackList, WhiteList, } from '../../../store/datatypes'; -import { Attachment, EncryptionNonCTemplar, Mail, Mailbox, MailFolderType } from '../../../store/models'; +import { Attachment, EncryptionNonCTemplar, Mail, Mailbox, MailFolderType, ReceiversInfo } from '../../../store/models'; import { AutocryptProcessService, MailService, SharedService, getCryptoRandom } from '../../../store/services'; import { DateTimeUtilService } from '../../../store/services/datetime-util.service'; import { OpenPgpService } from '../../../store/services/openpgp.service'; @@ -255,6 +255,8 @@ export class ComposeMailComponent implements OnInit, AfterViewInit, OnDestroy { bccIsPasted = false; + receiversInfo: ReceiversInfo[] = []; + private isMailSent = false; private isSavedInDraft = false; @@ -431,12 +433,12 @@ export class ComposeMailComponent implements OnInit, AfterViewInit, OnDestroy { }); /** - * Get user's contacts from store. + * Get user's contacts from store (contact and mail boxes) . */ this.store .select((state: AppState) => state.contacts) - .pipe(untilDestroyed(this)) - .subscribe((contactsState: ContactsState) => { + .pipe(untilDestroyed(this), withLatestFrom(this.store.select(state => state.mailboxes))) + .subscribe(([contactsState, mailBoxesState]: [ContactsState, MailBoxesState]) => { this.contacts = []; if (contactsState.emailContacts === undefined) { for (const x of contactsState.contacts) { @@ -455,6 +457,13 @@ export class ComposeMailComponent implements OnInit, AfterViewInit, OnDestroy { }); } } + mailBoxesState?.mailboxes?.forEach(m => { + this.contacts.push({ + name: m.display_name, + email: m.email, + display: EmailFormatPipe.transformToFormattedEmail(m.email, m.display_name), + }); + }); this.clonedContacts = contactsState.emailContacts === undefined ? contactsState.contacts : contactsState.emailContacts; this.contactsState = contactsState; @@ -633,10 +642,10 @@ export class ComposeMailComponent implements OnInit, AfterViewInit, OnDestroy { // replace the plain text quoted mail with the one we saved earlier in htmlQuotedMailContent if (this.textModeSwitching && this.htmlQuotedMailContent) { this.textModeSwitching = false; - let content = this.formatContent(this.mailData.content); + let { content } = this.mailData; const quoteIndex = content.indexOf('---------- Original Message ----------'); - content = content.slice(0, quoteIndex); - content = `${content}${this.htmlQuotedMailContent}`; + const currentContent = this.formatContent(content.slice(0, quoteIndex), true); + content = `${currentContent}${this.htmlQuotedMailContent}`; editor.setData(content); } else { editor.setData(this.formatContent(this.mailData.content)); @@ -686,7 +695,7 @@ export class ComposeMailComponent implements OnInit, AfterViewInit, OnDestroy { * @param content * @private */ - private formatContent(content: string) { + private formatContent(content: string, preserveNewLines = false) { if (this.draftMail?.is_html) { const allowedTags = new Set([ 'a', @@ -717,6 +726,9 @@ export class ComposeMailComponent implements OnInit, AfterViewInit, OnDestroy { 'i', 'blockquote', ]); + if (preserveNewLines) { + content = content.replace(/\n|\r|\r\n/g, '
'); // preserve newlines as
+ } // @ts-ignore const xssValue = xss(content, { onTag: (tag: string, html: string, options: any) => { @@ -1423,9 +1435,9 @@ export class ComposeMailComponent implements OnInit, AfterViewInit, OnDestroy { ); this.mailData.content = this.mailData.content.replace(new RegExp(`${previousSignature}$`), currentSignature); } else if (this.selectedMailbox.signature) { - this.mailData.content = `${this.mailData.content.trimEnd()}\n\n${this.getPlainText( + this.mailData.content = `\n\n${this.getPlainText( this.selectedMailbox.signature, - )}`; + )}${this.mailData.content.trimEnd()}`; } } @@ -1449,7 +1461,7 @@ export class ComposeMailComponent implements OnInit, AfterViewInit, OnDestroy { } else if (this.selectedMailbox && this.selectedMailbox.signature) { // add two lines and signature after message content with html format newSig = this.selectedMailbox.signature.slice(0, Math.max(0, this.selectedMailbox.signature.length)); - content = `${content}

 

${newSig}`; + content = `

 

${newSig}${content}`; this.isSignatureAdded = true; this.composerEditorInstance?.setData(content); } @@ -1959,6 +1971,7 @@ export class ComposeMailComponent implements OnInit, AfterViewInit, OnDestroy { localReceivers, this.usersKeys, ); + this.updateReceiversInfo(); if (localReceivers.length > 0) { for (const rec of localReceivers) { const keyInfo = this.sharedService.parseUserKey(this.usersKeys, rec); @@ -1990,34 +2003,17 @@ export class ComposeMailComponent implements OnInit, AfterViewInit, OnDestroy { // Set Editor style with encryption type // If all receiver is based on PGP Inline, Plain Text Editor // If PGP Mime or null, Do Nothing - const isPGPInline = localReceivers.every(rec => { - if (this.usersKeys.has(rec) && !this.usersKeys.get(rec).isFetching) { - const contactInfo: Contact = this.contactsState?.contacts.find((contact: Contact) => contact.email === rec); - if (contactInfo?.enabled_encryption && contactInfo?.encryption_type === PGPEncryptionType.PGP_INLINE) { - return true; - } - } - return false; - }); - const isPGPMime = localReceivers.every(rec => { - if (this.usersKeys.has(rec) && !this.usersKeys.get(rec).isFetching) { - const contactInfo: Contact = this.contactsState.contacts.find((contact: Contact) => contact.email === rec); - if (contactInfo?.enabled_encryption && contactInfo?.encryption_type === PGPEncryptionType.PGP_MIME) { - return true; - } - } - return false; - }); - - const isMixedContacts = this.isMixedContacts(localReceivers); + const pgpEncryptionType = this.getPGPEncryptionMethod(); + const isMixedContacts = this.isMixedContacts(); this.isMixedContacts$.next(isMixedContacts); - const pgpEncryptionType = isPGPInline - ? PGPEncryptionType.PGP_INLINE - : isPGPMime - ? PGPEncryptionType.PGP_MIME - : null; this.pgpEncryptionType = isMixedContacts ? null : pgpEncryptionType; + // if encryption is set to PGP_INLINE or PGP_MIME and we have at least one external with PGP enabled + this.draftMail.is_encrypted = this.pgpEncryptionType + ? !this.receiversInfo.some(c => c?.isExternal) + : !!this.pgpEncryptionType; + this.draftMail.encryption_type = this.pgpEncryptionType; + if (this.pgpEncryptionType === PGPEncryptionType.PGP_INLINE && this.draftMail?.is_html) { this.setHtmlEditor(false); } else if (this.pgpEncryptionType === PGPEncryptionType.PGP_MIME && !this.draftMail?.is_html) { @@ -2027,6 +2023,45 @@ export class ComposeMailComponent implements OnInit, AfterViewInit, OnDestroy { } } + private getPGPEncryptionMethod(): PGPEncryptionType { + // PGP_INLINE if, + // 1. we have internal contacts + external contacts, or just all external contacts. (TODO: or just all internal contacts) + // 2. and all the external contacts have encryption enabled and set to PGP_INLINE. + const internalContacts = this.receiversInfo.filter(rec => rec.isInternal); + const isPGPInline = this.receiversInfo.every(rec => { + if (rec?.enabled_encryption && rec?.encryption_type === PGPEncryptionType.PGP_INLINE) { + return true; + } + // not all receivers are internal and the contact without encryption from above block is internal, then go ahead with PGP_INLINE + if (internalContacts.length !== this.receiversInfo.length && rec.isInternal) { + return true; + } + return false; + }); + + // PGP_MIME if, + // 1. we have internal contacts + external contacts, or just all external contacts. (TODO: or just all internal contacts) + // 2. and all the external contacts have encryption enabled and set to PGP_MIME. + const isPGPMime = this.receiversInfo.every(rec => { + if (rec?.enabled_encryption && rec?.encryption_type === PGPEncryptionType.PGP_MIME) { + return true; + } + // not all receivers are internal and the contact without encryption from above block is internal, then go ahead with PGP_MIME + if (internalContacts.length !== this.receiversInfo.length && rec.isInternal) { + return true; + } + return false; + }); + + if (isPGPInline) { + return PGPEncryptionType.PGP_INLINE; + } + if (isPGPMime) { + return PGPEncryptionType.PGP_MIME; + } + return null; + } + setupMixedContactModal() { this.isMixedContacts$ .pipe( @@ -2043,29 +2078,28 @@ export class ComposeMailComponent implements OnInit, AfterViewInit, OnDestroy { }); } - isMixedContacts(localReceivers: string[]): boolean { - const contacts: any[] = localReceivers.map((rec: string) => { - const keyInfo = this.sharedService.parseUserKey(this.usersKeys, rec); - const contact = this.contactsState?.contacts?.find(x => x.email === rec); - return { keyInfo, contact }; - }); - + isMixedContacts(): boolean { // check if all are internal - const isAllInternal = contacts.every(c => c?.keyInfo?.isExistKey && c?.keyInfo?.isCTemplarKey); + const isAllInternal = this.receiversInfo.every(c => c?.isInternal); if (isAllInternal) return false; // check if all are internal + non-encrypted external - const isInternal_NonEncExternal = contacts.every( - c => (c?.keyInfo?.isExistKey && c?.keyInfo?.isCTemplarKey) || !c?.contact?.enabled_encryption, + const isInternal_NonEncExternal = this.receiversInfo.every( + c => c?.isInternal || (c?.isExternal && !c?.enabled_encryption), ); if (isInternal_NonEncExternal) return false; - const externalContacts = contacts.filter(c => !(c?.keyInfo?.isExistKey && c?.keyInfo?.isCTemplarKey)); + const externalContacts = this.receiversInfo.filter(c => c?.isExternal); + + // check if mix of internal and all external are same encryption type + const isAllExternalEncSameType = + externalContacts.every(c => c?.enabled_encryption && c?.encryption_type === PGPEncryptionType.PGP_INLINE) || + externalContacts.every(c => c?.enabled_encryption && c?.encryption_type === PGPEncryptionType.PGP_MIME); + if (isAllExternalEncSameType) return false; // check if both encrypted external + non-encrypted external are present const isEncExternal_NonEncExternal = - externalContacts.some(c => c?.contact?.enabled_encryption) && - externalContacts.some(c => !c?.contact?.enabled_encryption); + externalContacts.some(c => c?.enabled_encryption) && externalContacts.some(c => !c?.enabled_encryption); if (isEncExternal_NonEncExternal) return true; // check if all external are encrypted and different type @@ -2077,6 +2111,29 @@ export class ComposeMailComponent implements OnInit, AfterViewInit, OnDestroy { return false; } + updateReceiversInfo() { + const localReceivers: string[] = [ + ...this.mailData.receiver.map((receiver: any) => receiver.email), + ...this.mailData.cc.map((cc: any) => cc.email), + ...this.mailData.bcc.map((bcc: any) => bcc.email), + ]; + this.receiversInfo = localReceivers + .filter(rec => this.usersKeys.has(rec) && !this.usersKeys.get(rec).isFetching) + .map(rec => { + const keyInfo = this.sharedService.parseUserKey(this.usersKeys, rec); + const contact = this.contactsState?.contacts?.find(x => x.email === rec); + return { + localReceiver: rec, + isExistKey: keyInfo?.isExistKey, + isCTemplarKey: keyInfo?.isCTemplarKey, + isInternal: keyInfo?.isExistKey && keyInfo?.isCTemplarKey, + isExternal: !(keyInfo?.isExistKey && keyInfo?.isCTemplarKey), + enabled_encryption: contact?.enabled_encryption, + encryption_type: contact?.encryption_type, + }; + }); + } + // TODO should be moved to template getUserKeyFetchingStatus(email: string): boolean { return ( diff --git a/src/app/mail/mail.component.scss b/src/app/mail/mail.component.scss index e2125c6f2..feb094bd0 100644 --- a/src/app/mail/mail.component.scss +++ b/src/app/mail/mail.component.scss @@ -334,10 +334,10 @@ line-height: 1.25rem; text-align: center; opacity: 0; + transition: transform 0.4s; transition-property: opacity; transition-duration: 0.2s; transition-timing-function: linear; - transition: transform 0.4s; a { color: #fff; } @@ -388,9 +388,9 @@ } > .badge-hover-action { + transition: transform 0.4s; transition-duration: 0.2s; transition-timing-function: linear; - transition: transform 0.4s; } .mailbox-sidebar-nav-label { diff --git a/src/app/shared/config.ts b/src/app/shared/config.ts index 8a2fcffbf..9b13a056f 100644 --- a/src/app/shared/config.ts +++ b/src/app/shared/config.ts @@ -46,7 +46,7 @@ export function getEmailDomain(): string { } export const apiUrl = getBaseUrl(); -export const PRIMARY_DOMAIN = AppConfig.production ? 'ctemplar.com' : 'dev.ctemplar.net'; +export const PRIMARY_DOMAIN = AppConfig.production ? 'ctemplar.com' : 'dev.ctemplar.com'; export const PRIMARY_WEBSITE = 'https://ctemplar.com'; export const SENTRY_DSN = 'https://e768a553906d4f87bcb0419a151e36b0@o190614.ingest.sentry.io/5256284'; diff --git a/src/app/shared/services/dynamic-script-loader.service.ts b/src/app/shared/services/dynamic-script-loader.service.ts index 9acc6f222..e656d7ab7 100644 --- a/src/app/shared/services/dynamic-script-loader.service.ts +++ b/src/app/shared/services/dynamic-script-loader.service.ts @@ -5,6 +5,7 @@ import { AppConfig } from '../../../environments/environment'; interface Scripts { name: string; src: string; + sri?: string; } export const ScriptStore: Scripts[] = [{ name: 'stripe', src: 'https://js.stripe.com/v2/' }]; @@ -19,14 +20,23 @@ export class DynamicScriptLoaderService { constructor() { if (AppConfig.production) { - ScriptStore.push({ name: 'stripe-key', src: 'assets/js/stripe-key.js' }); + ScriptStore.push({ + name: 'stripe-key', + src: 'assets/js/stripe-key.js', + sri: 'sha384-aBGfLiD/BctqwvvHykCsbNdXCL0MRjUJKJs2bekbaz7fx8W9aw5gjsYvWrfjkfkY sha384-JDGkVN7k9z4zrfIU9vxsCnmRQmovcRLzQW5RgRyNwjxtyaQSGYYttwXnqE8HvmtZ', + }); } else { - ScriptStore.push({ name: 'stripe-key', src: 'assets/js/stripe-test-key.js' }); + ScriptStore.push({ + name: 'stripe-key', + src: 'assets/js/stripe-test-key.js', + sri: 'sha384-3ZBePXfjqgenQl/uMO2cCB5c3l8LpWDyz9AnBZb8k3Oh9YRv8OHSCoaGcwsxIF7Y sha384-eRbJtkTTlr+WrFY9Rzm8tcQOKkGgqHlk002ZR4S50s4CMM78iNIR/F/Sv38Opr02', + }); } ScriptStore.forEach((script: any) => { this.scripts[script.name] = { loaded: false, src: script.src, + sri: script.sri, }; }); } @@ -40,10 +50,15 @@ export class DynamicScriptLoaderService { loadScript(name: string) { return new Promise(resolve => { if (!this.scripts[name].loaded) { - const script = document.createElement('script'); + const script: HTMLScriptElement = document.createElement('script'); script.type = 'text/javascript'; script.src = this.scripts[name].src; script.id = name; + // Add Subresource Integrity (SRI) if found + if (this.scripts[name].sri) { + script.integrity = this.scripts[name].sri; + script.crossOrigin = 'anonymous'; + } script.addEventListener('load', () => { this.scripts[name].loaded = true; diff --git a/src/app/store/models/mail.model.ts b/src/app/store/models/mail.model.ts index 96970b677..8afe93bc6 100644 --- a/src/app/store/models/mail.model.ts +++ b/src/app/store/models/mail.model.ts @@ -185,6 +185,16 @@ export interface AdvancedSearchQueryParameters { size_operator?: string; } +export interface ReceiversInfo { + localReceiver: string; + isCTemplarKey: boolean; + isExistKey: boolean; + isInternal: boolean; + isExternal: boolean; + enabled_encryption: boolean; + encryption_type: PGPEncryptionType; +} + export function getMailFolderName(folderType: MailFolderType) { switch (folderType) { case MailFolderType.ALL_EMAILS: diff --git a/src/index.html b/src/index.html index d8d2f2c43..350978410 100644 --- a/src/index.html +++ b/src/index.html @@ -1,4 +1,4 @@ - + @@ -23,13 +23,28 @@ - + - + - +