* [web] split-tests: add helpers to using an already fetched mongo user
* [web] split-tests: add ignoreOverrides option to getAssignment
* [web] split-tests: reuse already fetched mongo users where possible
* [web] notifications: fetch users in bulk for split-test assignment
* [web] split-tests: use session for assignment where possible
* [web] split-tests: pass req/res down into domain-capture hook
* [web] split-tests: explicitly throw early
* [web] split-tests: refine jsdoc types
* [web] mark analyticsId and labsProgram as required in user model
* [web] split-tests: refine jsdoc types
* Revert "[web] split-tests: pass req/res down into domain-capture hook"
This reverts commit 897a158384cd2b3e416fc34495b20d1e2a0e68f0.
GitOrigin-RevId: ea4afd15c2506f369827b5193fd16d220d3c7d0e
* [web] adopt free/premium compile backend class
* [rails] adopt free compile backend class for pdf conversion
GitOrigin-RevId: e12c99a27363f5654132c2fcdc6c5d8ccc591868
* Migration and index setup script
* Helper script to clear linked library
* Pull users collections
* Implementing get items from users zotero library
* Initial zotero sync client
* Pulling users overleaf collection
* Pulling users overleaf collection
* Initial sync pull from Zotero to Library
* Sync and import workflow from Zotero into Library
* Using user getter and setter for updating user library info
* Using new collection for library sync states
* Adding tests for new sync functionality
* Harden Zotero sync route
* Simplify Zotero sync state read and skip accounting
Fold the ensure+get double round-trip in sync-state discovery into one
findOneAndUpdate, and derive itemsSkipped from items.length - itemsInserted
instead of a running counter.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* Redact URL-encoded token secret from OAuth1 request logs
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* fixing type failure
* Rename upsertZoteroReferences to importZoteroReferences
The method is insert-only; the bulkWrite upsert just makes re-runs
idempotent rather than updating, so the name now reflects that.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* Validate librarySyncStates reads with zod
Replace the `as` / `as unknown as` casts in getLibrarySyncState and
ensureLibrarySyncState with a zod schema parse, matching
LibraryReferenceRepository. Types are now derived from the schema.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* Defer Zotero sync watermark until pagination exists
Don't record lastSyncedVersion after a single-page sync: committing the
page-1 version before pagination (#34286) lands would let incremental
sync (#34282) skip items beyond the first page. Drops the commitSyncState
call and toVersion from SyncResult; the version envelope is left for the
follow-ups. commitSyncState stays in the repository for #34282.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* Extract OAuth1Transport from OAuth1Handler
Lift the OAuth1 client/config registry, getAccessToken, redactSecret and
classifyOAuth1Error into oauth1/OAuth1Transport.js, plus a signedGet
helper for provider modules to reuse. OAuth1Handler now imports them; its
method bodies are unchanged. Test mocks move to esmock's global scope so
they reach the transport, with Errors pinned for instanceof checks.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* Move Zotero collections/items into a per-provider ZoteroProvider
Carve the Zotero-only operations out of the generic OAuth1 handler into
providers/ZoteroProvider.js, dispatched via providers/ProviderRegistry.js.
ReferenceProviderHandler now resolves the provider from the registry and
throws UnsupportedOperationError (mapped to 501) for providers without the
operation, instead of bolting Zotero-only methods onto OAuth1Handler.
Zotero's collectionsUri/itemsTopUri are read from settings by ZoteroProvider
rather than copied into the shared OAuth1 registry. Removes getCollections/
getUsersItemsChanges (and their tests) from OAuth1Handler.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* Add ZoteroProvider unit tests and unsupported-provider coverage
Cover ZoteroProvider collections/items parsing, header envelope and
malformed-payload rejection, and assert ReferenceProviderHandler throws
UnsupportedOperationError for providers without a sync implementation.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* Make addSyncedCollection idempotent
Only push a synced collection when its key isn't already recorded, so a
repeat sync can't create duplicate syncedCollections entries regardless of
the caller-side guard. Adds a sequential test asserting no duplicate.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* Hardcode Zotero library endpoint paths, derive host from site
Move the collections/items-top endpoints out of settings and into
ZoteroProvider as path constants, taking only the host from the
provider's `site`. Aligns Zotero `site` with the other providers
(API host) and avoids adding per-endpoint config points.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* Make Zotero sync routes explicit, 404 unknown providers
Hardcode the collections/items routes to /zotero rather than a generic
/:ref_provider, so a non-Zotero request 404s on the missing route
instead of returning a misleading 501. Drop the UnsupportedOperationError
501 branch, reverting OAuth2 unsupported ops to their prior 500.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
GitOrigin-RevId: 50e16a9c46f995c2e9db6eed50ef81c3a71ea71d
* [web] always provide an analyticsId when registering new users
* [web] add recordEventForMongoUser and use it during login
* [web] use recordEventForMongoUser/recordEventForSession where possible
* [web] use session cache for analyticsId when refreshing features
* [web] add setUserPropertyForMongoUser and use it where possible
* [web] use setUserPropertyForSessionInBackground where possible
* [web] use mongo user for analyticsId when setting utm user properties
* [web] admin-panel: user updated user when sending events
* [web] enforce labsProgram being populated on user object
Co-authored-by: Andrew Rumble <andrew.rumble@overleaf.com>
* [web] revert FeaturesUpdater.refreshFeatures change
* [web] labs: move session maintenance into handler
* [web] analyticsMiddleware: ensure that all users have labsProgram field
* [web] migrate a few more events to recordEventForSession
* [migrations] mark back-fill of db.users.analyticsId as required
* [web] setUserPropertyForSessionInBackground: throw early
---------
Co-authored-by: Andrew Rumble <andrew.rumble@overleaf.com>
GitOrigin-RevId: 19c26bc36abd8c11f0a8f458d25abfb7cbda0bb7
* [web] throw early in UserCreator.createNewUser with missing analyticsId
* [migrations] rerun back-fill of db.users.analyticsId
GitOrigin-RevId: eabf16326192fca4dbea00ad643cee4315c426c6
* [web] add migration for back-filling db.users.labsProgram=false
Co-authored-by: Andrew Rumble <andrew.rumble@overleaf.com>
* [web] include user.labsProgram=false in session
---------
Co-authored-by: Andrew Rumble <andrew.rumble@overleaf.com>
GitOrigin-RevId: 0b3322007f6a36d2999d646869dc84f2c6ad4d2d
* [clsi] forward instanceType to web
* [clsi] expose zone in compile response
* [clsi-perf] add new fields for instanceType and zone
* [clsi] expose isPreEmptible flag in compile response
* [clsi-perf] also add isPreEmptible field
* [clsi] rename isPreEmptible -> isSpotInstance
GitOrigin-RevId: 2bc2d772e0b14da83afb51f5f3e4fefd3f60b733
* [web] Disabled AI state for project collaborators
Displays a disabled state, along with an informative tooltip in Editor AI controls.
GitOrigin-RevId: a749cd25d54f6ef3d7bf4276343400894bc193a4
The regression was introduced in 487e1767b8ac531588a272f0492a900c24162b33.
GeoIp is not configured in CE/SP, yielding an error log when rendering the project list.
GitOrigin-RevId: 55410611a5fef95daa5914a31969893107777593
* [web] gracefully handle notifications being down on project dashboard
* [monorepo] remove haproxy from service-to-service communication
Co-authored-by: Anna Claire Fields <acfields11@gmail.com>
* [monorepo] fix OVERLEAF_LATEXQC_API_BASE_URL in dev-env
* [SaaS test fix] update history v1 export base url
---------
Co-authored-by: Anna Claire Fields <acfields11@gmail.com>
GitOrigin-RevId: cf355ef061412a1b57b5383cb60d4be574055af5
* [web] determine rootResourcePath if not set in compile request
This affects the following compile requests:
- compile request from "Download PDF" button on project dashboard
Pre-resolving the rootResourcePath for all projects is too expensive.
Adding an endpoint to resolve it is too slow and doubles project auth
check.
- custom frontends
Can't do much about these. I tried blocking them, requests still come
in after weeks of returning 400s.
The rootDoc maintenance methods now return {rootResourcePath,rootDocId}
rather than just the rootDocId. This avoids an extra project lookup when
the rootDocId was updated.
GitOrigin-RevId: ab7c539b7ffd162bde0903fe1040a48834a245f5
* Basic focus mode implementation
* Exit button
* Toggle between editor pdf mode
* Keep the rail and file tree mounted for editor tabs to work properly
* Hide Switch to editor button in focus mode
* Show shortcuts in layout menu
* Gate the feature behind a focus-mode feature flag
* Add split test badges in the focus mode dropdown item and top bar
* Add analytics tracking for focusMode changes
* Cleanup leftover IdeView value
* Allow passing focusMode value in layout test provider
* Remove redundant listener for focus mode shortcut
* Update focus mode and full project search listeners to use the event key
* Post-rebase cleanup
---------
Co-authored-by: Anna Fields <acfields11@gmail.com>
GitOrigin-RevId: 207f8d136fa6662558c474cca4a867a9842191e7
* Replace token-link email with 6-digit code on SSO registration
Unverified SSO emails previously received a long-lived token link
(90-day TTL) via UserEmailsConfirmationHandler. This replaces that
flow with the same 6-digit code verification used for password
registration, redirecting through /registration/confirm-email.
- SSOManager.registerSSO now always confirms email (caller must
verify first); removes sendConfirmationEmail / _finishRegistration
- SSOController._signUp sends confirmation code and stores
pendingSSORegistration in session when IdP email_verified is false
- New SSOConfirmEmailHandler completes registration after code check
via completeSSOEmailConfirmation module hook
- OnboardingController confirm-email handlers accept
pendingSSORegistration alongside pendingUserRegistration
confirmEmailFromToken (POST /user/emails/confirm) removal is deferred
to a follow-up PR to avoid breaking in-flight 90-day tokens.
Closes#28607
* Fix unverified-email edge cases; Add ORCID e2e tests;
* Rename `confirmEmail` parameter to `emailVerifiedByIdP` in _signUp function
* Remove `sendConfirmationEmail`
* Mock getUserByAnyEmail in tests
* Extract _finishSSORegistration helper to deduplicate the register →
set session flags → allocate referral → finishSaasLogin → finishLogin
sequence shared by both the direct and deferred (code-confirmed) paths.
* Stop duplicating session data in pendingSSORegistration
analyticsId, splitTests, and referal_* are already in the session at
confirmation time — no need to copy them into pendingSSORegistration.
Re-fetch splitTests fresh on completion instead.
* Simplify the code
* Remove dead confirmEmail template
No callers remain after sendConfirmationEmail was deleted. The token-link
flow (confirmEmailFromToken) only validates tokens, never sends email.
* Remove dead reconfirmEmail template
* Address comments from Copilot
* Clear stale pending registration when starting a new flow
* Add unit tests for completeSSOEmailConfirmation
* Add `verificationMethod` param
* Fix camelcase issues
* Extract _createSSOUser and _registerAndFinish helpers to deduplicate registration logic
* Remove obscure "registration_error"
* Prevent FormTextIcon from shrinking
* Enable "email_already_registered_sso" error
* Misc. improvements to confirm-email-form.tsx
* Remove `UserEmailsConfirmationHandler` mock
Co-authored-by: Olzhas Askar <olzhas.askar@overleaf.com>
* Add info on sso_email.pug page
---------
Co-authored-by: Olzhas Askar <olzhas.askar@overleaf.com>
GitOrigin-RevId: d0196ebc6d81ff61bcd27726d0b899b743d08d64
* [web] Order plans in Change Plan modal consistently
Reorder the plans returned by `buildPlansListForSubscriptionDash` so the
Subscription page "Change plan" modal lists them top-to-bottom as:
1. Student annual
2. Student monthly
3. Standard monthly
4. Standard annual
5. Pro monthly
6. Pro annual
Previously `buildPlansList` produced three per-period buckets which the
dash function concatenated, giving an order that flipped per family.
Replace that with an explicit `CHANGE_PLAN_MODAL_PLAN_CODES` list so the
order matches the Design QA spec at a glance. The now-unused
`studentAccounts`, `individualMonthlyPlans`, `individualAnnualPlans`,
`groupMonthlyPlans`, and `groupAnnualPlans` buckets are dropped from
`buildPlansList` (no other callers).
Closes#34024
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* [web] Update personal-plan acceptance test for new buildPlansList shape
The previous test asserted `buildPlansList().individualMonthlyPlans`,
which no longer exists after the change-plan modal reorder dropped the
per-period buckets. Move the assertion to
`buildPlansListForSubscriptionDash()`, which is where the personal-plan
exclusion is now enforced (via `CHANGE_PLAN_MODAL_PLAN_CODES`).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* [web] Drop now-dead client-side plan filter
`IndividualPlansTable` used to filter out `paid-personal`,
`paid-personal-annual` and `institutional_commons` defensively because
the old `buildPlansListForSubscriptionDash` returned every non-group
plan that wasn't `hideFromUsers`. The previous commit pins the modal to
an explicit six-plan list (`CHANGE_PLAN_MODAL_PLAN_CODES`), so none of
those plan codes ever reach the frontend and the filter is dead. Remove
it and the now-unused `useMemo` import.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* Revert "[web] Drop now-dead client-side plan filter"
This reverts commit 83e8448f2cfa2c68e44b749d5a2bc350a7443c6d.
We'll do that in a later cleanup
* Swap "Student monthly" and "Student annual" for consistency
---------
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
GitOrigin-RevId: 046a235e14e7ad6622288f5a5a723f5a4f7f14da
* [web] Redirect missing AI add-on purchase to subscription dashboard
The two error paths in `previewAddonPurchase` redirected to
`/user/subscription/plans#ai-assist`, but the `#ai-assist` anchor was
removed when the AI Assist add-on was retired, so users land at the top
of the plans page with no context. Align both with the other error
branches in the same function and the `plans-2026-phase-1` enabled
branch, which already redirect to
`/user/subscription?redirect-reason=ai-assist-unavailable` — the
subscription dashboard shows the matching warning alert
(`redirect-alerts.tsx`).
Update the acceptance test to match the new redirect target.
Closes#34074
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* [web] Update ai-assist-unavailable warning to reflect bundled AI features
The previous copy said "AI Assist isn't available to you due to your
current subscription type", which read as a hard block. Now that the AI
Assist add-on has been retired and AI features are included with every
paid plan, the warning should point users to the pricing page instead of
implying their plan can't access AI at all.
Keep the existing translation key for now — a follow-up can rename it
once #33624 (AI page CTA destination) is resolved.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* [web] Link the ai-assist-unavailable warning to the pricing page
* [web] Rename key `ai_assist_unavailable_due_to_subscription_type` -> `ai_assist_unavailable`
* [web] Update french and german translations
---------
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
GitOrigin-RevId: ae1319fa5b857d8f292de77c82ef0bda1c7ad144
* Allow admin access to user PATs
* Tests for new screen in admin panel
* Adding error for invalid token and way to parse error for OAuth 2
* Git bridge handles expired PAT
* Script for alerting on close to expiry and expired git tokens
* Refactoring and simplifying
* Updating email templates to match agreed docs
* tweak to email subject to include Overleaf
* Allowing dry run in scripts and general tidy up
* removing redundant tests and dry running script
* Fixing CI errors
* Adding new tab to admin test expectation
* Address PR feedback on oauth2-server changes
- Replace ad-hoc overleafErrorCode prop with a TokenExpiredError subclass
- Collapse listTokens/listTokensForAdmin into a single hook
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
* Adding cron definitions for alerting on expiring git pat
---------
Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>
GitOrigin-RevId: 69b9fd901a201592a580c69abe7bd7d603e85d3a