Przeglądaj źródła

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

[web] email group admin when domain reverification failed

GitOrigin-RevId: d885f26b1b39184c25133f70024a72af66fa2d69
Kate Crichton 1 miesiąc temu
rodzic
commit
9034e81c9b

+ 37 - 0
services/web/app/src/Features/Email/EmailBuilder.mjs

@@ -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}`

+ 89 - 0
services/web/test/unit/src/Email/EmailBuilder.test.mjs

@@ -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')
+        })
+      })
     })
   })
 })