Skip to content

Commit

Permalink
Merge branch 'main' into issue-5792
Browse files Browse the repository at this point in the history
  • Loading branch information
cannikin authored Jul 21, 2022
2 parents 1296a32 + 8884255 commit 03c44ba
Show file tree
Hide file tree
Showing 11 changed files with 232 additions and 125 deletions.
2 changes: 1 addition & 1 deletion docs/docs/quick-start.md
Original file line number Diff line number Diff line change
Expand Up @@ -104,7 +104,7 @@ yarn rw setup ui --help
## Testing with Jest

It'd be hard to scale from side project to startup without a few tests.
Redwood fully integrates Jest with the front and the backends and makes it easy to keep your whole app covered by generating test files with all your components and services:
Redwood fully integrates Jest with both the front and backends, and makes it easy to keep your whole app covered by generating test files with all your components and services:

```
yarn rw test
Expand Down
5 changes: 3 additions & 2 deletions nx.json
Original file line number Diff line number Diff line change
@@ -1,13 +1,14 @@
{
"tasksRunnerOptions": {
"default": {
"runner": "nx/tasks-runners/default",
"runner": "@nrwl/nx-cloud",
"options": {
"cacheableOperations": [
"test",
"build"
],
"parallel": 5
"parallel": 5,
"accessToken": "ODMxYWQ1ZjgtMTJhNi00M2Q1LTg1YTAtNTk3NjFkNzNmZjk0fHJlYWQ="
}
}
},
Expand Down
3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -55,11 +55,12 @@
"@babel/preset-react": "7.16.7",
"@babel/preset-typescript": "7.16.7",
"@babel/runtime-corejs3": "7.16.7",
"@nrwl/nx-cloud": "14.2.0",
"@playwright/test": "1.23.4",
"@testing-library/jest-dom": "5.16.4",
"@testing-library/react": "12.1.5",
"@testing-library/react-hooks": "8.0.1",
"@testing-library/user-event": "14.2.6",
"@testing-library/user-event": "14.3.0",
"@types/fs-extra": "9.0.13",
"@types/jest": "27.5.2",
"@types/jscodeshift": "0.11.5",
Expand Down
3 changes: 3 additions & 0 deletions packages/api/src/functions/dbAuth/DbAuthHandler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -343,6 +343,9 @@ export class DbAuthHandler {
)
this.webAuthnExpiresDate = webAuthnExpiresAt.toUTCString()

// Note that we handle these headers differently in functions/graphql.ts
// because it's handled by graphql-yoga, so we map the cors config to yoga config
// See packages/graphql-server/src/__tests__/mapRwCorsToYoga.test.ts
if (options.cors) {
this.corsContext = createCorsContext(options.cors)
}
Expand Down
4 changes: 2 additions & 2 deletions packages/auth/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -27,15 +27,15 @@
"core-js": "3.23.5"
},
"devDependencies": {
"@auth0/auth0-spa-js": "1.22.1",
"@auth0/auth0-spa-js": "1.22.2",
"@azure/msal-browser": "2.28.0",
"@babel/cli": "7.16.7",
"@babel/core": "7.16.7",
"@clerk/clerk-js": "3.16.4",
"@clerk/clerk-react": "3.5.0",
"@clerk/clerk-sdk-node": "3.8.6",
"@clerk/types": "2.20.0",
"@nhost/hasura-auth-js": "1.3.4",
"@nhost/hasura-auth-js": "1.4.0",
"@nhost/nhost-js": "1.4.7",
"@okta/okta-auth-js": "6.7.2",
"@simplewebauthn/browser": "5.3.0",
Expand Down
2 changes: 1 addition & 1 deletion packages/forms/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@
"@testing-library/dom": "8.16.0",
"@testing-library/jest-dom": "5.16.4",
"@testing-library/react": "12.1.5",
"@testing-library/user-event": "14.2.6",
"@testing-library/user-event": "14.3.0",
"@types/pascalcase": "1.0.1",
"@types/react": "17.0.47",
"@types/react-dom": "17.0.17",
Expand Down
82 changes: 73 additions & 9 deletions packages/graphql-server/src/__tests__/cors.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -52,25 +52,32 @@ jest.mock('../directives/makeDirectives', () => {
}
})

interface MockLambdaParams {
headers?: { [key: string]: string }
body?: string | null
httpMethod: string
[key: string]: any
}

const mockLambdaEvent = ({
headers,
body = null,
httpMethod,
...others
}): APIGatewayProxyEvent => {
}: MockLambdaParams): APIGatewayProxyEvent => {
return {
headers,
headers: headers || {},
body,
httpMethod,
multiValueQueryStringParameters: null,
isBase64Encoded: false,
multiValueHeaders: {},
multiValueHeaders: {}, // this is silly - the types require a value. It definitely can be undefined, e.g. on Vercel.
path: '',
pathParameters: null,
stageVariables: null,
queryStringParameters: null,
requestContext: null,
resource: null,
requestContext: null as any,
resource: null as any,
...others,
}
}
Expand Down Expand Up @@ -103,6 +110,10 @@ describe('CORS', () => {
expect(response.multiValueHeaders['access-control-allow-origin']).toEqual([
'https://web.redwoodjs.com',
])

expect(response.headers['access-control-allow-origin']).toEqual(
'https://web.redwoodjs.com'
)
})

it('Returns requestOrigin if cors origin set to true', async () => {
Expand Down Expand Up @@ -132,6 +143,10 @@ describe('CORS', () => {
expect(response.multiValueHeaders['access-control-allow-origin']).toEqual([
'https://someothersite.newjsframework.com',
])

expect(response.headers['access-control-allow-origin']).toEqual(
'https://someothersite.newjsframework.com'
)
})

it('Returns the origin for OPTIONS requests', async () => {
Expand Down Expand Up @@ -160,6 +175,10 @@ describe('CORS', () => {
expect(response.multiValueHeaders['access-control-allow-origin']).toEqual([
'https://mycrossdomainsite.co.uk',
])

expect(response.headers['access-control-allow-origin']).toEqual(
'https://mycrossdomainsite.co.uk'
)
})

it('Does not return cross origin headers if option not specified', async () => {
Expand All @@ -182,13 +201,18 @@ describe('CORS', () => {
const response = await handler(mockedEvent, {} as Context)

expect(response.statusCode).toBe(204)
const responeHeaderKeys = Object.keys(response.multiValueHeaders)
const resHeaderKeys = Object.keys(response.headers)

expect(responeHeaderKeys).not.toContain('access-control-allow-origin')
expect(responeHeaderKeys).not.toContain('access-control-allow-credentials')
expect(resHeaderKeys).not.toContain('access-control-allow-origin')
expect(resHeaderKeys).not.toContain('access-control-allow-credentials')

// Also check the multiValueHeaders
expect(Object.keys(response.multiValueHeaders)).not.toContain(
'access-control-allow-origin'
)
})

it('Returns the requestOrigin when moore than one origin supplied in config', async () => {
it('Returns the requestOrigin when more than one origin supplied in config', async () => {
const handler = createGraphQLHandler({
loggerConfig: { logger: createLogger({}), options: {} },
sdls: {},
Expand All @@ -212,6 +236,46 @@ describe('CORS', () => {
const response = await handler(mockedEvent, {} as Context)

expect(response.statusCode).toBe(200)

// Note: no multiValueHeaders in request, so we expect response to be in headers too
expect(response.headers['access-control-allow-origin']).toEqual(
'https://site2.two'
)
})

it('Returns CORS headers with multiValueHeaders in request,as MVH in response', async () => {
const handler = createGraphQLHandler({
loggerConfig: { logger: createLogger({}), options: {} },
sdls: {},
directives: {},
services: {},
cors: {
origin: ['https://site1.one', 'https://site2.two'],
},
onException: () => {},
})

const mockedEvent: APIGatewayProxyEvent = mockLambdaEvent({
headers: {
origin: 'https://site2.two',
'Content-Type': 'application/json',
},
body: JSON.stringify({ query: '{ me { id, name } }' }),
multiValueHeaders: {
origin: ['https://site2.two'],
},
httpMethod: 'POST',
})

const response = await handler(mockedEvent, {} as Context)

expect(response.statusCode).toBe(200)

// Because original request has multiValueHeaders, we don't add it to headers
// to prevent unnecessary duplication in the response... This contradicts what AWS says in their docs,
// but it is actually how Vercel behaves
expect(response.headers).not.toContain('access-control-allow-origin')

expect(response.multiValueHeaders['access-control-allow-origin']).toEqual([
'https://site2.two',
])
Expand Down
58 changes: 43 additions & 15 deletions packages/graphql-server/src/functions/graphql.ts
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,15 @@ export const formatError: FormatErrorHandler = (err: any, message: string) => {
return err
}

const convertToMultiValueHeaders = (headers: Headers) => {
const multiValueHeaders: APIGatewayProxyResult['multiValueHeaders'] = {}
for (const [key, value] of headers) {
multiValueHeaders[key] = multiValueHeaders[key] || []
multiValueHeaders[key].push(value)
}
return multiValueHeaders
}

/**
* Creates an Enveloped GraphQL Server, configured with default Redwood plugins
*
Expand Down Expand Up @@ -198,16 +207,22 @@ export const createGraphQLHandler = ({

function buildRequestObject(event: APIGatewayProxyEvent) {
const requestHeaders = new Headers()
for (const headerName in event.headers) {
const headerValue = event.headers[headerName]
if (headerValue) {
requestHeaders.append(headerName, headerValue)
const supportsMultiValueHeaders =
event.multiValueHeaders && Object.keys(event.multiValueHeaders).length > 0
// Avoid duplicating header values, because Yoga gets confused with CORS
if (supportsMultiValueHeaders) {
for (const headerName in event.multiValueHeaders) {
const headerValues = event.multiValueHeaders[headerName]
if (headerValues) {
for (const headerValue of headerValues) {
requestHeaders.append(headerName, headerValue)
}
}
}
}
for (const headerName in event.multiValueHeaders) {
const headerValues = event.multiValueHeaders[headerName]
if (headerValues) {
for (const headerValue of headerValues) {
} else {
for (const headerName in event.headers) {
const headerValue = event.headers[headerName]
if (headerValue) {
requestHeaders.append(headerName, headerValue)
}
}
Expand Down Expand Up @@ -276,21 +291,34 @@ export const createGraphQLHandler = ({

let lambdaResponse: APIGatewayProxyResult

// @NOTE AWS types define that multiValueHeaders always exist, even as an empty object
// But this isn't true on Vercel, it's just undefined.
const supportsMultiValueHeaders =
event.multiValueHeaders && Object.keys(event.multiValueHeaders).length > 0

try {
const request = buildRequestObject(event)

const response = await yoga.handleRequest(request, {
event,
requestContext: lambdaContext,
})
const multiValueHeaders: APIGatewayProxyResult['multiValueHeaders'] = {}
for (const [key, value] of response.headers) {
multiValueHeaders[key] = multiValueHeaders[key] || []
multiValueHeaders[key].push(value)
}

// @WARN - multivalue headers aren't supported on all deployment targets correctly
// Netlify ✅, Vercel 🛑, AWS ✅,...
// From https://docs.aws.amazon.com/apigateway/latest/developerguide/set-up-lambda-proxy-integrations.html#api-gateway-simple-proxy-for-lambda-input-format
// If you specify values for both headers and multiValueHeaders, API Gateway merges them into a single list.

lambdaResponse = {
body: await response.text(),
statusCode: response.status,
multiValueHeaders,

// Only supply headers if MVH aren't supported, otherwise it causes duplicated headers
headers: supportsMultiValueHeaders
? {}
: Object.fromEntries(response.headers),
// Gets ignored if MVH isn't supported
multiValueHeaders: convertToMultiValueHeaders(response.headers),
}
} catch (e: any) {
logger.error(e)
Expand Down
10 changes: 5 additions & 5 deletions packages/internal/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -33,13 +33,13 @@
"@babel/register": "7.16.7",
"@babel/runtime-corejs3": "7.16.7",
"@babel/traverse": "7.16.7",
"@graphql-codegen/cli": "2.8.1",
"@graphql-codegen/cli": "2.9.1",
"@graphql-codegen/core": "2.6.0",
"@graphql-codegen/schema-ast": "2.5.0",
"@graphql-codegen/typescript": "2.7.1",
"@graphql-codegen/typescript-operations": "2.5.1",
"@graphql-codegen/typescript-react-apollo": "3.3.1",
"@graphql-codegen/typescript-resolvers": "2.7.1",
"@graphql-codegen/typescript": "2.7.2",
"@graphql-codegen/typescript-operations": "2.5.2",
"@graphql-codegen/typescript-react-apollo": "3.3.2",
"@graphql-codegen/typescript-resolvers": "2.7.2",
"@redwoodjs/graphql-server": "2.1.0",
"babel-plugin-graphql-tag": "3.3.0",
"babel-plugin-polyfill-corejs3": "0.5.0",
Expand Down
2 changes: 1 addition & 1 deletion packages/testing/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@
"@testing-library/jest-dom": "5.16.4",
"@testing-library/react": "12.1.5",
"@testing-library/react-hooks": "8.0.1",
"@testing-library/user-event": "14.2.6",
"@testing-library/user-event": "14.3.0",
"@types/aws-lambda": "8.10.101",
"@types/babel-core": "6.25.7",
"@types/jest": "27.5.2",
Expand Down
Loading

0 comments on commit 03c44ba

Please sign in to comment.