userAuditLogEntries is the audit log of user accounts: logins, password and email changes, linked accounts and admin actions. One document is one event on one account. web owns it; the schema is services/web/app/src/models/UserAuditLogEntry.mjs, and core writes through services/web/app/src/Features/User/UserAuditLogHandler.mjs.
Fields
| Field | Type | Meaning | Source |
|---|---|---|---|
_id | ObjectId | Entry id. | UserAuditLogEntry.mjs:4 |
userId | ObjectId | The account the event is about. | UserAuditLogEntry.mjs:6 |
operation | String | What happened, a kebab-case name such as login (see the table below). | UserAuditLogEntry.mjs:11 |
initiatorId | ObjectId | Who did it: the user, or an admin for admin actions. May be missing for a few operations, such as reset-password (UserAuditLogHandler.mjs:19-33). | UserAuditLogEntry.mjs:9 |
ipAddress | String | IP address of the request (req.ip). May be missing for a few operations (UserAuditLogHandler.mjs:8-17). | UserAuditLogEntry.mjs:10 |
info | Object | Details of the event; the keys depend on the operation. | UserAuditLogEntry.mjs:8 |
timestamp | Date | When the entry was written (default Date.now). | UserAuditLogEntry.mjs:12 |
managedSubscriptionId | ObjectId | Managed group subscription of the user. Only set when the saas feature is on (UserAuditLogHandler.mjs:92-107), so never in CE. | UserAuditLogEntry.mjs:7 |
__v | Number | Mongoose version key, added by UserAuditLogEntry.create. | UserAuditLogHandler.mjs:109 |
Who Writes It
| Code | What it does |
|---|---|
UserAuditLogHandler.mjs:62-110 (addEntry) | Creates one entry. Throws when operation is missing, or when ipAddress or initiatorId is missing for an operation that needs it. |
UserAuditLogHandler.mjs:112-126 (addEntryInBackground) | Same, without waiting; errors are only logged. |
services/web/modules/admin-tools/app/src/AdminAuditLog.mjs:14-38 | Our admin panel: logOnUser writes admin actions on the target account with the admin as initiator; logOnAdmin writes on the admin’s own account when the target has no log left (a purged user, a template). |
services/web/modules/authentication/auditLog.mjs:9-21 | Our LDAP, SAML and OIDC modules: logLoginEntry writes what an SSO login did to the account, with the account itself as initiator. |
services/web/app/src/Features/User/UserDeleter.mjs:141 | Deletes all entries of a deleted user when the deleted user expires (expireDeletedUsersAfterDuration). |
AdminAuditLog.mjs:41-43 (deleteUserEntries) | Our admin panel deletes all entries of a user it purges (services/web/modules/admin-tools/app/src/AdminUserManager.mjs:644). |
tools/migrations/20220913125500_migrate_auditLog_to_collections.mjs | One-time migration: moved the old users.auditLog arrays into this collection. |
operation | Written at (services/web/app/src/...) |
|---|---|
login | Features/Authentication/AuthenticationController.mjs:225 |
failed-password-match | Features/Authentication/AuthenticationManager.mjs:150 |
logout | Features/User/UserController.mjs:599 |
update-password | Features/User/UserController.mjs:131 |
clear-sessions | Features/User/UserController.mjs:167 |
reset-password | Features/PasswordReset/PasswordResetHandler.mjs:106 |
delete-account | Features/User/UserDeleter.mjs:50 |
account-suspension | Features/User/UserUpdater.mjs:621 |
must-reset-password-unset | Features/User/UserUpdater.mjs:604 |
add-email, remove-email, change-primary-email | Features/User/UserUpdater.mjs:99, :494, :247 |
migrate-default-email | Features/User/UserUpdater.mjs:355 |
clear-institution-sso-data, clear-third-party-identifiers | Features/User/UserUpdater.mjs:160, :196 |
confirm-email | Features/User/UserEmailsController.mjs:750 |
request-add-email-code, add-email-via-code, confirm-email-via-code | Features/User/UserEmailsController.mjs:138, :331, :354 |
resend-add-email-code, resend-confirm-email-code | Features/User/UserEmailsController.mjs:391 (names at :417, :423) |
link-sso, unlink-sso | Features/User/ThirdPartyIdentityManager.mjs:64, :131 (used by our OIDC module) |
link-institution-sso, unlink-institution-sso, migrate-institution-sso-id, unlink-institution-sso-not-migrated | Features/User/SAMLIdentityManager.mjs:103, :293, :424, :462 |
accept-group-invitation, join-group-subscription, leave-group-subscription, remove-from-group-subscription | Features/Subscription/TeamInvitesController.mjs:276, Features/Subscription/SubscriptionUpdater.mjs:104, :133, :193, Features/Subscription/SubscriptionGroupController.mjs:127 |
ai-quota-breach | infrastructure/rate-limiters/AiFeatureUsageRateLimiter.mjs:35, infrastructure/rate-limiters/TokenUsageRateLimiter.mjs:99, :191 |
services/web/modules/...):
operation | Written at |
|---|---|
create-account | authentication/ldap/app/src/LDAPAuthenticationManager.mjs:58, authentication/saml/app/src/SAMLAuthenticationManager.mjs:56, authentication/oidc/app/src/OIDCAuthenticationManager.mjs:75, admin-tools/app/src/AdminUserManager.mjs:167 |
remove-password | authentication/ldap/app/src/LDAPAuthenticationManager.mjs:78, authentication/saml/app/src/SAMLAuthenticationManager.mjs:94 |
link-institution-sso | authentication/saml/app/src/SAMLAuthenticationManager.mjs:71 |
change-admin-access, change-name | authentication/auditLog.mjs:37, :47 (SSO login); admin-tools/app/src/AdminUserManager.mjs:394, :391 (admin) |
failed-password-match | authentication/auditLog.mjs:80 (LDAP) |
logout | authentication/auditLog.mjs:58 |
send-activation-email, send-password-reset-email | admin-tools/app/src/AdminUserManager.mjs:184, :207 |
update-password, change-features, change-ai-features, account-reactivation | admin-tools/app/src/AdminUserManager.mjs:389, :397, :400, :403 |
clear-sessions, transfer-projects, restore-account, reset-ai-usage | admin-tools/app/src/AdminUserManager.mjs:514, :563, :694, :712 |
purge-deleted-user, purge-deleted-project, delete-template (on the admin’s account) | admin-tools/app/src/AdminUserManager.mjs:645, admin-tools/app/src/AdminProjectManager.mjs:525, admin-tools/app/src/AdminTemplateManager.mjs:67 |
publish-template, edit-template, delete-template | template-gallery/app/src/TemplateGalleryController.mjs:52, :107, :137 |
link-dropbox, unlink-dropbox | dropbox/app/src/DropboxController.mjs:87, :107, dropbox/index.mjs:38 |
link-github, unlink-github | github-sync/app/src/GitHubSyncController.mjs:155, :175 |
integration-account-linked, integration-account-unlinked | GitLab and Gitea sync (gitlab-sync/app/src/GitLabSyncController.mjs:155, :181, gitea-sync/app/src/GiteaSyncController.mjs:155, :181), Zotero and Mendeley (tpr-webmodule/app/src/ZoteroController.mjs:77, :99, tpr-webmodule/app/src/MendeleyController.mjs:77, :99) |
create-git-token, delete-git-token | oauth2-server/app/src/OAuthPersonalAccessTokenController.mjs:48, :74 |
Who Reads It
| Code | What it does |
|---|---|
services/web/modules/admin-tools/app/src/AdminAuditLog.mjs:124-126 (listUserEntries) | Our admin panel’s audit log tab, GET /admin/user/:userId/audit-log/list (services/web/modules/admin-tools/app/src/AdminToolsRouter.mjs:32-36). |
Indexes
| Name | Keys | Options | Migration |
|---|---|---|---|
timestamp_1 | { timestamp: 1 } | TTL, expireAfterSeconds: 78840000 (2.5 years) | tools/migrations/20220913105500_create_auditLog_indexes.mjs |
userId_1_timestamp_-1 | { userId: 1, timestamp: -1 } | tools/migrations/20251117093546_add_timestamp_to_userAuditLogEntries.mjs |
tools/migrations/20220929193200_add_auditLog_indexes.mjs first created user_id_1 on { userId: 1 }; 20251117093546 replaces it with the compound index. The schema marks userId and managedSubscriptionId with index: true, but Mongoose auto-indexing is off (services/web/app/src/infrastructure/Mongoose.mjs:7), so only the migrations above create indexes. The managedSubscriptionId index migration is tagged saas only.
Notes
- Entries expire 2.5 years after
timestampthrough the TTL index.

