Skip to content

Commit

Permalink
Merge branch 'main' of github.com:redwoodjs/redwood into codemod/repl…
Browse files Browse the repository at this point in the history
…ace-svg-as-components

* 'main' of github.com:redwoodjs/redwood: (25 commits)
  fix(deps): update dependency @whatwg-node/fetch to v0.9.7 (redwoodjs#8702)
  fix(deps): update dependency @heroicons/react to v2.0.18 (redwoodjs#8701)
  fix(deps): update dependency @headlessui/react to v1.7.15 (redwoodjs#8700)
  fix(deps): update dependency webpack to v5.88.0 (redwoodjs#8697)
  fix(deps): update dependency @graphiql/toolkit to v0.8.4 (redwoodjs#8698)
  fix(deps): update dependency react-error-boundary to v4.0.10 (redwoodjs#8693)
  Rename cache file (redwoodjs#8699)
  fix(clerk): add alternative decoder (redwoodjs#8642)
  fix(deps): update dependency @vitejs/plugin-react to v4.0.1 (redwoodjs#8692)
  chore(deps): update dependency @simplewebauthn/server to v7.3.1 (redwoodjs#8690)
  chore(rwfw): Add force optimise to vite.config when running project:sync (redwoodjs#8688)
  fix(deps): update storybook monorepo to v7.0.23 (redwoodjs#8696)
  fix(deps): update dependency react-toastify to v9.1.3 (redwoodjs#8694)
  fix(deps): update prisma monorepo to v4.16.1 (redwoodjs#8695)
  Mark broken gql prerender test as slow (redwoodjs#8687)
  fix(deps): update dependency @graphiql/plugin-explorer to v0.1.20 (redwoodjs#8691)
  fix(deps): update typescript-eslint monorepo to v5.60.0 (redwoodjs#8660)
  fix(deps): update dependency @fastify/http-proxy to v9.2.1 (redwoodjs#8680)
  chore(deps): update dependency vite to v4.3.9 (redwoodjs#8682)
  fix(deps): update prisma monorepo to v4.16.0 (redwoodjs#8684)
  ...
  • Loading branch information
dac09 committed Jun 23, 2023
2 parents 0115c69 + d559334 commit b9bbfdc
Show file tree
Hide file tree
Showing 108 changed files with 2,423 additions and 1,159 deletions.
56 changes: 44 additions & 12 deletions docs/docs/auth/clerk.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,16 @@ sidebar_label: Clerk

# Clerk Authentication

:::caution Did you set up Clerk a while ago?

If you set up Clerk a while ago, you may be using a deprecated `authDecoder` that's subject to rate limiting.
This decoder will be removed in the next major.
There's a new decoder you can use right now!
See the [migration guide](https://github.com/redwoodjs/redwood/releases/tag/v5.3.2) for how to upgrade.

:::


To get started, run the setup command:

```text
Expand All @@ -12,7 +22,7 @@ yarn rw setup auth clerk

This installs all the packages, writes all the files, and makes all the code modifications you need.
For a detailed explanation of all the api- and web-side changes that aren't exclusive to Clerk, see the top-level [Authentication](../authentication.md) doc.
There's one Clerk-specific thing we'll get to, but for now, let's focus on Clerk's side of things.
But for now, let's focus on Clerk's side of things.

If you don't have a Clerk account yet, now's the time to make one: navigate to https://clerk.dev, sign up, and create an application.
The defaults are good enough to get us going, but feel free to configure things as you wish.
Expand All @@ -27,9 +37,8 @@ How you get your API keys to production depends on your deploy provider.

:::

We're looking for two API keys.
Head over to the "Developers" section in the nav on the left and click "API Keys". Finally select RedwoodJS in the Framework dropdown in the Quick Copy section.
Do as it says and copy the two keys into your project's `.env` file:
After you create the application, you should be redirected to its dashboard where you should see the RedwoodJS logo.
Click on it and copy the two API keys it shows into your project's `.env` file:

```bash title=".env"
CLERK_PUBLISHABLE_KEY="..."
Expand All @@ -41,7 +50,9 @@ Lastly, in your project's `redwood.toml` file, include `CLERK_PUBLISHABLE_KEY` i
```toml title="redwood.toml"
[web]
# ...
includeEnvironmentVariables = ["CLERK_PUBLISHABLE_KEY"]
includeEnvironmentVariables = [
"CLERK_PUBLISHABLE_KEY",
]
```

That should be enough; now, things should just work.
Expand Down Expand Up @@ -71,19 +82,40 @@ Clicking sign up should open a sign-up box:

After you sign up, you should see `{"isAuthenticated":true}` on the page.

## Deep dive: the `ClerkStatusUpdater` component
## Customizing the session token

There's not a lot to the default session token.
Besides the standard claims, the only thing it really has is the user's `id`.
Eventually, you'll want to customize it so that you can get back more information from Clerk.
You can do so by navigating to the "Sessions" section in the nav on the left, then clicking on "Edit" in the "Customize session token" box:

At the start of this doc, we said that there's one Clerk-specific thing worth noting.
We'll discuss it here, but feel free to skip this section if you'd like—this is all extracurricular.
![clerk_customize_session_token](https://github.com/redwoodjs/redwood/assets/32992335/6d30c616-b4d2-4b44-971b-8addf3b79e5a)

Clerk is a bit unlike the other auth providers Redwood integrates with in that it puts an instance of its client SDK on the browser's `window` object.
That means we have to wait for it to be ready.
With other providers, we instantiate their client SDK in `web/src/auth.ts`, then pass it to `createAuth`.
Not so with Clerk—instead we use special Clerk components and hooks, like `ClerkLoaded` and `useUser`, to update Redwood's auth context with the client when it's ready.
As long as you're using the `clerkJwtDecoder`
all the properties you add will be available to the `getCurrentUser` function:

```ts title="api/src/lib/auth.ts"
export const getCurrentUser = async (
decoded, // 👈 All the claims you add will be available on the `decoded` object
// ...
) => {
decoded.myClaim...

// ...
}
````

## Avoiding feature duplication

Redwood's Clerk integration is based on [Clerk's React SDK](https://clerk.dev/docs/reference/clerk-react/installation).
This means that there's some duplication between the features in the SDK and the ones in `@redwoodjs/auth-clerk-web`.
For example, the SDK ha a `SignedOut` component that redirects a user away from a private pagevery much like wrapping a route with Redwood's `Private` component.
We recommend you use Redwood's way of doing things as much as possible since it's much more likely to get along with the rest of the framework.

## Deep dive: the `ClerkStatusUpdater` component

With Clerk, there's a bit more going on in the `web/src/auth.tsx` file than other auth providers.
This is because Clerk is a bit unlike the other auth providers Redwood integrates with in that it puts an instance of its client SDK on the browser's `window` object.
That means Redwood has to wait for it to be ready.
With other providers, Redwood instantiates their client SDK in `web/src/auth.ts{x}`, then passes it to `createAuth`.
With Clerk, instead Redwood uses Clerk components and hooks, like `ClerkLoaded` and `useUser`, to update Redwood's auth context with the client when it's ready.
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@
"@babel/preset-typescript": "7.22.5",
"@babel/runtime-corejs3": "7.22.5",
"@faker-js/faker": "8.0.2",
"@npmcli/arborist": "6.2.9",
"@npmcli/arborist": "6.2.10",
"@playwright/test": "1.34.3",
"@testing-library/jest-dom": "5.16.5",
"@testing-library/react": "14.0.0",
Expand Down
2 changes: 1 addition & 1 deletion packages/api-server/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@
"dependencies": {
"@babel/plugin-transform-runtime": "7.22.5",
"@babel/runtime-corejs3": "7.22.5",
"@fastify/http-proxy": "9.2.0",
"@fastify/http-proxy": "9.2.1",
"@fastify/static": "6.10.2",
"@fastify/url-data": "5.3.1",
"@redwoodjs/project-config": "5.0.0",
Expand Down
4 changes: 2 additions & 2 deletions packages/api/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -32,8 +32,8 @@
},
"dependencies": {
"@babel/runtime-corejs3": "7.22.5",
"@prisma/client": "4.15.0",
"@whatwg-node/fetch": "0.9.6",
"@prisma/client": "4.16.1",
"@whatwg-node/fetch": "0.9.7",
"core-js": "3.31.0",
"humanize-string": "2.1.0",
"jsonwebtoken": "9.0.0",
Expand Down
2 changes: 1 addition & 1 deletion packages/auth-providers/clerk/api/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@
},
"dependencies": {
"@babel/runtime-corejs3": "7.22.5",
"@clerk/clerk-sdk-node": "4.10.7",
"@clerk/clerk-sdk-node": "4.10.12",
"core-js": "3.31.0"
},
"devDependencies": {
Expand Down
32 changes: 25 additions & 7 deletions packages/auth-providers/clerk/api/src/__tests__/clerk.test.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import type { APIGatewayProxyEvent, Context as LambdaContext } from 'aws-lambda'

import { authDecoder } from '../decoder'
import { authDecoder, clerkAuthDecoder } from '../decoder'

const req = {
event: {} as APIGatewayProxyEvent,
Expand All @@ -18,14 +18,32 @@ afterAll(() => {
console.error = consoleError
})

test('returns null for unsupported type', async () => {
const decoded = await authDecoder('token', 'netlify', req)
describe('deprecated authDecoder', () => {
test('returns null for unsupported type', async () => {
const decoded = await authDecoder('token', 'netlify', req)

expect(decoded).toBe(null)
expect(decoded).toBe(null)
})

test('rejects when the token is invalid', async () => {
process.env.CLERK_JWT_KEY = 'jwt-key'

await expect(authDecoder('invalid-token', 'clerk', req)).rejects.toThrow()
})
})

test('rejects when the token is invalid', async () => {
process.env.CLERK_JWT_KEY = 'jwt-key'
describe('clerkAuthDecoder', () => {
test('returns null for unsupported type', async () => {
const decoded = await clerkAuthDecoder('token', 'netlify', req)

expect(decoded).toBe(null)
})

test('rejects when the token is invalid', async () => {
process.env.CLERK_JWT_KEY = 'jwt-key'

await expect(authDecoder('invalid-token', 'clerk', req)).rejects.toThrow()
await expect(
clerkAuthDecoder('invalid-token', 'clerk', req)
).rejects.toThrow()
})
})
36 changes: 36 additions & 0 deletions packages/auth-providers/clerk/api/src/decoder.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
import { Decoder } from '@redwoodjs/api'

/**
* @deprecated This function will be removed; it uses a rate-limited API. Use `clerkAuthDecoder` instead.
*/
export const authDecoder: Decoder = async (token: string, type: string) => {
if (type !== 'clerk') {
return null
Expand Down Expand Up @@ -34,3 +37,36 @@ export const authDecoder: Decoder = async (token: string, type: string) => {
return Promise.reject(error)
}
}

export const clerkAuthDecoder: Decoder = async (token: string, type: string) => {
if (type !== 'clerk') {
return null
}

const { verifyToken } = await import('@clerk/clerk-sdk-node')

try {
const issuer = (iss: string) =>
iss.startsWith('https://clerk.') || iss.includes('.clerk.accounts')

const jwtPayload = await verifyToken(token, {
issuer,
apiUrl: process.env.CLERK_API_URL || 'https://api.clerk.dev',
jwtKey: process.env.CLERK_JWT_KEY,
apiKey: process.env.CLERK_API_KEY,
secretKey: process.env.CLERK_SECRET_KEY,
})

if (!jwtPayload.sub) {
return Promise.reject(new Error('Session invalid'))
}

return {
...jwtPayload,
id: jwtPayload.sub,
}
} catch (error) {
console.error(error)
return Promise.reject(error)
}
}
2 changes: 1 addition & 1 deletion packages/auth-providers/clerk/api/src/index.ts
Original file line number Diff line number Diff line change
@@ -1 +1 @@
export { authDecoder } from './decoder'
export { authDecoder, clerkAuthDecoder } from './decoder'
9 changes: 3 additions & 6 deletions packages/auth-providers/clerk/setup/src/setupHandler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,30 +13,27 @@ export const handler = async ({ force: forceArg }: Args) => {
standardAuthHandler({
basedir: __dirname,
forceArg,
authDecoderImport: `import { authDecoder } from '@redwoodjs/auth-clerk-api'`,
authDecoderImport: `import { clerkAuthDecoder as authDecoder } from '@redwoodjs/auth-clerk-api'`,
provider: 'clerk',
webPackages: [
'@clerk/clerk-react@^4',
`@redwoodjs/auth-clerk-web@${version}`,
],
apiPackages: [`@redwoodjs/auth-clerk-api@${version}`],
notes: [
"You'll need to add three env vars to your .env file:",
"You'll need to add two env vars to your .env file:",
'',
'```title=".env"',
'CLERK_PUBLISHABLE_KEY="..."',
'CLERK_SECRET_KEY="..."',
'CLERK_JWT_KEY="-----BEGIN PUBLIC KEY-----',
'...',
'-----END PUBLIC KEY-----"',
'```',
'',
`You can find their values under "API Keys" on your Clerk app's dashboard.`,
'Be sure to include `CLERK_PUBLISHABLE_KEY` in the `includeEnvironmentVariables` array in redwood.toml.',
'',
'```toml title="redwood.toml"',
'includeEnvironmentVariables = [',
' "CLERK_PUBLISHABLE_KEY"',
' "CLERK_PUBLISHABLE_KEY,"',
']',
'```',
'',
Expand Down
Original file line number Diff line number Diff line change
@@ -1,12 +1,11 @@
import { parseJWT } from '@redwoodjs/api'
import { AuthenticationError, ForbiddenError } from '@redwoodjs/graphql-server'

import { logger } from 'src/lib/logger'

/**
* getCurrentUser returns the user information together with
* an optional collection of roles used by requireAuth() to check
* if the user is authenticated or has role-based access
* getCurrentUser returns the user information.
* Once you're ready you can also return a collection of roles
* for `requireAuth` and the Router to use.
*
* @param decoded - The decoded access token containing user info and JWT claims like `sub`. Note could be null.
* @param { token, SupportedAuthTypes type } - The access token itself as well as the auth provider type
Expand All @@ -27,16 +26,10 @@ export const getCurrentUser = async (
return null
}

const { roles } = parseJWT({ decoded })
const { id, ..._rest } = decoded

// Remove privateMetadata property from CurrentUser as it should not be accessible on the web
const { privateMetadata, ...userWithoutPrivateMetadata } = decoded

if (roles) {
return { ...userWithoutPrivateMetadata, roles }
}

return { ...userWithoutPrivateMetadata }
// Be careful to only return information that should be accessible on the web side.
return { id }
}

/**
Expand Down
2 changes: 1 addition & 1 deletion packages/auth-providers/dbAuth/api/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@
"@babel/cli": "7.22.5",
"@babel/core": "7.22.5",
"@redwoodjs/api": "5.0.0",
"@simplewebauthn/server": "7.3.0",
"@simplewebauthn/server": "7.3.1",
"@types/crypto-js": "4.1.1",
"@types/md5": "2.3.2",
"@types/uuid": "9.0.2",
Expand Down
1 change: 1 addition & 0 deletions packages/cli-helpers/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@
"dependencies": {
"@babel/core": "7.22.5",
"@babel/runtime-corejs3": "7.22.5",
"@opentelemetry/api": "1.4.1",
"@redwoodjs/project-config": "5.0.0",
"@redwoodjs/telemetry": "5.0.0",
"chalk": "4.1.2",
Expand Down
2 changes: 2 additions & 0 deletions packages/cli-helpers/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,3 +8,5 @@ export * from './lib/project'
export * from './auth/setupHelpers'

export * from './lib/installHelpers'

export * from './telemetry/index'
46 changes: 46 additions & 0 deletions packages/cli-helpers/src/telemetry/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
import opentelemetry, {
SpanStatusCode,
AttributeValue,
Span,
} from '@opentelemetry/api'

type TelemetryAttributes = {
[key: string]: AttributeValue
}

/**
* Safely records attributes to the opentelemetry span
*
* @param attributes An object of key-value pairs to be individually recorded as attributes
* @param span An optional span to record the attributes to. If not provided, the current active span will be used
*/
export function recordTelemetryAttributes(
attributes: TelemetryAttributes,
span?: Span
) {
const spanToRecord = span ?? opentelemetry.trace.getActiveSpan()
if (spanToRecord === undefined) {
return
}
for (const [key, value] of Object.entries(attributes)) {
spanToRecord.setAttribute(key, value)
}
}

/**
* Safely records an error to the opentelemetry span
*
* @param error An error to record to the span
* @param span An optional span to record the error to. If not provided, the current active span will be used
*/
export function recordTelemetryError(error: any, span?: Span) {
const spanToRecord = span ?? opentelemetry.trace.getActiveSpan()
if (spanToRecord === undefined) {
return
}
spanToRecord.setStatus({
code: SpanStatusCode.ERROR,
message: error.toString().split('\n')[0],
})
spanToRecord.recordException(error)
}
10 changes: 5 additions & 5 deletions packages/cli-packages/storybook/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -26,13 +26,13 @@
"dependencies": {
"@redwoodjs/project-config": "5.0.0",
"@redwoodjs/telemetry": "5.0.0",
"@storybook/addon-a11y": "7.0.22",
"@storybook/addon-docs": "7.0.22",
"@storybook/addon-essentials": "7.0.22",
"@storybook/react-webpack5": "7.0.22",
"@storybook/addon-a11y": "7.0.23",
"@storybook/addon-docs": "7.0.23",
"@storybook/addon-essentials": "7.0.23",
"@storybook/react-webpack5": "7.0.23",
"chalk": "4.1.2",
"execa": "5.1.1",
"storybook": "7.0.22",
"storybook": "7.0.23",
"terminal-link": "2.1.1",
"yargs": "17.7.2"
},
Expand Down
Loading

0 comments on commit b9bbfdc

Please sign in to comment.