Skip to content

Commit

Permalink
Merge branch 'main' into ds-chore/quality-of-life-updates
Browse files Browse the repository at this point in the history
  • Loading branch information
jtoar authored Dec 22, 2023
2 parents 5c6c22d + ae3a356 commit 4d6ae4f
Show file tree
Hide file tree
Showing 7 changed files with 26 additions and 34 deletions.
2 changes: 2 additions & 0 deletions .github/actions/detect-changes/cases/code_changes.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,8 @@ function isCodeFile(filePath) {
) {
return false
}

return true
}

/**
Expand Down
1 change: 0 additions & 1 deletion .github/actions/detect-changes/detectChanges.mjs
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
import fs from 'node:fs'

import core from '@actions/core'
import { exec, getExecOutput } from '@actions/exec'
import { hasCodeChanges } from './cases/code_changes.mjs'
import { rscChanged } from './cases/rsc.mjs'
import { ssrChanged } from './cases/ssr.mjs'
Expand Down
1 change: 1 addition & 0 deletions .gitpod.yml
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ tasks:
export REDWOOD_DISABLE_TELEMETRY=1
init: |
cd /workspace/redwood
corepack enable
yarn install
yarn run build:test-project ../rw-test-app --typescript --link --verbose
cd /workspace/rw-test-app && sed -i "s/\(open *= *\).*/\1false/" redwood.toml
Expand Down
32 changes: 12 additions & 20 deletions docs/docs/graphql/trusted-documents.md
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
# Trusted Documents

