Files
overleaf-cep/services/web/app/src/Features/User/UserAuditLogHandler.mjs
Jakob Ackermann 425e7b1e5b [web] enable mongo notablescan in CI (#29501)
* [monorepo] record ERROR/FATAL log messages in junit report

* [web] put SaaS specific code behind feature flag

* [web] use split test cache for getting user assignments

The unit tests needed updating as they did not replicate any of the
 mongo filtering. The acceptance tests cover this logic.

* [web] make better use of existing indexes

* [web] avoid col-scan in tests of notifications module

* [web] remove cleanup of empty feedbacks collection

* [web] add assertion for reason of rejected request in launchpad test

* [web] add missing indexes

* [web] enable mongo notablescan

* [web] make emailNotifications tests compatible with notablescan

GitOrigin-RevId: b888f2feeb3a0e915f068ae1c4ea23ec17821221
2026-01-13 09:06:38 +00:00

137 lines
4.0 KiB
JavaScript

import OError from '@overleaf/o-error'
import logger from '@overleaf/logger'
import { UserAuditLogEntry } from '../../models/UserAuditLogEntry.mjs'
import { callbackify } from 'node:util'
import SubscriptionLocator from '../Subscription/SubscriptionLocator.mjs'
import Features from '../../infrastructure/Features.mjs'
function _canHaveNoIpAddressId(operation, info) {
if (operation === 'add-email' && info.script) return true
if (operation === 'join-group-subscription') return true
if (operation === 'leave-group-subscription') return true
if (operation === 'must-reset-password-set') return true
if (operation === 'remove-email' && info.script) return true
if (operation === 'release-managed-user' && info.script) return true
if (operation === 'unlink-dropbox' && info.batch) return true
return false
}
function _canHaveNoInitiatorId(operation, info) {
if (operation === 'add-email' && info.script) return true
if (operation === 'reset-password') return true
if (operation === 'unlink-sso' && info.providerId === 'collabratec')
return true
if (operation === 'unlink-sso' && info.script === true) return true
if (operation === 'unlink-institution-sso-not-migrated') return true
if (operation === 'remove-email' && info.script) return true
if (operation === 'join-group-subscription') return true
if (operation === 'leave-group-subscription') return true
if (operation === 'must-reset-password-set') return true
if (operation === 'must-reset-password-unset') return true
if (operation === 'account-suspension' && info.script) return true
if (operation === 'release-managed-user' && info.script) return true
}
// events that are visible to managed user admins in Group Audit Logs view
const MANAGED_GROUP_USER_EVENTS = [
'login',
'reset-password',
'update-password',
'link-dropbox',
'unlink-dropbox',
'link-github',
'unlink-github',
'delete-account',
'leave-group-subscription',
'integration-account-linked',
'integration-account-unlinked',
]
/**
* Add an audit log entry
*
* The entry should include at least the following fields:
*
* - userId: the user on behalf of whom the operation was performed
* - operation: a string identifying the type of operation
* - initiatorId: who performed the operation
* - ipAddress: the IP address of the initiator
* - info: an object detailing what happened
*/
async function addEntry(userId, operation, initiatorId, ipAddress, info = {}) {
if (!operation) {
throw new OError('missing operation for audit log', {
initiatorId,
ipAddress,
})
}
if (!ipAddress && !_canHaveNoIpAddressId(operation, info)) {
throw new OError('missing ipAddress for audit log', {
operation,
initiatorId,
})
}
if (!initiatorId && !_canHaveNoInitiatorId(operation, info)) {
throw new OError('missing initiatorId for audit log', {
operation,
ipAddress,
})
}
const entry = {
userId,
operation,
initiatorId,
info,
ipAddress,
}
if (
MANAGED_GROUP_USER_EVENTS.includes(operation) &&
Features.hasFeature('saas')
) {
try {
const managedSubscription =
await SubscriptionLocator.promises.getUniqueManagedSubscriptionMemberOf(
userId
)
if (managedSubscription) {
entry.managedSubscriptionId = managedSubscription._id
}
} catch (err) {
logger.error({ err, userId }, 'failed to lookup managed subscription')
}
}
await UserAuditLogEntry.create(entry)
}
function addEntryInBackground(
userId,
operation,
initiatorId,
ipAddress,
info = {}
) {
// Intentionally not awaited
addEntry(userId, operation, initiatorId, ipAddress, info).catch(err => {
logger.error(
{ err, userId, operation, initiatorId, ipAddress, info },
'error adding user audit log entry'
)
})
}
const UserAuditLogHandler = {
MANAGED_GROUP_USER_EVENTS,
addEntry: callbackify(addEntry),
promises: {
addEntry,
},
addEntryInBackground,
}
export default UserAuditLogHandler