-
-
Notifications
You must be signed in to change notification settings - Fork 5.3k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
6 changed files
with
231 additions
and
37 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,44 @@ | ||
--- | ||
layout: default | ||
title: "CanAccess" | ||
--- | ||
|
||
# `CanAccess` | ||
|
||
This component calls the `authProvider.canAccess()` method on mount for a provided resource and action (and optionally a record). It will only display its children when users are authorized. | ||
|
||
## Usage | ||
|
||
```jsx | ||
import { CanAccess, Edit, SimpleForm } from 'react-admin'; | ||
|
||
const UserEdit = () => { | ||
return ( | ||
<Edit> | ||
<SimpleForm> | ||
<TextInput source="lastName"> | ||
<TextInput source="firstName"> | ||
<CanAccess action="editPermissions"> | ||
<SelectInput source="role" choices={['admin', 'user']}> | ||
</CanAccess> | ||
</SimpleForm> | ||
</Edit> | ||
) | ||
}; | ||
``` | ||
|
||
`<CanAccess>` will call the `authProvider.canAccess` method with the following parameters: `{ action: "editPermissions", resource: "users", record: {} }` where `record` wil be the currently edited record. | ||
|
||
## Parameters | ||
|
||
`<CanAccess>` expects the following props: | ||
|
||
| Name | Required | Type | Default | Description | | ||
| -------------- | -------- | -------------- | ------------------------------------- | --- | | ||
| `action` | Required | `string` | - | The action to check, e.g. 'read', 'list', 'export', 'delete', etc. | | ||
| `resource` | Optional | `string` | Resource from current ResourceContext | The resource to check, e.g. 'users', 'comments', 'posts', etc. | | ||
| `record` | Optional | `object` | Record from current RecordContext | The record to check. If passed, the child only renders if the user has permissions for that record, e.g. `{ id: 123, firstName: "John", lastName: "Doe" }` | | ||
| `loading` | Optional | `ReactElement` | `loading` from `Admin>` | The element displayed when authorizations are being checked | | ||
| `unauthorized` | Optional | `ReactElement` | `unauthorized` from `Admin>` | The element displayed when users are not authorized to see a page | | ||
|
||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,36 @@ | ||
import * as React from 'react'; | ||
import { render, screen } from '@testing-library/react'; | ||
import { | ||
Basic, | ||
CustomLoading, | ||
CustomUnauthorized, | ||
NoAuthProvider, | ||
Unauthorized, | ||
} from './CanAccess.stories'; | ||
|
||
describe('CanAccess', () => { | ||
it('shows the default loading component while loading', async () => { | ||
render(<Basic />); | ||
await screen.findByText('Loading...'); | ||
}); | ||
it('shows the custom loading element while loading', async () => { | ||
render(<CustomLoading />); | ||
await screen.findByText('Please wait...'); | ||
}); | ||
it('shows the default unauthorized component when users are unauthorized', async () => { | ||
render(<Unauthorized />); | ||
await screen.findByText('Loading...'); | ||
}); | ||
it('shows the custom unauthorized element when users are unauthorized', async () => { | ||
render(<CustomUnauthorized />); | ||
await screen.findByText('Not allowed'); | ||
}); | ||
it('shows the protected content when users are authorized', async () => { | ||
render(<Basic />); | ||
await screen.findByText('protected content'); | ||
}); | ||
it('shows the protected content when no authProvider', () => { | ||
render(<NoAuthProvider />); | ||
screen.getByText('protected content'); | ||
}); | ||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,86 @@ | ||
import * as React from 'react'; | ||
import { AuthProvider } from '../types'; | ||
import { CoreAdminContext } from '../core'; | ||
import { CanAccess } from './CanAccess'; | ||
|
||
export default { | ||
title: 'ra-core/auth/CanAccess', | ||
}; | ||
|
||
const defaultAuthProvider: AuthProvider = { | ||
login: () => Promise.reject('bad method'), | ||
logout: () => Promise.reject('bad method'), | ||
checkAuth: () => Promise.reject('bad method'), | ||
checkError: () => Promise.reject('bad method'), | ||
getPermissions: () => Promise.reject('bad method'), | ||
canAccess: ({ action }) => | ||
new Promise(resolve => setTimeout(resolve, 500, action === 'read')), | ||
}; | ||
|
||
export const Basic = () => ( | ||
<CoreAdminContext | ||
authProvider={defaultAuthProvider} | ||
loading={() => <div>Loading...</div>} | ||
unauthorized={() => <div>Unauthorized</div>} | ||
> | ||
<CanAccess action="read" resource="test"> | ||
protected content | ||
</CanAccess> | ||
</CoreAdminContext> | ||
); | ||
|
||
export const Unauthorized = () => ( | ||
<CoreAdminContext | ||
authProvider={defaultAuthProvider} | ||
loading={() => <div>Loading...</div>} | ||
unauthorized={() => <div>Unauthorized</div>} | ||
> | ||
<CanAccess action="show" resource="test"> | ||
protected content | ||
</CanAccess> | ||
</CoreAdminContext> | ||
); | ||
|
||
export const CustomLoading = () => ( | ||
<CoreAdminContext | ||
authProvider={defaultAuthProvider} | ||
loading={() => <div>Loading...</div>} | ||
unauthorized={() => <div>Unauthorized</div>} | ||
> | ||
<CanAccess | ||
action="read" | ||
resource="test" | ||
loading={<div>Please wait...</div>} | ||
> | ||
protected content | ||
</CanAccess> | ||
</CoreAdminContext> | ||
); | ||
|
||
export const CustomUnauthorized = () => ( | ||
<CoreAdminContext | ||
authProvider={defaultAuthProvider} | ||
loading={() => <div>Loading...</div>} | ||
unauthorized={() => <div>Unauthorized</div>} | ||
> | ||
<CanAccess | ||
action="show" | ||
resource="test" | ||
unauthorized={<div>Not allowed</div>} | ||
> | ||
protected content | ||
</CanAccess> | ||
</CoreAdminContext> | ||
); | ||
|
||
export const NoAuthProvider = () => ( | ||
<CoreAdminContext | ||
authProvider={undefined} | ||
loading={() => <div>Loading...</div>} | ||
unauthorized={() => <div>Unauthorized</div>} | ||
> | ||
<CanAccess action="read" resource="test"> | ||
protected content | ||
</CanAccess> | ||
</CoreAdminContext> | ||
); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,55 @@ | ||
import * as React from 'react'; | ||
import { useLoadingContext } from '../core/useLoadingContext'; | ||
import { useUnauthorizedContext } from '../core/useUnauthorizedContext'; | ||
import { useCanAccess } from './useCanAccess'; | ||
import { RaRecord } from '../types'; | ||
import { useRecordContext } from '../controller'; | ||
import { useResourceContext } from '../core'; | ||
|
||
/** | ||
* A component that only displays its children after checking whether users are authorized to access the provided resource and action. | ||
* @param options | ||
* @param options.action The action to check. One of 'list', 'create', 'edit', 'show', 'delete', or a custom action. | ||
* @param options.resource The resource to check. e.g. 'posts', 'comments', 'users' | ||
* @param options.children The component to render if users are authorized. | ||
* @param options.loading An optional element to render while the authorization is being checked. Defaults to the loading component provided on `Admin`. | ||
* @param options.unauthorized An optional element to render if users are not authorized. Defaults to the unauthorized component provided on `Admin`. | ||
*/ | ||
export const CanAccess = ({ | ||
action, | ||
children, | ||
loading, | ||
unauthorized, | ||
...props | ||
}: CanAccessProps) => { | ||
const resource = useResourceContext(props); | ||
if (!resource) { | ||
throw new Error( | ||
'<CanAccess> must be used inside a <Resource> component or provide a resource prop' | ||
); | ||
} | ||
const record = useRecordContext(props); | ||
const { canAccess, isPending } = useCanAccess({ | ||
action, | ||
resource, | ||
record, | ||
}); | ||
|
||
const Loading = useLoadingContext(); | ||
const Unauthorized = useUnauthorizedContext(); | ||
|
||
return isPending | ||
? loading ?? <Loading /> | ||
: canAccess === false | ||
? unauthorized ?? <Unauthorized /> | ||
: children; | ||
}; | ||
|
||
export interface CanAccessProps { | ||
action: string; | ||
resource?: string; | ||
record?: RaRecord; | ||
children: React.ReactNode; | ||
loading?: React.ReactElement; | ||
unauthorized?: React.ReactElement; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters