OwnershipTransferHandler.js 3.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132
  1. const logger = require('@overleaf/logger')
  2. const { Project } = require('../../models/Project')
  3. const ProjectGetter = require('../Project/ProjectGetter')
  4. const UserGetter = require('../User/UserGetter')
  5. const CollaboratorsHandler = require('./CollaboratorsHandler')
  6. const EmailHandler = require('../Email/EmailHandler')
  7. const Errors = require('../Errors/Errors')
  8. const PrivilegeLevels = require('../Authorization/PrivilegeLevels')
  9. const TpdsProjectFlusher = require('../ThirdPartyDataStore/TpdsProjectFlusher')
  10. const ProjectAuditLogHandler = require('../Project/ProjectAuditLogHandler')
  11. const AnalyticsManager = require('../Analytics/AnalyticsManager')
  12. module.exports = {
  13. promises: { transferOwnership },
  14. }
  15. async function transferOwnership(projectId, newOwnerId, options = {}) {
  16. const { allowTransferToNonCollaborators, sessionUserId } = options
  17. // Fetch project and user
  18. const [project, newOwner] = await Promise.all([
  19. _getProject(projectId),
  20. _getUser(newOwnerId),
  21. ])
  22. // Exit early if the transferee is already the project owner
  23. const previousOwnerId = project.owner_ref
  24. if (previousOwnerId.equals(newOwnerId)) {
  25. return
  26. }
  27. // Check that user is already a collaborator
  28. if (
  29. !allowTransferToNonCollaborators &&
  30. !_userIsCollaborator(newOwner, project)
  31. ) {
  32. throw new Errors.UserNotCollaboratorError({ info: { userId: newOwnerId } })
  33. }
  34. // Track the change of ownership in BigQuery.
  35. AnalyticsManager.recordEventForUser(
  36. previousOwnerId,
  37. 'project-ownership-transfer',
  38. { projectId, newOwnerId }
  39. )
  40. // Transfer ownership
  41. await ProjectAuditLogHandler.promises.addEntry(
  42. projectId,
  43. 'transfer-ownership',
  44. sessionUserId,
  45. { previousOwnerId, newOwnerId }
  46. )
  47. await _transferOwnership(projectId, previousOwnerId, newOwnerId)
  48. // Flush project to TPDS
  49. await TpdsProjectFlusher.promises.flushProjectToTpds(projectId)
  50. // Send confirmation emails
  51. const previousOwner = await UserGetter.promises.getUser(previousOwnerId)
  52. await _sendEmails(project, previousOwner, newOwner)
  53. }
  54. async function _getProject(projectId) {
  55. const project = await ProjectGetter.promises.getProject(projectId, {
  56. owner_ref: 1,
  57. collaberator_refs: 1,
  58. name: 1,
  59. })
  60. if (project == null) {
  61. throw new Errors.ProjectNotFoundError({ info: { projectId } })
  62. }
  63. return project
  64. }
  65. async function _getUser(userId) {
  66. const user = await UserGetter.promises.getUser(userId)
  67. if (user == null) {
  68. throw new Errors.UserNotFoundError({ info: { userId } })
  69. }
  70. return user
  71. }
  72. function _userIsCollaborator(user, project) {
  73. const collaboratorIds = project.collaberator_refs || []
  74. return collaboratorIds.some(collaboratorId => collaboratorId.equals(user._id))
  75. }
  76. async function _transferOwnership(projectId, previousOwnerId, newOwnerId) {
  77. await CollaboratorsHandler.promises.removeUserFromProject(
  78. projectId,
  79. newOwnerId
  80. )
  81. await Project.updateOne(
  82. { _id: projectId },
  83. { $set: { owner_ref: newOwnerId } }
  84. ).exec()
  85. await CollaboratorsHandler.promises.addUserIdToProject(
  86. projectId,
  87. newOwnerId,
  88. previousOwnerId,
  89. PrivilegeLevels.READ_AND_WRITE
  90. )
  91. }
  92. async function _sendEmails(project, previousOwner, newOwner) {
  93. if (previousOwner == null) {
  94. // The previous owner didn't exist. This is not supposed to happen, but
  95. // since we're changing the owner anyway, we'll just warn
  96. logger.warn(
  97. { projectId: project._id, ownerId: previousOwner._id },
  98. 'Project owner did not exist before ownership transfer'
  99. )
  100. } else {
  101. // Send confirmation emails
  102. await Promise.all([
  103. EmailHandler.promises.sendEmail(
  104. 'ownershipTransferConfirmationPreviousOwner',
  105. {
  106. to: previousOwner.email,
  107. project,
  108. newOwner,
  109. }
  110. ),
  111. EmailHandler.promises.sendEmail('ownershipTransferConfirmationNewOwner', {
  112. to: newOwner.email,
  113. project,
  114. previousOwner,
  115. }),
  116. ])
  117. }
  118. }