TeamInvitesController.js 9.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323
  1. const settings = require('@overleaf/settings')
  2. const logger = require('@overleaf/logger')
  3. const OError = require('@overleaf/o-error')
  4. const TeamInvitesHandler = require('./TeamInvitesHandler')
  5. const SessionManager = require('../Authentication/SessionManager')
  6. const SubscriptionLocator = require('./SubscriptionLocator')
  7. const ErrorController = require('../Errors/ErrorController')
  8. const EmailHelper = require('../Helpers/EmailHelper')
  9. const UserGetter = require('../User/UserGetter')
  10. const { expressify } = require('@overleaf/promise-utils')
  11. const HttpErrorHandler = require('../Errors/HttpErrorHandler')
  12. const PermissionsManager = require('../Authorization/PermissionsManager')
  13. const EmailHandler = require('../Email/EmailHandler')
  14. const { RateLimiter } = require('../../infrastructure/RateLimiter')
  15. const Modules = require('../../infrastructure/Modules')
  16. const UserAuditLogHandler = require('../User/UserAuditLogHandler')
  17. const SplitTestHandler = require('../SplitTests/SplitTestHandler')
  18. const rateLimiters = {
  19. resendGroupInvite: new RateLimiter('resend-group-invite', {
  20. points: 1,
  21. duration: 60 * 60,
  22. }),
  23. }
  24. async function createInvite(req, res, next) {
  25. const teamManagerId = SessionManager.getLoggedInUserId(req.session)
  26. const subscription = req.entity
  27. const email = EmailHelper.parseEmail(req.body.email)
  28. if (!email) {
  29. return res.status(422).json({
  30. error: {
  31. code: 'invalid_email',
  32. message: req.i18n.translate('invalid_email'),
  33. },
  34. })
  35. }
  36. try {
  37. const invitedUserData = await TeamInvitesHandler.promises.createInvite(
  38. teamManagerId,
  39. subscription,
  40. email
  41. )
  42. return res.json({ user: invitedUserData })
  43. } catch (err) {
  44. if (err.alreadyInTeam) {
  45. return res.status(400).json({
  46. error: {
  47. code: 'user_already_added',
  48. message: req.i18n.translate('user_already_added'),
  49. },
  50. })
  51. }
  52. if (err.limitReached) {
  53. return res.status(400).json({
  54. error: {
  55. code: 'group_full',
  56. message: req.i18n.translate('group_full'),
  57. },
  58. })
  59. }
  60. }
  61. }
  62. async function viewInvite(req, res, next) {
  63. const { token } = req.params
  64. const userId = SessionManager.getLoggedInUserId(req.session)
  65. const { invite, subscription } =
  66. await TeamInvitesHandler.promises.getInvite(token)
  67. if (!invite) {
  68. return ErrorController.notFound(req, res)
  69. }
  70. const { variant } = await SplitTestHandler.promises.getAssignment(
  71. req,
  72. res,
  73. 'team-invite-react'
  74. )
  75. let validationStatus = new Map()
  76. if (userId) {
  77. const personalSubscription =
  78. await SubscriptionLocator.promises.getUsersSubscription(userId)
  79. const hasIndividualRecurlySubscription =
  80. personalSubscription &&
  81. personalSubscription.groupPlan === false &&
  82. personalSubscription.recurlyStatus?.state !== 'canceled' &&
  83. personalSubscription.recurlySubscription_id &&
  84. personalSubscription.recurlySubscription_id !== ''
  85. const groupSSOActive = (
  86. await Modules.promises.hooks.fire('hasGroupSSOEnabled', subscription)
  87. )?.[0]
  88. if (subscription?.groupPolicy) {
  89. if (!subscription.populated('groupPolicy')) {
  90. // eslint-disable-next-line no-restricted-syntax
  91. await subscription.populate('groupPolicy')
  92. }
  93. const user = await UserGetter.promises.getUser(userId)
  94. const isUserEnrolledInDifferentGroup =
  95. (
  96. await Modules.promises.hooks.fire(
  97. 'isUserEnrolledInDifferentGroup',
  98. user.enrollment,
  99. subscription._id
  100. )
  101. )?.[0] === true
  102. if (isUserEnrolledInDifferentGroup) {
  103. return HttpErrorHandler.forbidden(
  104. req,
  105. res,
  106. 'User is already enrolled in a different subscription'
  107. )
  108. }
  109. validationStatus =
  110. await PermissionsManager.promises.getUserValidationStatus({
  111. user,
  112. groupPolicy: subscription.groupPolicy,
  113. subscription,
  114. })
  115. let currentManagedUserAdminEmail
  116. try {
  117. currentManagedUserAdminEmail =
  118. await SubscriptionLocator.promises.getAdminEmail(subscription._id)
  119. } catch (err) {
  120. logger.error({ err }, 'error getting subscription admin email')
  121. }
  122. return res.render('subscriptions/team/invite-managed', {
  123. inviterName: invite.inviterName,
  124. inviteToken: invite.token,
  125. expired: req.query.expired,
  126. validationStatus: Object.fromEntries(validationStatus),
  127. currentManagedUserAdminEmail,
  128. groupSSOActive,
  129. subscriptionId: subscription._id.toString(),
  130. })
  131. } else {
  132. let currentManagedUserAdminEmail
  133. try {
  134. currentManagedUserAdminEmail =
  135. await SubscriptionLocator.promises.getAdminEmail(req.managedBy)
  136. } catch (err) {
  137. logger.error({ err }, 'error getting subscription admin email')
  138. }
  139. if (variant === 'enabled') {
  140. return res.render('subscriptions/team/invite-react', {
  141. inviterName: invite.inviterName,
  142. inviteToken: invite.token,
  143. hasIndividualRecurlySubscription,
  144. expired: req.query.expired,
  145. userRestrictions: Array.from(req.userRestrictions || []),
  146. currentManagedUserAdminEmail,
  147. groupSSOActive,
  148. subscriptionId: subscription._id.toString(),
  149. })
  150. } else {
  151. return res.render('subscriptions/team/invite', {
  152. inviterName: invite.inviterName,
  153. inviteToken: invite.token,
  154. hasIndividualRecurlySubscription,
  155. appName: settings.appName,
  156. expired: req.query.expired,
  157. userRestrictions: Array.from(req.userRestrictions || []),
  158. currentManagedUserAdminEmail,
  159. groupSSOActive,
  160. subscriptionId: subscription._id.toString(),
  161. })
  162. }
  163. }
  164. } else {
  165. const userByEmail = await UserGetter.promises.getUserByMainEmail(
  166. invite.email
  167. )
  168. return res.render('subscriptions/team/invite_logged_out', {
  169. inviterName: invite.inviterName,
  170. inviteToken: invite.token,
  171. appName: settings.appName,
  172. accountExists: userByEmail != null,
  173. emailAddress: invite.email,
  174. })
  175. }
  176. }
  177. async function viewInvites(req, res, next) {
  178. const userId = SessionManager.getLoggedInUserId(req.session)
  179. const userEmail = await UserGetter.promises.getUserEmail(userId)
  180. const groupSubscriptions =
  181. await SubscriptionLocator.promises.getGroupsWithTeamInvitesEmail(userEmail)
  182. const teamInvites = groupSubscriptions.map(groupSubscription =>
  183. groupSubscription.teamInvites.find(invite => invite.email === userEmail)
  184. )
  185. return res.render('subscriptions/team/group-invites', {
  186. teamInvites,
  187. })
  188. }
  189. async function acceptInvite(req, res, next) {
  190. const { token } = req.params
  191. const userId = SessionManager.getLoggedInUserId(req.session)
  192. const subscription = await TeamInvitesHandler.promises.acceptInvite(
  193. token,
  194. userId
  195. )
  196. const groupSSOActive = (
  197. await Modules.promises.hooks.fire('hasGroupSSOEnabled', subscription)
  198. )?.[0]
  199. try {
  200. await UserAuditLogHandler.promises.addEntry(
  201. userId,
  202. 'accept-group-invitation',
  203. userId,
  204. req.ip,
  205. { subscriptionId: subscription._id }
  206. )
  207. } catch (e) {
  208. logger.error(
  209. { err: e, userId, subscriptionId: subscription._id },
  210. 'error adding audit log entry'
  211. )
  212. }
  213. res.json({ groupSSOActive })
  214. }
  215. function revokeInvite(req, res, next) {
  216. const subscription = req.entity
  217. const email = EmailHelper.parseEmail(req.params.email)
  218. const teamManagerId = SessionManager.getLoggedInUserId(req.session)
  219. if (!email) {
  220. return res.sendStatus(400)
  221. }
  222. TeamInvitesHandler.revokeInvite(
  223. teamManagerId,
  224. subscription,
  225. email,
  226. function (err, results) {
  227. if (err) {
  228. return next(err)
  229. }
  230. res.sendStatus(204)
  231. }
  232. )
  233. }
  234. async function resendInvite(req, res, next) {
  235. const { entity: subscription } = req
  236. const userEmail = EmailHelper.parseEmail(req.body.email)
  237. await subscription.populate('admin_id', ['email', 'first_name', 'last_name'])
  238. if (!userEmail) {
  239. throw new Error('invalid email')
  240. }
  241. const currentInvite = subscription.teamInvites.find(
  242. invite => invite?.email === userEmail
  243. )
  244. if (!currentInvite) {
  245. return await createInvite(req, res)
  246. }
  247. const opts = {
  248. to: userEmail,
  249. admin: subscription.admin_id,
  250. inviter: currentInvite.inviterName,
  251. acceptInviteUrl: `${settings.siteUrl}/subscription/invites/${currentInvite.token}/`,
  252. reminder: true,
  253. }
  254. try {
  255. await rateLimiters.resendGroupInvite.consume(userEmail, 1, {
  256. method: 'email',
  257. })
  258. const existingUser = await UserGetter.promises.getUserByAnyEmail(userEmail)
  259. let emailTemplate
  260. if (subscription.managedUsersEnabled) {
  261. if (existingUser) {
  262. emailTemplate = 'verifyEmailToJoinManagedUsers'
  263. } else {
  264. emailTemplate = 'inviteNewUserToJoinManagedUsers'
  265. }
  266. } else {
  267. emailTemplate = 'verifyEmailToJoinTeam'
  268. }
  269. EmailHandler.sendDeferredEmail(emailTemplate, opts)
  270. } catch (err) {
  271. if (err?.remainingPoints === 0) {
  272. return res.sendStatus(429)
  273. } else {
  274. throw OError.tag(err, 'Failed to resend group invite email')
  275. }
  276. }
  277. return res.status(200).json({ success: true })
  278. }
  279. module.exports = {
  280. createInvite: expressify(createInvite),
  281. viewInvite: expressify(viewInvite),
  282. viewInvites: expressify(viewInvites),
  283. acceptInvite: expressify(acceptInvite),
  284. revokeInvite,
  285. resendInvite: expressify(resendInvite),
  286. }