* [web] gracefully handle failed oauth token exchange for reference managers
Mendeley/ReadCube redirect back to /:ref_provider/oauth/token-exchange with
`error` and `error_description` instead of `code` when a user declines consent.
completeAuth forwarded the whole query string on to third-party-references,
whose strict zod schema (d8c1679bb08) accepts only oauth_token, oauth_verifier
and code, so the exchange failed and the user got a 500.
The same forwarding also sent `state` on every callback, success or failure.
`state` is web's own CSRF token and is never read by third-party-references, so
it tripped the log-only schema on every single token exchange, and would have
broken linking outright under REQ_VALIDATION_MODE=enforce.
Forward only the credentials the exchange needs, and show a translated
notification in the Reference managers section rather than erroring. Validate
ref_provider against the known providers so the provider name can be
interpolated into the message without reflecting a URL segment.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
* [web] fix providerName argument and validate before deriving refProvider
Address Copilot review on #36377:
- The token-exchange failure handler passed `false` positionally into the
`providerName` slot of _redirectWithLinkingError, so a failed exchange would
have rendered "your false account". Pass providerName and an explicit false.
- _getRefProviderBackendKey(req) ran before parseReq, so refProvider was derived
from raw, unvalidated req.params. Parse first and derive from the validated
params via _toRefProviderBackendKey; _getRefProviderBackendKey now delegates
to it so the other handlers are unchanged.
The completeAuth tests kept their assertions inside ctx.res.callback without
awaiting the controller, so the callback fired after the test had already
returned and the assertions were never enforced — which is why the suite was
green with the providerName bug in it. Await the controller and assert inline.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
* [web] add logOnly to the completeAuth schema and guard the provider lookup
logOnly returns the raw input on a schema failure, so ref_provider can be an
arbitrary URL segment. Check it against the display-name table rather than
trusting the schema: unknown providers 404 in both validation modes, and
providerName is guaranteed non-null for the rest of the handler.
Also covers the schema half under REQ_VALIDATION_MODE=enforce, so dropping
ref_provider from completeAuthSchema fails a test rather than passing unnoticed.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
GitOrigin-RevId: b98eb5414fe8b672f44aab8c3690f7637d434cd2
* Suppress skip-to-content link on pages without a navbar
* Add main content as target area for the skip link on the library page
* Suppress navbar, footer and skip-to-content on account security pages
GitOrigin-RevId: a85fac5667d84c6f60577cd0ea469cd215554cc4
* 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
* update doc links to point to docs.overleaf.com instead of the old learn wiki
* fix broken link to LaTeX positioning wiki article in figure modal help
* point figure positioning help link to the docs.overleaf.com page
* point figure/table label help links to specific anchors on the captioning-and-referencing-figures doc page
* point figure modal error-help link to the graphicx-loading doc page
* normalize target/rel on docs.overleaf.com links opened in a new tab
* open figure modal and SSO help links in a new tab now that they point off-site
* add eslint rule enforcing rel=noopener noreferrer on docs.overleaf.com links
* extend require-rel-for-docs-links to also check OLButton
* add lint script catching missing rel on docs.overleaf.com links in pug templates
* fix require-rel-for-docs-links autofix crash on valueless rel attribute
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
* add regression test for require-rel-for-docs-links valueless-rel autofix
* revert `target=_blank` on figure modal and SSO help links
---------
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
GitOrigin-RevId: b6111341e1080fd7e4b42f6a1a18c27ea0fd7d8c
* feat: migrate from aiErrorAssist naming for disabling AI features to aiFeatures.enabled to avoid confusion
feat: keep aiErrorAssistant as setting on user object until migration is run
* feat: migrate writefull.enabled unset to instead use promotionSet false
* feat: remove wf.enabled in favor for aiFeatures.enabled for display, and writefull.promotionSet for determining if we should autoload or autocreate
GitOrigin-RevId: 2b7a5b8a430a804f6c9804cc926cb5c057e34df5
* Replace the black Overleaf logo to the green one
* Move subscription, login, restricted, invite views to website-redesign layout
* Remove the Storybook showcase of all the layouts
---------
Co-authored-by: Antoine Clausse <antoine.clausse@overleaf.com>
GitOrigin-RevId: b07581834921750c9c6de44f05beb0dd9eafba23
* Remove focused style from the stepper
* Reduce spacing between password input and password policy
* Make input validation icon larger
* Make animated-tick.svg faster
* Move notification to below h1
* Move notification to below h1
* Fix: Warning: Invalid DOM property `autocomplete`. Did you mean `autoComplete`?
* Wrap form-text in a span so it's displayed correctly and not altered by flex
* Remove "Please request a new password reset email and follow the link." text after the email is entered
* Remove (unnecessary?) flex property on .ciam-form-text-icon
* Rename `messageTextNode` to `messageTextSpan`
* Revert changes to input-validator.ts
GitOrigin-RevId: ad83c92a59b9dbc872cf4e49362b0baec9fb5b93
* Remove double dots
* Make skip button Tertiary instead of Link
* Bold email
* New zotero logo
GitOrigin-RevId: 569fa4c18fc85abf55b492f7033472445c63ed6b
* Make CIAM copies of Pug files
passwordResetCiam.pug
setPasswordCiam.pug
* Update controller with split test assignment
* Use CIAM layout in passwordResetCiam.pug
* Style passwordResetCiam according to designs
* Use CIAM layout in setPasswordCiam.pug
* Style setPasswordCiam according to designs
* Use settings value in registration screen for must_be_at_least_n_characters
* Retrieve email input with a script
* Replace mb-4 by --ds-spacing-800
* Add eye icon to toggle password visibility
* Avoid double dots after some translated strings
* Use `ciamCustomFormDangerMessage`
* Use `ciamErrorNotification`
* Use `ciamButtonContentLoading`
* Replace remaining "mb" classes
* Move new password errors to the top of the form
* Fix CIAM mixins path after rebase
* Use `ciamCustomFormDangerMessage`
* Add `data-ol-spinner-inflight` to buttons
* Replace classname ciam-notification by notification-ds
Remove borders from CIAM notifications
Fix font size
* Revert "Use settings value in registration screen for must_be_at_least_n_characters"
This reverts commit a0af95c11e171097750ad7ee871f6baf89d5c0cb.
(It's Friday afternoon so I don't want to update unrelated stuff :D)
* Update: check_your_inbox
* Remove `.ciam-card` min-height.
Unnecessary thanks to `.confirm-email-success-form`'s min-height: 400px;
* Use phosphor icons
* Style `formMessagesNewStyle` with DS notifications within CIAM pages
Alternatively, we could extend/duplicate `showMessagesNewStyle` with a CIAM variant
* Revert "Style `formMessagesNewStyle` with DS notifications within CIAM pages"
This reverts commit ed382dc1e8cdf5b916c1527f4da0a825167e9675.
* Fix styling of dynamically-created DS notifications
* Set password length info to secondary color
* Move `ciamSamlErrorNotLoggedIn` to saas-authentication module
Prevents errors in CE:
Error: ENOENT: no such file or directory, open '/overleaf/services/web/modules/saas-authentication/app/views/_mixins.pug'
at /overleaf/services/web/app/views/_mixins/ciam_mixins.pug line 3
---------
Co-authored-by: Tim Down <158919+timdown@users.noreply.github.com>
GitOrigin-RevId: afe58f18ecee92460ab628a285b6edb48a5c678d
* Replace placeholders for labels
* Remove redundant aria-label and update labels with `.visually-hidden`
* Change "Invite more members" to a label
* Fix helper text for group members and fix responsiveness
* Change error message to an error notification
* Use label and helper text instead of a placeholder
* Remove redundant label and use the placeholder text instead for the admin user searchbar
* Extract translations
GitOrigin-RevId: a504fda9779da82920b57c7b8aad38a8b027d09a
* Rename `suppressGoogleAnalytics` to `suppressAnalytics`
* Add Propensity script
* Add LinkedIn Insight Tag script
* Version the cookie to prevent adding unconsented tracking
* Move tracking loaders to Typescript, insert them in foot_scripts.pug
* Show the cookie-banner when tracking other than GA is set
* Revert `oa` cookie versioning
* Remove `async` from propensity script
* Use shared tracking loader for Hotjar, LinkedIn, and Propensity
* Reusable `insertScript`
* Remove tracking-linkedin
* Test the scripts by adding fake ids
* Revert "Test the scripts by adding fake ids"
This reverts commit 50759bb6b40fd2684d1b967d83dd71e8517c3de9.
GitOrigin-RevId: 2a7b36bfc70ac1fc983f837dd4693a19a385cbc6
* Enable Bootstrap 5 for the subscription error pages
* Override contact modal on the plans page
* Convert AI Assist related styles to CSS
* Extend single layout-website-redesign.pug template for both Bootstrap 3 and 5
* Formatting to tab indentation
* Add the switcher for AI Assist
* Fix translations in heading
GitOrigin-RevId: 54ddc35602831f1ec1fa83c9f67a091eefda7a77