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

# OAuth Access Tokens Collection

`oauthAccessTokens` holds the bearer tokens that web accepts. In Ayakaleaf Pro these are the personal access tokens (Git authentication tokens) that a user creates on the account settings page and uses for git-bridge and the MCP endpoint. One document is one token. The schema is `services/web/app/src/models/OauthAccessToken.mjs`; the `oauth2-server` web module writes the collection, mostly with the native driver.

#### Fields

| Field | Type | Meaning | Source |
| - | - | - | - |
| `_id` | ObjectId | Token document id. The settings page deletes a token by this id. | `OauthAccessToken.mjs:6` |
| `accessToken` | String | SHA-256 hex digest of the token. The plain token, `olp_` plus 36 random letters and digits, is shown once when it is created and never stored. | `OauthAccessToken.mjs:8`, `services/web/modules/oauth2-server/app/src/OAuthPersonalAccessTokenManager.mjs:54,72`, `services/web/modules/oauth2-server/app/src/SecretsHelper.mjs:6-11` |
| `accessTokenPartial` | String | First 8 characters of the plain token, shown in the token lists. | `OauthAccessToken.mjs:9`, `OAuthPersonalAccessTokenManager.mjs:73` |
| `type` | String | `personal_access_token` for every token created on the settings page. The list, count and delete queries filter on it. | `OauthAccessToken.mjs:10`, `OAuthPersonalAccessTokenManager.mjs:75` |
| `accessTokenExpiresAt` | Date | One year after creation. Both token checks reject a token after this time, and the TTL index deletes it one day later. | `OauthAccessToken.mjs:11`, `OAuthPersonalAccessTokenManager.mjs:65-68,77` |
| `oauthApplication_id` | ObjectId | `_id` of the `Overleaf Git Bridge` client in [OAuth Applications](https://ayakaleaf-pro.mintlify.site/dev/data-model/oauth-applications-collection.md). | `OauthAccessToken.mjs:12`, `OAuthPersonalAccessTokenManager.mjs:55-56,74` |
| `scope` | String | Space-separated scopes. Personal access tokens get `git_bridge`, the scope that git-bridge and MCP routes require. | `OauthAccessToken.mjs:15`, `OAuthPersonalAccessTokenManager.mjs:76` |
| `user_id` | String (ObjectId in the schema) | Owner of the token. `createToken` stores the user id of the session as it is, a string, with the native driver; the admin panel queries it as a string (`services/web/modules/admin-tools/app/src/AdminUserListManager.mjs:391-392`). | `OauthAccessToken.mjs:16`, `services/web/modules/oauth2-server/app/src/OAuthPersonalAccessTokenController.mjs:29-41`, `OAuthPersonalAccessTokenManager.mjs:78,83-85` |
| `createdAt` | Date | Creation time. The token list is sorted by it, newest first. | `OauthAccessToken.mjs:17`, `OAuthPersonalAccessTokenManager.mjs:21-22,64,79` |
| `lastUsedAt` | Date | Last time the token passed a check. Both token checks set it. | `OauthAccessToken.mjs:19`, `services/web/modules/oauth2-server/app/src/OAuth2Model.mjs:58-61`, `OAuthPersonalAccessTokenManager.mjs:136-140` |
| `refreshToken`, `refreshTokenExpiresAt` | String, Date | Only `saveToken` writes them (the refresh token hashed), and nothing calls `saveToken`, see [OAuth Applications](https://ayakaleaf-pro.mintlify.site/dev/data-model/oauth-applications-collection.md). | `OauthAccessToken.mjs:13-14`, `OAuth2Model.mjs:249-252` |
| `expiresAt` | Date | In the schema only: no code in this repo writes it. A TTL index covers it. | `OauthAccessToken.mjs:18` |
| `lastNotifiedAt.warning`, `lastNotifiedAt.expired`, `notificationsSuppressedAt` | Date | In the schema only: no code in this repo reads or writes them. | `OauthAccessToken.mjs:20-27` |

#### Who Writes It

| Code | What it does |
| - | - |
| `OAuthPersonalAccessTokenManager.mjs:53-88` (`createToken`) | Inserts a personal access token. Route `POST /oauth/personal-access-tokens` (`services/web/modules/oauth2-server/app/src/OAuthPersonalAccessTokenRouter.mjs:22-27`) needs the `gitBridge` feature of the user and allows 10 tokens per user (`OAuthPersonalAccessTokenController.mjs:7,34-38`). |
| `OAuthPersonalAccessTokenManager.mjs:90-105` (`removeToken`) | Deletes one token of the user. Route `DELETE /oauth/personal-access-tokens/:token_id` (`OAuthPersonalAccessTokenRouter.mjs:29-32`). |
| `OAuthPersonalAccessTokenManager.mjs:136-140`, `OAuth2Model.mjs:58-61` | Set `lastUsedAt` after a successful check. |

#### Who Reads It

| Code | What it does |
| - | - |
| `OAuthPersonalAccessTokenManager.mjs:107-144` (`verifyToken`) | Checks a token by its hash for `GET /oauth/token/info` (`OAuthPersonalAccessTokenRouter.mjs:11-15`), which git-bridge calls on git clone and push (`services/git-bridge/src/main/java/uk/ac/ic/wlgitbridge/server/Oauth2Filter.java:285`). Rejects an expired token and the token of a suspended or deleted user. |
| `OAuth2Model.mjs:31-82` (`getAccessToken`) | Checks a bearer token for `AuthenticationController.requireOauth('git_bridge')` (`services/web/app/src/Features/Authentication/AuthenticationController.mjs:409-450`): the git-bridge API routes (`services/web/modules/git-bridge/app/src/GitBridgeRouter.mjs:33-59`) and the MCP endpoint (`services/web/modules/mcp-server/app/src/McpRouter.mjs:16-22`). Rejects an expired token and the token of a suspended or deleted user. |
| `OAuthPersonalAccessTokenManager.mjs:13-50` (`listTokens`, `getUserTokenNums`) | Lists the tokens of a user for the settings page (`GET /oauth/personal-access-tokens`) and counts them for the limit. |
| `AdminUserListManager.mjs:388-400` (module `admin-tools`) | Shows the token prefixes of a user in the admin panel. |

#### Indexes

| Name | Keys | Options | Migration |
| - | - | - | - |
| `refreshToken_1` | `{ refreshToken: 1 }` | unique, partial: `refreshToken` exists | `20230405190240_oauth_refresh_tokens_index.mjs` |
| `expiresAt_1` | `{ expiresAt: 1 }` | TTL, `expireAfterSeconds: 0`, partial: `expiresAt` exists | `20230406125632_oauth_tokens_ttl.mjs` |
| `user_id_1_type_1` | `{ user_id: 1, type: 1 }` | | `20250321160345_add_type_to_pat_user_id_index.mjs` (replaces `user_id_1` from `20250115110745_oauth_user_id_index_no_partial.mjs`, which replaced `pat_user_id_1` from `20230426095212_personal_oauth_tokens_user_index.mjs`) |
| `accessTokenExpiresAt_1` | `{ accessTokenExpiresAt: 1 }` | TTL, `expireAfterSeconds: 86400`, partial: `accessTokenExpiresAt` exists | `20260511104500_expire_oauthAccessTokens.mjs` |

Migrations are in `tools/migrations/`. The unique `accessToken_1` index comes only from migrations tagged `saas` and `server-pro`, so in Community Edition there is no index on `accessToken`.

#### Notes

* Deleting a user does not delete their tokens. `UserDeleter` fires the hook `cleanupPersonalAccessTokens` (`services/web/app/src/Features/User/UserDeleter.mjs:230-234`), but no module listens to it. Both token checks reject the tokens because the user is gone, and the TTL index removes them a day after `accessTokenExpiresAt`.
* Suspending a user ends the sessions only; the tokens stay, and both token checks reject them while `users.suspended` is `true` (`OAuth2Model.mjs:51-54`, `OAuthPersonalAccessTokenManager.mjs:129-134`).


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