Просмотр исходного кода

Merge pull request #30835 from overleaf/ii-domain-capture-invite-token

[web] Create invite token when domain capture is disabled

GitOrigin-RevId: 193b5b8567c3b593c98f432568b47cc4e9e17339
Jessica Lawshe 8 месяцев назад
Родитель
Сommit
59dc237e53

+ 6 - 0
services/web/app/src/Features/Subscription/TeamInvitesController.mjs

@@ -1,3 +1,4 @@
+import crypto from 'node:crypto'
 import settings from '@overleaf/settings'
 import settings from '@overleaf/settings'
 import logger from '@overleaf/logger'
 import logger from '@overleaf/logger'
 import OError from '@overleaf/o-error'
 import OError from '@overleaf/o-error'
@@ -280,6 +281,11 @@ async function resendInvite(req, res, next) {
     )?.[0]
     )?.[0]
     acceptInviteUrl = `${settings.siteUrl}${samlInitPath}`
     acceptInviteUrl = `${settings.siteUrl}${samlInitPath}`
   } else {
   } else {
+    if (!currentInvite.token) {
+      currentInvite.token = crypto.randomBytes(32).toString('hex')
+      currentInvite.domainCapture = false
+      await subscription.save()
+    }
     acceptInviteUrl = `${settings.siteUrl}/subscription/invites/${currentInvite.token}/`
     acceptInviteUrl = `${settings.siteUrl}/subscription/invites/${currentInvite.token}/`
   }
   }
 
 

+ 4 - 1
services/web/app/src/Features/Subscription/TeamInvitesHandler.mjs

@@ -233,8 +233,11 @@ async function _createInvite(subscription, email, inviter, auditLog) {
   let invite = subscription.teamInvites.find(invite => invite.email === email)
   let invite = subscription.teamInvites.find(invite => invite.email === email)
 
 
   if (invite) {
   if (invite) {
-    invite = invite.toObject()
     invite.sentAt = new Date()
     invite.sentAt = new Date()
+    if (!invite.token && !domainCaptureEnabled) {
+      invite.token = crypto.randomBytes(32).toString('hex')
+      invite.domainCapture = false
+    }
   } else {
   } else {
     if (domainCaptureEnabled) {
     if (domainCaptureEnabled) {
       invite = {
       invite = {

+ 34 - 1
services/web/test/unit/src/Subscription/TeamInvitesController.test.mjs

@@ -281,8 +281,12 @@ describe('TeamInvitesController', function () {
   describe('resendInvite', function () {
   describe('resendInvite', function () {
     const email = 'user@example.com'
     const email = 'user@example.com'
     const initPath = '/saml/ukamf/init?group_id=12345'
     const initPath = '/saml/ukamf/init?group_id=12345'
+    const token = 'token123'
     beforeEach(function (ctx) {
     beforeEach(function (ctx) {
-      ctx.subscription = { teamInvites: [{ email }], populate: sinon.stub() }
+      ctx.subscription = {
+        teamInvites: [{ email, token }],
+        populate: sinon.stub(),
+      }
       ctx.req = {
       ctx.req = {
         entity: ctx.subscription,
         entity: ctx.subscription,
         body: {
         body: {
@@ -330,5 +334,34 @@ describe('TeamInvitesController', function () {
         })
         })
       })
       })
     })
     })
+
+    describe('when invite was created with domain capture enabled but domain capture is now disabled', function () {
+      beforeEach(function (ctx) {
+        ctx.subscription = {
+          teamInvites: [
+            { email, inviterName: 'Test Inviter', domainCapture: true },
+          ],
+          populate: sinon.stub(),
+          save: sinon.stub().resolves(),
+        }
+        ctx.req.entity = ctx.subscription
+        ctx.req.entity.domainCaptureEnabled = false
+      })
+
+      it('generates a token and saves the subscription', async function (ctx) {
+        await new Promise(resolve => {
+          const res = new MockResponse(vi)
+          res.callback = () => {
+            expect(ctx.subscription.teamInvites[0].token).to.be.a('string')
+            expect(ctx.subscription.teamInvites[0].token).to.have.length(64)
+            sinon.assert.calledOnce(ctx.subscription.save)
+            res.statusCode.should.equal(200)
+            resolve()
+          }
+
+          ctx.Controller.resendInvite(ctx.req, res, ctx.next)
+        })
+      })
+    })
   })
   })
 })
 })

+ 36 - 0
services/web/test/unit/src/Subscription/TeamInvitesHandler.test.mjs

@@ -498,6 +498,42 @@ describe('TeamInvitesHandler', function () {
         })
         })
       })
       })
     })
     })
+
+    describe('when invite exists without token (created with domain capture) and domain capture is now disabled', function () {
+      it('generates a token for the existing invite', async function (ctx) {
+        const existingInvite = {
+          email: 'user@example.com',
+          inviterName: 'Daenerys Targaryen (daenerys@example.com)',
+          domainCapture: true,
+          sentAt: new Date(),
+        }
+        ctx.subscription.teamInvites = [existingInvite]
+        ctx.subscription.domainCaptureEnabled = false
+
+        const invite = await ctx.TeamInvitesHandler.promises.createInvite(
+          ctx.manager._id,
+          ctx.subscription,
+          'user@example.com'
+        )
+
+        expect(invite.token).to.eq(ctx.newToken)
+        expect(invite.domainCapture).to.be.false
+        expect(invite.email).to.eq('user@example.com')
+
+        sinon.assert.calledOnce(ctx.subscription.save)
+
+        ctx.EmailHandler.promises.sendEmail
+          .calledWith(
+            'verifyEmailToJoinTeam',
+            sinon.match({
+              to: 'user@example.com',
+              inviter: ctx.manager,
+              acceptInviteUrl: `http://example.com/subscription/invites/${ctx.newToken}/`,
+            })
+          )
+          .should.equal(true)
+      })
+    })
   })
   })
 
 
   describe('importInvite', function () {
   describe('importInvite', function () {