Skip to content

Commit

Permalink
feat(dbAuth): Only suggest dbAuth setup if needed (#10793)
Browse files Browse the repository at this point in the history
This is what we print now
<img width="504" alt="image"
src="https://github.com/redwoodjs/redwood/assets/30793/ff77748d-927c-40a1-b9c9-0107012087f5">

This part is not needed if dbAuth is already setup, which is fairly easy
to detect
```
Oh, and if you haven't already, add the necessary dbAuth functions and
   app setup by running:

     yarn rw setup auth dbAuth
```

This PR only prints that text if dbAuth isn't detected already
  • Loading branch information
Tobbe authored and Josh-Walker-GM committed Jun 17, 2024
1 parent 23562ca commit ae06c10
Show file tree
Hide file tree
Showing 4 changed files with 297 additions and 34 deletions.
3 changes: 3 additions & 0 deletions .changesets/10793.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
- feat(dbAuth): Only suggest dbAuth setup if needed (#10793) by @Tobbe

Detect if dbAuth is already setup, and don't suggest setting it up if it is.
Original file line number Diff line number Diff line change
@@ -0,0 +1,220 @@
global.__dirname = __dirname

vi.mock('fs-extra')

import path from 'path'

// Load mocks
import '../../../../lib/test'

const actualFs = await vi.importActual('fs-extra')
import { vol } from 'memfs'
import { afterEach, beforeEach, vi, describe, it, expect } from 'vitest'

import { getPaths } from '../../../../lib'
import * as dbAuth from '../dbAuth'

vi.mock('listr2', async () => {
return {
// Return a constructor function, since we're calling `new` on Listr
Listr: vi.fn().mockImplementation(() => ({
run: () => {},
ctx: {},
})),
}
})

beforeEach(() => {
vi.spyOn(console, 'log').mockImplementation(() => {})
})

afterEach(() => {
vi.mocked(console).log.mockRestore?.()
})

describe('dbAuth', () => {
describe('one more thing... message', () => {
it('does not include setup instructions when dbAuth is already set up in auth.js', async () => {
vol.reset()
vol.fromJSON({
[path.join(getPaths().web.src, 'auth.js')]: `
import { createDbAuthClient, createAuth } from '@redwoodjs/auth-dbauth-web'
const dbAuthClient = createDbAuthClient()
export const { AuthProvider, useAuth } = createAuth(dbAuthClient)
`,
})

await dbAuth.handler({
webauthn: false,
})

expect(vi.mocked(console).log.mock.calls.at(-1)[0]).toMatch(
/Look in LoginPage, Sign/,
)
expect(vi.mocked(console).log.mock.calls.at(-1)[0]).not.toMatch(
/yarn rw setup auth dbAuth/,
)
})

it('does not include setup instructions when dbAuth is already set up in auth.ts', async () => {
vol.reset()
vol.fromJSON({
[path.join(getPaths().web.src, 'auth.ts')]: `
import { createDbAuthClient, createAuth } from '@redwoodjs/auth-dbauth-web'
const dbAuthClient = createDbAuthClient()
export const { AuthProvider, useAuth } = createAuth(dbAuthClient)
`,
})

await dbAuth.handler({
webauthn: false,
})

expect(vi.mocked(console).log.mock.calls.at(-1)[0]).toMatch(
/Look in LoginPage, Sign/,
)
expect(vi.mocked(console).log.mock.calls.at(-1)[0]).not.toMatch(
/yarn rw setup auth dbAuth/,
)
})

it('does not include setup instructions when dbAuth is already set up in auth.jsx with a renamed import', async () => {
vol.reset()
vol.fromJSON({
[path.join(getPaths().web.src, 'auth.jsx')]: `
import { createDbAuthClient, createAuth: renamedCreateAuth } from '@redwoodjs/auth-dbauth-web'
const dbAuthClient = createDbAuthClient()
const { AuthProvider, useAuth } = renamedCreateAuth(dbAuthClient)
const CustomAuthProvider = ({ children }) => {
return (
<div className="custom-auth-provider">
<AuthProvider>{children}</AuthProvider>
</div>
)
}
export const AuthProvider = CustomAuthProvider
export { useAuth }
`,
})

await dbAuth.handler({
webauthn: false,
})

expect(vi.mocked(console).log.mock.calls.at(-1)[0]).toMatch(
/Look in LoginPage, Sign/,
)
expect(vi.mocked(console).log.mock.calls.at(-1)[0]).not.toMatch(
/yarn rw setup auth dbAuth/,
)
})

it("includes dbAuth setup instructions if dbAuth isn't already setup", async () => {
vol.reset()
vol.fromJSON({
[path.join(getPaths().web.src, 'auth.jsx')]: `
import React, { useEffect } from 'react'
import { ClerkProvider, useUser } from '@clerk/clerk-react'
import { createAuth } from '@redwoodjs/auth-clerk-web'
import { navigate } from '@redwoodjs/router'
export const { AuthProvider: ClerkRwAuthProvider, useAuth } = createAuth()
const ClerkProviderWrapper = ({ children, clerkOptions }) => {
const { reauthenticate } = useAuth()
return (
<ClerkProvider
{...clerkOptions}
navigate={(to) => reauthenticate().then(() => navigate(to))}
>
{children}
</ClerkProvider>
)
}
export const AuthProvider = ({ children }: Props) => {
const publishableKey = process.env.CLERK_PUBLISHABLE_KEY
return (
<ClerkRwAuthProvider>
<ClerkProviderWrapper clerkOptions={{ publishableKey }}>
{children}
</ClerkProviderWrapper>
</ClerkRwAuthProvider>
)
}
`,
})

await dbAuth.handler({
webauthn: false,
})

expect(vi.mocked(console).log.mock.calls.at(-1)[0]).toMatch(
/Look in LoginPage, Sign/,
)
expect(vi.mocked(console).log.mock.calls.at(-1)[0]).toMatch(
/yarn rw setup auth dbAuth/,
)
})

it('does not include setup instructions for when generating the pages in the test project', async () => {
vol.reset()
vol.fromJSON({
[path.join(getPaths().web.src, 'auth.js')]: actualFs.readFileSync(
path.join(
__dirname,
`../../../../../../../__fixtures__/test-project/web/src/auth.ts`,
),
'utf-8',
),
})

await dbAuth.handler({
webauthn: false,
})

expect(vi.mocked(console).log.mock.calls.at(-1)[0]).toMatch(
/Look in LoginPage, Sign/,
)
expect(vi.mocked(console).log.mock.calls.at(-1)[0]).not.toMatch(
/yarn rw setup auth dbAuth/,
)
})

it('is different for when WebAuthn is setup', async () => {
vol.reset()
vol.fromJSON({
[path.join(getPaths().web.src, 'auth.js')]: `
import { createDbAuthClient, createAuth } from '@redwoodjs/auth-dbauth-web'
const dbAuthClient = createDbAuthClient()
export const { AuthProvider, useAuth } = createAuth(dbAuthClient)
`,
})

await dbAuth.handler({
webauthn: true,
})

expect(vi.mocked(console).log.mock.calls.at(-1)[0]).toMatch(
/look for the `REDIRECT`/,
)
expect(vi.mocked(console).log.mock.calls.at(-1)[0]).not.toMatch(
/yarn rw setup auth dbAuth/,
)
})
})
})
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,11 @@ mockFiles[getPaths().web.app] = realfs

describe('dbAuth', () => {
beforeEach(() => {
delete mockFiles[path.join(getPaths().web.src, 'auth.ts')]
delete mockFiles[path.join(getPaths().web.src, 'auth.tsx')]
delete mockFiles[path.join(getPaths().web.src, 'auth.js')]
delete mockFiles[path.join(getPaths().web.src, 'auth.jsx')]

vol.reset()
vol.fromJSON(mockFiles)
})
Expand Down
103 changes: 69 additions & 34 deletions packages/cli/src/commands/generate/dbAuth/dbAuth.js
Original file line number Diff line number Diff line change
Expand Up @@ -29,40 +29,52 @@ const ROUTES = [
`<Route path="/reset-password" page={ResetPasswordPage} name="resetPassword" />`,
]

const POST_INSTALL =
` ${c.warning("Pages created! But you're not done yet:")}\n\n` +
` You'll need to tell your pages where to redirect after a user has logged in,\n` +
` signed up, or reset their password. Look in LoginPage, SignupPage,\n` +
` ForgotPasswordPage and ResetPasswordPage for these lines: \n\n` +
` if (isAuthenticated) {\n` +
` navigate(routes.home())\n` +
` }\n\n` +
` and change the route to where you want them to go if the user is already\n` +
` logged in. Also take a look in the onSubmit() functions in ForgotPasswordPage\n` +
` and ResetPasswordPage to change where the user redirects to after submitting\n` +
` those forms.\n\n` +
` Oh, and if you haven't already, add the necessary dbAuth functions and\n` +
` app setup by running:\n\n` +
` yarn rw setup auth dbAuth\n\n` +
` Happy authenticating!\n`
function getPostInstallMessage(isDbAuthSetup) {
return [
` ${c.warning("Pages created! But you're not done yet:")}\n`,
" You'll need to tell your pages where to redirect after a user has logged in,",
' signed up, or reset their password. Look in LoginPage, SignupPage,',
' ForgotPasswordPage and ResetPasswordPage for these lines: \n',
' if (isAuthenticated) {',
' navigate(routes.home())',
' }\n',
' and change the route to where you want them to go if the user is already',
' logged in. Also take a look in the onSubmit() functions in ForgotPasswordPage',
' and ResetPasswordPage to change where the user redirects to after submitting',
' those forms.\n',
!isDbAuthSetup &&
" Oh, and if you haven't already, add the necessary dbAuth functions and\n" +
' app setup by running:\n\n' +
' yarn rw setup auth dbAuth\n',
' Happy authenticating!',
]
.filter(Boolean)
.join('\n')
}

const WEBAUTHN_POST_INSTALL =
` ${c.warning("Pages created! But you're not done yet:")}\n\n` +
" You'll need to tell your pages where to redirect after a user has logged in,\n" +
' signed up, or reset their password. In LoginPage, look for the `REDIRECT`\n' +
` constant and change the route if it's something other than home().\n` +
` In SignupPage, ForgotPasswordPage and ResetPasswordPage look for these lines:\n\n` +
` if (isAuthenticated) {\n` +
` navigate(routes.home())\n` +
` }\n\n` +
` and change the route to where you want them to go if the user is already\n` +
` logged in. Also take a look in the onSubmit() functions in ForgotPasswordPage\n` +
` and ResetPasswordPage to change where the user redirects to after submitting\n` +
` those forms.\n\n` +
` Oh, and if you haven't already, add the necessary dbAuth functions and\n` +
` app setup by running:\n\n` +
` yarn rw setup auth dbAuth\n\n` +
` Happy authenticating!\n`
function getPostInstallWebauthnMessage(isDbAuthSetup) {
return [
` ${c.warning("Pages created! But you're not done yet:")}\n`,
" You'll need to tell your pages where to redirect after a user has logged in,",
' signed up, or reset their password. In LoginPage, look for the `REDIRECT`',
" constant and change the route if it's something other than home().",
' In SignupPage, ForgotPasswordPage and ResetPasswordPage look for these lines:\n',
' if (isAuthenticated) {',
' navigate(routes.home())',
' }\n',
' and change the route to where you want them to go if the user is already',
' logged in. Also take a look in the onSubmit() functions in ForgotPasswordPage',
' and ResetPasswordPage to change where the user redirects to after submitting',
' those forms.\n',
!isDbAuthSetup &&
" Oh, and if you haven't already, add the necessary dbAuth functions and\n" +
' app setup by running:\n\n' +
' yarn rw setup auth dbAuth\n',
' Happy authenticating!',
]
.filter(Boolean)
.join('\n')
}

export const command = 'dbAuth'
export const description =
Expand Down Expand Up @@ -395,9 +407,32 @@ export const handler = async (yargs) => {

console.log('')
console.log(
yargs.webauthn || t.ctx.webauthn ? WEBAUTHN_POST_INSTALL : POST_INSTALL,
yargs.webauthn || t.ctx.webauthn
? getPostInstallWebauthnMessage(isDbAuthSetup())
: getPostInstallMessage(isDbAuthSetup()),
)
} catch (e) {
console.log(c.error(e.message))
}
}

function isDbAuthSetup() {
const extensions = ['ts', 'js', 'tsx', 'jsx']
const webAuthExtension = extensions.find((ext) =>
fs.existsSync(path.join(getPaths().web.src, 'auth.' + ext)),
)

// If no `auth.ext` file was found auth is not set up
if (webAuthExtension) {
const webAuthPath = path.join(
getPaths().web.src,
'auth.' + webAuthExtension,
)

return /^import (.*) from ['"]@redwoodjs\/auth-dbauth-web['"]/m.test(
fs.readFileSync(webAuthPath),
)
}

return false
}

0 comments on commit ae06c10

Please sign in to comment.