From 37d686a44bb1ddf3576fb96b5137460c2690cad4 Mon Sep 17 00:00:00 2001 From: Dominic Saadi Date: Fri, 5 Aug 2022 22:25:56 +0900 Subject: [PATCH 1/4] remove clerk codemod see https://github.com/redwoodjs/redwood/pull/5969 --- .../src/codemods/v2.2.x/updateClerk/README.md | 5 -- .../updateClerk/__testfixtures__/app.input.js | 55 ------------ .../__testfixtures__/app.output.js | 55 ------------ .../updateClerk/__tests__/updateClerk.test.ts | 5 -- .../v2.2.x/updateClerk/updateClerk.ts | 85 ------------------- .../v2.2.x/updateClerk/updateClerk.yargs.ts | 21 ----- 6 files changed, 226 deletions(-) delete mode 100644 packages/codemods/src/codemods/v2.2.x/updateClerk/README.md delete mode 100644 packages/codemods/src/codemods/v2.2.x/updateClerk/__testfixtures__/app.input.js delete mode 100644 packages/codemods/src/codemods/v2.2.x/updateClerk/__testfixtures__/app.output.js delete mode 100644 packages/codemods/src/codemods/v2.2.x/updateClerk/__tests__/updateClerk.test.ts delete mode 100644 packages/codemods/src/codemods/v2.2.x/updateClerk/updateClerk.ts delete mode 100644 packages/codemods/src/codemods/v2.2.x/updateClerk/updateClerk.yargs.ts diff --git a/packages/codemods/src/codemods/v2.2.x/updateClerk/README.md b/packages/codemods/src/codemods/v2.2.x/updateClerk/README.md deleted file mode 100644 index d60e92943bb0..000000000000 --- a/packages/codemods/src/codemods/v2.2.x/updateClerk/README.md +++ /dev/null @@ -1,5 +0,0 @@ -# Update how we make the ClerkProvider context available to the app - -- Update imports -- Remove old ClerkAuthConsumer and ClerkAuthProvider -- Insert new ClerkAuthProvider implementation diff --git a/packages/codemods/src/codemods/v2.2.x/updateClerk/__testfixtures__/app.input.js b/packages/codemods/src/codemods/v2.2.x/updateClerk/__testfixtures__/app.input.js deleted file mode 100644 index 74a90d9e1eec..000000000000 --- a/packages/codemods/src/codemods/v2.2.x/updateClerk/__testfixtures__/app.input.js +++ /dev/null @@ -1,55 +0,0 @@ -import { ClerkProvider, withClerk } from '@clerk/clerk-react' - -import { AuthProvider } from '@redwoodjs/auth' -import { FatalErrorBoundary, RedwoodProvider } from '@redwoodjs/web' -import { RedwoodApolloProvider } from '@redwoodjs/web/apollo' - -import FatalErrorPage from 'src/pages/FatalErrorPage' -import Routes from 'src/Routes' - -import './index.css' - -// Wrap around the Redwood -// -// You can set user roles in a "roles" array on the public metadata in Clerk. -// -// Also, you need to add three env variables: CLERK_FRONTEND_API_URL for web and -// CLERK_API_KEY plus CLERK_JWT_KEY for api. All three can be found under "API Keys" -// on your Clerk.dev dashboard. -// -// Lastly, be sure to add the key "CLERK_FRONTEND_API_URL" in your app's redwood.toml -// [web] config "includeEnvironmentVariables" setting. - -const ClerkAuthConsumer = withClerk(({ children, clerk }) => { - return React.cloneElement(children as React.ReactElement, { client: clerk }) -}) - -// This Clerk Provider should wrap Redwood's AuthProvider -const ClerkAuthProvider = ({ children }) => { - const frontendApi = process.env.CLERK_FRONTEND_API_URL - if (!frontendApi) { - throw new Error('Need to define env variable CLERK_FRONTEND_API_URL') - } - - return ( - - {children} - - ) -} - -const App = () => ( - - - - - - - - - - - -) - -export default App diff --git a/packages/codemods/src/codemods/v2.2.x/updateClerk/__testfixtures__/app.output.js b/packages/codemods/src/codemods/v2.2.x/updateClerk/__testfixtures__/app.output.js deleted file mode 100644 index f499f088509b..000000000000 --- a/packages/codemods/src/codemods/v2.2.x/updateClerk/__testfixtures__/app.output.js +++ /dev/null @@ -1,55 +0,0 @@ -import { ClerkProvider, ClerkLoaded } from '@clerk/clerk-react' - -import { navigate } from '@redwoodjs/router' - -import { AuthProvider } from '@redwoodjs/auth' -import { FatalErrorBoundary, RedwoodProvider } from '@redwoodjs/web' -import { RedwoodApolloProvider } from '@redwoodjs/web/apollo' - -import FatalErrorPage from 'src/pages/FatalErrorPage' -import Routes from 'src/Routes' - -import './index.css' - -// Wrap around the Redwood -// -// You can set user roles in a "roles" array on the public metadata in Clerk. -// -// Also, you need to add three env variables: CLERK_FRONTEND_API_URL for web and -// CLERK_API_KEY plus CLERK_JWT_KEY for api. All three can be found under "API Keys" -// on your Clerk.dev dashboard. -// -// Lastly, be sure to add the key "CLERK_FRONTEND_API_URL" in your app's redwood.toml -// [web] config "includeEnvironmentVariables" setting. - -// This Clerk Provider should wrap Redwood's AuthProvider -const ClerkAuthProvider = ({ children }) => { - const frontendApi = process.env.CLERK_FRONTEND_API_URL - if (!frontendApi) { - throw new Error('Need to define env variable CLERK_FRONTEND_API_URL') - } - - return ( - navigate(to)}> - - {children} - - - ) -} - -const App = () => ( - - - - - - - - - - - -) - -export default App diff --git a/packages/codemods/src/codemods/v2.2.x/updateClerk/__tests__/updateClerk.test.ts b/packages/codemods/src/codemods/v2.2.x/updateClerk/__tests__/updateClerk.test.ts deleted file mode 100644 index 6427ad2f36cc..000000000000 --- a/packages/codemods/src/codemods/v2.2.x/updateClerk/__tests__/updateClerk.test.ts +++ /dev/null @@ -1,5 +0,0 @@ -describe('Update App.{js,tsx}', () => { - test('Update import and provider', async () => { - await matchTransformSnapshot('updateClerk', 'app') - }) -}) diff --git a/packages/codemods/src/codemods/v2.2.x/updateClerk/updateClerk.ts b/packages/codemods/src/codemods/v2.2.x/updateClerk/updateClerk.ts deleted file mode 100644 index 398425d6c797..000000000000 --- a/packages/codemods/src/codemods/v2.2.x/updateClerk/updateClerk.ts +++ /dev/null @@ -1,85 +0,0 @@ -import type { CommentKind } from 'ast-types/gen/kinds' -import type { FileInfo, API } from 'jscodeshift' - -export default function transform(file: FileInfo, api: API) { - const j = api.jscodeshift - - const ast = j(file.source) - - ast - .find(j.ImportDeclaration, { source: { value: '@clerk/clerk-react' } }) - .forEach((importDeclaration) => { - importDeclaration?.value.specifiers?.forEach((specifier) => { - if ( - j.ImportSpecifier.check(specifier) && - specifier.imported.name === 'withClerk' - ) { - // Found `withClerk` import. Now I want to replace that with a - // `ClerkLoaded` import instead - specifier.imported.name = 'ClerkLoaded' - - // And finally, for the imports, we need to add a new import to give us `navigate` - j(importDeclaration).insertAfter( - "import { navigate } from '@redwoodjs/router'" - ) - } - }) - }) - - let comments: CommentKind[] = [] - - // Remove old RW Clerk components - ast.find(j.VariableDeclaration).forEach((variableDeclaration) => { - if ( - variableDeclaration.value.declarations.find((declaration) => { - return ( - j.VariableDeclarator.check(declaration) && - j.Identifier.check(declaration.id) && - (declaration.id.name === 'ClerkAuthProvider' || - declaration.id.name === 'ClerkAuthConsumer') - ) - }) - ) { - comments = [...comments, ...(variableDeclaration.value.comments || [])] - j(variableDeclaration).remove() - } - }) - - const appVariableDeclaration = ast.find(j.VariableDeclaration, { - declarations: [ - { - id: { name: 'App' }, - }, - ], - }) - - const clerkAuthProvider = j.variableDeclaration('const', [ - j.variableDeclarator( - j.identifier('ClerkAuthProvider'), - j.arrowFunctionExpression( - [j.identifier('({ children })')], - j.jsxExpressionContainer( - j.identifier(` - const frontendApi = process.env.CLERK_FRONTEND_API_URL - if (!frontendApi) { - throw new Error('Need to define env variable CLERK_FRONTEND_API_URL') - } - - return ( - navigate(to)}> - - {children} - - - ) -`) - ) - ) - ), - ]) - clerkAuthProvider.comments = comments - - appVariableDeclaration.insertBefore([clerkAuthProvider]) - - return ast.toSource() -} diff --git a/packages/codemods/src/codemods/v2.2.x/updateClerk/updateClerk.yargs.ts b/packages/codemods/src/codemods/v2.2.x/updateClerk/updateClerk.yargs.ts deleted file mode 100644 index c22ca722afe1..000000000000 --- a/packages/codemods/src/codemods/v2.2.x/updateClerk/updateClerk.yargs.ts +++ /dev/null @@ -1,21 +0,0 @@ -import path from 'path' - -import task from 'tasuku' - -import getRWPaths from '../../../lib/getRWPaths' -import runTransform from '../../../lib/runTransform' - -export const command = 'update-clerk' -export const description = - '(v2.1.0->v2.2.0) Updates App.{js,tsx} to use the new Clerk auth provider' - -export const handler = () => { - task('Updating App.{js,tsx}', async () => { - const rwPaths = getRWPaths() - - await runTransform({ - transformPath: path.join(__dirname, 'updateClerk.js'), - targetPaths: [rwPaths.web.app], - }) - }) -} From 1e0cfef4ace1e390466575d96cbc493ae3a8b0d7 Mon Sep 17 00:00:00 2001 From: Dominic Saadi Date: Sat, 6 Aug 2022 15:00:28 +0900 Subject: [PATCH 2/4] revert 4880 (fix clerk) see https://github.com/redwoodjs/redwood/pull/4880 --- packages/auth/package.json | 12 - packages/auth/src/AuthProvider.tsx | 442 ++++++++---------- .../auth/src/__tests__/AuthProvider.test.tsx | 287 +++++------- packages/auth/src/authClients/AuthClient.ts | 44 -- .../src/authClients/SupportedAuthClients.ts | 104 ----- packages/auth/src/authClients/clerk.ts | 59 +-- packages/auth/src/authClients/index.ts | 138 +++++- .../commands/setup/auth/providers/clerk.js | 20 +- packages/core/config/webpack.common.js | 18 - packages/testing/config/storybook/main.js | 3 - yarn.lock | 8 - 11 files changed, 436 insertions(+), 699 deletions(-) delete mode 100644 packages/auth/src/authClients/AuthClient.ts delete mode 100644 packages/auth/src/authClients/SupportedAuthClients.ts diff --git a/packages/auth/package.json b/packages/auth/package.json index bfcd7d720fd6..f110897266a5 100644 --- a/packages/auth/package.json +++ b/packages/auth/package.json @@ -53,17 +53,5 @@ "supertokens-auth-react": "0.24.2", "typescript": "4.7.4" }, - "peerDependencies": { - "@clerk/clerk-react": "3.5.1", - "@clerk/clerk-sdk-node": "3.9.2" - }, - "peerDependenciesMeta": { - "@clerk/clerk-react": { - "optional": true - }, - "@clerk/clerk-sdk-node": { - "optional": true - } - }, "gitHead": "3905ed045508b861b495f8d5630d76c7a157d8f1" } diff --git a/packages/auth/src/AuthProvider.tsx b/packages/auth/src/AuthProvider.tsx index 8976d03ca770..f8d5d81bedcd 100644 --- a/packages/auth/src/AuthProvider.tsx +++ b/packages/auth/src/AuthProvider.tsx @@ -1,10 +1,4 @@ -import React, { - ReactNode, - useCallback, - useEffect, - useMemo, - useState, -} from 'react' +import React from 'react' import { createAuthClient } from './authClients' import type { @@ -14,12 +8,10 @@ import type { SupportedAuthClients, SupportedUserMetadata, } from './authClients' -import type { WebAuthnClientType } from './webAuthn' export interface CurrentUser { roles?: Array | string } - export interface AuthContextInterface { /* Determining your current authentication state */ loading: boolean @@ -83,39 +75,24 @@ export const AuthContext = React.createContext({ hasError: false, }) -const AuthUpdateListener = ({ - rwClient, - reauthenticate, -}: { - rwClient?: AuthClient - reauthenticate: () => Promise -}) => { - rwClient?.useListenForUpdates?.({ reauthenticate }) - - return null -} - type AuthProviderProps = | { client: SupportedAuthClients - type: Omit + type: Omit config?: never skipFetchCurrentUser?: boolean - children?: ReactNode | undefined } | { client?: never type: 'clerk' config?: never skipFetchCurrentUser?: boolean - children?: ReactNode | undefined } | { - client?: WebAuthnClientType + client?: never type: 'dbAuth' config?: SupportedAuthConfig skipFetchCurrentUser?: boolean - children?: ReactNode | undefined } type AuthProviderState = { @@ -126,15 +103,6 @@ type AuthProviderState = { hasError: boolean error?: Error } - -const defaultAuthProviderState: AuthProviderState = { - loading: true, - isAuthenticated: false, - userMetadata: null, - currentUser: null, - hasError: false, -} - /** * @example * ```js @@ -145,65 +113,52 @@ const defaultAuthProviderState: AuthProviderState = { * * ``` */ -export const AuthProvider = (props: AuthProviderProps) => { - const skipFetchCurrentUser = props.skipFetchCurrentUser || false - - const [hasRestoredState, setHasRestoredState] = useState(false) - - const [authProviderState, setAuthProviderState] = useState( - defaultAuthProviderState - ) - - const [rwClient, setRwClient] = useState() - - const rwClientPromise: Promise = useMemo(async () => { - // If ever we rebuild the rwClient, we need to re-restore the state. - // This is not desired behavior, but may happen if for some reason the host app's - // auth configuration changes mid-flight. - setHasRestoredState(false) - - const client = await createAuthClient( +export class AuthProvider extends React.Component< + AuthProviderProps, + AuthProviderState +> { + static defaultProps = { + skipFetchCurrentUser: false, + } + + state: AuthProviderState = { + loading: true, + isAuthenticated: false, + userMetadata: null, + currentUser: null, + hasError: false, + } + + rwClient: AuthClient + + constructor(props: AuthProviderProps) { + super(props) + this.rwClient = createAuthClient( props.client as SupportedAuthClients, props.type as SupportedAuthTypes, props.config as SupportedAuthConfig ) + } - setRwClient(client) + async componentDidMount() { + await this.rwClient.restoreAuthState?.() + return this.reauthenticate() + } - return client - }, [props.client, props.type, props.config]) - - /** - * Clients should always return null or token string. - * It is expected that they catch any errors internally. - * This catch is a last resort effort in case any errors are - * missed or slip through. - */ - const getToken = useCallback(async () => { - const client = await rwClientPromise - - try { - const token = await client.getToken() - return token - } catch (e) { - console.error('Caught internal:', e) - return null - } - }, [rwClientPromise]) + getApiGraphQLUrl = () => { + return global.RWJS_API_GRAPHQL_URL + } - const getCurrentUser = useCallback(async (): Promise< - Record - > => { - const client = await rwClientPromise + getCurrentUser = async (): Promise> => { // Always get a fresh token, rather than use the one in state - const token = await getToken() - const response = await global.fetch(global.RWJS_API_GRAPHQL_URL, { + const token = await this.getToken() + const response = await global.fetch(this.getApiGraphQLUrl(), { method: 'POST', // TODO: how can user configure this? inherit same `config` options given to auth client? credentials: 'include', headers: { 'content-type': 'application/json', - 'auth-provider': client.type, + 'auth-provider': this.rwClient.type, authorization: `Bearer ${token}`, }, body: JSON.stringify({ @@ -220,49 +175,7 @@ export const AuthProvider = (props: AuthProviderProps) => { `Could not fetch current user: ${response.statusText} (${response.status})` ) } - }, [rwClientPromise, getToken]) - - const reauthenticate = useCallback(async () => { - const client = await rwClientPromise - const notAuthenticatedState: AuthProviderState = { - isAuthenticated: false, - currentUser: null, - userMetadata: null, - loading: false, - hasError: false, - } - - try { - const userMetadata = await client.getUserMetadata() - if (!userMetadata) { - setAuthProviderState(notAuthenticatedState) - } else { - await getToken() - - const currentUser = skipFetchCurrentUser ? null : await getCurrentUser() - - setAuthProviderState((oldState) => ({ - ...oldState, - userMetadata, - currentUser, - isAuthenticated: true, - loading: false, - })) - } - } catch (e: any) { - setAuthProviderState({ - ...notAuthenticatedState, - hasError: true, - error: e as Error, - }) - } - }, [ - getToken, - rwClientPromise, - setAuthProviderState, - skipFetchCurrentUser, - getCurrentUser, - ]) + } /** * @example @@ -277,162 +190,171 @@ export const AuthProvider = (props: AuthProviderProps) => { * If the user is assigned any of the provided list of roles, * the hasRole is considered to be true. */ - const hasRole = useCallback( - (rolesToCheck: string | string[]): boolean => { - const currentUser = authProviderState.currentUser - - if (currentUser?.roles) { - if (typeof rolesToCheck === 'string') { - if (typeof currentUser.roles === 'string') { - // rolesToCheck is a string, currentUser.roles is a string - return currentUser.roles === rolesToCheck - } else if (Array.isArray(currentUser.roles)) { - // rolesToCheck is a string, currentUser.roles is an array - return currentUser.roles?.some( - (allowedRole) => rolesToCheck === allowedRole - ) - } + hasRole = (rolesToCheck: string | string[]): boolean => { + if (this.state.currentUser?.roles) { + if (typeof rolesToCheck === 'string') { + if (typeof this.state.currentUser.roles === 'string') { + // rolesToCheck is a string, currentUser.roles is a string + return this.state.currentUser.roles === rolesToCheck + } else if (Array.isArray(this.state.currentUser.roles)) { + // rolesToCheck is a string, currentUser.roles is an array + return this.state.currentUser.roles?.some( + (allowedRole) => rolesToCheck === allowedRole + ) } + } - if (Array.isArray(rolesToCheck)) { - if (Array.isArray(currentUser.roles)) { - // rolesToCheck is an array, currentUser.roles is an array - return currentUser.roles?.some((allowedRole) => - rolesToCheck.includes(allowedRole) - ) - } else if (typeof currentUser.roles === 'string') { - // rolesToCheck is an array, currentUser.roles is a string - return rolesToCheck.some( - (allowedRole) => currentUser?.roles === allowedRole - ) - } + if (Array.isArray(rolesToCheck)) { + if (Array.isArray(this.state.currentUser.roles)) { + // rolesToCheck is an array, currentUser.roles is an array + return this.state.currentUser.roles?.some((allowedRole) => + rolesToCheck.includes(allowedRole) + ) + } else if (typeof this.state.currentUser.roles === 'string') { + // rolesToCheck is an array, currentUser.roles is a string + return rolesToCheck.some( + (allowedRole) => this.state.currentUser?.roles === allowedRole + ) } } + } - return false - }, - [authProviderState.currentUser] - ) - - const logIn = useCallback( - async (options?: any) => { - setAuthProviderState(defaultAuthProviderState) - const client = await rwClientPromise - const loginOutput = await client.login(options) - await reauthenticate() + return false + } - return loginOutput - }, - [rwClientPromise, reauthenticate] - ) + /** + * Clients should always return null or token string. + * It is expected that they catch any errors internally. + * This catch is a last resort effort in case any errors are + * missed or slip through. + */ + getToken = async () => { + let token - const logOut = useCallback( - async (options?: any) => { - const client = await rwClientPromise - await client.logout(options) - setAuthProviderState({ - userMetadata: null, - currentUser: null, - isAuthenticated: false, - hasError: false, - error: undefined, - loading: false, - }) - }, - [rwClientPromise] - ) + try { + token = await this.rwClient.getToken() + } catch { + token = null + } - const signUp = useCallback( - async (options?: any) => { - const client = await rwClientPromise - const signupOutput = await client.signup(options) - await reauthenticate() - return signupOutput - }, - [rwClientPromise, reauthenticate] - ) + return token + } - const forgotPassword = useCallback( - async (username: string) => { - const client = await rwClientPromise + reauthenticate = async () => { + const notAuthenticatedState: AuthProviderState = { + isAuthenticated: false, + currentUser: null, + userMetadata: null, + loading: false, + hasError: false, + } - if (client.forgotPassword) { - return await client.forgotPassword(username) + try { + const userMetadata = await this.rwClient.getUserMetadata() + if (!userMetadata) { + this.setState(notAuthenticatedState) } else { - throw new Error( - `Auth client ${client.type} does not implement this function` - ) - } - }, - [rwClientPromise] - ) + await this.getToken() - const resetPassword = useCallback( - async (options?: any) => { - const client = await rwClientPromise + const currentUser = this.props.skipFetchCurrentUser + ? null + : await this.getCurrentUser() - if (client.resetPassword) { - return await client.resetPassword(options) - } else { - throw new Error( - `Auth client ${client.type} does not implement this function` - ) + this.setState({ + ...this.state, + userMetadata, + currentUser, + isAuthenticated: true, + loading: false, + }) } - }, - [rwClientPromise] - ) + } catch (e: any) { + this.setState({ + ...notAuthenticatedState, + hasError: true, + error: e as Error, + }) + } + } - const validateResetToken = useCallback( - async (resetToken: string | null) => { - const client = await rwClientPromise + logIn = async (options?: any) => { + this.setState({ loading: true }) + const loginOutput = await this.rwClient.login(options) + await this.reauthenticate() - if (client.validateResetToken) { - return await client.validateResetToken(resetToken) - } else { - throw new Error( - `Auth client ${client.type} does not implement this function` - ) - } - }, - [rwClientPromise] - ) + return loginOutput + } - /** Whenever the rwClient is ready to go, restore auth and reauthenticate */ - useEffect(() => { - if (rwClient && !hasRestoredState) { - setHasRestoredState(true) + logOut = async (options?: any) => { + await this.rwClient.logout(options) + this.setState({ + userMetadata: null, + currentUser: null, + isAuthenticated: false, + hasError: false, + error: undefined, + }) + } - const doRestoreState = async () => { - await rwClient.restoreAuthState?.() - reauthenticate() - } + signUp = async (options?: any) => { + const signupOutput = await this.rwClient.signup(options) + await this.reauthenticate() + return signupOutput + } - doRestoreState() + forgotPassword = async (username: string) => { + if (this.rwClient.forgotPassword) { + return await this.rwClient.forgotPassword(username) + } else { + throw new Error( + `Auth client ${this.rwClient.type} does not implement this function` + ) } - }, [rwClient, reauthenticate, hasRestoredState]) + } - const { client, type, children } = props + resetPassword = async (options?: any) => { + if (this.rwClient.resetPassword) { + return await this.rwClient.resetPassword(options) + } else { + throw new Error( + `Auth client ${this.rwClient.type} does not implement this function` + ) + } + } - return ( - - {children} - - - ) + validateResetToken = async (resetToken: string | null) => { + if (this.rwClient.validateResetToken) { + return await this.rwClient.validateResetToken(resetToken) + } else { + throw new Error( + `Auth client ${this.rwClient.type} does not implement this function` + ) + } + } + + render() { + const { client, type, children } = this.props + + return ( + + {children} + + ) + } } diff --git a/packages/auth/src/__tests__/AuthProvider.test.tsx b/packages/auth/src/__tests__/AuthProvider.test.tsx index f536205967de..15ea747cf75a 100644 --- a/packages/auth/src/__tests__/AuthProvider.test.tsx +++ b/packages/auth/src/__tests__/AuthProvider.test.tsx @@ -2,7 +2,7 @@ require('whatwg-fetch') import { useEffect, useState } from 'react' -import { render, screen, fireEvent, waitFor, act } from '@testing-library/react' +import { render, screen, fireEvent, waitFor } from '@testing-library/react' import '@testing-library/jest-dom/extend-expect' import { graphql } from 'msw' import { setupServer } from 'msw/node' @@ -63,7 +63,7 @@ const AuthConsumer = () => { error, } = useAuth() - const [authToken, setAuthToken] = useState(null) + const [authToken, setAuthToken] = useState(null) useEffect(() => { const retrieveToken = async () => { @@ -78,7 +78,7 @@ const AuthConsumer = () => { } if (hasError) { - return <>{error?.message} + return <>{error.message} } return ( @@ -133,15 +133,14 @@ test('Authentication flow (logged out -> login -> logged in -> logout) works as type: 'custom', } - await act(async () => { - render( - - - - ) - // We're booting up! - expect(screen.getByText('Loading...')).toBeInTheDocument() - }) + render( + + + + ) + + // We're booting up! + expect(screen.getByText('Loading...')).toBeInTheDocument() // The user is not authenticated await waitFor(() => screen.getByText('Log In')) @@ -190,17 +189,14 @@ test('Fetching the current user can be skipped', async () => { client: () => {}, type: 'custom', } + render( + + + + ) - await act(async () => { - render( - - - - ) - - // We're booting up! - expect(screen.getByText('Loading...')).toBeInTheDocument() - }) + // We're booting up! + expect(screen.getByText('Loading...')).toBeInTheDocument() // The user is not authenticated await waitFor(() => screen.getByText('Log In')) @@ -242,14 +238,11 @@ test('A user can be reauthenticated to update the "auth state"', async () => { client: () => {}, type: 'custom', } - - await act(async () => { - render( - - - - ) - }) + render( + + + + ) // The user is not authenticated await waitFor(() => screen.getByText('Log In')) @@ -308,16 +301,14 @@ test('When the current user cannot be fetched the user is not authenticated', as client: () => {}, type: 'custom', } - await act(async () => { - render( - - - - ) + render( + + + + ) - // We're booting up! - expect(screen.getByText('Loading...')).toBeInTheDocument() - }) + // We're booting up! + expect(screen.getByText('Loading...')).toBeInTheDocument() await waitFor(() => screen.getByText('Could not fetch current user: Not Found (404)') @@ -349,16 +340,14 @@ test('Authenticated user has assigned role access as expected', async () => { roles: [], } - await act(async () => { - render( - - - - ) + render( + + + + ) - // We're booting up! - expect(screen.getByText('Loading...')).toBeInTheDocument() - }) + // We're booting up! + expect(screen.getByText('Loading...')).toBeInTheDocument() // The user is not authenticated await waitFor(() => screen.getByText('Log In')) @@ -411,16 +400,14 @@ test('Authenticated user has not been assigned role access as expected', async ( roles: ['admin', 'superuser'], } - await act(async () => { - render( - - - - ) + render( + + + + ) - // We're booting up! - expect(screen.getByText('Loading...')).toBeInTheDocument() - }) + // We're booting up! + expect(screen.getByText('Loading...')).toBeInTheDocument() // The user is not authenticated await waitFor(() => screen.getByText('Log In')) @@ -473,16 +460,14 @@ test('Authenticated user has not been assigned some role access but not others a roles: ['admin'], } - await act(async () => { - render( - - - - ) + render( + + + + ) - // We're booting up! - expect(screen.getByText('Loading...')).toBeInTheDocument() - }) + // We're booting up! + expect(screen.getByText('Loading...')).toBeInTheDocument() // The user is not authenticated await waitFor(() => screen.getByText('Log In')) @@ -535,16 +520,14 @@ test('Authenticated user has assigned role access as expected', async () => { roles: ['admin'], } - await act(async () => { - render( - - - - ) + render( + + + + ) - // We're booting up! - expect(screen.getByText('Loading...')).toBeInTheDocument() - }) + // We're booting up! + expect(screen.getByText('Loading...')).toBeInTheDocument() // The user is not authenticated await waitFor(() => screen.getByText('Log In')) @@ -596,16 +579,14 @@ test('Authenticated user has assigned role access as expected', async () => { roles: ['admin'], } - await act(async () => { - render( - - - - ) + render( + + + + ) - // We're booting up! - expect(screen.getByText('Loading...')).toBeInTheDocument() - }) + // We're booting up! + expect(screen.getByText('Loading...')).toBeInTheDocument() // The user is not authenticated await waitFor(() => screen.getByText('Log In')) @@ -654,16 +635,14 @@ test('Checks roles successfully when roles in currentUser is a string', async () roles: 'admin', } - await act(async () => { - render( - - - - ) + render( + + + + ) - // We're booting up! - expect(screen.getByText('Loading...')).toBeInTheDocument() - }) + // We're booting up! + expect(screen.getByText('Loading...')).toBeInTheDocument() // The user is not authenticated await waitFor(() => screen.getByText('Log In')) @@ -712,16 +691,14 @@ test('Authenticated user has assigned role access as expected', async () => { roles: ['editor'], } - await act(async () => { - render( - - - - ) + render( + + + + ) - // We're booting up! - expect(screen.getByText('Loading...')).toBeInTheDocument() - }) + // We're booting up! + expect(screen.getByText('Loading...')).toBeInTheDocument() // The user is not authenticated await waitFor(() => screen.getByText('Log In')) @@ -773,16 +750,14 @@ test('Authenticated user has assigned role access as expected', async () => { roles: ['admin'], } - await act(async () => { - render( - - - - ) + render( + + + + ) - // We're booting up! - expect(screen.getByText('Loading...')).toBeInTheDocument() - }) + // We're booting up! + expect(screen.getByText('Loading...')).toBeInTheDocument() // The user is not authenticated await waitFor(() => screen.getByText('Log In')) @@ -824,26 +799,20 @@ test('proxies forgotPassword() calls to client', async () => { } const TestAuthConsumer = () => { - const { loading, forgotPassword } = useAuth() - - useEffect(() => { - if (!loading) { - forgotPassword('username') - } - }, [loading, forgotPassword]) + const { forgotPassword } = useAuth() + forgotPassword('username') return null } - await act(async () => { - render( - - - - ) - }) + render( + + + + ) - expect.assertions(1) + // for whatever reason, forgotPassword is invoked twice + expect.assertions(2) }) test('proxies resetPassword() calls to client', async () => { @@ -856,26 +825,20 @@ test('proxies resetPassword() calls to client', async () => { } const TestAuthConsumer = () => { - const { loading, resetPassword } = useAuth() - - useEffect(() => { - if (!loading) { - resetPassword('password') - } - }, [loading, resetPassword]) + const { resetPassword } = useAuth() + resetPassword('password') return null } - await act(async () => { - render( - - - - ) - }) + render( + + + + ) - expect.assertions(1) + // for whatever reason, forgotPassword is invoked twice + expect.assertions(2) }) test('proxies validateResetToken() calls to client', async () => { @@ -888,26 +851,20 @@ test('proxies validateResetToken() calls to client', async () => { } const TestAuthConsumer = () => { - const { loading, validateResetToken } = useAuth() - - useEffect(() => { - if (!loading) { - validateResetToken('12345') - } - }, [loading, validateResetToken]) + const { validateResetToken } = useAuth() + validateResetToken('12345') return null } - await act(async () => { - render( - - - - ) - }) + render( + + + + ) - expect.assertions(1) + // for whatever reason, validateResetToken is invoked twice + expect.assertions(2) }) test('getToken doesnt fail if client throws an error', async () => { @@ -920,10 +877,7 @@ test('getToken doesnt fail if client throws an error', async () => { const TestAuthConsumer = () => { const { getToken } = useAuth() - const [authTokenResult, setAuthTokenResult] = useState<{ - success: boolean - token: string - } | null>(null) + const [authTokenResult, setAuthTokenResult] = useState(null) useEffect(() => { const getTokenAsync = async () => { @@ -935,7 +889,6 @@ test('getToken doesnt fail if client throws an error', async () => { token = await getToken() setAuthTokenResult({ success: true, token }) } catch (error) { - console.error('Caught unexpected:', error) setAuthTokenResult({ success: false, token: 'FAIL' }) } } @@ -946,13 +899,11 @@ test('getToken doesnt fail if client throws an error', async () => { return
Token: {`${authTokenResult?.token}`}
} - await act(async () => { - render( - - - - ) - }) + render( + + + + ) await waitFor(() => screen.getByText('Token: null')) }) diff --git a/packages/auth/src/authClients/AuthClient.ts b/packages/auth/src/authClients/AuthClient.ts deleted file mode 100644 index a6feccae04eb..000000000000 --- a/packages/auth/src/authClients/AuthClient.ts +++ /dev/null @@ -1,44 +0,0 @@ -import { - SupportedAuthClients, - SupportedAuthConfig, - SupportedAuthTypes, - SupportedUserMetadata, - typesToClients, -} from './SupportedAuthClients' - -export interface AuthClient { - restoreAuthState?(): void | Promise - login(options?: any): Promise - logout(options?: any): void | Promise - signup(options?: any): void | Promise - getToken(options?: any): Promise - forgotPassword?(username: string): void | Promise - resetPassword?(options?: any): void | Promise - validateResetToken?(token: string | null): void | Promise - - /** The user's data from the AuthProvider */ - getUserMetadata(): Promise - - /** Hooks for managing the hosting AuthProvider's life-cycle */ - /** An optional hook to listen for updates from the 3rd party provider */ - useListenForUpdates?(handlers: { reauthenticate: () => Promise }): void - - client: SupportedAuthClients - type: SupportedAuthTypes -} - -export const createAuthClient = ( - client: SupportedAuthClients, - type: SupportedAuthTypes, - config?: SupportedAuthConfig -): Promise => { - if (!typesToClients[type]) { - const supportedClients = Object.keys(typesToClients).join(', ') - - throw new Error( - `Your client ${type} is not supported, we only support ${supportedClients}` - ) - } - - return Promise.resolve(typesToClients[type](client, config)) -} diff --git a/packages/auth/src/authClients/SupportedAuthClients.ts b/packages/auth/src/authClients/SupportedAuthClients.ts deleted file mode 100644 index f5e94ebc68e8..000000000000 --- a/packages/auth/src/authClients/SupportedAuthClients.ts +++ /dev/null @@ -1,104 +0,0 @@ -import { auth0 } from './auth0' -import type { Auth0, Auth0User } from './auth0' -import { AuthClient } from './AuthClient' -import { azureActiveDirectory } from './azureActiveDirectory' -import type { - AzureActiveDirectory, - AzureActiveDirectoryUser, -} from './azureActiveDirectory' -import { clerk } from './clerk' -import type { Clerk, ClerkUser } from './clerk' -import { custom } from './custom' -import type { Custom } from './custom' -import { dbAuth } from './dbAuth' -import type { DbAuth, DbAuthConfig } from './dbAuth' -import { ethereum } from './ethereum' -import type { Ethereum, EthereumUser } from './ethereum' -import { firebase } from './firebase' -import type { FirebaseClient, FirebaseUser } from './firebase' -import { goTrue } from './goTrue' -import type { GoTrue, GoTrueUser } from './goTrue' -import { magicLink } from './magicLink' -import type { MagicLink, MagicUser } from './magicLink' -import { netlify } from './netlify' -import type { NetlifyIdentity } from './netlify' -import { nhost } from './nhost' -import type { Nhost, NhostUser } from './nhost' -import { okta } from './okta' -import type { Okta, OktaUser } from './okta' -import { supabase } from './supabase' -import type { Supabase, SupabaseUser } from './supabase' -import { supertokens } from './supertokens' -import type { SuperTokensUser, SuperTokens } from './supertokens' - -export type AuthFactory< - ClientType, - ConfigType, - AuthClientType extends AuthClient -> = ( - client: ClientType, - config: ConfigType -) => AuthClientType | Promise - -export const typesToClients: Record> = { - netlify, - auth0, - azureActiveDirectory, - dbAuth, - goTrue, - magicLink, - firebase, - supabase, - ethereum, - nhost, - clerk, - supertokens, - okta, - /** Don't we support your auth client? No problem, define your own the `custom` type! */ - custom, -} - -export type SupportedAuthClients = - | Auth0 - | AzureActiveDirectory - | DbAuth - | GoTrue - | NetlifyIdentity - | MagicLink - | FirebaseClient - | Supabase - | Clerk - | Ethereum - | Nhost - | SuperTokens - | Okta - | Custom - -export type SupportedAuthTypes = keyof typeof typesToClients - -export type SupportedAuthConfig = DbAuthConfig - -export type { Auth0User } -export type { AzureActiveDirectoryUser } -export type { DbAuth } -export type { ClerkUser } -export type { FirebaseUser } -export type { GoTrueUser } -export type { MagicUser } -export type { SupabaseUser } -export type { EthereumUser } -export type { NhostUser } -export type { SuperTokensUser } -export type { OktaUser } -export type SupportedUserMetadata = - | Auth0User - | AzureActiveDirectoryUser - | ClerkUser - | FirebaseUser - | GoTrueUser - | MagicUser - | SupabaseUser - | EthereumUser - | NhostUser - | SuperTokensUser - | OktaUser diff --git a/packages/auth/src/authClients/clerk.ts b/packages/auth/src/authClients/clerk.ts index fd093d29ce40..6b4d86829781 100644 --- a/packages/auth/src/authClients/clerk.ts +++ b/packages/auth/src/authClients/clerk.ts @@ -1,12 +1,9 @@ -import { useEffect } from 'react' - +import Clerk from '@clerk/clerk-js' import { UserResource as ClerkUserResource, SignInProps, SignUpProps, SignOutCallback, - Resources, - Clerk, GetTokenOptions, SignOutOptions, SignOut, @@ -22,21 +19,18 @@ export type { Clerk } export type ClerkUser = ClerkUserResource & { roles: string[] | null } -// Because Clerk's client is nulled out while it is loading, there is a race -// condition under normal usage on a clean load of the app. This falls back -// to the window.Clerk property when necessary to circumvent that. -function clerkClient(propsClient: Clerk | null): Clerk | null { - if (!propsClient && typeof window !== undefined) { - return (window as any).Clerk ?? null +// In production, there is an issue where the AuthProvider sometimes captures +// Clerk as null. This intercepts that +// issue and falls back to `window.Clerk` to access the client. +function clerkClient(propsClient: Clerk | null) { + if (!propsClient) { + return window.Clerk ?? null } else { return propsClient } } -export const clerk = async (client: Clerk): Promise => { - // We use the typescript dynamic import feature to pull in the react library only if clerk is needed. - const { useUser: useClerkUser } = await import('@clerk/clerk-react') - +export const clerk = (client: Clerk): AuthClientClerk => { return { type: 'clerk', client, @@ -48,43 +42,6 @@ export const clerk = async (client: Clerk): Promise => { ) => clerkClient(client)?.signOut(callbackOrOptions as any, options), signup: async (options?: SignUpProps) => clerkClient(client)?.openSignUp(options || {}), - restoreAuthState: async () => { - const clerk = clerkClient(client) - if (!clerk) { - // If the client is null, we can't restore state or listen for it to happen. - // This behavior is somewhat undefined, which is why we instruct the user to wrap - // the auth provider in to prevent it. For now we'll just return. - - if (process.env.NODE_ENV === 'development') { - console.log('Please wrap your auth provider with ``') - } - - return - } - - // NOTE: Clerk's API docs say session will be undefined if loading (null if loaded and confirmed unset). - if (!clerk.client || clerk.session !== undefined) { - return new Promise((res) => { - clerk.addListener((msg: Resources) => { - if (msg.session !== undefined && msg.client) { - res() - } - }) - }) - } else { - // In this case, we assume everything has been restored already. - return - } - }, - // Hook to inform AuthProvider of Clerk's life-cycle - useListenForUpdates: ({ reauthenticate }) => { - const { isSignedIn, user, isLoaded } = useClerkUser() - useEffect(() => { - if (isLoaded) { - reauthenticate() - } - }, [isSignedIn, user, reauthenticate, isLoaded]) - }, getToken: async (options?: GetTokenOptions) => { let token diff --git a/packages/auth/src/authClients/index.ts b/packages/auth/src/authClients/index.ts index 3407ae36b7ec..8d1601c65903 100644 --- a/packages/auth/src/authClients/index.ts +++ b/packages/auth/src/authClients/index.ts @@ -1,21 +1,119 @@ -export type { - SupportedAuthClients, - SupportedAuthTypes, - SupportedAuthConfig, - SupportedUserMetadata, - Auth0User, +import { auth0 } from './auth0' +import type { Auth0, Auth0User } from './auth0' +import { azureActiveDirectory } from './azureActiveDirectory' +import type { + AzureActiveDirectory, AzureActiveDirectoryUser, - DbAuth, - ClerkUser, - FirebaseUser, - GoTrueUser, - MagicUser, - SupabaseUser, - EthereumUser, - NhostUser, - SuperTokensUser, - OktaUser, -} from './SupportedAuthClients' - -export type { AuthClient } from './AuthClient' -export { createAuthClient } from './AuthClient' +} from './azureActiveDirectory' +import { clerk } from './clerk' +import type { Clerk, ClerkUser } from './clerk' +import { custom } from './custom' +import type { Custom } from './custom' +import { dbAuth } from './dbAuth' +import type { DbAuth, DbAuthConfig } from './dbAuth' +import { ethereum } from './ethereum' +import type { Ethereum, EthereumUser } from './ethereum' +import { firebase } from './firebase' +import type { FirebaseClient, FirebaseUser } from './firebase' +import { goTrue } from './goTrue' +import type { GoTrue, GoTrueUser } from './goTrue' +import { magicLink } from './magicLink' +import type { MagicLink, MagicUser } from './magicLink' +import { netlify } from './netlify' +import type { NetlifyIdentity } from './netlify' +import { nhost } from './nhost' +import type { Nhost, NhostUser } from './nhost' +import { supabase } from './supabase' +import type { Supabase, SupabaseUser } from './supabase' +import { supertokens } from './supertokens' +import type { SuperTokensUser, SuperTokens } from './supertokens' + +const typesToClients = { + netlify, + auth0, + azureActiveDirectory, + dbAuth, + goTrue, + magicLink, + firebase, + supabase, + ethereum, + nhost, + clerk, + supertokens, + /** Don't we support your auth client? No problem, define your own the `custom` type! */ + custom, +} + +export type SupportedAuthClients = + | Auth0 + | AzureActiveDirectory + | DbAuth + | GoTrue + | NetlifyIdentity + | MagicLink + | FirebaseClient + | Supabase + | Clerk + | Ethereum + | Nhost + | SuperTokens + | Custom + +export type SupportedAuthTypes = keyof typeof typesToClients + +export type SupportedAuthConfig = DbAuthConfig + +export type { Auth0User } +export type { AzureActiveDirectoryUser } +export type { DbAuth } +export type { ClerkUser } +export type { FirebaseUser } +export type { GoTrueUser } +export type { MagicUser } +export type { SupabaseUser } +export type { EthereumUser } +export type { NhostUser } +export type { SuperTokensUser } +export type SupportedUserMetadata = + | Auth0User + | AzureActiveDirectoryUser + | ClerkUser + | FirebaseUser + | GoTrueUser + | MagicUser + | SupabaseUser + | EthereumUser + | NhostUser + | SuperTokensUser + +export interface AuthClient { + restoreAuthState?(): void | Promise + login(options?: any): Promise + logout(options?: any): void | Promise + signup(options?: any): void | Promise + getToken(options?: any): Promise + forgotPassword?(username: string): void | Promise + resetPassword?(options?: any): void | Promise + validateResetToken?(token: string | null): void | Promise + /** The user's data from the AuthProvider */ + getUserMetadata(): Promise + client: SupportedAuthClients + type: SupportedAuthTypes +} + +export const createAuthClient = ( + client: SupportedAuthClients, + type: SupportedAuthTypes, + config?: SupportedAuthConfig +): AuthClient => { + if (!typesToClients[type]) { + throw new Error( + `Your client ${type} is not supported, we only support ${Object.keys( + typesToClients + ).join(', ')}` + ) + } + + return typesToClients[type](client, config) +} diff --git a/packages/cli/src/commands/setup/auth/providers/clerk.js b/packages/cli/src/commands/setup/auth/providers/clerk.js index d8ce5ebe0692..b0278c86a8c0 100644 --- a/packages/cli/src/commands/setup/auth/providers/clerk.js +++ b/packages/cli/src/commands/setup/auth/providers/clerk.js @@ -1,13 +1,10 @@ // the lines that need to be added to App.{js,tsx} export const config = { - imports: [ - `import { ClerkLoaded, ClerkProvider } from '@clerk/clerk-react'`, - `import { navigate } from '@redwoodjs/router'`, - ], + imports: [`import { ClerkProvider, withClerk } from '@clerk/clerk-react'`], init: ` -// Wrap Redwood's with the . +// Wrap around the Redwood // -// You can set user roles in a "roles" array on the user's public_metadata in Clerk. +// You can set user roles in a "roles" array on the public metadata in Clerk. // // Also, you need to add three env variables: CLERK_FRONTEND_API_URL for web and // CLERK_API_KEY plus CLERK_JWT_KEY for api. All three can be found under "API Keys" @@ -16,6 +13,10 @@ export const config = { // Lastly, be sure to add the key "CLERK_FRONTEND_API_URL" in your app's redwood.toml // [web] config "includeEnvironmentVariables" setting. +const ClerkAuthConsumer = withClerk(({ children, clerk }) => { + return React.cloneElement(children as React.ReactElement, { client: clerk }) +}) + const ClerkAuthProvider = ({ children }) => { const frontendApi = process.env.CLERK_FRONTEND_API_URL if (!frontendApi) { @@ -23,15 +24,12 @@ const ClerkAuthProvider = ({ children }) => { } return ( - navigate(to)}> - - {children} - + + {children} ) }`, authProvider: { - render: ['ClerkAuthProvider'], type: 'clerk', }, } diff --git a/packages/core/config/webpack.common.js b/packages/core/config/webpack.common.js index 1eebc160139b..6694acb7b9b4 100644 --- a/packages/core/config/webpack.common.js +++ b/packages/core/config/webpack.common.js @@ -138,23 +138,6 @@ const getSharedPlugins = (isEnvProduction) => { mockCurrentUser: ['@redwoodjs/testing/web', 'mockCurrentUser'], } - // If one has an optional dependency that should be included if installed but not - // cause a compile error if missing, then this will do that for you. - // Checks at *compile time* if the dependency is present and ignores ONLY if not. - // Caveat - this does mean you might defer a missing dependency error until runtime. - function ignoreOptionalDependency(dependencyName) { - try { - require(dependencyName) - return [] - } catch { - return [ - new webpack.IgnorePlugin({ - resourceRegExp: new RegExp(`^${dependencyName}$`), - }), - ] - } - } - return [ isEnvProduction && new MiniCssExtractPlugin({ @@ -187,7 +170,6 @@ const getSharedPlugins = (isEnvProduction) => { ), ...getEnvVars(), }), - ...ignoreOptionalDependency('@clerk/clerk-react'), new Dotenv({ path: path.resolve(redwoodPaths.base, '.env'), silent: true, diff --git a/packages/testing/config/storybook/main.js b/packages/testing/config/storybook/main.js index 952371497b56..efa702ce966c 100644 --- a/packages/testing/config/storybook/main.js +++ b/packages/testing/config/storybook/main.js @@ -107,9 +107,6 @@ const baseConfig = { Boolean ) - // ** EXTERNALS * - sbConfig.externals = rwConfig.externals - // ** NODE ** sbConfig.node = rwConfig.node diff --git a/yarn.lock b/yarn.lock index ddac75f3c87b..eb5d02cda8cd 100644 --- a/yarn.lock +++ b/yarn.lock @@ -6287,14 +6287,6 @@ __metadata: react: 17.0.2 supertokens-auth-react: 0.24.2 typescript: 4.7.4 - peerDependencies: - "@clerk/clerk-react": 3.5.1 - "@clerk/clerk-sdk-node": 3.9.2 - peerDependenciesMeta: - "@clerk/clerk-react": - optional: true - "@clerk/clerk-sdk-node": - optional: true languageName: unknown linkType: soft From ac9508042c32ee046a9af6f0f496de3b2314d4b4 Mon Sep 17 00:00:00 2001 From: Dominic Saadi Date: Sat, 6 Aug 2022 15:17:27 +0900 Subject: [PATCH 3/4] changes for okta see https://github.com/redwoodjs/redwood/pull/5088 --- packages/auth/src/authClients/index.ts | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/packages/auth/src/authClients/index.ts b/packages/auth/src/authClients/index.ts index 8d1601c65903..55680ca06d98 100644 --- a/packages/auth/src/authClients/index.ts +++ b/packages/auth/src/authClients/index.ts @@ -23,6 +23,8 @@ import { netlify } from './netlify' import type { NetlifyIdentity } from './netlify' import { nhost } from './nhost' import type { Nhost, NhostUser } from './nhost' +import { okta } from './okta' +import type { Okta, OktaUser } from './okta' import { supabase } from './supabase' import type { Supabase, SupabaseUser } from './supabase' import { supertokens } from './supertokens' @@ -41,6 +43,7 @@ const typesToClients = { nhost, clerk, supertokens, + okta, /** Don't we support your auth client? No problem, define your own the `custom` type! */ custom, } @@ -58,6 +61,7 @@ export type SupportedAuthClients = | Ethereum | Nhost | SuperTokens + | Okta | Custom export type SupportedAuthTypes = keyof typeof typesToClients @@ -75,6 +79,7 @@ export type { SupabaseUser } export type { EthereumUser } export type { NhostUser } export type { SuperTokensUser } +export type { OktaUser } export type SupportedUserMetadata = | Auth0User | AzureActiveDirectoryUser @@ -86,6 +91,7 @@ export type SupportedUserMetadata = | EthereumUser | NhostUser | SuperTokensUser + | OktaUser export interface AuthClient { restoreAuthState?(): void | Promise From 14ddc26d3b3e36e9a7f3739d34d95504e0908f45 Mon Sep 17 00:00:00 2001 From: Dominic Saadi Date: Sat, 6 Aug 2022 15:25:16 +0900 Subject: [PATCH 4/4] changes for webauthn see https://github.com/redwoodjs/redwood/pull/5680 --- packages/auth/src/AuthProvider.tsx | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/packages/auth/src/AuthProvider.tsx b/packages/auth/src/AuthProvider.tsx index f8d5d81bedcd..275c1bca6833 100644 --- a/packages/auth/src/AuthProvider.tsx +++ b/packages/auth/src/AuthProvider.tsx @@ -8,6 +8,7 @@ import type { SupportedAuthClients, SupportedUserMetadata, } from './authClients' +import type { WebAuthnClientType } from './webAuthn' export interface CurrentUser { roles?: Array | string @@ -89,7 +90,7 @@ type AuthProviderProps = skipFetchCurrentUser?: boolean } | { - client?: never + client?: WebAuthnClientType type: 'dbAuth' config?: SupportedAuthConfig skipFetchCurrentUser?: boolean