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 })
|
res.json({ invite })
|
||||||
},
|
},
|
||||||
|
|
||||||
async revokeInvite(req, res) {
|
async revokeInvite(req, res) {
|
||||||
const projectId = req.params.Project_id
|
const projectId = req.params.Project_id
|
||||||
const inviteId = req.params.invite_id
|
const inviteId = req.params.invite_id
|
||||||
|
@ -404,6 +403,9 @@ module.exports = {
|
||||||
getAllInvites: expressify(CollaboratorsInviteController.getAllInvites),
|
getAllInvites: expressify(CollaboratorsInviteController.getAllInvites),
|
||||||
inviteToProject: expressify(CollaboratorsInviteController.inviteToProject),
|
inviteToProject: expressify(CollaboratorsInviteController.inviteToProject),
|
||||||
revokeInvite: expressify(CollaboratorsInviteController.revokeInvite),
|
revokeInvite: expressify(CollaboratorsInviteController.revokeInvite),
|
||||||
|
revokeInviteForUser: expressify(
|
||||||
|
CollaboratorsInviteController.revokeInviteForUser
|
||||||
|
),
|
||||||
generateNewInvite: expressify(
|
generateNewInvite: expressify(
|
||||||
CollaboratorsInviteController.generateNewInvite
|
CollaboratorsInviteController.generateNewInvite
|
||||||
),
|
),
|
||||||
|
|
|
@ -3,6 +3,7 @@ const { ProjectInvite } = require('../../models/ProjectInvite')
|
||||||
const logger = require('@overleaf/logger')
|
const logger = require('@overleaf/logger')
|
||||||
const CollaboratorsEmailHandler = require('./CollaboratorsEmailHandler')
|
const CollaboratorsEmailHandler = require('./CollaboratorsEmailHandler')
|
||||||
const CollaboratorsHandler = require('./CollaboratorsHandler')
|
const CollaboratorsHandler = require('./CollaboratorsHandler')
|
||||||
|
const CollaboratorsInviteGetter = require('./CollaboratorsInviteGetter')
|
||||||
const CollaboratorsInviteHelper = require('./CollaboratorsInviteHelper')
|
const CollaboratorsInviteHelper = require('./CollaboratorsInviteHelper')
|
||||||
const UserGetter = require('../User/UserGetter')
|
const UserGetter = require('../User/UserGetter')
|
||||||
const ProjectGetter = require('../Project/ProjectGetter')
|
const ProjectGetter = require('../Project/ProjectGetter')
|
||||||
|
@ -91,6 +92,21 @@ const CollaboratorsInviteHandler = {
|
||||||
return _.pick(invite, ['_id', 'email', 'privileges'])
|
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) {
|
async revokeInvite(projectId, inviteId) {
|
||||||
logger.debug({ projectId, inviteId }, 'removing invite')
|
logger.debug({ projectId, inviteId }, 'removing invite')
|
||||||
const invite = await ProjectInvite.findOneAndDelete({
|
const invite = await ProjectInvite.findOneAndDelete({
|
||||||
|
@ -185,6 +201,9 @@ const CollaboratorsInviteHandler = {
|
||||||
module.exports = {
|
module.exports = {
|
||||||
promises: CollaboratorsInviteHandler,
|
promises: CollaboratorsInviteHandler,
|
||||||
inviteToProject: callbackify(CollaboratorsInviteHandler.inviteToProject),
|
inviteToProject: callbackify(CollaboratorsInviteHandler.inviteToProject),
|
||||||
|
revokeInviteForUser: callbackify(
|
||||||
|
CollaboratorsInviteHandler.revokeInviteForUser
|
||||||
|
),
|
||||||
revokeInvite: callbackify(CollaboratorsInviteHandler.revokeInvite),
|
revokeInvite: callbackify(CollaboratorsInviteHandler.revokeInvite),
|
||||||
generateNewInvite: callbackify(CollaboratorsInviteHandler.generateNewInvite),
|
generateNewInvite: callbackify(CollaboratorsInviteHandler.generateNewInvite),
|
||||||
acceptInvite: callbackify(CollaboratorsInviteHandler.acceptInvite),
|
acceptInvite: callbackify(CollaboratorsInviteHandler.acceptInvite),
|
||||||
|
|
|
@ -10,6 +10,7 @@ const AuthorizationManager = require('../Authorization/AuthorizationManager')
|
||||||
const PrivilegeLevels = require('../Authorization/PrivilegeLevels')
|
const PrivilegeLevels = require('../Authorization/PrivilegeLevels')
|
||||||
const ProjectAuditLogHandler = require('../Project/ProjectAuditLogHandler')
|
const ProjectAuditLogHandler = require('../Project/ProjectAuditLogHandler')
|
||||||
const SplitTestHandler = require('../SplitTests/SplitTestHandler')
|
const SplitTestHandler = require('../SplitTests/SplitTestHandler')
|
||||||
|
const CollaboratorsInviteHandler = require('../Collaborators/CollaboratorsInviteHandler')
|
||||||
const CollaboratorsHandler = require('../Collaborators/CollaboratorsHandler')
|
const CollaboratorsHandler = require('../Collaborators/CollaboratorsHandler')
|
||||||
const EditorRealTimeController = require('../Editor/EditorRealTimeController')
|
const EditorRealTimeController = require('../Editor/EditorRealTimeController')
|
||||||
const CollaboratorsGetter = require('../Collaborators/CollaboratorsGetter')
|
const CollaboratorsGetter = require('../Collaborators/CollaboratorsGetter')
|
||||||
|
@ -372,13 +373,20 @@ async function grantTokenAccessReadAndWrite(req, res, next) {
|
||||||
: PrivilegeLevels.READ_AND_WRITE,
|
: PrivilegeLevels.READ_AND_WRITE,
|
||||||
{ pendingEditor }
|
{ 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,
|
// Should be a noop if the user is already a member,
|
||||||
// and would redirect transparently into the project.
|
// and would redirect transparently into the project.
|
||||||
EditorRealTimeController.emitToRoom(
|
EditorRealTimeController.emitToRoom(
|
||||||
project._id,
|
project._id,
|
||||||
'project:membership:changed',
|
'project:membership:changed',
|
||||||
{ members: true }
|
{ members: true, invites: true }
|
||||||
)
|
)
|
||||||
|
|
||||||
return res.json({
|
return res.json({
|
||||||
|
|
|
@ -47,6 +47,12 @@ describe('CollaboratorsInviteHandler', function () {
|
||||||
hashInviteToken: sinon.stub().returns(this.tokenHmac),
|
hashInviteToken: sinon.stub().returns(this.tokenHmac),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
this.CollaboratorsInviteGetter = {
|
||||||
|
promises: {
|
||||||
|
getAllInvites: sinon.stub(),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
this.SplitTestHandler = {
|
this.SplitTestHandler = {
|
||||||
promises: {
|
promises: {
|
||||||
getAssignmentForUser: sinon.stub().resolves(),
|
getAssignmentForUser: sinon.stub().resolves(),
|
||||||
|
@ -76,6 +82,7 @@ describe('CollaboratorsInviteHandler', function () {
|
||||||
'../Project/ProjectGetter': this.ProjectGetter,
|
'../Project/ProjectGetter': this.ProjectGetter,
|
||||||
'../Notifications/NotificationsBuilder': this.NotificationsBuilder,
|
'../Notifications/NotificationsBuilder': this.NotificationsBuilder,
|
||||||
'./CollaboratorsInviteHelper': this.CollaboratorsInviteHelper,
|
'./CollaboratorsInviteHelper': this.CollaboratorsInviteHelper,
|
||||||
|
'./CollaboratorsInviteGetter': this.CollaboratorsInviteGetter,
|
||||||
'../SplitTests/SplitTestHandler': this.SplitTestHandler,
|
'../SplitTests/SplitTestHandler': this.SplitTestHandler,
|
||||||
'../Subscription/LimitationsManager': this.LimitationsManager,
|
'../Subscription/LimitationsManager': this.LimitationsManager,
|
||||||
'../Project/ProjectAuditLogHandler': this.ProjectAuditLogHandler,
|
'../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 () {
|
describe('revokeInvite', function () {
|
||||||
beforeEach(function () {
|
beforeEach(function () {
|
||||||
|
|
|
@ -83,6 +83,12 @@ describe('TokenAccessController', function () {
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
this.CollaboratorsInviteHandler = {
|
||||||
|
promises: {
|
||||||
|
revokeInviteForUser: sinon.stub().resolves(),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
this.CollaboratorsHandler = {
|
this.CollaboratorsHandler = {
|
||||||
promises: {
|
promises: {
|
||||||
addUserIdToProject: sinon.stub().resolves(),
|
addUserIdToProject: sinon.stub().resolves(),
|
||||||
|
@ -120,6 +126,8 @@ describe('TokenAccessController', function () {
|
||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
}),
|
}),
|
||||||
|
getUserEmail: sinon.stub().resolves(),
|
||||||
|
getUserConfirmedEmails: sinon.stub().resolves(),
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -143,6 +151,8 @@ describe('TokenAccessController', function () {
|
||||||
'../SplitTests/SplitTestHandler': this.SplitTestHandler,
|
'../SplitTests/SplitTestHandler': this.SplitTestHandler,
|
||||||
'../Errors/Errors': (this.Errors = { NotFoundError: sinon.stub() }),
|
'../Errors/Errors': (this.Errors = { NotFoundError: sinon.stub() }),
|
||||||
'../Collaborators/CollaboratorsHandler': this.CollaboratorsHandler,
|
'../Collaborators/CollaboratorsHandler': this.CollaboratorsHandler,
|
||||||
|
'../Collaborators/CollaboratorsInviteHandler':
|
||||||
|
this.CollaboratorsInviteHandler,
|
||||||
'../Collaborators/CollaboratorsGetter': this.CollaboratorsGetter,
|
'../Collaborators/CollaboratorsGetter': this.CollaboratorsGetter,
|
||||||
'../Editor/EditorRealTimeController': this.EditorRealTimeController,
|
'../Editor/EditorRealTimeController': this.EditorRealTimeController,
|
||||||
'../Project/ProjectGetter': this.ProjectGetter,
|
'../Project/ProjectGetter': this.ProjectGetter,
|
||||||
|
@ -279,7 +289,7 @@ describe('TokenAccessController', function () {
|
||||||
).to.have.been.calledWith(
|
).to.have.been.calledWith(
|
||||||
this.project._id,
|
this.project._id,
|
||||||
'project:membership:changed',
|
'project:membership:changed',
|
||||||
{ members: true }
|
{ members: true, invites: true }
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
@ -365,7 +375,7 @@ describe('TokenAccessController', function () {
|
||||||
).to.have.been.calledWith(
|
).to.have.been.calledWith(
|
||||||
this.project._id,
|
this.project._id,
|
||||||
'project:membership:changed',
|
'project:membership:changed',
|
||||||
{ members: true }
|
{ members: true, invites: true }
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
@ -440,7 +450,7 @@ describe('TokenAccessController', function () {
|
||||||
).to.have.been.calledWith(
|
).to.have.been.calledWith(
|
||||||
this.project._id,
|
this.project._id,
|
||||||
'project:membership:changed',
|
'project:membership:changed',
|
||||||
{ members: true }
|
{ members: true, invites: true }
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue