Skip to content

Patch 3.13.1 - #4311

Merged
SayakaOno merged 43 commits into
mainfrom
integration
Aug 13, 2026
Merged

Patch 3.13.1#4311
SayakaOno merged 43 commits into
mainfrom
integration

Conversation

@SayakaOno

Copy link
Copy Markdown
Collaborator

dependabot Bot and others added 30 commits August 4, 2026 21:34
Bumps [ip-address](https://github.com/beaugunderson/ip-address) from 10.0.1 to 10.4.0.
- [Release notes](https://github.com/beaugunderson/ip-address/releases)
- [Commits](beaugunderson/ip-address@v10.0.1...v10.4.0)

---
updated-dependencies:
- dependency-name: ip-address
  dependency-version: 10.4.0
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
Bumps [fast-uri](https://github.com/fastify/fast-uri) from 3.1.2 to 3.1.5.
- [Release notes](https://github.com/fastify/fast-uri/releases)
- [Commits](fastify/fast-uri@v3.1.2...v3.1.5)

---
updated-dependencies:
- dependency-name: fast-uri
  dependency-version: 3.1.5
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
POST /login/dashboard/ticket mints a 30-second signed ticket naming the
caller's own user and, optionally, one farm they are an Active member of.
The Analytics Dashboard trades the ticket server-to-server for that
identity, so no identity travels in a browser-visible URL. Each ticket
carries a randomUUID jti for the replay table added in a follow-up.

JWT_DASHBOARD_SECRET is a sixth signing secret, so a leaked ticket can
never be presented as a login token. return_to must match an entry of
DASHBOARD_ALLOWED_RETURN_TO exactly. Without that check, an attacker
could send a user to a sign-in URL bearing their own return address and
be handed a valid ticket. Empty entries are filtered out of the parsed
list, so an unset or blank variable rejects every address rather than
matching an empty return_to.

checkJwt is attached to the route directly, because the login router is
mounted before the global checkJwt in server.ts. user_id is read from
req.auth only; a user_id in the request body is ignored.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…gle-use-dashboard-ticket-issued-only-to-an-allowlisted-return-address

LF-5416 Add a 30-second single-use dashboard ticket endpoint
POST /login/dashboard/exchange trades the 30-second ticket minted by
/login/dashboard/ticket for the user's identity and their current farm
memberships. The Analytics Dashboard's server calls it from its own
/auth/finish handler; until now that call received a 404 and the user was
told LiteFarm could not be reached.

The new dashboard_ticket_use table is what makes the ticket single-use. Its
primary key is the ticket's jti, and the insert is the claim:
onConflict('jti').ignore().returning('jti') reports whether this request was
the one that inserted, so two concurrent exchanges cannot both succeed. The
transaction holds that insert and the five-minute prune and nothing else, so
a later rejection cannot roll the claim back and release the ticket.

The farm list is read live and filtered to Active memberships on farms that
are not deleted, because the Dashboard's own database is a nightly copy. The
farm_id the ticket names is checked against that list before it is returned:
the Dashboard writes that field straight into its session as the active farm
without comparing it to the farms it was sent.

The route carries no checkJwt. Express mounts /login before the global
checkJwt in server.ts, so nothing under that path reaches the middleware, and
the caller is a server holding no LiteFarm token.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
'claimed' felt ambiguous as it could have also referred to the ticket state; what it is actually tracking is the outcome of *this* request
…at-exchanges-a-dashboard-ticket-for-identity-and-live-farm-membership

LF-5417 Add the dashboard ticket exchange endpoint
getReturnToFromSearch reads the return_to query parameter from a location
search string. buildDashboardTicketUrl attaches the ticket with
URLSearchParams, so a return address that already carries a query string
still works.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The slice holds the return address captured on arrival plus a flag for the
request being in flight. handOffToDashboardIfRequested is the single place
that calls POST /login/dashboard/ticket: it reports false when no return
address is stored, clears the address on success and on failure, and
navigates to the address the API returned rather than the stored copy.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…gle sign-in

customLoginWithPasswordSaga, customCreateUserSaga and loginWithGoogleSaga
each ask handOffToDashboardIfRequested before navigating. It answers false
unless the user arrived from the Dashboard with a return address, and a
sign-in without one reaches its ordinary destination.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…live

Access tokens last seven days, so a user sent over from the Dashboard
usually still has a LiteFarm session and would otherwise land on the Home
page. useDashboardHandoff reads the return address during render, because
CustomSignUp is a child of Routes and replaces the location with one that
carries no search string. On mount it stores the value or null and starts
the hand-off when a token is present; Routes renders a Spinner in place of
the route tree until the browser leaves.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The endpoint moves to store/api/dashboardTicketApi.ts as a mutation, where
new endpoints belong, and prepareHeaders supplies the Authorization header.
handOffToDashboardIfRequested becomes a plain async function in
containers/dashboardTicketHandoff.ts; yield call accepts it unchanged, so
the three sign-in sagas keep the same two lines and the watcher and its
action are no longer needed.

request.reset() drops the mutation result once the ticket has been read.
The whole store is persisted to localStorage, so without it the ticket
would be written to disk.

Starting the endpoint needs a cast to the thunk dispatch signature, because
store.ts annotates its middleware array as Middleware[] and that reduces
store.dispatch to Dispatch<AnyAction>.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
* Set X-Forwarded headers and remove incorrect ones
* access req.ip for client IP logging
401 is returned from this endpoint only from checkJwt, before the controller runs. Note that an invalid return_to is a 400
…n ticket minting sounding like an error on redirect (which was never even run)
…e-dashboard-after-password-new-account-and-google-sign-in

LF-5418 Send the user to the dashboard after password new account and google sign in
The include glob was src/tests/**/*.test.js?(x), so a .ts or .tsx test file was
collected by nothing and passed by not running.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
id_token in localStorage and user_id in the persisted store are written
separately and can disagree. A token with no identity is not a usable session,
and the token is cleared.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
isAuthenticated() reports only that a token exists. Every farm selector filters
on user_id, so a token-only session rendered the signed-in tree with no farms
and no request that could 401.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Concern is the a second tab now getting into the opposite broken state -- Redux user_id but no id_token
SayakaOno and others added 11 commits August 12, 2026 10:00
* set gzip_proxied to any and add image/svg+xml to gzip_types
…second-tab-sits-on-the-sign-on-screen-locks-the-user-on-the-welcome-screen-with-no-way-back

LF-5443 Signing in while a second tab sits on the sign on screen locks the user on the welcome screen with no way back
…address-10.4.0

Bump ip-address from 10.0.1 to 10.4.0
…kages/api/fast-uri-3.1.5

Bump fast-uri from 3.1.2 to 3.1.5 in /packages/api
@socket-security

socket-security Bot commented Aug 13, 2026

Copy link
Copy Markdown

Review the following changes in direct dependencies. Learn more about Socket for GitHub.

Diff Package Supply Chain
Security
Vulnerability Quality Maintenance License
Added@​testing-library/​react@​16.3.210010010087100

View full report

…ntegration_webapp_locales

New Crowdin translations webapp_locales
@SayakaOno
SayakaOno marked this pull request as ready for review August 13, 2026 17:54
@SayakaOno
SayakaOno requested review from a team as code owners August 13, 2026 17:54
@SayakaOno
SayakaOno requested review from den4ik1203 and kathyavini and removed request for a team August 13, 2026 17:54

@kathyavini kathyavini left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks good!

@SayakaOno
SayakaOno merged commit f0d45a5 into main Aug 13, 2026
7 of 8 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants