mirror of
https://github.com/overleaf/overleaf.git
synced 2024-11-07 20:31:06 -05:00
Merge pull request #17799 from overleaf/ab-account-deletion-audit-log
[web] Add audit log when user account is deleted or recovered GitOrigin-RevId: 3d5f99705fbd6192ccae430e040be4b7fcb3f740
This commit is contained in:
parent
491bc2628d
commit
5f8db6ee23
2 changed files with 94 additions and 21 deletions
|
@ -12,6 +12,7 @@ const SubscriptionUpdater = require('../Subscription/SubscriptionUpdater')
|
|||
const SubscriptionLocator = require('../Subscription/SubscriptionLocator')
|
||||
const UserMembershipsHandler = require('../UserMembership/UserMembershipsHandler')
|
||||
const UserSessionsManager = require('./UserSessionsManager')
|
||||
const UserAuditLogHandler = require('./UserAuditLogHandler')
|
||||
const InstitutionsAPI = require('../Institutions/InstitutionsAPI')
|
||||
const Modules = require('../../infrastructure/Modules')
|
||||
const Errors = require('../Errors/Errors')
|
||||
|
@ -47,6 +48,12 @@ async function deleteUser(userId, options) {
|
|||
await ensureCanDeleteUser(user)
|
||||
await _cleanupUser(user)
|
||||
await Modules.promises.hooks.fire('deleteUser', userId)
|
||||
await UserAuditLogHandler.promises.addEntry(
|
||||
userId,
|
||||
'delete-account',
|
||||
options.deleterUser ? options.deleterUser._id : userId,
|
||||
options.ipAddress
|
||||
)
|
||||
await _createDeletedUser(user, options)
|
||||
await ProjectDeleter.promises.deleteUsersProjects(user._id)
|
||||
await _sendDeleteEmail(user)
|
||||
|
|
|
@ -15,6 +15,7 @@ describe('UserDeleter', function () {
|
|||
tk.freeze(Date.now())
|
||||
|
||||
this.userId = new ObjectId()
|
||||
this.ipAddress = '1.2.3.4'
|
||||
|
||||
this.UserMock = sinon.mock(User)
|
||||
this.DeletedUserMock = sinon.mock(DeletedUser)
|
||||
|
@ -106,6 +107,12 @@ describe('UserDeleter', function () {
|
|||
},
|
||||
}
|
||||
|
||||
this.UserAuditLogHandler = {
|
||||
promises: {
|
||||
addEntry: sinon.stub().resolves(),
|
||||
},
|
||||
}
|
||||
|
||||
this.UserDeleter = SandboxedModule.require(modulePath, {
|
||||
requires: {
|
||||
'../../models/User': { User },
|
||||
|
@ -122,6 +129,7 @@ describe('UserDeleter', function () {
|
|||
'../../models/UserAuditLogEntry': {
|
||||
UserAuditLogEntry: this.UserAuditLogEntry,
|
||||
},
|
||||
'./UserAuditLogHandler': this.UserAuditLogHandler,
|
||||
'../../infrastructure/Modules': this.Modules,
|
||||
'../OnboardingDataCollection/OnboardingDataCollectionManager':
|
||||
this.OnboardingDataCollectionManager,
|
||||
|
@ -151,7 +159,7 @@ describe('UserDeleter', function () {
|
|||
deleterData: {
|
||||
deletedAt: new Date(),
|
||||
deletedUserId: this.userId,
|
||||
deleterIpAddress: undefined,
|
||||
deleterIpAddress: this.ipAddress,
|
||||
deleterId: undefined,
|
||||
deletedUserLastLoggedIn: this.user.lastLoggedIn,
|
||||
deletedUserSignUpDate: this.user.signUpDate,
|
||||
|
@ -164,7 +172,7 @@ describe('UserDeleter', function () {
|
|||
}
|
||||
})
|
||||
|
||||
describe('when no options are passed', function () {
|
||||
describe('when only the ip address is passed', function () {
|
||||
beforeEach(function () {
|
||||
this.DeletedUserMock.expects('updateOne')
|
||||
.withArgs(
|
||||
|
@ -185,40 +193,52 @@ describe('UserDeleter', function () {
|
|||
})
|
||||
|
||||
it('should find and the user in mongo by its id', async function () {
|
||||
await this.UserDeleter.promises.deleteUser(this.userId, {})
|
||||
await this.UserDeleter.promises.deleteUser(this.userId, {
|
||||
ipAddress: this.ipAddress,
|
||||
})
|
||||
this.UserMock.verify()
|
||||
})
|
||||
|
||||
it('should delete the user from mailchimp', async function () {
|
||||
await this.UserDeleter.promises.deleteUser(this.userId, {})
|
||||
await this.UserDeleter.promises.deleteUser(this.userId, {
|
||||
ipAddress: this.ipAddress,
|
||||
})
|
||||
expect(
|
||||
this.NewsletterManager.promises.unsubscribe
|
||||
).to.have.been.calledWith(this.user, { delete: true })
|
||||
})
|
||||
|
||||
it('should delete all the projects of a user', async function () {
|
||||
await this.UserDeleter.promises.deleteUser(this.userId, {})
|
||||
await this.UserDeleter.promises.deleteUser(this.userId, {
|
||||
ipAddress: this.ipAddress,
|
||||
})
|
||||
expect(
|
||||
this.ProjectDeleter.promises.deleteUsersProjects
|
||||
).to.have.been.calledWith(this.userId)
|
||||
})
|
||||
|
||||
it("should cancel the user's subscription", async function () {
|
||||
await this.UserDeleter.promises.deleteUser(this.userId, {})
|
||||
await this.UserDeleter.promises.deleteUser(this.userId, {
|
||||
ipAddress: this.ipAddress,
|
||||
})
|
||||
expect(
|
||||
this.SubscriptionHandler.promises.cancelSubscription
|
||||
).to.have.been.calledWith(this.user)
|
||||
})
|
||||
|
||||
it('should delete user affiliations', async function () {
|
||||
await this.UserDeleter.promises.deleteUser(this.userId, {})
|
||||
await this.UserDeleter.promises.deleteUser(this.userId, {
|
||||
ipAddress: this.ipAddress,
|
||||
})
|
||||
expect(
|
||||
this.InstitutionsApi.promises.deleteAffiliations
|
||||
).to.have.been.calledWith(this.userId)
|
||||
})
|
||||
|
||||
it('should fire the deleteUser hook for modules', async function () {
|
||||
await this.UserDeleter.promises.deleteUser(this.userId, {})
|
||||
await this.UserDeleter.promises.deleteUser(this.userId, {
|
||||
ipAddress: this.ipAddress,
|
||||
})
|
||||
expect(this.Modules.promises.hooks.fire).to.have.been.calledWith(
|
||||
'deleteUser',
|
||||
this.userId
|
||||
|
@ -226,21 +246,27 @@ describe('UserDeleter', function () {
|
|||
})
|
||||
|
||||
it('should stop the user sessions', async function () {
|
||||
await this.UserDeleter.promises.deleteUser(this.userId, {})
|
||||
await this.UserDeleter.promises.deleteUser(this.userId, {
|
||||
ipAddress: this.ipAddress,
|
||||
})
|
||||
expect(
|
||||
this.UserSessionsManager.promises.revokeAllUserSessions
|
||||
).to.have.been.calledWith(this.userId, [])
|
||||
})
|
||||
|
||||
it('should remove user from group subscriptions', async function () {
|
||||
await this.UserDeleter.promises.deleteUser(this.userId, {})
|
||||
await this.UserDeleter.promises.deleteUser(this.userId, {
|
||||
ipAddress: this.ipAddress,
|
||||
})
|
||||
expect(
|
||||
this.SubscriptionUpdater.promises.removeUserFromAllGroups
|
||||
).to.have.been.calledWith(this.userId)
|
||||
})
|
||||
|
||||
it('should remove user memberships', async function () {
|
||||
await this.UserDeleter.promises.deleteUser(this.userId, {})
|
||||
await this.UserDeleter.promises.deleteUser(this.userId, {
|
||||
ipAddress: this.ipAddress,
|
||||
})
|
||||
expect(
|
||||
this.UserMembershipsHandler.promises.removeUserFromAllEntities
|
||||
).to.have.been.calledWith(this.userId)
|
||||
|
@ -255,12 +281,16 @@ describe('UserDeleter', function () {
|
|||
})
|
||||
|
||||
it('should create a deletedUser', async function () {
|
||||
await this.UserDeleter.promises.deleteUser(this.userId, {})
|
||||
await this.UserDeleter.promises.deleteUser(this.userId, {
|
||||
ipAddress: this.ipAddress,
|
||||
})
|
||||
this.DeletedUserMock.verify()
|
||||
})
|
||||
|
||||
it('should email the user', async function () {
|
||||
await this.UserDeleter.promises.deleteUser(this.userId, {})
|
||||
await this.UserDeleter.promises.deleteUser(this.userId, {
|
||||
ipAddress: this.ipAddress,
|
||||
})
|
||||
const emailOptions = {
|
||||
to: 'bob@bob.com',
|
||||
action: 'account deleted',
|
||||
|
@ -270,6 +300,20 @@ describe('UserDeleter', function () {
|
|||
this.EmailHandler.promises.sendEmail
|
||||
).to.have.been.calledWith('securityAlert', emailOptions)
|
||||
})
|
||||
|
||||
it('should add an audit log entry', async function () {
|
||||
await this.UserDeleter.promises.deleteUser(this.userId, {
|
||||
ipAddress: this.ipAddress,
|
||||
})
|
||||
expect(
|
||||
this.UserAuditLogHandler.promises.addEntry
|
||||
).to.have.been.calledWith(
|
||||
this.userId,
|
||||
'delete-account',
|
||||
this.userId,
|
||||
this.ipAddress
|
||||
)
|
||||
})
|
||||
})
|
||||
|
||||
describe('when unsubscribing from mailchimp fails', function () {
|
||||
|
@ -280,8 +324,11 @@ describe('UserDeleter', function () {
|
|||
})
|
||||
|
||||
it('should return an error and not delete the user', async function () {
|
||||
await expect(this.UserDeleter.promises.deleteUser(this.userId, {}))
|
||||
.to.be.rejected
|
||||
await expect(
|
||||
this.UserDeleter.promises.deleteUser(this.userId, {
|
||||
ipAddress: this.ipAddress,
|
||||
})
|
||||
).to.be.rejected
|
||||
this.UserMock.verify()
|
||||
})
|
||||
})
|
||||
|
@ -295,12 +342,16 @@ describe('UserDeleter', function () {
|
|||
})
|
||||
|
||||
it('should delete the user', function (done) {
|
||||
this.UserDeleter.deleteUser(this.userId, {}, err => {
|
||||
expect(err).not.to.exist
|
||||
this.UserMock.verify()
|
||||
this.DeletedUserMock.verify()
|
||||
done()
|
||||
})
|
||||
this.UserDeleter.deleteUser(
|
||||
this.userId,
|
||||
{ ipAddress: this.ipAddress },
|
||||
err => {
|
||||
expect(err).not.to.exist
|
||||
this.UserMock.verify()
|
||||
this.DeletedUserMock.verify()
|
||||
done()
|
||||
}
|
||||
)
|
||||
})
|
||||
})
|
||||
})
|
||||
|
@ -335,6 +386,21 @@ describe('UserDeleter', function () {
|
|||
this.DeletedUserMock.verify()
|
||||
})
|
||||
|
||||
it('should add an audit log entry', async function () {
|
||||
await this.UserDeleter.promises.deleteUser(this.userId, {
|
||||
deleterUser: { _id: this.deleterId },
|
||||
ipAddress: this.ipAddress,
|
||||
})
|
||||
expect(
|
||||
this.UserAuditLogHandler.promises.addEntry
|
||||
).to.have.been.calledWith(
|
||||
this.userId,
|
||||
'delete-account',
|
||||
this.deleterId,
|
||||
this.ipAddress
|
||||
)
|
||||
})
|
||||
|
||||
describe('when called as a callback', function () {
|
||||
it('should delete the user', function (done) {
|
||||
this.UserDeleter.deleteUser(
|
||||
|
|
Loading…
Reference in a new issue