Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(trusted-docs): Allows useRedwoodTrustedDocuments to set more custom UsePersistedOperationsOptions #10894

Merged
merged 8 commits into from
Jul 19, 2024
34 changes: 34 additions & 0 deletions .changesets/10894.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
feat(trusted-docs): Allows useRedwoodTrustedDocuments to set more custom UsePersistedOperationsOptions (#10894) by @dthyresson

Allows useRedwoodTrustedDocuments to set more custom UsePersistedOperationsOptions

Allows the useRedwoodTrustedDocuments plugin to define:

```ts
/**
* Whether to allow execution of arbitrary GraphQL operations aside from persisted operations.
*/
allowArbitraryOperations?: boolean | AllowArbitraryOperationsHandler;
/**
* The path to the persisted operation id
*/
extractPersistedOperationId?: ExtractPersistedOperationId;

/**
* Whether to skip validation of the persisted operation
*/
skipDocumentValidation?: boolean;

/**
* Custom errors to be thrown
*/
customErrors?: CustomPersistedQueryErrors;
```

This can let you override to allow certain ops or skip validation etc:

> If you validate your persisted operations while building your store, we recommend to skip the validation on the server. So this will reduce the work done by the server and the latency of the requests.

The allow authenticated request is still considered, but `allowArbitraryOperations` can override.

Omitted `getPersistedOperation` as this extracts hash from store.
75 changes: 74 additions & 1 deletion docs/docs/graphql/trusted-documents.md
Original file line number Diff line number Diff line change
Expand Up @@ -118,6 +118,8 @@ As part of GraphQL type and codegen, the `trustedDocumentsStore` is created in `

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.

#### Store

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

```ts title=api/src/functions/graphql.ts
Expand All @@ -140,13 +142,84 @@ export const handler = createGraphQLHandler({
})
```

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

You can disable the trustedDocuments `useRedwoodTrustedDocuments` plugin. The `store` is then optional.

```
trustedDocuments: {
disabled: true,
}
```

#### Custom Errors

The `persistedQueryOnly` error message defaults to `'Use Trusted Only!'`.

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

```ts
trustedDocuments: {
store,
customErrors: {
persistedQueryOnly: 'This ad-hoc query is not allowed.'
},
}
```

You can also define a function to returns a `GraphQLError`. This function has access to the `payload`.

```ts
trustedDocuments: {
store,
customErrors: {
persistedQueryOnly: (payload) => {
console.log('payload', payload)
return new GraphQLError('Sorry!')
},
},
}
```

In addition to the `persistedQueryOnly` custom error option, you can define error message for:

* `notFound` - Error to be thrown when the persisted operation is not found
* `keyNotFound` - Error to be thrown when the extraction of the persisted operation id failed


#### Skipping validation of persisted operations

If you validate your persisted operations while building your store, we recommend to skip the validation on the server. So this will reduce the work done by the server and the latency of the requests.

```
trustedDocuments: {
store,
skipDocumentValidation: true,
}
```


#### Allowing arbitrary GraphQL operations

Sometimes it is handy to allow non-persisted operations aside from the persisted ones. E.g. you want to allow developers to execute arbitrary GraphQL operations on your production server.

:::note Info
To support authentication, the `redwood.currentUser` query is always allowed.

Even if you define `allowArbitraryOperations` the plugin will always check for this request, so you don't need to add this check to any custom logic.
:::

This can be achieved using the `allowArbitraryOperations` option.

:::warning Important
Override this option with caution!
:::

For example, you can get a header from the request and allow:

```ts
allowArbitraryOperations: (request) => {
return request.headers.get('x-allow-arbitrary-operations') === 'true'
}
```

33 changes: 27 additions & 6 deletions packages/graphql-server/src/plugins/useRedwoodTrustedDocuments.ts
Original file line number Diff line number Diff line change
@@ -1,14 +1,28 @@
import { usePersistedOperations } from '@graphql-yoga/plugin-persisted-operations'
import type { CustomPersistedQueryErrors } from '@graphql-yoga/plugin-persisted-operations'
import type { UsePersistedOperationsOptions } from '@graphql-yoga/plugin-persisted-operations'
import type { Plugin } from 'graphql-yoga'

import type { RedwoodGraphQLContext } from '../types'

export type RedwoodTrustedDocumentOptions = {
store: Record<string, string>
export type RedwoodTrustedDocumentOptions = Omit<
UsePersistedOperationsOptions,
'getPersistedOperation'
> & {
/**
* Whether to disable the plugin
* @default false
*/
disabled?: boolean
customErrors?: CustomPersistedQueryErrors
}

/**
* The store to get the persisted operation hash from
* Required when the plugin is not disabled
*/
store?: Readonly<Record<string, string>>
} & (
| { disabled: true; store?: Readonly<Record<string, string>> }
| { disabled?: false; store: Readonly<Record<string, string>> }
)

const REDWOOD__AUTH_GET_CURRENT_USER_QUERY =
'{"query":"query __REDWOOD__AUTH_GET_CURRENT_USER { redwood { currentUser } }"}'
Expand Down Expand Up @@ -44,14 +58,21 @@ export const useRedwoodTrustedDocuments = (
options: RedwoodTrustedDocumentOptions,
): Plugin<RedwoodGraphQLContext> => {
return usePersistedOperations({
...options,
customErrors: {
persistedQueryOnly: 'Use Trusted Only!',
...options.customErrors,
},
getPersistedOperation(sha256Hash: string) {
return options.store[sha256Hash]
return options.store ? options.store[sha256Hash] : null
},
allowArbitraryOperations: async (request) => {
if (typeof options.allowArbitraryOperations === 'function') {
const result = await options.allowArbitraryOperations(request)
if (result === true) {
return true
}
}
return allowRedwoodAuthCurrentUserQuery(request)
},
dthyresson marked this conversation as resolved.
Show resolved Hide resolved
})
Expand Down