Skip to content

fix(proxy): forward the query string on the two routes that dropped it - #158

Merged
Bccorb merged 1 commit into
mainfrom
fix/forward-admin-users-query
Sep 8, 2026
Merged

fix(proxy): forward the query string on the two routes that dropped it#158
Bccorb merged 1 commit into
mainfrom
fix/forward-admin-users-query

Conversation

@Bccorb

@Bccorb Bccorb commented Sep 8, 2026

Copy link
Copy Markdown
Contributor

Found while checking which callers send a large limit for fells-code/seamless-auth-api#299.

The bug

GET /admin/users and GET /internal/auth-events/login-stats were built without a query, so both adapters called the auth API with a bare path. Every other route that reads query parameters forwarded them, which is exactly what made these two hard to notice: the endpoints answered 200 with the wrong page or the wrong window, and nothing reported an error.

I swept every query-consuming route in the auth API against the built Express adapter rather than reading the code. Before:

DROPPED  /admin/users
  KEPT   /admin/sessions
  KEPT   /admin/auth-events
  KEPT   /admin/organizations
  KEPT   /internal/auth-events/summary
  KEPT   /internal/auth-events/timeseries
  KEPT   /internal/auth-events/grouped
DROPPED  /internal/auth-events/login-stats

Those eight are the complete set: they are every path in the API's openapi.json carrying query parameters, cross-checked against every req.query reader in its controllers. GET /magic-link also takes redirectUri but is not exposed through the adapter, which routes /magic-link/verify/:token instead.

What users saw

The admin dashboard's user search and paging did nothing. It sends ?search=...&limit=10&offset=N, the adapter forwarded /admin/users, and the API answered with its default first 50 users. The Users screen therefore rendered 50 rows while its pager claimed a page size of 10, and Next re-fetched the same rows.

The login statistics panel ignored its time range the same way, so the range control on the Security screen moved every other panel and not that one.

The dashboard's e2e tests pass because its Playwright mock intercepts at the dashboard boundary and never exercises an adapter. That is why nothing caught either of these.

The fix

getUsersHandler and getLoginStatsHandler in @seamless-auth/core now take an optional query like the other list handlers, which is additive for anyone calling core directly. Both adapters pass the incoming query through. Five changed lines in total.

The test

Rather than two one-off tests, each adapter is now held to one table of every route the auth API reads query parameters on. The failure this guards against is silent, so a route added to an adapter without forwarding its query fails a test instead of shipping quiet.

I checked the table is load-bearing by reverting each fix and confirming the matching case fails: reverting the Express users fix fails 1 of 8 there, reverting both Fastify entries fails 2 of 8, and reverting the Express login-stats fix fails 2 of 8.

The parity suite is deliberately not the home for this. It compares Express and Fastify responses to each other, and both adapters were equally broken here, so it passed throughout.

Release ordering

Independent of fells-code/seamless-auth-api#299 and safe in either order. Worth noting the combined effect once both land: a caller whose window was previously discarded will get the page it actually asked for rather than the API's default, and a query the API rejects will surface as a 400 rather than being silently dropped. The changeset says so.

Verification

  • pnpm build clean across all three packages
  • pnpm test: core 246, express 167 (up from 159), fastify 62 (up from 54), all passing
  • Re-ran the sweep after the fix: all eight routes forward

GET /admin/users and GET /internal/auth-events/login-stats were built without a
query, so both adapters called the auth API with a bare path. Every other route
that reads query parameters forwarded them, which is what made these two hard to
notice: the endpoints answered 200 with the wrong page or the wrong window, and
nothing reported an error.

The visible effect was that the admin dashboard's user search and paging did
nothing. It sent ?search=...&limit=10&offset=N, the adapter forwarded
/admin/users, and the API answered with its default first 50 users. The login
statistics panel ignored its time range the same way, so the range control on
the Security screen moved every other panel and not that one. The dashboard's
own e2e tests pass because its Playwright mock intercepts at the dashboard
boundary and never exercises an adapter, which is why nothing caught this.

getUsersHandler and getLoginStatsHandler take an optional query like the other
list handlers now, which is additive for anyone calling core directly, and both
adapters pass the incoming query through.

Both adapters are held to one table of every route the auth API reads query
parameters on, rather than a test per route. The failure this guards against is
silent, so a route added to an adapter without forwarding its query now fails a
test instead of shipping quiet. The table was checked by reverting each fix and
confirming the matching case fails.
@Bccorb
Bccorb merged commit 9e9afb2 into main Sep 8, 2026
2 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.

1 participant