In addition, RedwoodJS can be setup to enforce [persisted operations](https://the-guild.dev/graphql/yoga-server/docs/features/persisted-operations) -- alternatively called [Trusted Documents](https://benjie.dev/graphql/trusted-documents).
RedwoodJS can be setup to enforce [persisted operations](https://the-guild.dev/graphql/yoga-server/docs/features/persisted-operations) alternatively called [Trusted Documents](https://benjie.dev/graphql/trusted-documents).

Use trusted documents if your GraphQL API is only for your own apps (which is the case for most GraphQL APIs) for a massively decreased attack-surface, increased performance, and decreased bandwidth usage.
Use trusted documents if your GraphQL API is only for your own app (which is the case for most GraphQL APIs) for a massively decreased attack-surface, increased performance, and decreased bandwidth usage.

At app build time, Redwood will extract the GraphQL documents (queries, etc) and make them available to the server. At run time, you can then send documentId or "hash" instead of the whole document; only accept requests with a documentId and one that it knows about.
At app build time, Redwood will extract the GraphQL documents (queries, etc) and make them available to the server. At run time, you can then send "document id" or "hash" instead of the whole document; only accept requests with a known document id.

This prevents malicious attackers from executing arbitrary GraphQL thus helping with unwanted resolver traversal of information leaking.
This prevents malicious attackers from executing arbitrary GraphQL thus helping with unwanted resolver traversal or information leaking.

See [Configure Trusted Documents](#configure-trusted-documents) for more information and usage instructions.

Expand Down Expand Up @@ -37,7 +37,7 @@ When configured to use Trusted Documents, your project will:
}
```

2. The contain the query and hash that represents and identifies that query
2. They contain the query and hash that represents and identifies that query
3. Files with functions to lookup the generated trusted document such as:

```ts title=web/src/graphql/gql.ts
Expand All @@ -60,13 +60,13 @@ export const FindPostsDocument = {"__meta__":{"hash":"76308e971322b1ece4cdff7518
// ...
```

so that when a query or mutation is made, the web side GraphQL client doesn't send the query, but rather **just the has id** so that the GraphQL Server can lookup the pre-generated query to run.
so that when a query or mutation is made, the web side GraphQL client doesn't send the query, but rather **just the hash id** so that the GraphQL Server can lookup the pre-generated query to run.

```http
{"operationName":"FindPosts","variables":{},"extensions":{"persistedQuery":{"version":1,"sha256Hash":"76308e971322b1ece4cdff75185bb61d7139e343"}}}
```

It does so by adding a `api/src/lib/trustedDocumentsStore.ts` for use on the GraphQL api side.
It does so by adding a `api/src/lib/trustedDocumentsStore.ts` file for use on the GraphQL api side.

```ts title=api/src/lib/trustedDocumentsStore.ts
export const store = {
Expand All @@ -84,7 +84,7 @@ See how the `76308e971322b1ece4cdff75185bb61d7139e343` hash ids match?

Now, when the client requests to make a query for `76308e971322b1ece4cdff75185bb61d7139e343`, the GraphQL server knows to execute the corresponding query associated with that hash.

This means that because queries are pre-generated and teh has ids ***must match**, there is no way for any un-trusted or ad-hock queries to get executed by the GraphQL server.
This means that because queries are pre-generated and the hash ids ***must match**, there is no way for any un-trusted or ad-hock queries to get executed by the GraphQL server.

Thus preventing unwanted queries or GraphQl traversal attacks,

Expand All @@ -93,14 +93,6 @@ Thus preventing unwanted queries or GraphQl traversal attacks,

## Configure Trusted Documents

In addition, RedwoodJS can be setup to enforce [persisted operations](https://the-guild.dev/graphql/yoga-server/docs/features/persisted-operations) -- alternatively called [Trusted Documents](https://benjie.dev/graphql/trusted-documents).

Use trusted documents if your GraphQL API is only for your own apps (which is the case for most GraphQL APIs) for a massively decreased attack-surface, increased performance, and decreased bandwidth usage.

At build time, Redwood will extract the GraphQL documents (queries, etc) and make them available to the server. At run time, you can then send documentId or "hash" instead of the whole document; only accept requests with a documentId and one that it knows about.

This prevents malicious attackers from executing arbitrary GraphQL thus helping with unwanted resolver traversal of information leaking.

### Configure redwood.toml

Setting `trustedDocuments` to true will
Expand All @@ -114,11 +106,12 @@ Setting `trustedDocuments` to true will
trustedDocuments = true
...
```

### Configure GraphQL Handler

As part or GraphQL type and codegen, the `trustedDocumentsStore` is created in `api/src/lib`.
As part of GraphQL type and codegen, the `trustedDocumentsStore` is created in `api/src/lib`.

This is a the same information that is created in `web/src/graphql/persisted-documents.json` but wrapped in a `store` that can be easily be imported and passed to the GraphQL Handler.
This is the same information that is created in `web/src/graphql/persisted-documents.json` but wrapped in a `store` that can be easily imported and passed to the GraphQL Handler.

To enable trusted documents, configure `trustedDocuments` with the store.

Expand All @@ -140,10 +133,9 @@ export const handler = createGraphQLHandler({
db.$disconnect()
},
})

```
If you would like to customize the message when a query is not permitted, you can set the `persistedQueryOnly` in the `customErrors` configuration setting:

If you'd like to customize the message when a query is not permitted, you can set the `persistedQueryOnly` configuration setting in `customErrors`:

```
trustedDocuments: {
Expand Down
4 changes: 2 additions & 2 deletions packages/cli/src/commands/__tests__/dev.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -105,7 +105,7 @@ describe('yarn rw dev', () => {
)

expect(apiCommand.command).toMatchInlineSnapshot(
`"yarn cross-env NODE_ENV=development NODE_OPTIONS=--enable-source-maps yarn nodemon --quiet --watch "/mocked/project/redwood.toml" --exec "yarn rw-api-server-watch --port 8911 --debug-port 18911 | rw-log-formatter""`
`"yarn cross-env NODE_ENV=development NODE_OPTIONS="--enable-source-maps" yarn nodemon --quiet --watch "/mocked/project/redwood.toml" --exec "yarn rw-api-server-watch --port 8911 --debug-port 18911 | rw-log-formatter""`
)

expect(generateCommand.command).toEqual('yarn rw-gen-watch')
Expand Down Expand Up @@ -144,7 +144,7 @@ describe('yarn rw dev', () => {
)

expect(apiCommand.command).toMatchInlineSnapshot(
`"yarn cross-env NODE_ENV=development NODE_OPTIONS=--enable-source-maps yarn nodemon --quiet --watch "/mocked/project/redwood.toml" --exec "yarn rw-api-server-watch --port 8911 --debug-port 18911 | rw-log-formatter""`
`"yarn cross-env NODE_ENV=development NODE_OPTIONS="--enable-source-maps" yarn nodemon --quiet --watch "/mocked/project/redwood.toml" --exec "yarn rw-api-server-watch --port 8911 --debug-port 18911 | rw-log-formatter""`
)

expect(generateCommand.command).toEqual('yarn rw-gen-watch')
Expand Down
7 changes: 3 additions & 4 deletions packages/cli/src/commands/devHandler.js
Original file line number Diff line number Diff line change
Expand Up @@ -178,7 +178,7 @@ export const handler = async ({
const jobs = {
api: {
name: 'api',
command: `yarn cross-env NODE_ENV=development ${getDevNodeOptions()} yarn nodemon --quiet --watch "${redwoodConfigPath}" --exec "yarn rw-api-server-watch --port ${apiAvailablePort} ${getApiDebugFlag()} | rw-log-formatter"`,
command: `yarn cross-env NODE_ENV=development NODE_OPTIONS="${getDevNodeOptions()}" yarn nodemon --quiet --watch "${redwoodConfigPath}" --exec "yarn rw-api-server-watch --port ${apiAvailablePort} ${getApiDebugFlag()} | rw-log-formatter"`,
prefixColor: 'cyan',
runWhen: () => fs.existsSync(rwjsPaths.api.src),
},
Expand Down Expand Up @@ -224,18 +224,17 @@ export const handler = async ({
}

/**
* Gets the NODE_OPTIONS environment variable from `process.env`, appending `--enable-source-maps` if it's not already there.
* Gets the value of the `NODE_OPTIONS` env var from `process.env`, appending `--enable-source-maps` if it's not already there.
* See https://nodejs.org/api/cli.html#node_optionsoptions.
*
* @returns {string}
*/
export function getDevNodeOptions() {
const { NODE_OPTIONS } = process.env

const enableSourceMapsOption = '--enable-source-maps'

if (!NODE_OPTIONS) {
return `NODE_OPTIONS=${enableSourceMapsOption}`
return enableSourceMapsOption
}

if (NODE_OPTIONS.includes(enableSourceMapsOption)) {
Expand Down
13 changes: 6 additions & 7 deletions packages/cli/src/lib/__tests__/getDevNodeOptions.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,23 +5,22 @@ describe('getNodeOptions', () => {

it('defaults to enable-source-maps', () => {
const nodeOptions = getDevNodeOptions()
expect(nodeOptions).toEqual(`NODE_OPTIONS=${enableSourceMapsOption}`)
expect(nodeOptions).toEqual(enableSourceMapsOption)
})

it("doesn't specify `--enable-source-maps` twice", () => {
process.env.NODE_OPTIONS = `NODE_OPTIONS=${enableSourceMapsOption}`
process.env.NODE_OPTIONS = enableSourceMapsOption

const nodeOptions = getDevNodeOptions()
expect(nodeOptions).toEqual(`NODE_OPTIONS=${enableSourceMapsOption}`)
expect(nodeOptions).toEqual(enableSourceMapsOption)
})

it('merges existing options with `--enable-source-maps`', () => {
const existingOptions = '--inspect --no-experimental-fetch'
process.env.NODE_OPTIONS = `NODE_OPTIONS=${existingOptions}`
process.env.NODE_OPTIONS = existingOptions

const nodeOptions = getDevNodeOptions()
expect(nodeOptions).toEqual(
`NODE_OPTIONS=${existingOptions} ${enableSourceMapsOption}`
)

expect(nodeOptions).toEqual(`${existingOptions} ${enableSourceMapsOption}`)
})
})

0 comments on commit 4d6ae4f

Please sign in to comment.