| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285 |
- let TeamInvitesHandler
- const logger = require('@overleaf/logger')
- const crypto = require('crypto')
- const async = require('async')
- const settings = require('@overleaf/settings')
- const { ObjectId } = require('mongodb')
- 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) {
- Subscription.findOne(
- { 'teamInvites.token': token },
- function (err, subscription) {
- if (err) {
- return callback(err)
- }
- if (!subscription) {
- return callback(new Errors.NotFoundError('team not found'))
- }
- const invite = subscription.teamInvites.find(i => i.token === token)
- callback(null, invite, subscription)
- }
- )
- },
- createInvite(teamManagerId, subscription, email, callback) {
- email = EmailHelper.parseEmail(email)
- if (!email) {
- return callback(new Error('invalid email'))
- }
- UserGetter.getUser(teamManagerId, function (error, teamManager) {
- if (error) {
- return callback(error)
- }
- removeLegacyInvite(subscription.id, email, function (error) {
- if (error) {
- return callback(error)
- }
- createInvite(subscription, email, teamManager, callback)
- })
- })
- },
- importInvite(subscription, inviterName, email, token, sentAt, callback) {
- checkIfInviteIsPossible(
- subscription,
- email,
- function (error, possible, reason) {
- if (error) {
- return callback(error)
- }
- if (!possible) {
- return callback(reason)
- }
- subscription.teamInvites.push({
- email,
- inviterName,
- token,
- sentAt,
- })
- subscription.save(callback)
- }
- )
- },
- acceptInvite(token, userId, callback) {
- TeamInvitesHandler.getInvite(token, function (err, invite, subscription) {
- if (err) {
- return callback(err)
- }
- if (!invite) {
- return callback(new Errors.NotFoundError('invite not found'))
- }
- SubscriptionUpdater.addUserToGroup(
- subscription._id,
- userId,
- function (err) {
- if (err) {
- return callback(err)
- }
- removeInviteFromTeam(subscription.id, invite.email, callback)
- }
- )
- })
- },
- revokeInvite(teamManagerId, subscription, email, callback) {
- email = EmailHelper.parseEmail(email)
- if (!email) {
- return callback(new Error('invalid email'))
- }
- removeInviteFromTeam(subscription.id, email, callback)
- },
- // 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) {
- SubscriptionLocator.getGroupsWithEmailInvite(email, function (err, teams) {
- if (err) {
- return callback(err)
- }
- async.map(
- teams,
- (team, cb) =>
- TeamInvitesHandler.createInvite(team.admin_id, team, email, cb),
- callback
- )
- })
- },
- }
- function createInvite(subscription, email, inviter, callback) {
- checkIfInviteIsPossible(
- subscription,
- email,
- function (error, possible, reason) {
- if (error) {
- return callback(error)
- }
- if (!possible) {
- return callback(reason)
- }
- // 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,
- invite: false,
- }
- callback(error, inviteUserData)
- })
- }
- )
- }
- const inviterName = getInviterName(inviter)
- let invite = subscription.teamInvites.find(
- invite => invite.email === email
- )
- if (invite) {
- invite = invite.toObject()
- invite.sentAt = new Date()
- } else {
- invite = {
- email,
- inviterName,
- token: crypto.randomBytes(32).toString('hex'),
- sentAt: new Date(),
- }
- subscription.teamInvites.push(invite)
- }
- subscription.save(function (error) {
- if (error) {
- return callback(error)
- }
- const opts = {
- to: email,
- inviter,
- acceptInviteUrl: `${settings.siteUrl}/subscription/invites/${invite.token}/`,
- appName: settings.appName,
- }
- EmailHandler.sendEmail('verifyEmailToJoinTeam', opts, error => {
- Object.assign(invite, { invite: true })
- callback(error, invite)
- })
- })
- }
- )
- }
- function removeInviteFromTeam(subscriptionId, email, callback) {
- const searchConditions = { _id: new ObjectId(subscriptionId.toString()) }
- const removeInvite = { $pull: { teamInvites: { email } } }
- async.series(
- [
- cb => Subscription.updateOne(searchConditions, removeInvite, cb),
- cb => removeLegacyInvite(subscriptionId, email, cb),
- ],
- callback
- )
- }
- const removeLegacyInvite = (subscriptionId, email, callback) =>
- Subscription.updateOne(
- {
- _id: new ObjectId(subscriptionId.toString()),
- },
- {
- $pull: {
- invited_emails: email,
- },
- },
- callback
- )
- function checkIfInviteIsPossible(subscription, email, callback) {
- if (!subscription.groupPlan) {
- logger.debug(
- { 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.debug(
- { subscriptionId: subscription.id },
- 'team has reached member limit'
- )
- return callback(null, false, { limitReached: true })
- }
- UserGetter.getUserByAnyEmail(email, function (error, existingUser) {
- if (error) {
- return callback(error)
- }
- if (!existingUser) {
- return callback(null, true)
- }
- const existingMember = subscription.member_ids.find(
- memberId => memberId.toString() === existingUser._id.toString()
- )
- if (existingMember) {
- logger.debug(
- { subscriptionId: subscription.id, email },
- 'user already in team'
- )
- callback(null, false, { alreadyInTeam: true })
- } else {
- callback(null, true)
- }
- })
- }
- function getInviterName(inviter) {
- let inviterName
- if (inviter.first_name && inviter.last_name) {
- inviterName = `${inviter.first_name} ${inviter.last_name} (${inviter.email})`
- } else {
- inviterName = inviter.email
- }
- return inviterName
- }
|