[web] Project collaboration events for Group Audit Logs (#30055)

* [web] Project collaboration events for Group Audit Logs

* Using `role` instead of `privileges` for `(send|revoke)-invite` audit log

GitOrigin-RevId: 88961afcc0580243ba9c8ab661b8fc1ca127775e
This commit is contained in:
Miguel Serrano
2026-02-13 09:05:43 +00:00
committed by Copybot
parent 860f77a06c
commit 05d04e864d
5 changed files with 41 additions and 20 deletions
@@ -7,6 +7,7 @@ import ContactManager from '../Contacts/ContactManager.mjs'
import PrivilegeLevels from '../Authorization/PrivilegeLevels.mjs'
import TpdsProjectFlusher from '../ThirdPartyDataStore/TpdsProjectFlusher.mjs'
import CollaboratorsGetter from './CollaboratorsGetter.mjs'
import CollaboratorsInviteHelper from './CollaboratorsInviteHelper.mjs'
import Errors from '../Errors/Errors.js'
import TpdsUpdateSender from '../ThirdPartyDataStore/TpdsUpdateSender.mjs'
import EditorRealTimeController from '../Editor/EditorRealTimeController.mjs'
@@ -361,7 +362,7 @@ async function setCollaboratorPrivilegeLevel(
auditInfo.ipAddress,
{
userId,
role: _privilegeLevelToRole(privilegeLevel),
role: CollaboratorsInviteHelper.privilegeLevelToRole(privilegeLevel),
}
)
@@ -374,19 +375,6 @@ async function setCollaboratorPrivilegeLevel(
}
}
function _privilegeLevelToRole(privilegeLevel) {
switch (privilegeLevel) {
case 'readOnly':
return 'Viewer'
case 'readAndWrite':
return 'Editor'
case 'review':
return 'Reviewer'
default:
return privilegeLevel
}
}
async function userIsTokenMember(userId, projectId) {
if (!userId) {
return false
@@ -4,6 +4,7 @@ import UserGetter from '../User/UserGetter.mjs'
import CollaboratorsGetter from './CollaboratorsGetter.mjs'
import CollaboratorsInviteHandler from './CollaboratorsInviteHandler.mjs'
import CollaboratorsInviteGetter from './CollaboratorsInviteGetter.mjs'
import CollaboratorsInviteHelper from './CollaboratorsInviteHelper.mjs'
import logger from '@overleaf/logger'
import Settings from '@overleaf/settings'
import EmailHelper from '../Helpers/EmailHelper.mjs'
@@ -171,7 +172,7 @@ async function inviteToProject(req, res) {
req.ip,
{
inviteId: invite._id,
privileges,
role: CollaboratorsInviteHelper.privilegeLevelToRole(invite.privileges),
}
)
@@ -202,7 +203,7 @@ async function revokeInvite(req, res) {
req.ip,
{
inviteId: invite._id,
privileges: invite.privileges,
role: CollaboratorsInviteHelper.privilegeLevelToRole(invite.privileges),
}
)
EditorRealTimeController.emitToRoom(
@@ -11,7 +11,21 @@ function hashInviteToken(token) {
.digest('hex')
}
function privilegeLevelToRole(privilegeLevel) {
switch (privilegeLevel) {
case 'readOnly':
return 'Viewer'
case 'readAndWrite':
return 'Editor'
case 'review':
return 'Reviewer'
default:
return privilegeLevel
}
}
export default {
generateToken,
hashInviteToken,
privilegeLevelToRole,
}
@@ -19,6 +19,9 @@ const MANAGED_GROUP_PROJECT_EVENTS = [
'project-history-version-restored',
'project-history-version-downloaded',
'transfer-ownership',
'remove-collaborator',
'revoke-invite',
'toggle-access-level',
'project-downloaded',
]
@@ -22,6 +22,7 @@ describe('CollaboratorsInviteController', function () {
ctx.tokenHmac = 'some-hmac-token'
ctx.targetEmail = 'user@example.com'
ctx.privileges = 'readAndWrite'
ctx.role = 'Editor'
ctx.projectOwner = {
_id: 'project-owner-id',
email: 'project-owner@example.com',
@@ -366,7 +367,7 @@ describe('CollaboratorsInviteController', function () {
ctx.req.ip,
{
inviteId: ctx.invite._id,
privileges: ctx.privileges,
role: ctx.role,
}
)
})
@@ -381,6 +382,7 @@ describe('CollaboratorsInviteController', function () {
beforeEach(async function (ctx) {
await new Promise(resolve => {
ctx.privileges = 'readAndWrite'
ctx.role = 'Editor'
ctx.CollaboratorsInviteController._checkShouldInviteEmail = sinon
.stub()
.resolves(true)
@@ -421,10 +423,23 @@ describe('CollaboratorsInviteController', function () {
describe('readOnly collaborator (always allowed)', function () {
beforeEach(async function (ctx) {
ctx.privileges = 'readOnly'
ctx.role = 'Viewer'
// Update the invite data to reflect the new privileges
ctx.invite.privileges = ctx.privileges
ctx.inviteReducedData = _.pick(ctx.invite, [
'_id',
'email',
'privileges',
])
ctx.CollaboratorsInviteHandler.promises.inviteToProject.resolves(
ctx.inviteReducedData
)
await new Promise(resolve => {
ctx.req.body = {
email: ctx.targetEmail,
privileges: (ctx.privileges = 'readOnly'),
privileges: ctx.privileges,
}
ctx.CollaboratorsInviteController._checkShouldInviteEmail = sinon
.stub()
@@ -492,7 +507,7 @@ describe('CollaboratorsInviteController', function () {
ctx.req.ip,
{
inviteId: ctx.invite._id,
privileges: ctx.privileges,
role: ctx.role,
}
)
})
@@ -1389,7 +1404,7 @@ describe('CollaboratorsInviteController', function () {
ctx.req.ip,
{
inviteId: ctx.invite._id,
privileges: ctx.privileges,
role: ctx.role,
}
)
})