Skip to content

Commit

Permalink
Merge branch 'main' into dt-async-mock-directive
Browse files Browse the repository at this point in the history
  • Loading branch information
dthyresson authored Jul 4, 2022
2 parents 1c22e47 + a613e56 commit e94ab1a
Show file tree
Hide file tree
Showing 12 changed files with 507 additions and 12 deletions.
19 changes: 19 additions & 0 deletions docs/docs/cli-commands.md
Original file line number Diff line number Diff line change
Expand Up @@ -358,6 +358,7 @@ yarn redwood destroy <type>
| `sdl <model>` | Destroy a GraphQL schema and service component based on a given DB schema Model |
| `service <name>` | Destroy a service component |
| `directive <name>` | Destroy a directive |
| `graphiql` | Destroy a generated graphiql file |
## exec
Expand Down Expand Up @@ -1671,6 +1672,24 @@ yarn redwood setup auth <provider>
See [Authentication](authentication.md).
### setup graphiQL headers
Redwood automatically sets up your authentication headers in your GraphiQL playground. Currently supported auth providers include Supabase, dbAuth, and Netlify.
A `generateGraphiQLHeader` file will be created in your `api/lib` folder and included in your gitignore. You can edit this file to customize your header. The function in the file is passed into your `createGraphQLHandler` and only called in dev.
```
yarn redwood setup graphiql <provider>
```
| Arguments & Options | Description |
| :------------------ | :---------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `provider` | Auth provider to configure. Choices are `dbAuth`, `netlify`, and `supabase` |
| `--id, -i` | Unique id to identify current user (required only for DBAuth) |
| `--token, -t` | Generated JWT token. If not provided, a mock JWT payload is returned in `api/lib/generateGraphiQLHeader` that can be modified and turned into a token |
| `--expiry, -e` | Token expiry in minutes. Default is 60 |
| `--view, -v` | Print out generated headers to console |
### setup custom-web-index
Redwood automatically mounts your `<App />` to the DOM, but if you want to customize how that happens, you can use this setup command to generate an `index.js` file in `web/src`.
Expand Down
16 changes: 14 additions & 2 deletions packages/api/src/functions/dbAuth/shared.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,20 @@ import * as DbAuthError from './errors'

// Extracts the cookie from an event, handling lower and upper case header
// names.
// Checks for cookie in headers in dev when user has generated graphiql headers
export const extractCookie = (event: APIGatewayProxyEvent) => {
return event.headers.cookie || event.headers.Cookie
let cookieFromGraphiqlHeader
if (process.env.NODE_ENV === 'development') {
try {
cookieFromGraphiqlHeader = JSON.parse(event.body ?? '{}').extensions
?.headers?.cookie
} catch (e) {
return event.headers.cookie || event.headers.Cookie
}
}
return (
event.headers.cookie || event.headers.Cookie || cookieFromGraphiqlHeader
)
}

