* [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
* Record a metric for Stripe API request statuses to monitor rate limiting
* Add on() stub to Stripe mock in StripeClient unit tests
* Record the Stripe rate-limit reason for 429 responses
* Add acceptance test for Stripe rate-limit reason metric
GitOrigin-RevId: 1674b389f5a7fee403f980b6e813a3e2df81063c
* Fix negative AI quota countdown after reset period elapses
Backend (`FeatureUsageRateLimiter`, `TokenUsageRateLimiter`): once the
24h period had lapsed, `remainingUsage`/`remainingTokens` reset to the
full allowance but `resetDate` kept pointing at the old `periodStart +
24h` (i.e. in the past). Advance to `now + 24h` in that case.
Frontend (`NewPlanPaywall`, `ErrorGetPremium`): the "wait X hours and Y
minutes" copy computed `restoreTime - now` without clamping, so a past
`restoreTime` produced negative hours/minutes (users saw "wait -118
hours and -35 minutes"). Clamp to 0.
* Address PR review on quota reset fix
- Keep `resetDate` tied to the stored `periodStart + PERIOD` rather
than jumping to `now + PERIOD` when the period has lapsed. The
moving-target variant unnecessarily reinterpreted a stable field
(existing consumers such as `use-ai-access-gate` check whether the
reset date is in the past).
- Clamp `remainingUsage` / `remainingTokens` at 0 so a concurrent
over-usage never leaks a negative count.
- Align the boundary check with `_checkRateLimit` / `checkUsage`
(inclusive `<=`).
- Cover the period boundary and the clamp in unit tests for both
`FeatureUsageRateLimiter` and `WorkbenchRateLimiter`.
* Truncate test fixtures to whole seconds
`resetDate` is serialized with `Date.toString()`, which has second
resolution, so fixtures with a millisecond component failed the
boundary assertions.
* Revert code changes; keep tests that document the bug
The fixes masked the symptom (negative countdown) without addressing
the underlying issue, and it is not fully understood yet. Revert the
rate limiters and the writefull paywall components to their original
behaviour, and instead pin the current behaviour in tests:
- `it.fails` tests document that `remainingUsage`/`remainingTokens`
go negative when usage exceeds the allowance — these flip red when
the bug is fixed, at which point they should become plain `it`.
- The lapsed-period tests assert (with explanatory comments) that
`resetDate` keeps pointing at the end of the lapsed period, which is
the server-side ingredient of the negative-countdown support ticket.
* Reapply quota fixes
Restores the post-review state of the rate limiters (clamp remaining
counts at 0, inclusive period boundary, resetDate tied to periodStart)
and the writefull paywall components (clamp countdown at 0).
Tests intentionally untouched in this commit; the two `it.fails` tests
now need flipping to plain `it` in a follow-up.
* Flip bug-documenting tests to plain assertions
The clamp fixes are reapplied, so the `it.fails` tests now assert the
fixed behaviour directly. Trim the explanatory comments.
* Set resetDate to `Date.now() + PERIOD_IN_MILLISECONDS` instead of the expired/stale date.
Co-authored-by: Mathias Jakobsen <mathias.jakobsen@overleaf.com>
* Update lapsed-period tests for the fresh resetDate
`resetDate` is now `Date.now() + PERIOD` once the period has lapsed, so
assert an approximately-fresh date instead of the stale period end.
---------
Co-authored-by: Mathias Jakobsen <mathias.jakobsen@overleaf.com>
GitOrigin-RevId: ad39627ab9e5ceccb2a61443b2b7d39c68600594
[web] Add Stripe wallet payments (Apple Pay / Google Pay) in analytics and remove conversion to "card"
GitOrigin-RevId: f6d404d35eb108d7ddaf8ca1b6c2164cb5100c9c
* [editor-core] gracefully handle no-op TextOperation
* [web] avoid sending sync to pdf requests without a ?file
* [validation-tools] log deeper nested violations
* [editor-core] fix round trip of EditNoOperation
* [web] use custom error for sync to pdf w/o file, fix tests
* [validation-tools] include request params in log msg for violation
* Revert "[editor-core] gracefully handle no-op TextOperation"
This reverts commit c1d37261d5db72b37dbf39428c54edcfb5423f41.
* [web] gracefully handle rejected sync to pdf from context menu
GitOrigin-RevId: 21de7dc96b23575b0d67b6eb0c68ed9c2f86292b
* Simplify the new project dropdown
* Simplify the New project dropdown
* removing artefact of rebase
GitOrigin-RevId: e7e4dc48c10f2297abcd7c5b489ff2308bf9e18b
* feat: transitioning optimized images from a compile mode to a project level toggle. We still persist it for the proect and between users, and ensure that draft mode does not interact with it (silently disabled while draft mode is on)
* feat: cleaning up unused var, tests, and locales
* feat: updating copy
GitOrigin-RevId: 8010edfab164980f14125739e481d8b512f6b3df
* [web] Read subscription.features on Group Management rendering
Fixes 'ai-toggling' not properly read on Group Management rendering, causing the Feature Controls option not being displayed in certain scenarios.
GitOrigin-RevId: 4465fea15279d650d9d231e85072f1d85f2358be
* [editor-core] address zod violation for group_id
* [monorepo] use discriminated union in zod schemas where possible
* [editor-core] v1_source_doc_id is a number
GitOrigin-RevId: 38f591b07cca4e22954bfe30bf855545a6af675d