Files
Verso/services/web/app/src/Features/Collaborators/OwnershipTransferHandler.js
T
Brian Gough a04d3198a8 Merge pull request #24181 from overleaf/bg-Project-Audit-Log-Transfer-ownership-is-missing-the-IP-address
add missing ip adress to audit log for transfer ownership

GitOrigin-RevId: c14494efce25efa92a6cd81bcf197d8f897571bb
2025-03-11 09:06:43 +00:00

169 lines
4.6 KiB
JavaScript

const logger = require('@overleaf/logger')
const { Project } = require('../../models/Project')
const ProjectGetter = require('../Project/ProjectGetter')
const UserGetter = require('../User/UserGetter')
const CollaboratorsHandler = require('./CollaboratorsHandler')
const EmailHandler = require('../Email/EmailHandler')
const Errors = require('../Errors/Errors')
const PrivilegeLevels = require('../Authorization/PrivilegeLevels')
const TpdsProjectFlusher = require('../ThirdPartyDataStore/TpdsProjectFlusher')
const ProjectAuditLogHandler = require('../Project/ProjectAuditLogHandler')
const AnalyticsManager = require('../Analytics/AnalyticsManager')
module.exports = {
promises: { transferOwnership },
}
async function transferOwnership(projectId, newOwnerId, options = {}) {
const {
allowTransferToNonCollaborators,
sessionUserId,
skipEmails,
ipAddress,
} = options
// Fetch project and user
const [project, newOwner] = await Promise.all([
_getProject(projectId),
_getUser(newOwnerId),
])
// Exit early if the transferee is already the project owner
const previousOwnerId = project.owner_ref
if (previousOwnerId.equals(newOwnerId)) {
return
}
// Check that user is already a collaborator
if (
!allowTransferToNonCollaborators &&
!_userIsCollaborator(newOwner, project)
) {
throw new Errors.UserNotCollaboratorError({ info: { userId: newOwnerId } })
}
// Track the change of ownership in BigQuery.
AnalyticsManager.recordEventForUserInBackground(
previousOwnerId,
'project-ownership-transfer',
{ projectId, newOwnerId }
)
// Transfer ownership
await ProjectAuditLogHandler.promises.addEntry(
projectId,
'transfer-ownership',
sessionUserId,
ipAddress,
{ previousOwnerId, newOwnerId }
)
// Determine which permissions to give old owner based on
// new owner's existing permissions
const newPermissions =
_getUserPermissions(newOwner, project) || PrivilegeLevels.READ_ONLY
await _transferOwnership(
projectId,
previousOwnerId,
newOwnerId,
newPermissions
)
// Flush project to TPDS
await TpdsProjectFlusher.promises.flushProjectToTpds(projectId)
// Send confirmation emails
const previousOwner = await UserGetter.promises.getUser(previousOwnerId)
if (!skipEmails) {
await _sendEmails(project, previousOwner, newOwner)
}
}
async function _getProject(projectId) {
const project = await ProjectGetter.promises.getProject(projectId, {
owner_ref: 1,
collaberator_refs: 1,
readOnly_refs: 1,
name: 1,
})
if (project == null) {
throw new Errors.ProjectNotFoundError({ info: { projectId } })
}
return project
}
async function _getUser(userId) {
const user = await UserGetter.promises.getUser(userId)
if (user == null) {
throw new Errors.UserNotFoundError({ info: { userId } })
}
return user
}
function _getUserPermissions(user, project) {
const collaboratorIds = project.collaberator_refs || []
const readOnlyIds = project.readOnly_refs || []
if (collaboratorIds.some(collaboratorId => collaboratorId.equals(user._id))) {
return PrivilegeLevels.READ_AND_WRITE
} else if (
readOnlyIds.some(collaboratorId => collaboratorId.equals(user._id))
) {
return PrivilegeLevels.READ_ONLY
}
}
function _userIsCollaborator(user, project) {
return Boolean(_getUserPermissions(user, project))
}
async function _transferOwnership(
projectId,
previousOwnerId,
newOwnerId,
newPermissions
) {
await CollaboratorsHandler.promises.removeUserFromProject(
projectId,
newOwnerId
)
await Project.updateOne(
{ _id: projectId },
{ $set: { owner_ref: newOwnerId } }
).exec()
await CollaboratorsHandler.promises.addUserIdToProject(
projectId,
newOwnerId,
previousOwnerId,
newPermissions
)
}
async function _sendEmails(project, previousOwner, newOwner) {
if (previousOwner == null) {
// The previous owner didn't exist. This is not supposed to happen, but
// since we're changing the owner anyway, we'll just warn
logger.warn(
{ projectId: project._id, ownerId: previousOwner._id },
'Project owner did not exist before ownership transfer'
)
} else {
// Send confirmation emails
await Promise.all([
EmailHandler.promises.sendEmail(
'ownershipTransferConfirmationPreviousOwner',
{
to: previousOwner.email,
project,
newOwner,
}
),
EmailHandler.promises.sendEmail('ownershipTransferConfirmationNewOwner', {
to: newOwner.email,
project,
previousOwner,
}),
])
}
}