// decrypts the session cookie and returns an array: [data, csrf]
Expand All @@ -31,7 +43,7 @@ export const decryptSession = (text: string | null) => {

// returns the actual value of the session cookie
export const getSession = (text?: string) => {
if (typeof text === 'undefined') {
if (typeof text === 'undefined' || text === null) {
return null
}

Expand Down
56 changes: 56 additions & 0 deletions packages/cli/src/commands/destroy/graphiql/graphiql.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
import Listr from 'listr'

import {
existsAnyExtensionSync,
deleteFile,
readFile,
writeFile,
getGraphqlPath,
} from '../../../lib'
import c from '../../../lib/colors'
import { getOutputPath } from '../../setup/graphiql/graphiql'

const removeGraphiqlFromGraphqlHandler = () => {
const graphqlPath = getGraphqlPath()
let content = readFile(graphqlPath).toString()
const [_, hasHeaderImport] =
content.match(/(import .* from 'src\/lib\/generateGraphiQLHeader.*')/s) ||
[]
if (hasHeaderImport) {
// remove header import statement
content = content.replace(
`\n\nimport generateGraphiQLHeader from 'src/lib/generateGraphiQLHeader'`,
''
)
// remove object from handler
content = content.replace(`generateGraphiQLHeader,\n`, '')
}
writeFile(graphqlPath, content, {
overwriteExisting: true,
})
}
export const command = 'graphiql'
export const description = 'Destroy graphiql header'

export const handler = () => {
const path = getOutputPath()
const tasks = new Listr(
[
{
title: 'Destroying graphiql files...',
skip: () => !existsAnyExtensionSync(path) && `File doesn't exist`,
task: () => deleteFile(path),
},
{
title: 'Removing graphiql import from createGraphQLHandler',
task: removeGraphiqlFromGraphqlHandler,
},
],
{ collapse: false, exitOnError: true }
)
try {
tasks.run()
} catch (e) {
console.log(c.error(e.message))
}
}
16 changes: 7 additions & 9 deletions packages/cli/src/commands/setup/auth/auth.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,16 @@ import Listr from 'listr'
import prompts from 'prompts'
import terminalLink from 'terminal-link'

import { resolveFile } from '@redwoodjs/internal'
import { getProject } from '@redwoodjs/structure'
import { errorTelemetry } from '@redwoodjs/telemetry'

import { getPaths, writeFilesTask, transformTSToJS } from '../../../lib'
import {
getPaths,
writeFilesTask,
transformTSToJS,
getGraphqlPath,
graphFunctionDoesExist,
} from '../../../lib'
import c from '../../../lib/colors'

const AUTH_PROVIDER_IMPORT = `import { AuthProvider } from '@redwoodjs/auth'`
Expand All @@ -26,9 +31,6 @@ const OUTPUT_PATHS = {
),
}

const getGraphqlPath = () =>
resolveFile(path.join(getPaths().api.functions, 'graphql'))

const getWebAppPath = () => getPaths().web.app

const getSupportedProviders = () =>
Expand Down Expand Up @@ -289,10 +291,6 @@ export const webIndexDoesExist = () => {
return fs.existsSync(getWebAppPath())
}

export const graphFunctionDoesExist = () => {
return fs.existsSync(getGraphqlPath())
}

export const command = 'auth <provider>'
export const description = 'Generate an auth configuration'
export const builder = (yargs) => {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
global.__dirname = __dirname

import '../../../../lib/mockTelemetry'

jest.mock('@redwoodjs/internal', () => {
return {
registerApiSideBabelHook: () => null,
}
})
jest.mock('../../../../lib', () => ({
getPaths: () => ({
api: { lib: '', functions: '' },
}),
existsAnyExtensionSync: () => false,
}))

jest.mock('listr')
import chalk from 'chalk'
import listr from 'listr'

import * as graphiql from '../graphiql'

describe('Graphiql generator tests', () => {
const processExitSpy = jest
.spyOn(process, 'exit')
.mockImplementation(() => {})
const cSpy = jest.spyOn(console, 'error').mockImplementation(() => {})

const mockListrRun = jest.fn()
listr.mockImplementation(() => {
return {
run: mockListrRun,
}
})

afterEach(() => {
processExitSpy.mockReset()
cSpy.mockReset()
})

it('throws an error if source path does not exist when viewing headers', async () => {
jest.spyOn(graphiql, 'getOutputPath').mockImplementation(() => '')
await graphiql.handler({ view: true, provider: 'dbAuth' })
expect(console.error).toHaveBeenCalledWith(
chalk.bold.red(
'Must run yarn rw setup graphiql <provider> to generate headers before viewing'
)
)
expect(processExitSpy).toHaveBeenCalledWith(1)
})

it('throws an error if auth provider is dbAuth and no user id is provided', () => {
try {
graphiql.generatePayload('dbAuth')
} catch (e) {
expect(e.message).toBe('Require an unique id to generate session cookie')
}
})

it('throws an error if auth provider is dbAuth and no supabase env is set', () => {
process.env.SESSION_SECRET = null
try {
graphiql.generatePayload('dbAuth', 'user-id-123')
} catch (e) {
expect(e.message).toBe(
'dbAuth requires a SESSION_SECRET environment variable that is used to encrypt session cookies. Use `yarn rw g secret` to create one, then add to your `.env` file. DO NOT check this variable in your version control system!!'
)
}
})

it('returns a payload if a token is provided', async () => {
const provider = 'supabase'
const token = 'mock-token'
const response = graphiql.generatePayload(provider, null, token)
expect(response).toEqual({
'auth-provider': provider,
authorization: `Bearer ${token}`,
})
})
})
Loading

0 comments on commit e94ab1a

Please sign in to comment.