Merge pull request #34508 from overleaf/kc-email-admin-domain-reverification-failed

[web] email group admin when domain reverification failed

GitOrigin-RevId: d885f26b1b39184c25133f70024a72af66fa2d69
This commit is contained in:
Kate Crichton
2026-06-26 08:07:01 +00:00
committed by Copybot
parent 907f5a969a
commit 9034e81c9b
2 changed files with 126 additions and 0 deletions
@@ -648,6 +648,43 @@ templates.surrenderAccountForManagedUsers = ctaTemplate({
},
})
templates.domainReverificationFailed = ctaTemplate({
subject(opts) {
if (opts.capturedByGroup) {
return `Action needed: re-verify ${opts.domain} to keep adding users automatically`
}
return `${opts.domain} needs re-verifying`
},
title(opts) {
const domain = _.escape(opts.domain)
if (opts.capturedByGroup) {
return `Action needed: re-verify ${domain}`
}
return `${domain} needs re-verifying`
},
message(opts) {
const domain = _.escape(opts.domain)
if (opts.capturedByGroup) {
const date = moment(opts.gracePeriodEndDate).format('MMMM D, YYYY')
return [
`We weren't able to verify your domain ${domain} during our latest check, so its verification has lapsed.`,
`Right now, anyone with an email address at ${domain} is added to your group automatically. If the domain isn't re-verified by ${date}, we'll stop adding them. Existing members aren't affected.`,
`To fix this, check that your DNS TXT record still matches the one shown in your group settings. Once it's in place, we'll re-verify the domain automatically.`,
]
}
return [
`We weren't able to verify your domain ${domain} during our latest check, so it's no longer verified.`,
`To re-verify it, check that your DNS TXT record still matches the one shown in your group settings. Once it's in place, we'll verify it automatically.`,
]
},
ctaURL(opts) {
return opts.domainSettingsUrl
},
ctaText() {
return 'Go to group settings'
},
})
templates.testEmail = ctaTemplate({
subject() {
return `A Test Email from ${settings.appName}`
@@ -1128,6 +1128,95 @@ describe('EmailBuilder', function () {
)
})
})
describe('domainReverificationFailed', function () {
beforeEach(function (ctx) {
ctx.opts = {
to: 'admin@example.com',
domain: 'example.com',
domainSettingsUrl:
'https://www.overleaf.com/manage/groups/abc/settings',
}
})
describe('when the domain is captured by the group', function () {
beforeEach(function (ctx) {
ctx.opts.capturedByGroup = true
// local-time date so moment's local formatting is timezone-safe
ctx.opts.gracePeriodEndDate = new Date(2026, 5, 30)
ctx.email = ctx.EmailBuilder.buildEmail(
'domainReverificationFailed',
ctx.opts
)
})
it('builds html and text without undefined', function (ctx) {
expect(ctx.email.html).to.exist
expect(ctx.email.text).to.exist
expect(ctx.email.html.indexOf('undefined')).to.equal(-1)
expect(ctx.email.subject.indexOf('undefined')).to.equal(-1)
})
it('leads with action needed and the domain in the subject', function (ctx) {
expect(ctx.email.subject).to.equal(
'Action needed: re-verify example.com to keep adding users automatically'
)
})
it('includes the grace period deadline', function (ctx) {
expect(ctx.email.html).to.contain('June 30, 2026')
expect(ctx.email.html).to.contain("we'll stop adding them")
})
it('links to the domain settings page', function (ctx) {
expect(ctx.email.html).to.contain(ctx.opts.domainSettingsUrl)
expect(ctx.email.text).to.contain(ctx.opts.domainSettingsUrl)
})
})
describe('when the domain is not captured by the group', function () {
beforeEach(function (ctx) {
ctx.opts.capturedByGroup = false
ctx.email = ctx.EmailBuilder.buildEmail(
'domainReverificationFailed',
ctx.opts
)
})
it('uses the lower-stakes subject', function (ctx) {
expect(ctx.email.subject).to.equal('example.com needs re-verifying')
})
it('does not mention a deadline or capture', function (ctx) {
expect(ctx.email.html).to.not.contain("we'll stop adding them")
expect(ctx.email.html.indexOf('undefined')).to.equal(-1)
})
it('still links to the domain settings page', function (ctx) {
expect(ctx.email.html).to.contain(ctx.opts.domainSettingsUrl)
})
})
// The cta-email title and body are lodash templates, where <%= %>
// interpolates without escaping (the opposite of EJS), so the domain must
// be escaped in both title() and message(). The domain regex makes such
// input impossible in practice, but this guards the escaping against being
// applied zero or two times. (a&b.example.com appears in both the title and
// the body, so we expect two single-escaped occurrences and none
// double-escaped.)
it('escapes the domain exactly once in the title and body', function (ctx) {
ctx.opts.capturedByGroup = false
ctx.opts.domain = 'a&b.example.com'
ctx.email = ctx.EmailBuilder.buildEmail(
'domainReverificationFailed',
ctx.opts
)
expect(ctx.email.html.split('a&amp;b.example.com')).to.have.lengthOf(
3
)
expect(ctx.email.html).to.not.contain('a&amp;amp;b.example.com')
})
})
})
})
})