Skip to content

LF-5416 Add a 30-second single-use dashboard ticket endpoint - #4304

Merged
SayakaOno merged 2 commits into
integrationfrom
LF-5416-add-a-30-second-single-use-dashboard-ticket-issued-only-to-an-allowlisted-return-address
Aug 6, 2026
Merged

LF-5416 Add a 30-second single-use dashboard ticket endpoint#4304
SayakaOno merged 2 commits into
integrationfrom
LF-5416-add-a-30-second-single-use-dashboard-ticket-issued-only-to-an-allowlisted-return-address

Conversation

@kathyavini

@kathyavini kathyavini commented Aug 5, 2026

Copy link
Copy Markdown
Collaborator

Description

This change adds the LiteFarm half of a ticket exchange with the Analytics Dashboard at https://data.litefarm.org.

POST /login/dashboard/ticket mints a 30-second signed string naming the caller's user and, optionally, one farm they are an Active member of. The Dashboard's server will later redeem it against LiteFarm for that identity — that endpoint is a follow-up. Tickets are signed with a sixth secret, JWT_DASHBOARD_SECRET, so a leaked ticket can never be presented as a login token for the LiteFarm API.

Using the endpoint

The web app calls this with a normal Authorization: Bearer login token. Request body:

{
  "return_to": "https://dashboard.example/auth/finish",
  "farm_id": "8fd0a3c2-…"
}

Response body:

{
  "ticket": "eyJhbGciOiJIUzI1NiIs…",
  "return_to": "https://dashboard.example/auth/finish"
}

return_to is where the web app will send the browser next, carrying the ticket, and must be one of the addresses in DASHBOARD_ALLOWED_RETURN_TO.

farm_id names the farm the user is currently viewing. It is sent when the user clicks through from Insights, and omitted when the user came from the Dashboard and has not chosen a farm yet, in which case the ticket's farm_id claim is null.

Before this reaches beta or production

JWT_DASHBOARD_SECRET and DASHBOARD_ALLOWED_RETURN_TO both have to be added by hand to packages/api/.env on each host, the secret being a different long random string per environment. Nothing calls the endpoint yet.

Jira link: https://lite-farm.atlassian.net/browse/LF-5416

Type of change

  • Bug fix (non-breaking change which fixes an issue)
  • New feature (non-breaking change which adds functionality)
  • Breaking change (fix or feature that would cause existing functionality to not work as expected)
  • This change requires a documentation update

How Has This Been Tested?

  • Passes test case
  • UI components visually reviewed on desktop view
  • UI components visually reviewed on mobile view
  • Other (please explain)

Checklist:

  • I have commented my code, particularly in hard-to-understand areas
  • My changes generate no new warnings
  • I have added tests that prove my fix is effective or that my feature works
  • New and existing unit tests pass locally with my changes
  • The precommit and linting ran successfully
  • I have added or updated language tags for text that's part of the UI
  • I have ordered translation keys alphabetically (optional: run pnpm i18n to help with this)
  • I have added the GNU General Public License to all new files

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>
@kathyavini kathyavini self-assigned this Aug 5, 2026
@kathyavini kathyavini added enhancement New feature or request draft labels Aug 5, 2026
Comment thread packages/api/.env.default

# Comma-separated exact addresses an Analytics Dashboard ticket may be returned to.
# Contact the Analytics Dashboard team for the value for your environment.
DASHBOARD_ALLOWED_RETURN_TO=?

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

This being an .env variable was taken from the spec so I maintained it, but these are public URLs and to consider them secrets is a bit of a stretch. Still it will give us some flexibility to adjust without a full release.

@kathyavini kathyavini removed the draft label Aug 6, 2026
@kathyavini
kathyavini marked this pull request as ready for review August 6, 2026 19:51
@kathyavini
kathyavini requested review from a team as code owners August 6, 2026 19:51
@kathyavini
kathyavini requested review from SayakaOno and removed request for a team August 6, 2026 19:51

@SayakaOno SayakaOno 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 to me!

@SayakaOno
SayakaOno added this pull request to the merge queue Aug 6, 2026
Merged via the queue into integration with commit a46fd54 Aug 6, 2026
2 checks passed
@SayakaOno
SayakaOno deleted the LF-5416-add-a-30-second-single-use-dashboard-ticket-issued-only-to-an-allowlisted-return-address branch August 6, 2026 22:12
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

enhancement New feature or request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants