|
|
|
@@ -1,4 +1,3 @@
|
|
|
|
|
import { callbackify } from 'node:util'
|
|
|
|
|
import ProjectGetter from '../Project/ProjectGetter.js'
|
|
|
|
|
import LimitationsManager from '../Subscription/LimitationsManager.js'
|
|
|
|
|
import UserGetter from '../User/UserGetter.js'
|
|
|
|
@@ -34,336 +33,218 @@ const rateLimiter = new RateLimiter('invite-to-project-by-user-id', {
|
|
|
|
|
duration: 60 * 30,
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
const CollaboratorsInviteController = {
|
|
|
|
|
async getAllInvites(req, res) {
|
|
|
|
|
const projectId = req.params.Project_id
|
|
|
|
|
logger.debug({ projectId }, 'getting all active invites for project')
|
|
|
|
|
const invites =
|
|
|
|
|
await CollaboratorsInviteGetter.promises.getAllInvites(projectId)
|
|
|
|
|
res.json({ invites })
|
|
|
|
|
},
|
|
|
|
|
async function getAllInvites(req, res) {
|
|
|
|
|
const projectId = req.params.Project_id
|
|
|
|
|
logger.debug({ projectId }, 'getting all active invites for project')
|
|
|
|
|
const invites =
|
|
|
|
|
await CollaboratorsInviteGetter.promises.getAllInvites(projectId)
|
|
|
|
|
res.json({ invites })
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
async _checkShouldInviteEmail(email) {
|
|
|
|
|
if (Settings.restrictInvitesToExistingAccounts === true) {
|
|
|
|
|
logger.debug({ email }, 'checking if user exists with this email')
|
|
|
|
|
const user = await UserGetter.promises.getUserByAnyEmail(email, {
|
|
|
|
|
_id: 1,
|
|
|
|
|
})
|
|
|
|
|
const userExists = user?._id != null
|
|
|
|
|
return userExists
|
|
|
|
|
} else {
|
|
|
|
|
return true
|
|
|
|
|
}
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
async _checkRateLimit(userId) {
|
|
|
|
|
let collabLimit =
|
|
|
|
|
await LimitationsManager.promises.allowedNumberOfCollaboratorsForUser(
|
|
|
|
|
userId
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
if (collabLimit == null || collabLimit === 0) {
|
|
|
|
|
collabLimit = 1
|
|
|
|
|
} else if (collabLimit < 0 || collabLimit > 20) {
|
|
|
|
|
collabLimit = 20
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Consume enough points to hit the rate limit at 10 * collabLimit
|
|
|
|
|
const maxRequests = 10 * collabLimit
|
|
|
|
|
const points = Math.floor(RATE_LIMIT_POINTS / maxRequests)
|
|
|
|
|
try {
|
|
|
|
|
await rateLimiter.consume(userId, points, { method: 'userId' })
|
|
|
|
|
} catch (err) {
|
|
|
|
|
if (err instanceof Error) {
|
|
|
|
|
throw err
|
|
|
|
|
} else {
|
|
|
|
|
return false
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
return true
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
async inviteToProject(req, res) {
|
|
|
|
|
const projectId = req.params.Project_id
|
|
|
|
|
let { email, privileges } = req.body
|
|
|
|
|
const sendingUser = SessionManager.getSessionUser(req.session)
|
|
|
|
|
const sendingUserId = sendingUser._id
|
|
|
|
|
req.logger.addFields({ email, sendingUserId })
|
|
|
|
|
|
|
|
|
|
if (email === sendingUser.email) {
|
|
|
|
|
logger.debug(
|
|
|
|
|
{ projectId, email, sendingUserId },
|
|
|
|
|
'cannot invite yourself to project'
|
|
|
|
|
)
|
|
|
|
|
return res.json({ invite: null, error: 'cannot_invite_self' })
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
logger.debug({ projectId, email, sendingUserId }, 'inviting to project')
|
|
|
|
|
|
|
|
|
|
const project = await ProjectGetter.promises.getProject(projectId, {
|
|
|
|
|
owner_ref: 1,
|
|
|
|
|
async function _checkShouldInviteEmail(email) {
|
|
|
|
|
if (Settings.restrictInvitesToExistingAccounts === true) {
|
|
|
|
|
logger.debug({ email }, 'checking if user exists with this email')
|
|
|
|
|
const user = await UserGetter.promises.getUserByAnyEmail(email, {
|
|
|
|
|
_id: 1,
|
|
|
|
|
})
|
|
|
|
|
const linkSharingChanges =
|
|
|
|
|
await SplitTestHandler.promises.getAssignmentForUser(
|
|
|
|
|
project.owner_ref,
|
|
|
|
|
'link-sharing-warning'
|
|
|
|
|
)
|
|
|
|
|
const userExists = user?._id != null
|
|
|
|
|
return userExists
|
|
|
|
|
} else {
|
|
|
|
|
return true
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
let allowed = false
|
|
|
|
|
if (linkSharingChanges?.variant === 'active') {
|
|
|
|
|
// if link-sharing-warning is active, can always invite read-only collaborators
|
|
|
|
|
if (privileges === PrivilegeLevels.READ_ONLY) {
|
|
|
|
|
allowed = true
|
|
|
|
|
} else {
|
|
|
|
|
allowed = await LimitationsManager.promises.canAddXEditCollaborators(
|
|
|
|
|
projectId,
|
|
|
|
|
1
|
|
|
|
|
)
|
|
|
|
|
}
|
|
|
|
|
async function _checkRateLimit(userId) {
|
|
|
|
|
let collabLimit =
|
|
|
|
|
await LimitationsManager.promises.allowedNumberOfCollaboratorsForUser(
|
|
|
|
|
userId
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
if (collabLimit == null || collabLimit === 0) {
|
|
|
|
|
collabLimit = 1
|
|
|
|
|
} else if (collabLimit < 0 || collabLimit > 20) {
|
|
|
|
|
collabLimit = 20
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Consume enough points to hit the rate limit at 10 * collabLimit
|
|
|
|
|
const maxRequests = 10 * collabLimit
|
|
|
|
|
const points = Math.floor(RATE_LIMIT_POINTS / maxRequests)
|
|
|
|
|
try {
|
|
|
|
|
await rateLimiter.consume(userId, points, { method: 'userId' })
|
|
|
|
|
} catch (err) {
|
|
|
|
|
if (err instanceof Error) {
|
|
|
|
|
throw err
|
|
|
|
|
} else {
|
|
|
|
|
allowed = await LimitationsManager.promises.canAddXCollaborators(
|
|
|
|
|
return false
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
return true
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
async function inviteToProject(req, res) {
|
|
|
|
|
const projectId = req.params.Project_id
|
|
|
|
|
let { email, privileges } = req.body
|
|
|
|
|
const sendingUser = SessionManager.getSessionUser(req.session)
|
|
|
|
|
const sendingUserId = sendingUser._id
|
|
|
|
|
req.logger.addFields({ email, sendingUserId })
|
|
|
|
|
|
|
|
|
|
if (email === sendingUser.email) {
|
|
|
|
|
logger.debug(
|
|
|
|
|
{ projectId, email, sendingUserId },
|
|
|
|
|
'cannot invite yourself to project'
|
|
|
|
|
)
|
|
|
|
|
return res.json({ invite: null, error: 'cannot_invite_self' })
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
logger.debug({ projectId, email, sendingUserId }, 'inviting to project')
|
|
|
|
|
|
|
|
|
|
const project = await ProjectGetter.promises.getProject(projectId, {
|
|
|
|
|
owner_ref: 1,
|
|
|
|
|
})
|
|
|
|
|
const linkSharingChanges =
|
|
|
|
|
await SplitTestHandler.promises.getAssignmentForUser(
|
|
|
|
|
project.owner_ref,
|
|
|
|
|
'link-sharing-warning'
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
let allowed = false
|
|
|
|
|
if (linkSharingChanges?.variant === 'active') {
|
|
|
|
|
// if link-sharing-warning is active, can always invite read-only collaborators
|
|
|
|
|
if (privileges === PrivilegeLevels.READ_ONLY) {
|
|
|
|
|
allowed = true
|
|
|
|
|
} else {
|
|
|
|
|
allowed = await LimitationsManager.promises.canAddXEditCollaborators(
|
|
|
|
|
projectId,
|
|
|
|
|
1
|
|
|
|
|
)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (!allowed) {
|
|
|
|
|
logger.debug(
|
|
|
|
|
{ projectId, email, sendingUserId },
|
|
|
|
|
'not allowed to invite more users to project'
|
|
|
|
|
)
|
|
|
|
|
return res.json({ invite: null })
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
email = EmailHelper.parseEmail(email, true)
|
|
|
|
|
if (email == null || email === '') {
|
|
|
|
|
logger.debug({ projectId, email, sendingUserId }, 'invalid email address')
|
|
|
|
|
return res.status(400).json({ errorReason: 'invalid_email' })
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const underRateLimit =
|
|
|
|
|
await CollaboratorsInviteController._checkRateLimit(sendingUserId)
|
|
|
|
|
if (!underRateLimit) {
|
|
|
|
|
return res.sendStatus(429)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const shouldAllowInvite =
|
|
|
|
|
await CollaboratorsInviteController._checkShouldInviteEmail(email)
|
|
|
|
|
if (!shouldAllowInvite) {
|
|
|
|
|
logger.debug(
|
|
|
|
|
{ email, projectId, sendingUserId },
|
|
|
|
|
'not allowed to send an invite to this email address'
|
|
|
|
|
)
|
|
|
|
|
return res.json({
|
|
|
|
|
invite: null,
|
|
|
|
|
error: 'cannot_invite_non_user',
|
|
|
|
|
})
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const invite = await CollaboratorsInviteHandler.promises.inviteToProject(
|
|
|
|
|
} else {
|
|
|
|
|
allowed = await LimitationsManager.promises.canAddXCollaborators(
|
|
|
|
|
projectId,
|
|
|
|
|
sendingUser,
|
|
|
|
|
email,
|
|
|
|
|
privileges
|
|
|
|
|
1
|
|
|
|
|
)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (!allowed) {
|
|
|
|
|
logger.debug(
|
|
|
|
|
{ projectId, email, sendingUserId },
|
|
|
|
|
'not allowed to invite more users to project'
|
|
|
|
|
)
|
|
|
|
|
return res.json({ invite: null })
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
email = EmailHelper.parseEmail(email, true)
|
|
|
|
|
if (email == null || email === '') {
|
|
|
|
|
logger.debug({ projectId, email, sendingUserId }, 'invalid email address')
|
|
|
|
|
return res.status(400).json({ errorReason: 'invalid_email' })
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const underRateLimit =
|
|
|
|
|
await CollaboratorsInviteController._checkRateLimit(sendingUserId)
|
|
|
|
|
if (!underRateLimit) {
|
|
|
|
|
return res.sendStatus(429)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const shouldAllowInvite =
|
|
|
|
|
await CollaboratorsInviteController._checkShouldInviteEmail(email)
|
|
|
|
|
if (!shouldAllowInvite) {
|
|
|
|
|
logger.debug(
|
|
|
|
|
{ email, projectId, sendingUserId },
|
|
|
|
|
'not allowed to send an invite to this email address'
|
|
|
|
|
)
|
|
|
|
|
return res.json({
|
|
|
|
|
invite: null,
|
|
|
|
|
error: 'cannot_invite_non_user',
|
|
|
|
|
})
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const invite = await CollaboratorsInviteHandler.promises.inviteToProject(
|
|
|
|
|
projectId,
|
|
|
|
|
sendingUser,
|
|
|
|
|
email,
|
|
|
|
|
privileges
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
ProjectAuditLogHandler.addEntryInBackground(
|
|
|
|
|
projectId,
|
|
|
|
|
'send-invite',
|
|
|
|
|
sendingUserId,
|
|
|
|
|
req.ip,
|
|
|
|
|
{
|
|
|
|
|
inviteId: invite._id,
|
|
|
|
|
privileges,
|
|
|
|
|
}
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
logger.debug({ projectId, email, sendingUserId }, 'invite created')
|
|
|
|
|
|
|
|
|
|
EditorRealTimeController.emitToRoom(projectId, 'project:membership:changed', {
|
|
|
|
|
invites: true,
|
|
|
|
|
})
|
|
|
|
|
res.json({ invite })
|
|
|
|
|
}
|
|
|
|
|
async function revokeInvite(req, res) {
|
|
|
|
|
const projectId = req.params.Project_id
|
|
|
|
|
const inviteId = req.params.invite_id
|
|
|
|
|
const user = SessionManager.getSessionUser(req.session)
|
|
|
|
|
|
|
|
|
|
logger.debug({ projectId, inviteId }, 'revoking invite')
|
|
|
|
|
|
|
|
|
|
const invite = await CollaboratorsInviteHandler.promises.revokeInvite(
|
|
|
|
|
projectId,
|
|
|
|
|
inviteId
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
if (invite != null) {
|
|
|
|
|
ProjectAuditLogHandler.addEntryInBackground(
|
|
|
|
|
projectId,
|
|
|
|
|
'send-invite',
|
|
|
|
|
sendingUserId,
|
|
|
|
|
'revoke-invite',
|
|
|
|
|
user._id,
|
|
|
|
|
req.ip,
|
|
|
|
|
{
|
|
|
|
|
inviteId: invite._id,
|
|
|
|
|
privileges,
|
|
|
|
|
privileges: invite.privileges,
|
|
|
|
|
}
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
logger.debug({ projectId, email, sendingUserId }, 'invite created')
|
|
|
|
|
|
|
|
|
|
EditorRealTimeController.emitToRoom(
|
|
|
|
|
projectId,
|
|
|
|
|
'project:membership:changed',
|
|
|
|
|
{ invites: true }
|
|
|
|
|
)
|
|
|
|
|
res.json({ invite })
|
|
|
|
|
},
|
|
|
|
|
async revokeInvite(req, res) {
|
|
|
|
|
const projectId = req.params.Project_id
|
|
|
|
|
const inviteId = req.params.invite_id
|
|
|
|
|
const user = SessionManager.getSessionUser(req.session)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
logger.debug({ projectId, inviteId }, 'revoking invite')
|
|
|
|
|
res.sendStatus(204)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const invite = await CollaboratorsInviteHandler.promises.revokeInvite(
|
|
|
|
|
async function generateNewInvite(req, res) {
|
|
|
|
|
const projectId = req.params.Project_id
|
|
|
|
|
const inviteId = req.params.invite_id
|
|
|
|
|
const user = SessionManager.getSessionUser(req.session)
|
|
|
|
|
|
|
|
|
|
logger.debug({ projectId, inviteId }, 'resending invite')
|
|
|
|
|
const sendingUser = SessionManager.getSessionUser(req.session)
|
|
|
|
|
const underRateLimit = await CollaboratorsInviteController._checkRateLimit(
|
|
|
|
|
sendingUser._id
|
|
|
|
|
)
|
|
|
|
|
if (!underRateLimit) {
|
|
|
|
|
return res.sendStatus(429)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const invite = await CollaboratorsInviteHandler.promises.generateNewInvite(
|
|
|
|
|
projectId,
|
|
|
|
|
sendingUser,
|
|
|
|
|
inviteId
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
EditorRealTimeController.emitToRoom(projectId, 'project:membership:changed', {
|
|
|
|
|
invites: true,
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
if (invite != null) {
|
|
|
|
|
ProjectAuditLogHandler.addEntryInBackground(
|
|
|
|
|
projectId,
|
|
|
|
|
inviteId
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
if (invite != null) {
|
|
|
|
|
ProjectAuditLogHandler.addEntryInBackground(
|
|
|
|
|
projectId,
|
|
|
|
|
'revoke-invite',
|
|
|
|
|
user._id,
|
|
|
|
|
req.ip,
|
|
|
|
|
{
|
|
|
|
|
inviteId: invite._id,
|
|
|
|
|
privileges: invite.privileges,
|
|
|
|
|
}
|
|
|
|
|
)
|
|
|
|
|
EditorRealTimeController.emitToRoom(
|
|
|
|
|
projectId,
|
|
|
|
|
'project:membership:changed',
|
|
|
|
|
{ invites: true }
|
|
|
|
|
)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
res.sendStatus(204)
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
async generateNewInvite(req, res) {
|
|
|
|
|
const projectId = req.params.Project_id
|
|
|
|
|
const inviteId = req.params.invite_id
|
|
|
|
|
const user = SessionManager.getSessionUser(req.session)
|
|
|
|
|
|
|
|
|
|
logger.debug({ projectId, inviteId }, 'resending invite')
|
|
|
|
|
const sendingUser = SessionManager.getSessionUser(req.session)
|
|
|
|
|
const underRateLimit = await CollaboratorsInviteController._checkRateLimit(
|
|
|
|
|
sendingUser._id
|
|
|
|
|
)
|
|
|
|
|
if (!underRateLimit) {
|
|
|
|
|
return res.sendStatus(429)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const invite = await CollaboratorsInviteHandler.promises.generateNewInvite(
|
|
|
|
|
projectId,
|
|
|
|
|
sendingUser,
|
|
|
|
|
inviteId
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
EditorRealTimeController.emitToRoom(
|
|
|
|
|
projectId,
|
|
|
|
|
'project:membership:changed',
|
|
|
|
|
{ invites: true }
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
if (invite != null) {
|
|
|
|
|
ProjectAuditLogHandler.addEntryInBackground(
|
|
|
|
|
projectId,
|
|
|
|
|
'resend-invite',
|
|
|
|
|
user._id,
|
|
|
|
|
req.ip,
|
|
|
|
|
{
|
|
|
|
|
inviteId: invite._id,
|
|
|
|
|
privileges: invite.privileges,
|
|
|
|
|
}
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
res.sendStatus(201)
|
|
|
|
|
} else {
|
|
|
|
|
res.sendStatus(404)
|
|
|
|
|
}
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
async viewInvite(req, res) {
|
|
|
|
|
const projectId = req.params.Project_id
|
|
|
|
|
const { token } = req.params
|
|
|
|
|
const _renderInvalidPage = function () {
|
|
|
|
|
res.status(404)
|
|
|
|
|
logger.debug({ projectId }, 'invite not valid, rendering not-valid page')
|
|
|
|
|
res.render('project/invite/not-valid', { title: 'Invalid Invite' })
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// check if the user is already a member of the project
|
|
|
|
|
const currentUser = SessionManager.getSessionUser(req.session)
|
|
|
|
|
if (currentUser) {
|
|
|
|
|
const isMember =
|
|
|
|
|
await CollaboratorsGetter.promises.isUserInvitedMemberOfProject(
|
|
|
|
|
currentUser._id,
|
|
|
|
|
projectId
|
|
|
|
|
)
|
|
|
|
|
if (isMember) {
|
|
|
|
|
logger.debug(
|
|
|
|
|
{ projectId, userId: currentUser._id },
|
|
|
|
|
'user is already a member of this project, redirecting'
|
|
|
|
|
)
|
|
|
|
|
return res.redirect(`/project/${projectId}`)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// get the invite
|
|
|
|
|
const invite = await CollaboratorsInviteGetter.promises.getInviteByToken(
|
|
|
|
|
projectId,
|
|
|
|
|
token
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
// check if invite is gone, or otherwise non-existent
|
|
|
|
|
if (invite == null) {
|
|
|
|
|
logger.debug({ projectId }, 'no invite found for this token')
|
|
|
|
|
return _renderInvalidPage()
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// check the user who sent the invite exists
|
|
|
|
|
const owner = await UserGetter.promises.getUser(
|
|
|
|
|
{ _id: invite.sendingUserId },
|
|
|
|
|
{ email: 1, first_name: 1, last_name: 1 }
|
|
|
|
|
)
|
|
|
|
|
if (owner == null) {
|
|
|
|
|
logger.debug({ projectId }, 'no project owner found')
|
|
|
|
|
return _renderInvalidPage()
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// fetch the project name
|
|
|
|
|
const project = await ProjectGetter.promises.getProject(projectId, {
|
|
|
|
|
name: 1,
|
|
|
|
|
})
|
|
|
|
|
if (project == null) {
|
|
|
|
|
logger.debug({ projectId }, 'no project found')
|
|
|
|
|
return _renderInvalidPage()
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (!currentUser) {
|
|
|
|
|
req.session.sharedProjectData = {
|
|
|
|
|
project_name: project.name,
|
|
|
|
|
user_first_name: owner.first_name,
|
|
|
|
|
}
|
|
|
|
|
AuthenticationController.setRedirectInSession(req)
|
|
|
|
|
return res.redirect('/register')
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// cleanup if set for register page
|
|
|
|
|
delete req.session.sharedProjectData
|
|
|
|
|
|
|
|
|
|
// finally render the invite
|
|
|
|
|
res.render('project/invite/show', {
|
|
|
|
|
invite,
|
|
|
|
|
token,
|
|
|
|
|
project,
|
|
|
|
|
owner,
|
|
|
|
|
title: 'Project Invite',
|
|
|
|
|
})
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
async acceptInvite(req, res) {
|
|
|
|
|
const { Project_id: projectId, token } = req.params
|
|
|
|
|
const currentUser = SessionManager.getSessionUser(req.session)
|
|
|
|
|
logger.debug(
|
|
|
|
|
{ projectId, userId: currentUser._id },
|
|
|
|
|
'got request to accept invite'
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
const invite = await CollaboratorsInviteGetter.promises.getInviteByToken(
|
|
|
|
|
projectId,
|
|
|
|
|
token
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
if (invite == null) {
|
|
|
|
|
throw new Errors.NotFoundError('no matching invite found')
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
await ProjectAuditLogHandler.promises.addEntry(
|
|
|
|
|
projectId,
|
|
|
|
|
'accept-invite',
|
|
|
|
|
currentUser._id,
|
|
|
|
|
'resend-invite',
|
|
|
|
|
user._id,
|
|
|
|
|
req.ip,
|
|
|
|
|
{
|
|
|
|
|
inviteId: invite._id,
|
|
|
|
@@ -371,48 +252,154 @@ const CollaboratorsInviteController = {
|
|
|
|
|
}
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
await CollaboratorsInviteHandler.promises.acceptInvite(
|
|
|
|
|
invite,
|
|
|
|
|
projectId,
|
|
|
|
|
currentUser
|
|
|
|
|
)
|
|
|
|
|
res.sendStatus(201)
|
|
|
|
|
} else {
|
|
|
|
|
res.sendStatus(404)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
await EditorRealTimeController.emitToRoom(
|
|
|
|
|
projectId,
|
|
|
|
|
'project:membership:changed',
|
|
|
|
|
{ invites: true, members: true }
|
|
|
|
|
)
|
|
|
|
|
AnalyticsManager.recordEventForUserInBackground(
|
|
|
|
|
currentUser._id,
|
|
|
|
|
'project-invite-accept',
|
|
|
|
|
{
|
|
|
|
|
projectId,
|
|
|
|
|
}
|
|
|
|
|
)
|
|
|
|
|
async function viewInvite(req, res) {
|
|
|
|
|
const projectId = req.params.Project_id
|
|
|
|
|
const { token } = req.params
|
|
|
|
|
const _renderInvalidPage = function () {
|
|
|
|
|
res.status(404)
|
|
|
|
|
logger.debug({ projectId }, 'invite not valid, rendering not-valid page')
|
|
|
|
|
res.render('project/invite/not-valid', { title: 'Invalid Invite' })
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (req.xhr) {
|
|
|
|
|
res.sendStatus(204) // Done async via project page notification
|
|
|
|
|
} else {
|
|
|
|
|
res.redirect(`/project/${projectId}`)
|
|
|
|
|
// check if the user is already a member of the project
|
|
|
|
|
const currentUser = SessionManager.getSessionUser(req.session)
|
|
|
|
|
if (currentUser) {
|
|
|
|
|
const isMember =
|
|
|
|
|
await CollaboratorsGetter.promises.isUserInvitedMemberOfProject(
|
|
|
|
|
currentUser._id,
|
|
|
|
|
projectId
|
|
|
|
|
)
|
|
|
|
|
if (isMember) {
|
|
|
|
|
logger.debug(
|
|
|
|
|
{ projectId, userId: currentUser._id },
|
|
|
|
|
'user is already a member of this project, redirecting'
|
|
|
|
|
)
|
|
|
|
|
return res.redirect(`/project/${projectId}`)
|
|
|
|
|
}
|
|
|
|
|
},
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// get the invite
|
|
|
|
|
const invite = await CollaboratorsInviteGetter.promises.getInviteByToken(
|
|
|
|
|
projectId,
|
|
|
|
|
token
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
// check if invite is gone, or otherwise non-existent
|
|
|
|
|
if (invite == null) {
|
|
|
|
|
logger.debug({ projectId }, 'no invite found for this token')
|
|
|
|
|
return _renderInvalidPage()
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// check the user who sent the invite exists
|
|
|
|
|
const owner = await UserGetter.promises.getUser(
|
|
|
|
|
{ _id: invite.sendingUserId },
|
|
|
|
|
{ email: 1, first_name: 1, last_name: 1 }
|
|
|
|
|
)
|
|
|
|
|
if (owner == null) {
|
|
|
|
|
logger.debug({ projectId }, 'no project owner found')
|
|
|
|
|
return _renderInvalidPage()
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// fetch the project name
|
|
|
|
|
const project = await ProjectGetter.promises.getProject(projectId, {
|
|
|
|
|
name: 1,
|
|
|
|
|
})
|
|
|
|
|
if (project == null) {
|
|
|
|
|
logger.debug({ projectId }, 'no project found')
|
|
|
|
|
return _renderInvalidPage()
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (!currentUser) {
|
|
|
|
|
req.session.sharedProjectData = {
|
|
|
|
|
project_name: project.name,
|
|
|
|
|
user_first_name: owner.first_name,
|
|
|
|
|
}
|
|
|
|
|
AuthenticationController.setRedirectInSession(req)
|
|
|
|
|
return res.redirect('/register')
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// cleanup if set for register page
|
|
|
|
|
delete req.session.sharedProjectData
|
|
|
|
|
|
|
|
|
|
// finally render the invite
|
|
|
|
|
res.render('project/invite/show', {
|
|
|
|
|
invite,
|
|
|
|
|
token,
|
|
|
|
|
project,
|
|
|
|
|
owner,
|
|
|
|
|
title: 'Project Invite',
|
|
|
|
|
})
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export default {
|
|
|
|
|
promises: CollaboratorsInviteController,
|
|
|
|
|
getAllInvites: expressify(CollaboratorsInviteController.getAllInvites),
|
|
|
|
|
inviteToProject: expressify(CollaboratorsInviteController.inviteToProject),
|
|
|
|
|
revokeInvite: expressify(CollaboratorsInviteController.revokeInvite),
|
|
|
|
|
revokeInviteForUser: expressify(
|
|
|
|
|
CollaboratorsInviteController.revokeInviteForUser
|
|
|
|
|
),
|
|
|
|
|
generateNewInvite: expressify(
|
|
|
|
|
CollaboratorsInviteController.generateNewInvite
|
|
|
|
|
),
|
|
|
|
|
viewInvite: expressify(CollaboratorsInviteController.viewInvite),
|
|
|
|
|
acceptInvite: expressify(CollaboratorsInviteController.acceptInvite),
|
|
|
|
|
_checkShouldInviteEmail: callbackify(
|
|
|
|
|
CollaboratorsInviteController._checkShouldInviteEmail
|
|
|
|
|
),
|
|
|
|
|
_checkRateLimit: callbackify(CollaboratorsInviteController._checkRateLimit),
|
|
|
|
|
async function acceptInvite(req, res) {
|
|
|
|
|
const { Project_id: projectId, token } = req.params
|
|
|
|
|
const currentUser = SessionManager.getSessionUser(req.session)
|
|
|
|
|
logger.debug(
|
|
|
|
|
{ projectId, userId: currentUser._id },
|
|
|
|
|
'got request to accept invite'
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
const invite = await CollaboratorsInviteGetter.promises.getInviteByToken(
|
|
|
|
|
projectId,
|
|
|
|
|
token
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
if (invite == null) {
|
|
|
|
|
throw new Errors.NotFoundError('no matching invite found')
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
await ProjectAuditLogHandler.promises.addEntry(
|
|
|
|
|
projectId,
|
|
|
|
|
'accept-invite',
|
|
|
|
|
currentUser._id,
|
|
|
|
|
req.ip,
|
|
|
|
|
{
|
|
|
|
|
inviteId: invite._id,
|
|
|
|
|
privileges: invite.privileges,
|
|
|
|
|
}
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
await CollaboratorsInviteHandler.promises.acceptInvite(
|
|
|
|
|
invite,
|
|
|
|
|
projectId,
|
|
|
|
|
currentUser
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
await EditorRealTimeController.emitToRoom(
|
|
|
|
|
projectId,
|
|
|
|
|
'project:membership:changed',
|
|
|
|
|
{ invites: true, members: true }
|
|
|
|
|
)
|
|
|
|
|
AnalyticsManager.recordEventForUserInBackground(
|
|
|
|
|
currentUser._id,
|
|
|
|
|
'project-invite-accept',
|
|
|
|
|
{
|
|
|
|
|
projectId,
|
|
|
|
|
}
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
if (req.xhr) {
|
|
|
|
|
res.sendStatus(204) // Done async via project page notification
|
|
|
|
|
} else {
|
|
|
|
|
res.redirect(`/project/${projectId}`)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const CollaboratorsInviteController = {
|
|
|
|
|
getAllInvites: expressify(getAllInvites),
|
|
|
|
|
inviteToProject: expressify(inviteToProject),
|
|
|
|
|
revokeInvite: expressify(revokeInvite),
|
|
|
|
|
generateNewInvite: expressify(generateNewInvite),
|
|
|
|
|
viewInvite: expressify(viewInvite),
|
|
|
|
|
acceptInvite: expressify(acceptInvite),
|
|
|
|
|
_checkShouldInviteEmail,
|
|
|
|
|
_checkRateLimit,
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export default CollaboratorsInviteController
|
|
|
|
|