logger = require("logger-sharelatex") crypto = require("crypto") async = require("async") settings = require("settings-sharelatex") ObjectId = require("mongojs").ObjectId TeamInvite = require("../../models/TeamInvite").TeamInvite Subscription = require("../../models/Subscription").Subscription UserGetter = require("../User/UserGetter") SubscriptionLocator = require("./SubscriptionLocator") SubscriptionUpdater = require("./SubscriptionUpdater") LimitationsManager = require("./LimitationsManager") EmailHandler = require("../Email/EmailHandler") EmailHelper = require("../Helpers/EmailHelper") Errors = require "../Errors/Errors" module.exports = TeamInvitesHandler = getInvite: (token, callback) -> Subscription.findOne 'teamInvites.token': token, (err, subscription) -> return callback(err) if err? return callback(new Errors.NotFoundError('team not found')) unless subscription? invite = subscription.teamInvites.find (i) -> i.token == token return callback(null, invite, subscription) createInvite: (teamManagerId, email, callback) -> email = EmailHelper.parseEmail(email) return callback(new Error('invalid email')) if !email? logger.log {teamManagerId, email}, "Creating manager team invite" UserGetter.getUser teamManagerId, (error, teamManager) -> return callback(error) if error? SubscriptionLocator.getUsersSubscription teamManagerId, (error, subscription) -> return callback(error) if error? if teamManager.first_name and teamManager.last_name inviterName = "#{teamManager.first_name} #{teamManager.last_name} (#{teamManager.email})" else inviterName = teamManager.email removeLegacyInvite subscription.id, email, (error) -> return callback(error) if error? createInvite(subscription, email, inviterName, callback) createDomainInvite: (user, licence, callback) -> email = EmailHelper.parseEmail(user.email) return callback(new Error('invalid email')) if !email? logger.log {licence, email: email}, "Creating domain team invite" # If name == 'Uni of X License', make the email read only # 'Uni of X has invited you...' inviterName = licence.name.replace(/\s+(site\s+)?licence$/i, '') SubscriptionLocator.getSubscription licence.subscription_id, (error, subscription) -> return callback(error) if error? createInvite(subscription, email, inviterName, callback) importInvite: (subscription, inviterName, email, token, sentAt, callback) -> checkIfInviteIsPossible subscription, email, (error, possible, reason) -> return callback(error) if error? return callback(reason) unless possible subscription.teamInvites.push({ email: email inviterName: inviterName token: token sentAt: sentAt }) subscription.save callback acceptInvite: (token, userId, callback) -> logger.log {userId}, "Accepting invite" TeamInvitesHandler.getInvite token, (err, invite, subscription) -> return callback(err) if err? return callback(new Errors.NotFoundError('invite not found')) unless invite? SubscriptionUpdater.addUserToGroup subscription._id, userId, (err) -> return callback(err) if err? removeInviteFromTeam(subscription.id, invite.email, callback) revokeInvite: (teamManagerId, email, callback) -> email = EmailHelper.parseEmail(email) return callback(new Error('invalid email')) if !email? logger.log {teamManagerId, email}, "Revoking invite" SubscriptionLocator.getUsersSubscription teamManagerId, (err, teamSubscription) -> return callback(err) if err? removeInviteFromTeam(teamSubscription.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, (err, teams) -> return callback(err) if err? async.map teams, (team, cb) -> TeamInvitesHandler.createInvite(team.admin_id, email, cb) , callback createInvite = (subscription, email, inviterName, callback) -> logger.log {subscriptionId: subscription.id, email, inviterName}, "Creating invite" checkIfInviteIsPossible subscription, email, (error, possible, reason) -> return callback(error) if error? return callback(reason) unless possible invite = subscription.teamInvites.find (invite) -> invite.email == email if !invite? invite = { email: email inviterName: inviterName token: crypto.randomBytes(32).toString("hex") sentAt: new Date() } subscription.teamInvites.push(invite) else invite.sentAt = new Date() subscription.save (error) -> return callback(error) if error? opts = to: email inviterName: inviterName acceptInviteUrl: "#{settings.siteUrl}/subscription/invites/#{invite.token}/" appName: settings.appName EmailHandler.sendEmail "verifyEmailToJoinTeam", opts, (error) -> return callback(error, invite) removeInviteFromTeam = (subscriptionId, email, callback) -> searchConditions = { _id: new ObjectId(subscriptionId.toString()) } removeInvite = { $pull: { teamInvites: { email: email } } } logger.log {subscriptionId, email, searchConditions, removeInvite}, 'removeInviteFromTeam' async.series [ (cb) -> Subscription.update(searchConditions, removeInvite, cb), (cb) -> removeLegacyInvite(subscriptionId, email, cb), ], callback removeLegacyInvite = (subscriptionId, email, callback) -> Subscription.update({ _id: new ObjectId(subscriptionId.toString()) }, { $pull: { invited_emails: email } }, callback) checkIfInviteIsPossible = (subscription, email, callback = (error, possible, reason) -> ) -> unless 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) UserGetter.getUserByAnyEmail email, (error, existingUser) -> return callback(error) if error? return callback(null, true) unless existingUser? existingMember = subscription.member_ids.find (memberId) -> memberId.toString() == existingUser._id.toString() if existingMember logger.log {subscriptionId: subscription.id, email}, "user already in team" return callback(null, false, alreadyInTeam: true) else return callback(null, true)