Skip to content

Commit

Permalink
Don't create an AuthProvider if one exists (#1251)
Browse files Browse the repository at this point in the history
* Don't create an AuthProvider if one exists

* Handle --force flag

* Remove old auth provider imports

* Fix broken test

* Make variable for existing auth provider error

* Fix formatting with Prettier

* Handle GraphQL import

* Make sure web imports are included

* Handle null regex match

Co-authored-by: David Price <thedavid@thedavidprice.com>
  • Loading branch information
amorriscode and thedavidprice committed Oct 8, 2020
1 parent e69bb0b commit 0ba96d8
Show file tree
Hide file tree
Showing 4 changed files with 130 additions and 24 deletions.
46 changes: 42 additions & 4 deletions packages/cli/src/commands/generate/auth/__tests__/auth.test.js
Original file line number Diff line number Diff line change
@@ -1,8 +1,46 @@
global.__dirname = __dirname
// import { loadGeneratorFixture } from 'src/lib/test'

// import * as auth from '../auth'
import {
waitFor,
} from '@testing-library/react'

test('true', () => {
expect(true).toEqual(true)
jest.mock('fs')
jest.mock('src/lib', () => ({
getPaths: () => ({
api: { functions: '', src: '', lib: '' },
web: { src: '' },
}),
}))

import fs from 'fs'
import chalk from 'chalk'

import * as auth from '../auth'

const EXISTING_AUTH_PROVIDER_ERROR = 'Existing auth provider found.\nUse --force to override existing provider.';

test(`no error thrown when auth provider not found`, async () => {
// Mock process.exit to make sure CLI quites
const cSpy = jest.spyOn(console, 'log').mockImplementation(() => {})

auth.handler({ provider: 'netlify' })
await waitFor(() => expect(console.log).toHaveBeenCalledTimes(1))
expect(console.log).not.toHaveBeenCalledWith(chalk.bold.red(EXISTING_AUTH_PROVIDER_ERROR))

// Restore mocks
cSpy.mockRestore()
})

test('throws an error if auth provider exists', async () => {
// Mock process.exit to make sure CLI quites
const fsSpy = jest.spyOn(fs, 'readFileSync').mockImplementation(() => `import { AuthProvider } from '@redwoodjs/auth'`)
const cSpy = jest.spyOn(console, 'log').mockImplementation(() => {})

auth.handler({ provider: 'netlify' })
await waitFor(() => expect(console.log).toHaveBeenCalledTimes(1))
expect(console.log).toHaveBeenCalledWith(chalk.bold.red(EXISTING_AUTH_PROVIDER_ERROR))

// Restore mocks
fsSpy.mockRestore()
cSpy.mockRestore()
})
102 changes: 82 additions & 20 deletions packages/cli/src/commands/generate/auth/auth.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@ const API_GRAPHQL_PATH = resolveFile(
path.join(getPaths().api.functions, 'graphql')
)

const AUTH_PROVIDER_IMPORT = `import { AuthProvider } from '@redwoodjs/auth'`

const API_SRC_PATH = path.join(getPaths().api.src)
const TEMPLATES = fs
.readdirSync(path.resolve(__dirname, 'templates'))
Expand All @@ -36,12 +38,7 @@ const SUPPORTED_PROVIDERS = fs

// returns the content of index.js with import statements added
const addWebImports = (content, imports) => {
return (
`import { AuthProvider } from '@redwoodjs/auth'\n` +
imports.join('\n') +
'\n' +
content
)
return `${AUTH_PROVIDER_IMPORT}\n` + imports.join('\n') + '\n' + content
}

// returns the content of index.js with init lines added
Expand Down Expand Up @@ -71,6 +68,53 @@ const addWebRender = (content, authProvider) => {
)
}

// returns the content of index.js with <AuthProvider> updated
const updateWebRender = (content, authProvider) => {
const renderContent = `<AuthProvider client={${authProvider.client}} type="${authProvider.type}">`
return content.replace(/<AuthProvider client={.*} type=".*">/s, renderContent)
}

// returns the content of index.js without the old auth import
const removeOldWebImports = (content, imports) => {
return content.replace(`${AUTH_PROVIDER_IMPORT}\n` + imports.join('\n'), '')
}

// returns the content of index.js without the old auth init
const removeOldWebInit = (content, init) => {
return content.replace(init, '')
}

// returns content with old auth provider removes
const removeOldAuthProvider = async (content) => {
// get the current auth provider
const [_, currentAuthProvider] = content.match(
/<AuthProvider client={.*} type="(.*)">/s
)

let oldAuthProvider
try {
oldAuthProvider = await import(`./providers/${currentAuthProvider}`)
} catch (e) {
throw new Error('Could not replace existing auth provider init')
}

content = removeOldWebImports(content, oldAuthProvider.config.imports)
content = removeOldWebInit(content, oldAuthProvider.config.init)

return content
}

// check to make sure AuthProvider doesn't exist
const checkAuthProviderExists = () => {
const content = fs.readFileSync(WEB_SRC_INDEX_PATH).toString()

if (content.includes(AUTH_PROVIDER_IMPORT)) {
throw new Error(
'Existing auth provider found.\nUse --force to override existing provider.'
)
}
}

// the files to create to support auth
export const files = (provider) => {
const template = TEMPLATES[provider] ?? TEMPLATES.base
Expand All @@ -80,30 +124,43 @@ export const files = (provider) => {
}

// actually inserts the required config lines into index.js
export const addConfigToIndex = (config) => {
export const addConfigToIndex = async (config, force) => {
let content = fs.readFileSync(WEB_SRC_INDEX_PATH).toString()

// update existing AuthProvider if --force else add new AuthProvider
if (content.includes(AUTH_PROVIDER_IMPORT) && force) {
content = await removeOldAuthProvider(content)
content = updateWebRender(content, config.authProvider)
} else {
content = addWebRender(content, config.authProvider)
}

content = addWebImports(content, config.imports)
content = addWebInit(content, config.init)
content = addWebRender(content, config.authProvider)

fs.writeFileSync(WEB_SRC_INDEX_PATH, content)
}

export const addApiConfig = () => {
let content = fs.readFileSync(API_GRAPHQL_PATH).toString()

// add import statement
content = content.replace(
/^(.*services.*)$/m,
`$1\n\nimport { getCurrentUser } from 'src/lib/auth'`
)
// add object to handler
content = content.replace(
/^(\s*)(schema: makeMergedSchema)(.*)$/m,
`$1getCurrentUser,\n$1$2$3`
)
fs.writeFileSync(API_GRAPHQL_PATH, content)
// default to an array to avoid destructure errors
const [_, hasAuthImport] =
content.match(/(import {.*} from 'src\/lib\/auth.*')/s) || []

if (!hasAuthImport) {
// add import statement
content = content.replace(
/^(.*services.*)$/m,
`$1\n\nimport { getCurrentUser } from 'src/lib/auth'`
)
// add object to handler
content = content.replace(
/^(\s*)(schema: makeMergedSchema)(.*)$/m,
`$1getCurrentUser,\n$1$2$3`
)
fs.writeFileSync(API_GRAPHQL_PATH, content)
}
}

export const isProviderSupported = (provider) => {
Expand Down Expand Up @@ -164,7 +221,7 @@ export const handler = async ({ provider, force }) => {
title: 'Adding auth config to web...',
task: (_ctx, task) => {
if (webIndexDoesExist()) {
addConfigToIndex(providerData.config)
addConfigToIndex(providerData.config, force)
} else {
task.skip('web/src/index.js not found, skipping')
}
Expand Down Expand Up @@ -228,6 +285,11 @@ export const handler = async ({ provider, force }) => {
)

try {
// Don't throw existing provider error when --force exists
if (!force) {
checkAuthProviderExists()
}

await tasks.run()
} catch (e) {
console.log(c.error(e.message))
Expand Down
3 changes: 3 additions & 0 deletions packages/cli/src/commands/generate/auth/providers/firebase.js
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
// the lines that need to be added to index.js
export const config = {
imports: [
`import * as firebase from 'firebase/app'`,
Expand All @@ -20,9 +21,11 @@ const firebaseClient = ((config) => {
authProvider: { client: 'firebaseClient', type: 'firebase' },
}

// required packages to install
export const webPackages = ['firebase']
export const apiPackages = ['firebase-admin']

// any notes to print out when the job is done
export const notes = [
'You will need to create several environment variables with your Firebase config options.',
'Check out web/src/index.js for the variables you need to add.',
Expand Down
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
// the lines that need to be added to index.js
export const config = {
imports: [`import { Magic } from 'magic-sdk'`],
init: 'const m = new Magic(process.env.MAGICLINK_PUBLIC)',
Expand All @@ -7,9 +8,11 @@ export const config = {
},
}

// required packages to install
export const webPackages = ['magic-sdk']
export const apiPackages = []

// any notes to print out when the job is done
export const notes = [
'To get your application keys, go to https://dashboard.magic.link/login ',
'Then navigate to the API keys add them to your .env config options.',
Expand Down

0 comments on commit 0ba96d8

Please sign in to comment.