2019-05-29 05:21:06 -04:00
|
|
|
let TeamInvitesHandler
|
2021-11-01 11:05:16 -04:00
|
|
|
const logger = require('@overleaf/logger')
|
2019-05-29 05:21:06 -04:00
|
|
|
const crypto = require('crypto')
|
|
|
|
const async = require('async')
|
|
|
|
|
2021-07-07 05:38:56 -04:00
|
|
|
const settings = require('@overleaf/settings')
|
2020-09-23 04:49:26 -04:00
|
|
|
const { ObjectId } = require('mongodb')
|
2019-05-29 05:21:06 -04:00
|
|
|
|
|
|
|
const { Subscription } = require('../../models/Subscription')
|
|
|
|
|
|
|
|
const UserGetter = require('../User/UserGetter')
|
|
|
|
const SubscriptionLocator = require('./SubscriptionLocator')
|
|
|
|
const SubscriptionUpdater = require('./SubscriptionUpdater')
|
|
|
|
const LimitationsManager = require('./LimitationsManager')
|
|
|
|
|
|
|
|
const EmailHandler = require('../Email/EmailHandler')
|
|
|
|
const EmailHelper = require('../Helpers/EmailHelper')
|
|
|
|
|
|
|
|
const Errors = require('../Errors/Errors')
|
|
|
|
|
|
|
|
module.exports = TeamInvitesHandler = {
|
|
|
|
getInvite(token, callback) {
|
2021-04-14 09:17:21 -04:00
|
|
|
return Subscription.findOne(
|
|
|
|
{ 'teamInvites.token': token },
|
|
|
|
function (err, subscription) {
|
|
|
|
if (err) {
|
|
|
|
return callback(err)
|
|
|
|
}
|
|
|
|
if (!subscription) {
|
|
|
|
return callback(new Errors.NotFoundError('team not found'))
|
|
|
|
}
|
2019-05-29 05:21:06 -04:00
|
|
|
|
2021-04-14 09:17:21 -04:00
|
|
|
const invite = subscription.teamInvites.find(i => i.token === token)
|
|
|
|
callback(null, invite, subscription)
|
|
|
|
}
|
|
|
|
)
|
2019-05-29 05:21:06 -04:00
|
|
|
},
|
|
|
|
|
|
|
|
createInvite(teamManagerId, subscription, email, callback) {
|
|
|
|
email = EmailHelper.parseEmail(email)
|
2019-06-11 09:14:08 -04:00
|
|
|
if (!email) {
|
2019-05-29 05:21:06 -04:00
|
|
|
return callback(new Error('invalid email'))
|
|
|
|
}
|
2021-04-14 09:17:21 -04:00
|
|
|
return UserGetter.getUser(teamManagerId, function (error, teamManager) {
|
2019-06-11 09:14:08 -04:00
|
|
|
if (error) {
|
2019-05-29 05:21:06 -04:00
|
|
|
return callback(error)
|
|
|
|
}
|
|
|
|
|
2021-04-14 09:17:21 -04:00
|
|
|
removeLegacyInvite(subscription.id, email, function (error) {
|
2019-06-11 09:14:08 -04:00
|
|
|
if (error) {
|
2019-05-29 05:21:06 -04:00
|
|
|
return callback(error)
|
|
|
|
}
|
2019-06-11 09:14:08 -04:00
|
|
|
createInvite(subscription, email, teamManager, callback)
|
2019-05-29 05:21:06 -04:00
|
|
|
})
|
|
|
|
})
|
|
|
|
},
|
|
|
|
|
|
|
|
importInvite(subscription, inviterName, email, token, sentAt, callback) {
|
2021-04-14 09:17:21 -04:00
|
|
|
checkIfInviteIsPossible(
|
|
|
|
subscription,
|
|
|
|
email,
|
|
|
|
function (error, possible, reason) {
|
|
|
|
if (error) {
|
|
|
|
return callback(error)
|
|
|
|
}
|
|
|
|
if (!possible) {
|
|
|
|
return callback(reason)
|
|
|
|
}
|
2019-05-29 05:21:06 -04:00
|
|
|
|
2021-04-14 09:17:21 -04:00
|
|
|
subscription.teamInvites.push({
|
|
|
|
email,
|
|
|
|
inviterName,
|
|
|
|
token,
|
2021-04-27 03:52:58 -04:00
|
|
|
sentAt,
|
2021-04-14 09:17:21 -04:00
|
|
|
})
|
2019-05-29 05:21:06 -04:00
|
|
|
|
2021-04-14 09:17:21 -04:00
|
|
|
subscription.save(callback)
|
|
|
|
}
|
|
|
|
)
|
2019-05-29 05:21:06 -04:00
|
|
|
},
|
|
|
|
|
|
|
|
acceptInvite(token, userId, callback) {
|
2021-04-14 09:17:21 -04:00
|
|
|
TeamInvitesHandler.getInvite(token, function (err, invite, subscription) {
|
2019-06-11 09:14:08 -04:00
|
|
|
if (err) {
|
2019-05-29 05:21:06 -04:00
|
|
|
return callback(err)
|
|
|
|
}
|
2019-06-11 09:14:08 -04:00
|
|
|
if (!invite) {
|
2019-05-29 05:21:06 -04:00
|
|
|
return callback(new Errors.NotFoundError('invite not found'))
|
|
|
|
}
|
|
|
|
|
2021-04-14 09:17:21 -04:00
|
|
|
SubscriptionUpdater.addUserToGroup(
|
|
|
|
subscription._id,
|
|
|
|
userId,
|
|
|
|
function (err) {
|
|
|
|
if (err) {
|
|
|
|
return callback(err)
|
|
|
|
}
|
2019-06-11 09:14:08 -04:00
|
|
|
|
2021-04-14 09:17:21 -04:00
|
|
|
removeInviteFromTeam(subscription.id, invite.email, callback)
|
|
|
|
}
|
|
|
|
)
|
2019-05-29 05:21:06 -04:00
|
|
|
})
|
|
|
|
},
|
|
|
|
|
|
|
|
revokeInvite(teamManagerId, subscription, email, callback) {
|
|
|
|
email = EmailHelper.parseEmail(email)
|
2019-06-11 09:14:08 -04:00
|
|
|
if (!email) {
|
2019-05-29 05:21:06 -04:00
|
|
|
return callback(new Error('invalid email'))
|
|
|
|
}
|
2019-06-11 09:14:08 -04:00
|
|
|
removeInviteFromTeam(subscription.id, email, callback)
|
2019-05-29 05:21:06 -04:00
|
|
|
},
|
|
|
|
|
|
|
|
// Legacy method to allow a user to receive a confirmation email if their
|
|
|
|
// email is in Subscription.invited_emails when they join. We'll remove this
|
|
|
|
// after a short while.
|
|
|
|
createTeamInvitesForLegacyInvitedEmail(email, callback) {
|
2021-04-14 09:17:21 -04:00
|
|
|
SubscriptionLocator.getGroupsWithEmailInvite(email, function (err, teams) {
|
2019-06-11 09:14:08 -04:00
|
|
|
if (err) {
|
2019-05-29 05:21:06 -04:00
|
|
|
return callback(err)
|
|
|
|
}
|
|
|
|
|
2019-06-11 09:14:08 -04:00
|
|
|
async.map(
|
2019-05-29 05:21:06 -04:00
|
|
|
teams,
|
|
|
|
(team, cb) =>
|
|
|
|
TeamInvitesHandler.createInvite(team.admin_id, team, email, cb),
|
|
|
|
callback
|
|
|
|
)
|
|
|
|
})
|
2021-04-27 03:52:58 -04:00
|
|
|
},
|
2019-05-29 05:21:06 -04:00
|
|
|
}
|
|
|
|
|
2021-10-26 04:08:56 -04:00
|
|
|
function createInvite(subscription, email, inviter, callback) {
|
2021-04-14 09:17:21 -04:00
|
|
|
checkIfInviteIsPossible(
|
|
|
|
subscription,
|
|
|
|
email,
|
|
|
|
function (error, possible, reason) {
|
|
|
|
if (error) {
|
|
|
|
return callback(error)
|
|
|
|
}
|
|
|
|
if (!possible) {
|
|
|
|
return callback(reason)
|
|
|
|
}
|
2019-05-29 05:21:06 -04:00
|
|
|
|
2021-04-14 09:17:21 -04:00
|
|
|
// don't send invites when inviting self; add user directly to the group
|
|
|
|
const isInvitingSelf = inviter.emails.some(
|
|
|
|
emailData => emailData.email === email
|
|
|
|
)
|
|
|
|
if (isInvitingSelf) {
|
|
|
|
return SubscriptionUpdater.addUserToGroup(
|
|
|
|
subscription._id,
|
|
|
|
inviter._id,
|
|
|
|
err => {
|
|
|
|
if (err) {
|
|
|
|
return callback(err)
|
|
|
|
}
|
|
|
|
|
|
|
|
// legacy: remove any invite that might have been created in the past
|
|
|
|
removeInviteFromTeam(subscription._id, email, error => {
|
|
|
|
const inviteUserData = {
|
|
|
|
email: inviter.email,
|
|
|
|
first_name: inviter.first_name,
|
|
|
|
last_name: inviter.last_name,
|
2021-04-27 03:52:58 -04:00
|
|
|
invite: false,
|
2021-04-14 09:17:21 -04:00
|
|
|
}
|
|
|
|
callback(error, inviteUserData)
|
|
|
|
})
|
2019-06-11 09:14:08 -04:00
|
|
|
}
|
2021-04-14 09:17:21 -04:00
|
|
|
)
|
|
|
|
}
|
2019-06-11 09:14:08 -04:00
|
|
|
|
2021-04-14 09:17:21 -04:00
|
|
|
const inviterName = getInviterName(inviter)
|
|
|
|
let invite = subscription.teamInvites.find(
|
|
|
|
invite => invite.email === email
|
2019-06-11 09:14:08 -04:00
|
|
|
)
|
2019-05-29 05:21:06 -04:00
|
|
|
|
2021-04-14 09:17:21 -04:00
|
|
|
if (invite) {
|
|
|
|
invite.sentAt = new Date()
|
|
|
|
} else {
|
|
|
|
invite = {
|
|
|
|
email,
|
|
|
|
inviterName,
|
|
|
|
token: crypto.randomBytes(32).toString('hex'),
|
2021-04-27 03:52:58 -04:00
|
|
|
sentAt: new Date(),
|
2021-04-14 09:17:21 -04:00
|
|
|
}
|
|
|
|
subscription.teamInvites.push(invite)
|
2019-05-29 05:21:06 -04:00
|
|
|
}
|
|
|
|
|
2021-04-14 09:17:21 -04:00
|
|
|
subscription.save(function (error) {
|
|
|
|
if (error) {
|
|
|
|
return callback(error)
|
|
|
|
}
|
2019-05-29 05:21:06 -04:00
|
|
|
|
2021-04-14 09:17:21 -04:00
|
|
|
const opts = {
|
|
|
|
to: email,
|
|
|
|
inviter,
|
|
|
|
acceptInviteUrl: `${settings.siteUrl}/subscription/invites/${invite.token}/`,
|
2021-04-27 03:52:58 -04:00
|
|
|
appName: settings.appName,
|
2021-04-14 09:17:21 -04:00
|
|
|
}
|
|
|
|
EmailHandler.sendEmail('verifyEmailToJoinTeam', opts, error => {
|
|
|
|
Object.assign(invite, { invite: true })
|
|
|
|
callback(error, invite)
|
|
|
|
})
|
2019-06-17 10:46:08 -04:00
|
|
|
})
|
2021-04-14 09:17:21 -04:00
|
|
|
}
|
|
|
|
)
|
2019-05-29 05:21:06 -04:00
|
|
|
}
|
|
|
|
|
2021-10-26 04:08:56 -04:00
|
|
|
function removeInviteFromTeam(subscriptionId, email, callback) {
|
2019-05-29 05:21:06 -04:00
|
|
|
const searchConditions = { _id: new ObjectId(subscriptionId.toString()) }
|
|
|
|
const removeInvite = { $pull: { teamInvites: { email } } }
|
|
|
|
|
2019-06-11 09:14:08 -04:00
|
|
|
async.series(
|
2019-05-29 05:21:06 -04:00
|
|
|
[
|
2020-11-03 04:19:05 -05:00
|
|
|
cb => Subscription.updateOne(searchConditions, removeInvite, cb),
|
2021-04-27 03:52:58 -04:00
|
|
|
cb => removeLegacyInvite(subscriptionId, email, cb),
|
2019-05-29 05:21:06 -04:00
|
|
|
],
|
|
|
|
callback
|
|
|
|
)
|
|
|
|
}
|
|
|
|
|
2021-10-26 04:08:56 -04:00
|
|
|
const removeLegacyInvite = (subscriptionId, email, callback) =>
|
2020-11-03 04:19:05 -05:00
|
|
|
Subscription.updateOne(
|
2019-05-29 05:21:06 -04:00
|
|
|
{
|
2021-04-27 03:52:58 -04:00
|
|
|
_id: new ObjectId(subscriptionId.toString()),
|
2019-05-29 05:21:06 -04:00
|
|
|
},
|
|
|
|
{
|
|
|
|
$pull: {
|
2021-04-27 03:52:58 -04:00
|
|
|
invited_emails: email,
|
|
|
|
},
|
2019-05-29 05:21:06 -04:00
|
|
|
},
|
|
|
|
callback
|
|
|
|
)
|
|
|
|
|
2021-10-26 04:08:56 -04:00
|
|
|
function checkIfInviteIsPossible(subscription, email, callback) {
|
2019-05-29 05:21:06 -04:00
|
|
|
if (!subscription.groupPlan) {
|
|
|
|
logger.log(
|
|
|
|
{ subscriptionId: subscription.id },
|
|
|
|
'can not add members to a subscription that is not in a group plan'
|
|
|
|
)
|
|
|
|
return callback(null, false, { wrongPlan: true })
|
|
|
|
}
|
|
|
|
|
|
|
|
if (LimitationsManager.teamHasReachedMemberLimit(subscription)) {
|
|
|
|
logger.log(
|
|
|
|
{ subscriptionId: subscription.id },
|
|
|
|
'team has reached member limit'
|
|
|
|
)
|
|
|
|
return callback(null, false, { limitReached: true })
|
|
|
|
}
|
|
|
|
|
2021-04-14 09:17:21 -04:00
|
|
|
UserGetter.getUserByAnyEmail(email, function (error, existingUser) {
|
2019-06-11 09:14:08 -04:00
|
|
|
if (error) {
|
2019-05-29 05:21:06 -04:00
|
|
|
return callback(error)
|
|
|
|
}
|
2019-06-11 09:14:08 -04:00
|
|
|
if (!existingUser) {
|
2019-05-29 05:21:06 -04:00
|
|
|
return callback(null, true)
|
|
|
|
}
|
|
|
|
|
|
|
|
const existingMember = subscription.member_ids.find(
|
|
|
|
memberId => memberId.toString() === existingUser._id.toString()
|
|
|
|
)
|
|
|
|
|
|
|
|
if (existingMember) {
|
|
|
|
logger.log(
|
|
|
|
{ subscriptionId: subscription.id, email },
|
|
|
|
'user already in team'
|
|
|
|
)
|
2019-06-11 09:14:08 -04:00
|
|
|
callback(null, false, { alreadyInTeam: true })
|
2019-05-29 05:21:06 -04:00
|
|
|
} else {
|
2019-06-11 09:14:08 -04:00
|
|
|
callback(null, true)
|
2019-05-29 05:21:06 -04:00
|
|
|
}
|
|
|
|
})
|
|
|
|
}
|
2019-06-11 09:14:08 -04:00
|
|
|
|
2021-10-26 04:08:56 -04:00
|
|
|
function getInviterName(inviter) {
|
2019-06-11 09:14:08 -04:00
|
|
|
let inviterName
|
|
|
|
if (inviter.first_name && inviter.last_name) {
|
2020-12-15 05:23:54 -05:00
|
|
|
inviterName = `${inviter.first_name} ${inviter.last_name} (${inviter.email})`
|
2019-06-11 09:14:08 -04:00
|
|
|
} else {
|
|
|
|
inviterName = inviter.email
|
|
|
|
}
|
|
|
|
|
|
|
|
return inviterName
|
|
|
|
}
|