diff --git a/services/web/app/src/Features/Email/EmailBuilder.js b/services/web/app/src/Features/Email/EmailBuilder.js index 13c3958263..cf67cf6831 100644 --- a/services/web/app/src/Features/Email/EmailBuilder.js +++ b/services/web/app/src/Features/Email/EmailBuilder.js @@ -105,7 +105,7 @@ ${content.message(opts, true).join('\r\n\r\n')} Regards, The ${settings.appName} Team - ${settings.siteUrl}\ -` + ` }, compiledTemplate(opts) { return NoCTAEmailBody({ @@ -244,27 +244,54 @@ templates.confirmEmail = ctaTemplate({ templates.projectInvite = ctaTemplate({ subject(opts) { - return `${_.escape( - SpamSafe.safeProjectName(opts.project.name, 'New Project') - )} - shared by ${_.escape( - SpamSafe.safeEmail(opts.owner.email, 'a collaborator') - )}` + const safeName = SpamSafe.isSafeProjectName(opts.project.name) + const safeEmail = SpamSafe.isSafeEmail(opts.owner.email) + + if (safeName && safeEmail) { + return `"${_.escape(opts.project.name)}" — shared by ${_.escape( + opts.owner.email + )}` + } + if (safeName) { + return `${settings.appName} project shared with you — "${_.escape( + opts.project.name + )}"` + } + if (safeEmail) { + return `${_.escape(opts.owner.email)} shared an ${ + settings.appName + } project with you` + } + + return `An ${settings.appName} project has been shared with you` }, title(opts) { - return `${_.escape( - SpamSafe.safeProjectName(opts.project.name, 'New Project') - )} - shared by ${_.escape( - SpamSafe.safeEmail(opts.owner.email, 'a collaborator') - )}` + return 'Project Invite' }, - message(opts) { - return [ - `${_.escape( - SpamSafe.safeEmail(opts.owner.email, 'a collaborator') - )} wants to share ${_.escape( - SpamSafe.safeProjectName(opts.project.name, 'a new project') - )} with you.`, - ] + greeting(opts) { + return '' + }, + message(opts, isPlainText) { + // build message depending on spam-safe variables + var message = [`You have been invited to an ${settings.appName} project.`] + + if (SpamSafe.isSafeProjectName(opts.project.name)) { + message.push('
Project:') + message.push(`${_.escape(opts.project.name)}`) + } + + if (SpamSafe.isSafeEmail(opts.owner.email)) { + message.push(`
Shared by:`) + message.push(`${_.escape(opts.owner.email)}`) + } + + if (message.length === 1) { + message.push('
Please view the project to find out more.') + } + + return message.map(m => { + return EmailMessageHelper.cleanHTML(m, isPlainText) + }) }, ctaText() { return 'View project' diff --git a/services/web/test/unit/src/Email/EmailBuilderTests.js b/services/web/test/unit/src/Email/EmailBuilderTests.js index c65a0be6c1..d06de2f09f 100644 --- a/services/web/test/unit/src/Email/EmailBuilderTests.js +++ b/services/web/test/unit/src/Email/EmailBuilderTests.js @@ -63,18 +63,44 @@ describe('EmailBuilder', function () { }) describe('when someone is up to no good', function () { - beforeEach(function () { + it('should not contain the project name at all if unsafe', function () { this.opts.project.name = "" this.email = this.EmailBuilder.buildEmail('projectInvite', this.opts) + expect(this.email.html).to.not.contain('evilsite.com') + expect(this.email.subject).to.not.contain('evilsite.com') + + // but email should appear + expect(this.email.html).to.contain(this.opts.owner.email) + expect(this.email.subject).to.contain(this.opts.owner.email) }) - it('should not contain unescaped html in the html part', function () { - expect(this.email.html).to.contain('New Project') + it('should not contain the inviter email at all if unsafe', function () { + this.opts.owner.email = + 'verylongemailaddressthatwillfailthecheck@longdomain.domain' + this.email = this.EmailBuilder.buildEmail('projectInvite', this.opts) + + expect(this.email.html).to.not.contain(this.opts.owner.email) + expect(this.email.subject).to.not.contain(this.opts.owner.email) + + // but title should appear + expect(this.email.html).to.contain(this.opts.project.name) + expect(this.email.subject).to.contain(this.opts.project.name) }) - it('should not have undefined in it', function () { - this.email.html.indexOf('undefined').should.equal(-1) - this.email.subject.indexOf('undefined').should.equal(-1) + it('should handle both email and title being unsafe', function () { + this.opts.project.name = "" + this.opts.owner.email = + 'verylongemailaddressthatwillfailthecheck@longdomain.domain' + this.email = this.EmailBuilder.buildEmail('projectInvite', this.opts) + + expect(this.email.html).to.not.contain('evilsite.com') + expect(this.email.subject).to.not.contain('evilsite.com') + expect(this.email.html).to.not.contain(this.opts.owner.email) + expect(this.email.subject).to.not.contain(this.opts.owner.email) + + expect(this.email.html).to.contain( + 'Please view the project to find out more' + ) }) }) }) @@ -84,7 +110,7 @@ describe('EmailBuilder', function () { this.opts = { to: 'bob@joe.com', first_name: 'bob', - owner: { + newOwner: { email: 'sally@hally.com', }, inviteUrl: 'http://example.com/invite', @@ -93,12 +119,14 @@ describe('EmailBuilder', function () { name: 'come buy my product at http://notascam.com', }, } - this.email = this.EmailBuilder.buildEmail('projectInvite', this.opts) + this.email = this.EmailBuilder.buildEmail( + 'ownershipTransferConfirmationPreviousOwner', + this.opts + ) }) it('should replace spammy project name', function () { - this.email.html.indexOf('a new project').should.not.equal(-1) - this.email.subject.indexOf('New Project').should.not.equal(-1) + this.email.html.indexOf('your project').should.not.equal(-1) }) })