Skip to content

Commit

Permalink
Update private routes and Set to use roles vs role (#4681)
Browse files Browse the repository at this point in the history
* Change set role to roles and deprecate

* Adds role private router checks

* Remove singular role and clarify rolesTioCheck

Co-authored-by: David Price <[email protected]>
  • Loading branch information
dthyresson and thedavidprice authored Mar 16, 2022
1 parent 20c9a8a commit 5291913
Show file tree
Hide file tree
Showing 4 changed files with 67 additions and 12 deletions.
2 changes: 1 addition & 1 deletion packages/auth/src/AuthProvider.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ export interface AuthContextInterface {
* If the user is assigned any of the provided list of roles,
* the hasRole is considered to be true.
**/
hasRole(role: string | string[]): boolean
hasRole(rolesToCheck: string | string[]): boolean
/**
* Redetermine authentication state and update the state.
*/
Expand Down
2 changes: 1 addition & 1 deletion packages/auth/src/__tests__/AuthProvider.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ import { AuthProvider } from '../AuthProvider'
import { useAuth } from '../useAuth'

type HasRoleAuthClient = AuthClient & {
hasRole: (role?: string | string[]) => Promise<boolean | null>
hasRole: (rolesToCheck?: string | string[]) => Promise<boolean | null>
}

let CURRENT_USER_DATA: {
Expand Down
13 changes: 7 additions & 6 deletions packages/router/src/Set.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,8 @@ type SetProps<P> = P & {
private?: boolean
/** The page name where a user will be redirected when not authenticated */
unauthenticated?: string
role?: string | string[]
/** Route is permitted when authenticated and use has any of the provided roles such as "admin" or ["admin", "editor"] */
roles?: string | string[]
/** Prerender all pages in the set */
prerender?: boolean
children: ReactNode
Expand All @@ -42,7 +43,7 @@ export function Set<WrapperProps>(props: SetProps<WrapperProps>) {
children,
private: privateSet,
unauthenticated,
role,
roles,
whileLoadingAuth,
...rest
} = props
Expand All @@ -56,8 +57,8 @@ export function Set<WrapperProps>(props: SetProps<WrapperProps>) {
}

const unauthorized = useCallback(() => {
return !(isAuthenticated && (!role || hasRole(role)))
}, [isAuthenticated, role, hasRole])
return !(isAuthenticated && (!roles || hasRole(roles)))
}, [isAuthenticated, roles, hasRole])

// Make sure `wrappers` is always an array with at least one wrapper component
const wrappers = Array.isArray(wrap) ? wrap : [wrap ? wrap : IdentityWrapper]
Expand Down Expand Up @@ -103,7 +104,7 @@ type PrivateProps<P> = Omit<
}

export function Private<WrapperProps>(props: PrivateProps<WrapperProps>) {
const { children, unauthenticated, role, wrap, ...rest } = props
const { children, unauthenticated, roles, wrap, ...rest } = props

return (
// @MARK Doesn't matter that we pass `any` here
Expand All @@ -114,7 +115,7 @@ export function Private<WrapperProps>(props: PrivateProps<WrapperProps>) {
<Set<any>
private
unauthenticated={unauthenticated}
role={role}
roles={roles}
wrap={wrap}
{...rest}
>
Expand Down
62 changes: 58 additions & 4 deletions packages/router/src/__tests__/router.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -65,13 +65,19 @@ const mockUseAuth =
{
isAuthenticated = false,
loading = false,
}: { isAuthenticated?: boolean; loading?: boolean } = {
hasRole = false,
}: { isAuthenticated?: boolean; loading?: boolean; hasRole?: boolean } = {
isAuthenticated: false,
loading: false,
hasRole: false,
}
) =>
() =>
createDummyAuthContextValues({ loading, isAuthenticated })
createDummyAuthContextValues({
loading,
isAuthenticated,
hasRole: () => hasRole,
})

// SETUP
const HomePage = () => <h1>Home Page</h1>
Expand Down Expand Up @@ -115,9 +121,15 @@ describe('slow imports', () => {
)
}

const TestRouter = ({ authenticated }: { authenticated?: boolean }) => (
const TestRouter = ({
authenticated,
hasRole,
}: {
authenticated?: boolean
hasRole?: boolean
}) => (
<Router
useAuth={mockUseAuth({ isAuthenticated: authenticated })}
useAuth={mockUseAuth({ isAuthenticated: authenticated, hasRole })}
pageLoadingDelay={100}
>
<Route
Expand Down Expand Up @@ -153,6 +165,22 @@ describe('slow imports', () => {
whileLoadingPage={PrivatePagePlaceholder}
/>
</Private>
<Private unauthenticated="login" roles="admin">
<Route
path="/private_with_role"
page={PrivatePage}
name="private_with_role"
whileLoadingPage={PrivatePagePlaceholder}
/>
</Private>
<Private unauthenticated="login" roles={['admin', 'moderator']}>
<Route
path="/private_with_several_roles"
page={PrivatePage}
name="private_with_several_roles"
whileLoadingPage={PrivatePagePlaceholder}
/>
</Private>
<Route
path="/param-test/{value}"
page={ParamPage}
Expand Down Expand Up @@ -246,6 +274,32 @@ describe('slow imports', () => {
})
})

test('Private page when authenticated but does not have the role', async () => {
act(() => navigate('/private_with_role'))
const screen = render(<TestRouter authenticated={true} hasRole={false} />)

await waitFor(() => {
expect(
screen.queryByText('PrivatePagePlaceholder')
).not.toBeInTheDocument()
expect(screen.queryByText('Private Page')).not.toBeInTheDocument()
expect(screen.queryByText('LoginPagePlaceholder')).toBeInTheDocument()
})
await waitFor(() => screen.getByText('Login Page'))
})

test('Private page when authenticated but does have the role', async () => {
act(() => navigate('/private_with_role'))
const screen = render(<TestRouter authenticated={true} hasRole={true} />)

await waitFor(() => {
expect(
screen.queryByText('PrivatePagePlaceholder')
).not.toBeInTheDocument()
expect(screen.queryByText('Private Page')).toBeInTheDocument()
})
})

test('useLocation', async () => {
act(() => navigate('/location'))
const screen = render(<TestRouter />)
Expand Down

0 comments on commit 5291913

Please sign in to comment.