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

# GitHub Sync User Credentials Collection

`githubSyncUserCredentials` holds one document per user who linked a GitHub account for GitHub Sync. It stores the user's OAuth access token, encrypted. It belongs to the `github-sync` web module (`services/web/modules/github-sync`). The schema is in `services/web/modules/github-sync/app/models/githubSyncUserCredentials.mjs`, and all access goes through `services/web/modules/github-sync/app/src/TokenManager.mjs`.

#### Fields

| Field | Type | Meaning | Source |
| - | - | - | - |
| `_id` | ObjectId | Set by Mongoose when the upsert inserts. | |
| `userId` | ObjectId | The Overleaf user. Required. The schema says `unique: true`, but no index is built (see Indexes); the upsert by `userId` keeps one document per user. | `githubSyncUserCredentials.mjs:8`, `TokenManager.mjs:44-48` |
| `github` | String | The GitHub OAuth access token, encrypted with `@overleaf/access-token-encryptor`. The password comes from `GITHUB_TOKEN_CIPHER_PASSWORD`; the cipher label is `GITHUB_TOKEN_CIPHER_LABEL`, default `2026.3-v3`. Required. | `githubSyncUserCredentials.mjs:9`, `TokenManager.mjs:12-25` |
| `__v` | Number | The Mongoose version key. The schema does not turn it off. | Mongoose default |

Only the access token is stored: the OAuth callback keeps `access_token` from GitHub's answer (`services/web/modules/github-sync/app/src/GitHubApiClient.mjs:180-191`). There is no refresh token or expiry.

#### Who Writes It

| Code | What it does |
| - | - |
| `TokenManager.mjs:42-58` (`saveUserToken`) | Upserts the encrypted token for the user. When the user links again, the replaced token is revoked at GitHub in the background. Called from the OAuth callback (`services/web/modules/github-sync/app/src/GitHubSyncController.mjs:146`). |
| `TokenManager.mjs:61-76` (`removeUserToken`) | Revokes the token at GitHub (best effort), then deletes the document. Called on unlink (`GitHubSyncController.mjs:167`) and when a deleted user expires (`services/web/modules/github-sync/index.mjs:66-68`). |

#### Who Reads It

| Code | What it does |
| - | - |
| `TokenManager.mjs:36-40` (`getUserToken`) | Decrypts the token for every GitHub API call: `services/web/modules/github-sync/app/src/GitHubSyncHandler.mjs` (for example `:27`) and `services/web/modules/github-sync/app/src/GitMerge.mjs:43`. With no document, or a token that cannot be decrypted, it throws `InvalidTokenError`, and the user counts as not linked (`GitHubSyncHandler.mjs:25-37`). |
| `services/web/modules/admin-tools/app/src/AdminUserListManager.mjs:393` | Checks whether the user has a document, for the integrations shown in the admin panel. |

#### Indexes

| Name | Keys | Options | Migration |
| - | - | - | - |
| `_id_` | `_id: 1` | | default |

Mongoose `autoIndex` is off (`services/web/app/src/infrastructure/Mongoose.mjs:7`), so the schema's `unique: true` on `userId` creates nothing. `tools/migrations/20190912145011_create_githubSyncUserCredentials_indexes.mjs` (upstream's `user_id`) is tagged `saas` only and does not run in CE.

#### Notes

* Changing `GITHUB_TOKEN_CIPHER_PASSWORD` makes the stored tokens unreadable. Those users then count as not linked until they link again.
* A deleted user keeps the document until the user expires.


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