redwoodjs / redwood

The App Framework for Startups
https://redwoodjs.com
MIT License
17.25k stars 991 forks source link

Vercel base64 encoding empty body throws 'CORS' error from v0.37.0+ #3897

Closed aaronvanston closed 2 years ago

aaronvanston commented 2 years ago

Hey team,

I've come across an issue with base64 decoding of an empty event body, affecting Redwood instances deployed to Vercel on version 0.37.0 and up.

Overview

This issue is very similar to #3309 in which Vercel says the event body is encoded even if the event body is empty. This typically occurs when a preflight/ OPTIONS call happens from a remote server, the body is left empty. This check will fail and the browser will interpret this as failed CORS as the function exited before setting the correct response headers.

This affects any use of the GraphQL function from a remote server, despite correct CORS settings. Applies to instances with and without auth set-ups.

I believe it fails on the execution of this function:

https://github.com/redwoodjs/redwood/blob/main/packages/graphql-server/src/functions/graphql.ts#L50-L56

The server will return:

 {
  "data": {
    "errorMessage": "Unexpected end of JSON input",
    "errorType": "SyntaxError",
    "stack": [
      "SyntaxError: Unexpected end of JSON input",
      "    at JSON.parse (<anonymous>)",
      "    at parseEventBody (/var/task/node_modules/@redwoodjs/graphql-server/dist/functions/graphql.js:82:17)",
      "    at normalizeRequest (/var/task/node_modules/@redwoodjs/graphql-server/dist/functions/graphql.js:89:16)",
      "    at handlerFn (/var/task/node_modules/@redwoodjs/graphql-server/dist/functions/graphql.js:233:21)",
      "    at execFn (/var/task/node_modules/@redwoodjs/graphql-server/dist/functions/graphql.js:328:22)",
      "    at AsyncLocalStorage.run (async_hooks.js:314:14)",
      "    at Object.<anonymous> (/var/task/node_modules/@redwoodjs/graphql-server/dist/functions/graphql.js:337:58)",
      "    at Runtime.internal [as handler] (/var/task/___vc_launcher.js:33:25)",
      "    at Runtime.handleOnce (/var/runtime/Runtime.js:66:25)"
    ]
  }
}

Steps to reproduce

  1. Create a new instance of RW from 0.37.0 and up. I've replicated this on 0.39.4
  2. Configure and deploy this to Vercel
  3. Try to call the graphQL server from a remote server in which a preflight check occurs. I've done this through a new install of nextJS and connected to the RW API via ApolloClient
  4. Optionally: Configure the CORS settings for your remote server. Regardless of these settings, it will exit early with the mentioned error

This should fail on the OPTIONS call to the server with chrome reporting a CORS error.

Vercel event body

I intercepted the failing Vercel event before it hit the GraphQL handler, and the event body consisted of:

{
  resource: '/{proxy+}',
  path: '/api/graphql',
  httpMethod: 'OPTIONS',
  body: undefined,
  isBase64Encoded: true,
  queryStringParameters: {},
  multiValueQueryStringParameters: [Object: null prototype] {},
  headers: {
    ...
  }
}

In this instance, you can see Vercel has marked it as base64 encoded at the same time it's undefined.

Extrapolating the parseEventBody function, and running this with an undefined body returns the same error:

Screen Shot 2021-12-13 at 9 40 01 pm

Public Example

I've created and publically hosted brand new instances of a Redwood API/Web (version 0.39.4) and a separate NextJS application calling the Redwood API.

Redwood: Repo: https://github.com/aaronvanston/rw-vercel-test URL: https://rw-vercel-test.vercel.app/

NextJS UI: Repo: https://github.com/aaronvanston/nextjs-ui-rw-test URL: https://nextjs-ui-rw-test.vercel.app/

If you visit the NextJS UI, I've set up a call to the Redwood API that triggers on page load. You can inspect your network and see it fails with a CORS error.

I believe the fix, is similar to that #3309 if I'm not mistaken? Users affected would be those deploying to Vercel and calling the Redwood API from an external source, potentially not a common use case?

Temporary fix

I've managed to put in a hacky fix that behaves like a middleware for the event and sends through an empty encoded object if the body is undefined.

export const handler = async (...all) => {
  const [event = {}, ...rest] = all

  const eventWithBody = {
    ...event,
    ...(event?.isBase64Encoded && { body: event?.body || 'e30=' }),
  }

  return rwGqlHandler(eventWithBody, rest)
}

Where rwGqlHandler is the original handler within the graphql.{ts|js} function file.

This fixes the issue but is quite hacky.

aaronvanston commented 2 years ago

Please note, this error could have occurred on the previous version prior to 0.37.0, this just happens to be the version we upgraded to and the issue arose. We're currently stable on version 0.36.2 in production with no issue.

thedavidprice commented 2 years ago

@viperfx I know you ran into this back in the day and we added support. Do you have any thoughts about this?

Here's an Issue and PR about this related to Auth specifically:

viperfx commented 2 years ago

So because Vercel uses aws and this behavior is part of api gateway - the base64 encoding part I mean.

However, I've never seen it being undefined.

dac09 commented 2 years ago

@dthyresson and I have confirmed this is an issue, specific to vercel deploys. Thank you very much for the reproduction. We will fix for next patch!

dac09 commented 2 years ago

Confirming that this has been fixed!

e.g.

curl -X "OPTIONS" "https://cors-options-test-5rgrdffw2-redwoodjs.vercel.app/api/graphql"
aaronvanston commented 2 years ago

Fantastic, thanks @dac09! 🎉