Liam O'BrienandClaude Sonnet 4.6 77970c98c9 Fix GitHub Sync unlink robustness and detect expired credentials (#35147)
* github-sync: only swallow permanent revocation failures during unlink

404 (token gone) and 422 (token issued under a mismatched client_id,
the confirmed cause of #35108) are treated as safe to proceed past;
any other revocation error now aborts the unlink instead of silently
deleting the local credential row, so a token that might still be
live isn't orphaned by a transient GitHub failure.

* web: surface genuine github-sync unlink failures instead of a 500

When GithubSyncApiHandler.unlink rejects for a reason other than the
permanent 404/422 cases now handled by github-sync, show an error
notification on the settings page instead of crashing the request,
mirroring the existing projectSyncSuccessMessage session-flash pattern.

* github-sync: detect expired credentials in user status check

getStatus now validates a stored token against GitHub (GET /user)
before reporting it as linked. A GithubInvalidCredentialsError (401)
now surfaces as expired: true alongside enabled: true, distinguishing
a dead token from a genuinely working link. Any other validation
failure fails open and reports enabled: true as before.

* web: propagate github-sync expired status through to the client

Thread the new expired field from github-sync's status endpoint
through GithubSyncController.getUserStatus and the settings-page
injectUserSettings middleware, and extend the corresponding
TypeScript types on both the app and frontend side.

* web: show a re-link path on the settings widget when github credentials expire

The widget now checks expired status: it shows an error indicator
with a "connection expired" message and renders the Link action
instead of a dead-end Unlink button, since relinking upserts a fresh
token regardless of the stale row.

* web: keep unlink available for expired github credentials

Some users just want to remove an expired connection rather than
relink it. Keep Unlink as the primary action regardless of expired
status, and surface the relink path as an inline link next to the
expired message instead of replacing the primary action.

* web: correct the github unlink confirmation warning

Unlinking never touches a project's githubSyncProjectStates row,
regardless of whether the credential was healthy or expired -
verified by tracing unlinkAccount/deleteUser and confirming no other
code path deletes project sync state on account unlink. Sync pauses
until relink, but no project loses its GitHub connection, so the
warning text was simply wrong; corrected rather than adding a
separate expired-specific message.

* web: add Storybook story for expired github-sync credentials

Covers the new expired state alongside the existing NoFeature,
Unlinked, Linked and Error stories for the settings widget.

* simplify: collapse duplicated branches and extract session-flash helper

- Merge the near-identical 404/422 checks in _revokeToken into one
  status-set check.
- Use optional chaining instead of null-guard ternaries in
  GithubSyncMiddleware.
- Extract popSessionValue() in UserPagesController to de-duplicate the
  read-then-delete session flash-message pattern, now used 3 times.

* review-self: close test coverage gaps found in self-review

- AuthenticationController: cover 401/403 revocation responses and a
  plain network error with no .response, alongside existing 404/422/500.
- GithubSyncController: assert projectSyncErrorMessage stays unset on
  the unlink success path.
- UserPagesController: cover projectSyncErrorMessage set-then-cleared
  and absent-stays-undefined cases.
- Add GithubSyncMiddleware.test.mjs (previously untested): not-logged-in,
  successful status fetch, and callback-error branches.
- Cypress: cover cancelling the unlink confirmation modal while expired.
- Storybook: add ErrorWhileUnlinked story for the disabled-Link +
  error-indicator combination.

* review-self: fix audit-log ordering, cache getStatus validation safely

- GithubSyncApiHandler: write the link-github/unlink-github audit log
  entry only after the corresponding github-sync API call succeeds,
  instead of before (resolves the discrepancy tracked in issue 3354).
- UserController/UserManager: cache getStatus's live token-validation
  result for 5 minutes to cut down on redundant GitHub API calls,
  with an optimistic-lock write (matched against the encrypted token
  that was actually validated) so a relink can never be overwritten by
  a stale in-flight validation of the old token.
- github-integration-linking-widget: dedupe the relink href against
  the linkPath already passed to IntegrationLinkingWidget.

* web: surface expired github credentials proactively in the sync modal

The modal previously only showed the reauthorize prompt after a real
GitHub API call failed with 401 mid-action. It now also checks the
expired field already returned by the status endpoint and shows the
same prompt immediately on open, before ever rendering the export
form against a token known to be dead. Extracted the existing
reauthorize markup into GithubCredentialsExpired so both call sites
share it.

* github-sync: avoid double-fetching the credential doc in getStatus

getStatus called UserManager.getUser, then getAuthToken which
internally fetched the same document again. Add getAuthTokenFromUser
to decrypt from an already-fetched doc and use it in getStatus,
removing the redundant Mongo query and the now-unreachable
GithubUserNotLinkedError handling (the user doc's existence was
already confirmed by the first fetch).

* github-sync: make expired-token handling reactive, drop proactive getStatus validation

Reverts getStatus in the github-sync service to a pure DB check — no live
GitHub API call, no 5-minute cache, no token_expired/token_validated_at
fields. The expired state is already surfaced reactively: the unmerged-commits
fetch on modal open returns 401 when the token is dead (handled by the existing
case 401 branch), and ExportToGithub handles 401 from the orgs call. The
proactive check added complexity (caching, optimistic-lock writes) without a
meaningful UX gain over the reactive paths.

Removes the expired indicator from the settings widget and the proactive
GithubCredentialsExpired screen from the modal's pre-flight check. The
github_sync_expired locale key is removed as it is no longer referenced.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* github-sync: show reauthorize prompt when push/pull fails with expired credentials

When a merge (push or pull) returns 401, GithubSyncMergeAction now renders
GithubCredentialsExpired with the Reauthorize button instead of the raw
"GithubInvalidCredentialsError" string that was previously falling through
to the default error branch.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* github-sync: wrap all async controller handlers with expressify

Without expressify, an unhandled throw from an async route handler
(e.g. a token decryption failure in the github-sync service producing a
non-4xx error that _reportError re-throws) becomes an unhandled rejection.
Express never sends a response and the modal is left stuck on loading.
expressify catches the error and passes it to next(), so Express's error
handler sends a 500 and the frontend can surface it.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* removing redundant tests

* removing getAuthTokenFromUser function

* removing redundant key

---------

Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
GitOrigin-RevId: 0d9a314bf70bea9e6037593f2a6f8b8d1f2c6e71
2026-09-17 11:22:04 +00:00
2026-03-09 09:06:41 +00:00
2026-09-17 11:17:47 +00:00


Overleaf

An open-source online real-time collaborative LaTeX editor.

Wiki • Server Pro • Contributing • Mailing List • Authors • License

A screenshot of a project being edited in Overleaf Community Edition

Figure 1: A screenshot of a project being edited in Overleaf Community Edition.

Community Edition

Overleaf is an open-source online real-time collaborative LaTeX editor. We run a hosted version at www.overleaf.com, but you can also run your own local version, and contribute to the development of Overleaf.

Caution

Overleaf Community Edition is intended for use in environments where all users are trusted. Community Edition is not appropriate for scenarios where isolation of users is required due to Sandbox Compiles not being available. When not using Sandboxed Compiles, users have full read and write access to the sharelatex container resources (filesystem, network, environment variables) when running LaTeX compiles.

For more information on Sandbox Compiles check out our documentation.

Enterprise

If you want help installing and maintaining Overleaf in your lab or workplace, we offer an officially supported version called Overleaf Server Pro. It also includes more features for security (SSO with LDAP or SAML), administration and collaboration (e.g. tracked changes). Find out more!

Keeping up to date

Sign up to the mailing list to get updates on Overleaf releases and development.

Installation

We have detailed installation instructions in the Overleaf Toolkit.

Upgrading

If you are upgrading from a previous version of Overleaf, please see the Release Notes section on the Wiki for all of the versions between your current version and the version you are upgrading to.

Overleaf Docker Image

This repo contains two dockerfiles, Dockerfile-base, which builds the sharelatex/sharelatex-base image, and Dockerfile which builds the sharelatex/sharelatex (or "community") image.

The Base image generally contains the basic dependencies like wget, plus texlive. We split this out because it's a pretty heavy set of dependencies, and it's nice to not have to rebuild all of that every time.

The sharelatex/sharelatex image extends the base image and adds the actual Overleaf code and services.

Use make build-base and make build-community from server-ce/ to build these images.

We use the Phusion base-image (which is extended by our base image) to provide us with a VM-like container in which to run the Overleaf services. Baseimage uses the runit service manager to manage services, and we add our init-scripts from the server-ce/runit folder.

Contributing

Please see the CONTRIBUTING file for information on contributing to the development of Overleaf.

Authors

The Overleaf Team

License

The code in this repository is released under the GNU AFFERO GENERAL PUBLIC LICENSE, version 3. A copy can be found in the LICENSE file.

Copyright (c) Overleaf, 2014-2025.

Languages
JavaScript 61.6%
TypeScript 31.8%
SCSS 2.5%
Java 2%
Pug 0.7%
Other 1.2%