Files
overleaf-cep/services/web/app/src/Features/Subscription/TeamInvitesController.js
Jessica Lawshe 90c86cd1e9 Merge pull request #17841 from overleaf/jel-lint-populate
[web] Add linting rule for mongoose `populate`

GitOrigin-RevId: 625b2b5f9db4e88ce0d629752f083b8be71c7766
2024-04-12 08:06:18 +00:00

304 lines
8.7 KiB
JavaScript

const settings = require('@overleaf/settings')
const logger = require('@overleaf/logger')
const OError = require('@overleaf/o-error')
const TeamInvitesHandler = require('./TeamInvitesHandler')
const SessionManager = require('../Authentication/SessionManager')
const SubscriptionLocator = require('./SubscriptionLocator')
const ErrorController = require('../Errors/ErrorController')
const EmailHelper = require('../Helpers/EmailHelper')
const UserGetter = require('../User/UserGetter')
const { expressify } = require('@overleaf/promise-utils')
const HttpErrorHandler = require('../Errors/HttpErrorHandler')
const PermissionsManager = require('../Authorization/PermissionsManager')
const EmailHandler = require('../Email/EmailHandler')
const { RateLimiter } = require('../../infrastructure/RateLimiter')
const Modules = require('../../infrastructure/Modules')
const UserAuditLogHandler = require('../User/UserAuditLogHandler')
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)
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,
appName: settings.appName,
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)
)
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 })
}
module.exports = {
createInvite: expressify(createInvite),
viewInvite: expressify(viewInvite),
viewInvites: expressify(viewInvites),
acceptInvite: expressify(acceptInvite),
revokeInvite,
resendInvite: expressify(resendInvite),
}