| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316 |
- import settings from '@overleaf/settings'
- import logger from '@overleaf/logger'
- import OError from '@overleaf/o-error'
- import TeamInvitesHandler from './TeamInvitesHandler.js'
- import SessionManager from '../Authentication/SessionManager.js'
- import SubscriptionLocator from './SubscriptionLocator.js'
- import ErrorController from '../Errors/ErrorController.js'
- import EmailHelper from '../Helpers/EmailHelper.js'
- import UserGetter from '../User/UserGetter.js'
- import { expressify } from '@overleaf/promise-utils'
- import HttpErrorHandler from '../Errors/HttpErrorHandler.js'
- import PermissionsManager from '../Authorization/PermissionsManager.js'
- import EmailHandler from '../Email/EmailHandler.js'
- import { RateLimiter } from '../../infrastructure/RateLimiter.js'
- import Modules from '../../infrastructure/Modules.js'
- import UserAuditLogHandler from '../User/UserAuditLogHandler.js'
- import SplitTestHandler from '../SplitTests/SplitTestHandler.js'
- const rateLimiters = {
- resendGroupInvite: new RateLimiter('resend-group-invite', {
- points: 1,
- duration: 60 * 60,
- }),
- }
- async function createInvite(req, res, next) {
- const teamManagerId = SessionManager.getLoggedInUserId(req.session)
- const subscription = req.entity
- const email = EmailHelper.parseEmail(req.body.email)
- if (!email) {
- return res.status(422).json({
- error: {
- code: 'invalid_email',
- message: req.i18n.translate('invalid_email'),
- },
- })
- }
- try {
- const invitedUserData = await TeamInvitesHandler.promises.createInvite(
- teamManagerId,
- subscription,
- email
- )
- return res.json({ user: invitedUserData })
- } catch (err) {
- if (err.alreadyInTeam) {
- return res.status(400).json({
- error: {
- code: 'user_already_added',
- message: req.i18n.translate('user_already_added'),
- },
- })
- }
- if (err.limitReached) {
- return res.status(400).json({
- error: {
- code: 'group_full',
- message: req.i18n.translate('group_full'),
- },
- })
- }
- }
- }
- async function viewInvite(req, res, next) {
- const { token } = req.params
- const userId = SessionManager.getLoggedInUserId(req.session)
- const { invite, subscription } =
- await TeamInvitesHandler.promises.getInvite(token)
- await SplitTestHandler.promises.getAssignment(
- req,
- res,
- 'bootstrap-5-subscription'
- )
- if (!invite) {
- return ErrorController.notFound(req, res)
- }
- let validationStatus = new Map()
- if (userId) {
- const personalSubscription =
- await SubscriptionLocator.promises.getUsersSubscription(userId)
- const hasIndividualRecurlySubscription =
- personalSubscription &&
- personalSubscription.groupPlan === false &&
- personalSubscription.recurlyStatus?.state !== 'canceled' &&
- personalSubscription.recurlySubscription_id &&
- personalSubscription.recurlySubscription_id !== ''
- const groupSSOActive = (
- await Modules.promises.hooks.fire('hasGroupSSOEnabled', subscription)
- )?.[0]
- if (subscription?.groupPolicy) {
- if (!subscription.populated('groupPolicy')) {
- // eslint-disable-next-line no-restricted-syntax
- await subscription.populate('groupPolicy')
- }
- const user = await UserGetter.promises.getUser(userId)
- const isUserEnrolledInDifferentGroup =
- (
- await Modules.promises.hooks.fire(
- 'isUserEnrolledInDifferentGroup',
- user.enrollment,
- subscription._id
- )
- )?.[0] === true
- if (isUserEnrolledInDifferentGroup) {
- return HttpErrorHandler.forbidden(
- req,
- res,
- 'User is already enrolled in a different subscription'
- )
- }
- validationStatus =
- await PermissionsManager.promises.getUserValidationStatus({
- user,
- groupPolicy: subscription.groupPolicy,
- subscription,
- })
- let currentManagedUserAdminEmail
- try {
- currentManagedUserAdminEmail =
- await SubscriptionLocator.promises.getAdminEmail(subscription._id)
- } catch (err) {
- logger.error({ err }, 'error getting subscription admin email')
- }
- return res.render('subscriptions/team/invite-managed', {
- inviterName: invite.inviterName,
- inviteToken: invite.token,
- expired: req.query.expired,
- validationStatus: Object.fromEntries(validationStatus),
- currentManagedUserAdminEmail,
- groupSSOActive,
- subscriptionId: subscription._id.toString(),
- })
- } else {
- let currentManagedUserAdminEmail
- try {
- currentManagedUserAdminEmail =
- await SubscriptionLocator.promises.getAdminEmail(req.managedBy)
- } catch (err) {
- logger.error({ err }, 'error getting subscription admin email')
- }
- return res.render('subscriptions/team/invite', {
- inviterName: invite.inviterName,
- inviteToken: invite.token,
- hasIndividualRecurlySubscription,
- expired: req.query.expired,
- userRestrictions: Array.from(req.userRestrictions || []),
- currentManagedUserAdminEmail,
- groupSSOActive,
- subscriptionId: subscription._id.toString(),
- })
- }
- } else {
- const userByEmail = await UserGetter.promises.getUserByMainEmail(
- invite.email
- )
- return res.render('subscriptions/team/invite_logged_out', {
- inviterName: invite.inviterName,
- inviteToken: invite.token,
- appName: settings.appName,
- accountExists: userByEmail != null,
- emailAddress: invite.email,
- })
- }
- }
- async function viewInvites(req, res, next) {
- const userId = SessionManager.getLoggedInUserId(req.session)
- const userEmail = await UserGetter.promises.getUserEmail(userId)
- const groupSubscriptions =
- await SubscriptionLocator.promises.getGroupsWithTeamInvitesEmail(userEmail)
- const teamInvites = groupSubscriptions.map(groupSubscription =>
- groupSubscription.teamInvites.find(invite => invite.email === userEmail)
- )
- await SplitTestHandler.promises.getAssignment(
- req,
- res,
- 'bootstrap-5-subscription'
- )
- return res.render('subscriptions/team/group-invites', {
- teamInvites,
- })
- }
- async function acceptInvite(req, res, next) {
- const { token } = req.params
- const userId = SessionManager.getLoggedInUserId(req.session)
- const subscription = await TeamInvitesHandler.promises.acceptInvite(
- token,
- userId
- )
- const groupSSOActive = (
- await Modules.promises.hooks.fire('hasGroupSSOEnabled', subscription)
- )?.[0]
- try {
- await UserAuditLogHandler.promises.addEntry(
- userId,
- 'accept-group-invitation',
- userId,
- req.ip,
- { subscriptionId: subscription._id }
- )
- } catch (e) {
- logger.error(
- { err: e, userId, subscriptionId: subscription._id },
- 'error adding audit log entry'
- )
- }
- res.json({ groupSSOActive })
- }
- function revokeInvite(req, res, next) {
- const subscription = req.entity
- const email = EmailHelper.parseEmail(req.params.email)
- const teamManagerId = SessionManager.getLoggedInUserId(req.session)
- if (!email) {
- return res.sendStatus(400)
- }
- TeamInvitesHandler.revokeInvite(
- teamManagerId,
- subscription,
- email,
- function (err, results) {
- if (err) {
- return next(err)
- }
- res.sendStatus(204)
- }
- )
- }
- async function resendInvite(req, res, next) {
- const { entity: subscription } = req
- const userEmail = EmailHelper.parseEmail(req.body.email)
- await subscription.populate('admin_id', ['email', 'first_name', 'last_name'])
- if (!userEmail) {
- throw new Error('invalid email')
- }
- const currentInvite = subscription.teamInvites.find(
- invite => invite?.email === userEmail
- )
- if (!currentInvite) {
- return await createInvite(req, res)
- }
- const opts = {
- to: userEmail,
- admin: subscription.admin_id,
- inviter: currentInvite.inviterName,
- acceptInviteUrl: `${settings.siteUrl}/subscription/invites/${currentInvite.token}/`,
- reminder: true,
- }
- try {
- await rateLimiters.resendGroupInvite.consume(userEmail, 1, {
- method: 'email',
- })
- const existingUser = await UserGetter.promises.getUserByAnyEmail(userEmail)
- let emailTemplate
- if (subscription.managedUsersEnabled) {
- if (existingUser) {
- emailTemplate = 'verifyEmailToJoinManagedUsers'
- } else {
- emailTemplate = 'inviteNewUserToJoinManagedUsers'
- }
- } else {
- emailTemplate = 'verifyEmailToJoinTeam'
- }
- EmailHandler.sendDeferredEmail(emailTemplate, opts)
- } catch (err) {
- if (err?.remainingPoints === 0) {
- return res.sendStatus(429)
- } else {
- throw OError.tag(err, 'Failed to resend group invite email')
- }
- }
- return res.status(200).json({ success: true })
- }
- export default {
- createInvite: expressify(createInvite),
- viewInvite: expressify(viewInvite),
- viewInvites: expressify(viewInvites),
- acceptInvite: expressify(acceptInvite),
- revokeInvite,
- resendInvite: expressify(resendInvite),
- }
|