> ## Documentation Index
> Fetch the complete documentation index at: https://ayakaleaf-pro.mintlify.site/llms.txt
> Use this file to discover all available pages before exploring further.

# User Audit Log Entries Collection

`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. |

Operations written by core:

| `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` |

Operations written by our modules (`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`). |

Core CE has no page that shows this log.

#### 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 `timestamp` through the TTL index.


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.