Skip to content

v5.0.6

Choose a tag to compare

@Tobbe Tobbe released this 01 Aug 19:44
· 3242 commits to main since this release

Release Notes

This release has the PR that was included in v5.0.5, plus another fix that makes newly created Cedar apps work as well. Plus an e2e test to verify that they work, and that we don't regress on this again.

This is the version to use for anyone on the v5 release track.

Changelog

πŸ› οΈ Fixes

fix(api): Always pass authDecoder for graphql requests (#2271) by @Tobbe

Follow-up to #2270, which introduced a regression.

Problem

On a project with no auth set up, every GraphQL request dies with:

🚨 graphql-server Error building context. Error: Exception in getAuthenticationContext: unusable
    at onContextBuilding (@cedarjs/graphql-server/dist/cjs/plugins/useRedwoodAuthContext.js:39:15)

Reproduced from a fresh create-cedar-app project running yarn cedar dev.

Root cause

#2270 stopped buildCedarContext from resolving serverAuthState for routes that don't need it, and used "was an authDecoder supplied?" as the proxy for "is this a GraphQL route?".

That proxy is wrong. A project without auth configured has no authDecoder at all, so graphqlOptions.authDecoder is undefined and GraphQL stopped resolving auth state up front. From there:

  1. buildCedarContext skips the eager resolve, leaving serverAuthState undefined
  2. Yoga parses the GraphQL params, consuming the request body
  3. useRedwoodAuthContext's if (!authContext) fallback resolves auth state itself
  4. getAuthenticationContext β†’ requestToBaseEvent β†’ request.clone() on an already-consumed body β†’ TypeError: unusable

Any request carrying an auth-provider cookie or header hits it β€” a stale auth-provider cookie left on localhost by an earlier test project is enough to trigger it on a project that has never had auth.

The timing is the crux: resolving auth state is only safe before the GraphQL server reads the body, so it can't be left to a fallback that runs during context building.

Fix

GraphQL call sites pass a noopAuthDecoder when the project has no decoder of its own, which keeps buildCedarContext's eager resolve running. Resolving with it produces the same payload as resolving with no decoders at all β€” decoded is null either way β€” so nothing else changes.

#2270's actual fix is preserved: plain function routes still skip the resolve, so a consumer that always sends AUTH_PROVIDER_HEADER no longer 500s an endpoint that doesn't use auth. buildCedarContext and BuildCedarContextOptions are unchanged from main apart from a comment.

This is scaffolding

The underlying constraint is that requestToBaseEvent materialises the request body lazily, from inside the auth path. In 2.x there was no such constraint: the Fastify handler converted to a Lambda event eagerly (event: lambdaEventForFastifyRequest(req)), and getAuthenticationContext passed that event straight through without ever touching a body. useRedwoodAuthContext resolved auth state lazily during onContextBuilding β€” after Yoga had read the body β€” and that was fine.

The real fix is to capture the body at the entry point instead. Both Fastify entry points already construct the Request from a body string they hold (req.body / req.rawBody), so it costs nothing there; the fetch-native entry points need one clone() before anything reads. That drops the ordering constraint entirely and lets noopAuthDecoder be deleted. noopAuthDecoder's JSDoc says as much.

It's also worth knowing that whether the late fallback survives is currently a matter of timing rather than design. Under cedar dev --ud the fallback runs while the body is still readable, so it works; under cedar serve api --ud the body has already been consumed, so it doesn't. Both are covered below. Capturing at the entry point would make that difference stop mattering.

Testing

New assertions in tasks/ud-tests, against __fixtures__/cedar-ud-app β€” the fixture matters, because it has no authDecoder. test-project configures dbAuth, so the eager resolve always happens there and the failure cannot be reproduced.

The trigger has to be the auth-provider cookie, not the header. A cookie takes the schema: 'cookie' branch, which sets token/type/schema and so reaches the point where the event is built from a consumed Request. A bare auth-provider header throws earlier, in parseAuthorizationHeader β€” that's #2270's failure, covered separately by #2274.

Verified by reverting the three fixed files to main, rebuilding, and re-running:

without fix with fix
udServe β€” cedar serve api --ud fails passes
udDev β€” cedar dev --ud passes passes

udDev never failed, so its assertion is there to keep that path working rather than as a repro, and its comment says so.

  • tasks/ud-tests β€” 9 passing
  • @cedarjs/api β€” 256 passing
  • @cedarjs/graphql-server β€” 141 passing
  • @cedarjs/api-server β€” 100 passing, 2 skipped
  • @cedarjs/vite β€” 234 passing
  • yarn build β€” clean
  • eslint and prettier on all changed files β€” clean

🧹 Chore

  • chore(test): Cover function routes receiving a stray auth-provider (#2274) by @Tobbe