Merge pull request #24357 from overleaf/em-reviewers-limit

Count reviewers towards the collaborator limit in the backend

GitOrigin-RevId: 352fa37023ffbb032e23a474a81897ca871fee69
This commit is contained in:
Eric Mc Sween
2025-03-24 10:51:14 +00:00
committed by Copybot
parent e4dae982d2
commit 1fb94dee18
6 changed files with 301 additions and 269 deletions
@@ -14,7 +14,6 @@ import { hasAdminAccess } from '../Helpers/AdminAuthorizationHelper.js'
import TokenAccessHandler from '../TokenAccess/TokenAccessHandler.js'
import ProjectAuditLogHandler from '../Project/ProjectAuditLogHandler.js'
import LimitationsManager from '../Subscription/LimitationsManager.js'
import PrivilegeLevels from '../Authorization/PrivilegeLevels.js'
const ObjectId = mongodb.ObjectId
@@ -80,17 +79,20 @@ async function setCollaboratorInfo(req, res, next) {
const userId = req.params.user_id
const { privilegeLevel } = req.body
if (privilegeLevel !== PrivilegeLevels.READ_ONLY) {
const allowed =
await LimitationsManager.promises.canAddXEditCollaborators(projectId, 1)
if (!allowed) {
return HttpErrorHandler.forbidden(
req,
res,
'edit collaborator limit reached'
)
}
const allowed =
await LimitationsManager.promises.canChangeCollaboratorPrivilegeLevel(
projectId,
userId,
privilegeLevel
)
if (!allowed) {
return HttpErrorHandler.forbidden(
req,
res,
'edit collaborator limit reached'
)
}
await CollaboratorsHandler.promises.setCollaboratorPrivilegeLevel(
projectId,
userId,
@@ -128,12 +128,13 @@ async function getMemberIdPrivilegeLevel(userId, projectId) {
}
async function getInvitedEditCollaboratorCount(projectId) {
// Only counts invited members with readAndWrite privilege
// Counts invited members with editor or reviewer roles
const members = await getMemberIdsWithPrivilegeLevels(projectId)
return members.filter(
m =>
m.source === Sources.INVITE &&
m.privilegeLevel === PrivilegeLevels.READ_AND_WRITE
(m.privilegeLevel === PrivilegeLevels.READ_AND_WRITE ||
m.privilegeLevel === PrivilegeLevels.REVIEW)
).length
}
@@ -1,3 +1,5 @@
// @ts-check
const logger = require('@overleaf/logger')
const ProjectGetter = require('../Project/ProjectGetter')
const UserGetter = require('../User/UserGetter')
@@ -5,6 +7,7 @@ const SubscriptionLocator = require('./SubscriptionLocator')
const Settings = require('@overleaf/settings')
const CollaboratorsGetter = require('../Collaborators/CollaboratorsGetter')
const CollaboratorsInvitesGetter = require('../Collaborators/CollaboratorsInviteGetter')
const PrivilegeLevels = require('../Authorization/PrivilegeLevels')
const {
callbackify,
callbackifyMultiResult,
@@ -58,6 +61,54 @@ async function canAddXEditCollaborators(
)
}
/**
* Check whether a collaborator can be switched to the given privilege level
*
* @param {string} projectId
* @param {string} userId
* @param {'readOnly' | 'review' | 'readAndWrite'} privilegeLevel
* @return {Promise<boolean>}
*/
async function canChangeCollaboratorPrivilegeLevel(
projectId,
userId,
privilegeLevel
) {
if (privilegeLevel === PrivilegeLevels.READ_ONLY) {
return true
}
const currentPrivilegeLevel =
await CollaboratorsGetter.promises.getMemberIdPrivilegeLevel(
userId,
projectId
)
if (
[PrivilegeLevels.READ_AND_WRITE, PrivilegeLevels.REVIEW].includes(
currentPrivilegeLevel
)
) {
// Current collaborator already takes a slot, so changing the privilege
// level won't increase the collaborator count
return true
}
const allowedNumber = await allowedNumberOfCollaboratorsInProject(projectId)
if (allowedNumber < 0) {
// -1 means unlimited
return true
}
const slotsTaken =
await CollaboratorsGetter.promises.getInvitedEditCollaboratorCount(
projectId
)
const inviteCount =
await CollaboratorsInvitesGetter.promises.getEditInviteCount(projectId)
return slotsTaken + inviteCount < allowedNumber
}
async function hasPaidSubscription(user) {
const { hasSubscription, subscription } = await userHasSubscription(user)
const { isMember } = await userIsMemberOfGroupSubscription(user)
@@ -122,6 +173,9 @@ const LimitationsManager = {
allowedNumberOfCollaboratorsForUser
),
canAddXEditCollaborators: callbackify(canAddXEditCollaborators),
canChangeCollaboratorPrivilegeLevel: callbackify(
canChangeCollaboratorPrivilegeLevel
),
hasPaidSubscription: callbackifyMultiResult(hasPaidSubscription, [
'hasPaidSubscription',
'subscription',
@@ -150,6 +204,7 @@ const LimitationsManager = {
allowedNumberOfCollaboratorsForUser,
canAcceptEditCollaboratorInvite,
canAddXEditCollaborators,
canChangeCollaboratorPrivilegeLevel,
hasPaidSubscription,
userHasSubscriptionOrIsGroupMember,
userHasSubscription,