TeamInvitesHandler.coffee 6.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176
  1. logger = require("logger-sharelatex")
  2. crypto = require("crypto")
  3. async = require("async")
  4. settings = require("settings-sharelatex")
  5. ObjectId = require("mongojs").ObjectId
  6. TeamInvite = require("../../models/TeamInvite").TeamInvite
  7. Subscription = require("../../models/Subscription").Subscription
  8. UserGetter = require("../User/UserGetter")
  9. SubscriptionLocator = require("./SubscriptionLocator")
  10. SubscriptionUpdater = require("./SubscriptionUpdater")
  11. LimitationsManager = require("./LimitationsManager")
  12. EmailHandler = require("../Email/EmailHandler")
  13. EmailHelper = require("../Helpers/EmailHelper")
  14. Errors = require "../Errors/Errors"
  15. module.exports = TeamInvitesHandler =
  16. getInvite: (token, callback) ->
  17. Subscription.findOne 'teamInvites.token': token, (err, subscription) ->
  18. return callback(err) if err?
  19. return callback(new Errors.NotFoundError('team not found')) unless subscription?
  20. invite = subscription.teamInvites.find (i) -> i.token == token
  21. return callback(null, invite, subscription)
  22. createInvite: (teamManagerId, email, callback) ->
  23. email = EmailHelper.parseEmail(email)
  24. return callback(new Error('invalid email')) if !email?
  25. logger.log {teamManagerId, email}, "Creating manager team invite"
  26. UserGetter.getUser teamManagerId, (error, teamManager) ->
  27. return callback(error) if error?
  28. SubscriptionLocator.getUsersSubscription teamManagerId, (error, subscription) ->
  29. return callback(error) if error?
  30. if teamManager.first_name and teamManager.last_name
  31. inviterName = "#{teamManager.first_name} #{teamManager.last_name} (#{teamManager.email})"
  32. else
  33. inviterName = teamManager.email
  34. removeLegacyInvite subscription.id, email, (error) ->
  35. return callback(error) if error?
  36. createInvite(subscription, email, inviterName, callback)
  37. createDomainInvite: (user, licence, callback) ->
  38. email = EmailHelper.parseEmail(user.email)
  39. return callback(new Error('invalid email')) if !email?
  40. logger.log {licence, email: email}, "Creating domain team invite"
  41. # If name == 'Uni of X License', make the email read only
  42. # 'Uni of X has invited you...'
  43. inviterName = licence.name.replace(/\s+(site\s+)?licence$/i, '')
  44. SubscriptionLocator.getSubscription licence.subscription_id, (error, subscription) ->
  45. return callback(error) if error?
  46. createInvite(subscription, email, inviterName, callback)
  47. importInvite: (subscription, inviterName, email, token, sentAt, callback) ->
  48. checkIfInviteIsPossible subscription, email, (error, possible, reason) ->
  49. return callback(error) if error?
  50. return callback(reason) unless possible
  51. subscription.teamInvites.push({
  52. email: email
  53. inviterName: inviterName
  54. token: token
  55. sentAt: sentAt
  56. })
  57. subscription.save callback
  58. acceptInvite: (token, userId, callback) ->
  59. logger.log {userId}, "Accepting invite"
  60. TeamInvitesHandler.getInvite token, (err, invite, subscription) ->
  61. return callback(err) if err?
  62. return callback(new Errors.NotFoundError('invite not found')) unless invite?
  63. SubscriptionUpdater.addUserToGroup subscription._id, userId, (err) ->
  64. return callback(err) if err?
  65. removeInviteFromTeam(subscription.id, invite.email, callback)
  66. revokeInvite: (teamManagerId, email, callback) ->
  67. email = EmailHelper.parseEmail(email)
  68. return callback(new Error('invalid email')) if !email?
  69. logger.log {teamManagerId, email}, "Revoking invite"
  70. SubscriptionLocator.getUsersSubscription teamManagerId, (err, teamSubscription) ->
  71. return callback(err) if err?
  72. removeInviteFromTeam(teamSubscription.id, email, callback)
  73. # Legacy method to allow a user to receive a confirmation email if their
  74. # email is in Subscription.invited_emails when they join. We'll remove this
  75. # after a short while.
  76. createTeamInvitesForLegacyInvitedEmail: (email, callback) ->
  77. SubscriptionLocator.getGroupsWithEmailInvite email, (err, teams) ->
  78. return callback(err) if err?
  79. async.map teams,
  80. (team, cb) -> TeamInvitesHandler.createInvite(team.admin_id, email, cb)
  81. , callback
  82. createInvite = (subscription, email, inviterName, callback) ->
  83. logger.log {subscriptionId: subscription.id, email, inviterName}, "Creating invite"
  84. checkIfInviteIsPossible subscription, email, (error, possible, reason) ->
  85. return callback(error) if error?
  86. return callback(reason) unless possible
  87. invite = subscription.teamInvites.find (invite) -> invite.email == email
  88. if !invite?
  89. invite = {
  90. email: email
  91. inviterName: inviterName
  92. token: crypto.randomBytes(32).toString("hex")
  93. sentAt: new Date()
  94. }
  95. subscription.teamInvites.push(invite)
  96. else
  97. invite.sentAt = new Date()
  98. subscription.save (error) ->
  99. return callback(error) if error?
  100. opts =
  101. to: email
  102. inviterName: inviterName
  103. acceptInviteUrl: "#{settings.siteUrl}/subscription/invites/#{invite.token}/"
  104. appName: settings.appName
  105. EmailHandler.sendEmail "verifyEmailToJoinTeam", opts, (error) ->
  106. return callback(error, invite)
  107. removeInviteFromTeam = (subscriptionId, email, callback) ->
  108. searchConditions = { _id: new ObjectId(subscriptionId.toString()) }
  109. removeInvite = { $pull: { teamInvites: { email: email } } }
  110. logger.log {subscriptionId, email, searchConditions, removeInvite}, 'removeInviteFromTeam'
  111. async.series [
  112. (cb) -> Subscription.update(searchConditions, removeInvite, cb),
  113. (cb) -> removeLegacyInvite(subscriptionId, email, cb),
  114. ], callback
  115. removeLegacyInvite = (subscriptionId, email, callback) ->
  116. Subscription.update({
  117. _id: new ObjectId(subscriptionId.toString())
  118. }, {
  119. $pull: {
  120. invited_emails: email
  121. }
  122. }, callback)
  123. checkIfInviteIsPossible = (subscription, email, callback = (error, possible, reason) -> ) ->
  124. unless subscription.groupPlan
  125. logger.log {subscriptionId: subscription.id},
  126. "can not add members to a subscription that is not in a group plan"
  127. return callback(null, false, wrongPlan: true)
  128. if LimitationsManager.teamHasReachedMemberLimit(subscription)
  129. logger.log {subscriptionId: subscription.id}, "team has reached member limit"
  130. return callback(null, false, limitReached: true)
  131. UserGetter.getUserByAnyEmail email, (error, existingUser) ->
  132. return callback(error) if error?
  133. return callback(null, true) unless existingUser?
  134. existingMember = subscription.member_ids.find (memberId) ->
  135. memberId.toString() == existingUser._id.toString()
  136. if existingMember
  137. logger.log {subscriptionId: subscription.id, email}, "user already in team"
  138. return callback(null, false, alreadyInTeam: true)
  139. else
  140. return callback(null, true)