mirror of
https://github.com/overleaf/overleaf.git
synced 2024-11-21 20:47:08 -05:00
Merge pull request #20561 from overleaf/rh-duplicate-invite
[web] Revoke invite after joining project via link sharing GitOrigin-RevId: 5071c9fbb226e5eef8c3e5c82991da73529d7396
This commit is contained in:
parent
d6de6da781
commit
7d53b3e4a8
5 changed files with 121 additions and 6 deletions
|
@ -187,7 +187,6 @@ const CollaboratorsInviteController = {
|
|||
)
|
||||
res.json({ invite })
|
||||
},
|
||||
|
||||
async revokeInvite(req, res) {
|
||||
const projectId = req.params.Project_id
|
||||
const inviteId = req.params.invite_id
|
||||
|
@ -404,6 +403,9 @@ module.exports = {
|
|||
getAllInvites: expressify(CollaboratorsInviteController.getAllInvites),
|
||||
inviteToProject: expressify(CollaboratorsInviteController.inviteToProject),
|
||||
revokeInvite: expressify(CollaboratorsInviteController.revokeInvite),
|
||||
revokeInviteForUser: expressify(
|
||||
CollaboratorsInviteController.revokeInviteForUser
|
||||
),
|
||||
generateNewInvite: expressify(
|
||||
CollaboratorsInviteController.generateNewInvite
|
||||
),
|
||||
|
|
|
@ -3,6 +3,7 @@ const { ProjectInvite } = require('../../models/ProjectInvite')
|
|||
const logger = require('@overleaf/logger')
|
||||
const CollaboratorsEmailHandler = require('./CollaboratorsEmailHandler')
|
||||
const CollaboratorsHandler = require('./CollaboratorsHandler')
|
||||
const CollaboratorsInviteGetter = require('./CollaboratorsInviteGetter')
|
||||
const CollaboratorsInviteHelper = require('./CollaboratorsInviteHelper')
|
||||
const UserGetter = require('../User/UserGetter')
|
||||
const ProjectGetter = require('../Project/ProjectGetter')
|
||||
|
@ -91,6 +92,21 @@ const CollaboratorsInviteHandler = {
|
|||
return _.pick(invite, ['_id', 'email', 'privileges'])
|
||||
},
|
||||
|
||||
async revokeInviteForUser(projectId, targetEmails) {
|
||||
logger.debug({ projectId }, 'getting all active invites for project')
|
||||
const invites =
|
||||
await CollaboratorsInviteGetter.promises.getAllInvites(projectId)
|
||||
const matchingInvite = invites.find(invite =>
|
||||
targetEmails.some(emailData => emailData.email === invite.email)
|
||||
)
|
||||
if (matchingInvite) {
|
||||
await CollaboratorsInviteHandler.revokeInvite(
|
||||
projectId,
|
||||
matchingInvite._id
|
||||
)
|
||||
}
|
||||
},
|
||||
|
||||
async revokeInvite(projectId, inviteId) {
|
||||
logger.debug({ projectId, inviteId }, 'removing invite')
|
||||
const invite = await ProjectInvite.findOneAndDelete({
|
||||
|
@ -185,6 +201,9 @@ const CollaboratorsInviteHandler = {
|
|||
module.exports = {
|
||||
promises: CollaboratorsInviteHandler,
|
||||
inviteToProject: callbackify(CollaboratorsInviteHandler.inviteToProject),
|
||||
revokeInviteForUser: callbackify(
|
||||
CollaboratorsInviteHandler.revokeInviteForUser
|
||||
),
|
||||
revokeInvite: callbackify(CollaboratorsInviteHandler.revokeInvite),
|
||||
generateNewInvite: callbackify(CollaboratorsInviteHandler.generateNewInvite),
|
||||
acceptInvite: callbackify(CollaboratorsInviteHandler.acceptInvite),
|
||||
|
|
|
@ -10,6 +10,7 @@ const AuthorizationManager = require('../Authorization/AuthorizationManager')
|
|||
const PrivilegeLevels = require('../Authorization/PrivilegeLevels')
|
||||
const ProjectAuditLogHandler = require('../Project/ProjectAuditLogHandler')
|
||||
const SplitTestHandler = require('../SplitTests/SplitTestHandler')
|
||||
const CollaboratorsInviteHandler = require('../Collaborators/CollaboratorsInviteHandler')
|
||||
const CollaboratorsHandler = require('../Collaborators/CollaboratorsHandler')
|
||||
const EditorRealTimeController = require('../Editor/EditorRealTimeController')
|
||||
const CollaboratorsGetter = require('../Collaborators/CollaboratorsGetter')
|
||||
|
@ -372,13 +373,20 @@ async function grantTokenAccessReadAndWrite(req, res, next) {
|
|||
: PrivilegeLevels.READ_AND_WRITE,
|
||||
{ pendingEditor }
|
||||
)
|
||||
// Does not remove any pending invite or the invite notification
|
||||
|
||||
// remove pending invite and notification
|
||||
const userEmails =
|
||||
await UserGetter.promises.getUserConfirmedEmails(userId)
|
||||
await CollaboratorsInviteHandler.promises.revokeInviteForUser(
|
||||
project._id,
|
||||
userEmails
|
||||
)
|
||||
// Should be a noop if the user is already a member,
|
||||
// and would redirect transparently into the project.
|
||||
EditorRealTimeController.emitToRoom(
|
||||
project._id,
|
||||
'project:membership:changed',
|
||||
{ members: true }
|
||||
{ members: true, invites: true }
|
||||
)
|
||||
|
||||
return res.json({
|
||||
|
|
|
@ -47,6 +47,12 @@ describe('CollaboratorsInviteHandler', function () {
|
|||
hashInviteToken: sinon.stub().returns(this.tokenHmac),
|
||||
}
|
||||
|
||||
this.CollaboratorsInviteGetter = {
|
||||
promises: {
|
||||
getAllInvites: sinon.stub(),
|
||||
},
|
||||
}
|
||||
|
||||
this.SplitTestHandler = {
|
||||
promises: {
|
||||
getAssignmentForUser: sinon.stub().resolves(),
|
||||
|
@ -76,6 +82,7 @@ describe('CollaboratorsInviteHandler', function () {
|
|||
'../Project/ProjectGetter': this.ProjectGetter,
|
||||
'../Notifications/NotificationsBuilder': this.NotificationsBuilder,
|
||||
'./CollaboratorsInviteHelper': this.CollaboratorsInviteHelper,
|
||||
'./CollaboratorsInviteGetter': this.CollaboratorsInviteGetter,
|
||||
'../SplitTests/SplitTestHandler': this.SplitTestHandler,
|
||||
'../Subscription/LimitationsManager': this.LimitationsManager,
|
||||
'../Project/ProjectAuditLogHandler': this.ProjectAuditLogHandler,
|
||||
|
@ -249,6 +256,75 @@ describe('CollaboratorsInviteHandler', function () {
|
|||
})
|
||||
})
|
||||
})
|
||||
describe('revokeInviteForUser', function () {
|
||||
beforeEach(function () {
|
||||
this.targetInvite = {
|
||||
_id: new ObjectId(),
|
||||
email: 'fake2@example.org',
|
||||
two: 2,
|
||||
}
|
||||
this.fakeInvites = [
|
||||
{ _id: new ObjectId(), email: 'fake1@example.org', one: 1 },
|
||||
this.targetInvite,
|
||||
]
|
||||
this.fakeInvitesWithoutUser = [
|
||||
{ _id: new ObjectId(), email: 'fake1@example.org', one: 1 },
|
||||
{ _id: new ObjectId(), email: 'fake3@example.org', two: 2 },
|
||||
]
|
||||
this.targetEmail = [{ email: 'fake2@example.org' }]
|
||||
|
||||
this.CollaboratorsInviteGetter.promises.getAllInvites.resolves(
|
||||
this.fakeInvites
|
||||
)
|
||||
this.CollaboratorsInviteHandler.promises.revokeInvite = sinon
|
||||
.stub()
|
||||
.resolves(this.targetInvite)
|
||||
|
||||
this.call = async () => {
|
||||
return await this.CollaboratorsInviteHandler.promises.revokeInviteForUser(
|
||||
this.projectId,
|
||||
this.targetEmail
|
||||
)
|
||||
}
|
||||
})
|
||||
|
||||
describe('for a valid user', function () {
|
||||
it('should have called CollaboratorsInviteGetter.getAllInvites', async function () {
|
||||
await this.call()
|
||||
this.CollaboratorsInviteGetter.promises.getAllInvites.callCount.should.equal(
|
||||
1
|
||||
)
|
||||
this.CollaboratorsInviteGetter.promises.getAllInvites
|
||||
.calledWith(this.projectId)
|
||||
.should.equal(true)
|
||||
})
|
||||
|
||||
it('should have called revokeInvite', async function () {
|
||||
await this.call()
|
||||
this.CollaboratorsInviteHandler.promises.revokeInvite.callCount.should.equal(
|
||||
1
|
||||
)
|
||||
|
||||
this.CollaboratorsInviteHandler.promises.revokeInvite
|
||||
.calledWith(this.projectId, this.targetInvite._id)
|
||||
.should.equal(true)
|
||||
})
|
||||
})
|
||||
|
||||
describe('for a user without an invite in the project', function () {
|
||||
beforeEach(function () {
|
||||
this.CollaboratorsInviteGetter.promises.getAllInvites.resolves(
|
||||
this.fakeInvitesWithoutUser
|
||||
)
|
||||
})
|
||||
it('should not have called CollaboratorsInviteHandler.revokeInvite', async function () {
|
||||
await this.call()
|
||||
this.CollaboratorsInviteHandler.promises.revokeInvite.callCount.should.equal(
|
||||
0
|
||||
)
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
describe('revokeInvite', function () {
|
||||
beforeEach(function () {
|
||||
|
|
|
@ -83,6 +83,12 @@ describe('TokenAccessController', function () {
|
|||
},
|
||||
}
|
||||
|
||||
this.CollaboratorsInviteHandler = {
|
||||
promises: {
|
||||
revokeInviteForUser: sinon.stub().resolves(),
|
||||
},
|
||||
}
|
||||
|
||||
this.CollaboratorsHandler = {
|
||||
promises: {
|
||||
addUserIdToProject: sinon.stub().resolves(),
|
||||
|
@ -120,6 +126,8 @@ describe('TokenAccessController', function () {
|
|||
return null
|
||||
}
|
||||
}),
|
||||
getUserEmail: sinon.stub().resolves(),
|
||||
getUserConfirmedEmails: sinon.stub().resolves(),
|
||||
},
|
||||
}
|
||||
|
||||
|
@ -143,6 +151,8 @@ describe('TokenAccessController', function () {
|
|||
'../SplitTests/SplitTestHandler': this.SplitTestHandler,
|
||||
'../Errors/Errors': (this.Errors = { NotFoundError: sinon.stub() }),
|
||||
'../Collaborators/CollaboratorsHandler': this.CollaboratorsHandler,
|
||||
'../Collaborators/CollaboratorsInviteHandler':
|
||||
this.CollaboratorsInviteHandler,
|
||||
'../Collaborators/CollaboratorsGetter': this.CollaboratorsGetter,
|
||||
'../Editor/EditorRealTimeController': this.EditorRealTimeController,
|
||||
'../Project/ProjectGetter': this.ProjectGetter,
|
||||
|
@ -279,7 +289,7 @@ describe('TokenAccessController', function () {
|
|||
).to.have.been.calledWith(
|
||||
this.project._id,
|
||||
'project:membership:changed',
|
||||
{ members: true }
|
||||
{ members: true, invites: true }
|
||||
)
|
||||
})
|
||||
|
||||
|
@ -365,7 +375,7 @@ describe('TokenAccessController', function () {
|
|||
).to.have.been.calledWith(
|
||||
this.project._id,
|
||||
'project:membership:changed',
|
||||
{ members: true }
|
||||
{ members: true, invites: true }
|
||||
)
|
||||
})
|
||||
|
||||
|
@ -440,7 +450,7 @@ describe('TokenAccessController', function () {
|
|||
).to.have.been.calledWith(
|
||||
this.project._id,
|
||||
'project:membership:changed',
|
||||
{ members: true }
|
||||
{ members: true, invites: true }
|
||||
)
|
||||
})
|
||||
|
||||
|
|
Loading…
Reference in a new issue