@availity/mui-authorize 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +13 -0
- package/README.md +61 -0
- package/dist/index.d.mts +27 -0
- package/dist/index.d.ts +27 -0
- package/dist/index.js +54 -0
- package/dist/index.mjs +27 -0
- package/introduction.stories.mdx +7 -0
- package/jest.config.js +7 -0
- package/package.json +53 -0
- package/project.json +43 -0
- package/src/index.ts +1 -0
- package/src/lib/Authorize.stories.tsx +61 -0
- package/src/lib/Authorize.test.tsx +196 -0
- package/src/lib/Authorize.tsx +52 -0
- package/tsconfig.json +5 -0
- package/tsconfig.spec.json +10 -0
package/CHANGELOG.md
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
This file was generated using [@jscutlery/semver](https://github.com/jscutlery/semver).
|
|
4
|
+
|
|
5
|
+
## 0.1.0 (2024-05-10)
|
|
6
|
+
|
|
7
|
+
### Dependency Updates
|
|
8
|
+
|
|
9
|
+
* `mui-block-ui` updated to version `0.1.0`
|
|
10
|
+
|
|
11
|
+
### Features
|
|
12
|
+
|
|
13
|
+
* **mui-authorize:** add authorize package ([ff64501](https://github.com/Availity/element/commit/ff6450141021d74657e53e85213ce00b638a6465))
|
package/README.md
ADDED
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
# @availity/mui-authorize
|
|
2
|
+
|
|
3
|
+
> Availity MUI Authorize component to be used with @availity/element design system.
|
|
4
|
+
|
|
5
|
+
[](https://www.npmjs.com/package/@availity/mui-authorize)
|
|
6
|
+
[](https://www.npmjs.com/package/@availity/mui-authorize)
|
|
7
|
+
[](https://github.com/Availity/element/blob/main/packages/mui-authorize/package.json)
|
|
8
|
+
|
|
9
|
+
## Documentation
|
|
10
|
+
|
|
11
|
+
This package extends the MUI Authorize component: [MUI Authorize Docs](https://mui.com/components/authorize/)
|
|
12
|
+
|
|
13
|
+
Live demo and documentation in our [Storybook](https://availity.github.io/element/?path=/docs/components-authorize-introduction--docs)
|
|
14
|
+
|
|
15
|
+
Availity standards for design and usage can be found in the [Availity Design Guide](https://zeroheight.com/2e36e50c7)
|
|
16
|
+
|
|
17
|
+
## Installation
|
|
18
|
+
|
|
19
|
+
### Import Through @availity/element (Recommended)
|
|
20
|
+
|
|
21
|
+
#### NPM
|
|
22
|
+
|
|
23
|
+
```bash
|
|
24
|
+
npm install @availity/element
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
#### Yarn
|
|
28
|
+
|
|
29
|
+
```bash
|
|
30
|
+
yarn add @availity/element
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
### Direct Import
|
|
34
|
+
|
|
35
|
+
#### NPM
|
|
36
|
+
|
|
37
|
+
_This package has a few peer dependencies. Add `@mui/material` & `@emotion/react` to your project if not already installed._
|
|
38
|
+
|
|
39
|
+
```bash
|
|
40
|
+
npm install @availity/mui-authorize
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
#### Yarn
|
|
44
|
+
|
|
45
|
+
```bash
|
|
46
|
+
yarn add @availity/mui-authorize
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
### Usage
|
|
50
|
+
|
|
51
|
+
#### Import through @availity/element
|
|
52
|
+
|
|
53
|
+
```tsx
|
|
54
|
+
import { Authorize } from '@availity/element';
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
#### Direct import
|
|
58
|
+
|
|
59
|
+
```tsx
|
|
60
|
+
import { Authorize } from '@availity/mui-authorize';
|
|
61
|
+
```
|
package/dist/index.d.mts
ADDED
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { ReactNode } from 'react';
|
|
2
|
+
import { UseAuthorizeProps } from '@availity/authorize';
|
|
3
|
+
|
|
4
|
+
type AuthorizeProps = {
|
|
5
|
+
/** The content that renders when the user does have the permissions required. */
|
|
6
|
+
children?: ReactNode;
|
|
7
|
+
/** When true, BlockUi is used when loading the permissions. When a node, that node is rendered instead of BlockUi when loading the permissions. When false, nothing is rendered when loading the permissions. Default: true. */
|
|
8
|
+
loader?: boolean | ReactNode;
|
|
9
|
+
/** Negate the authorization. If the user does have the permissions specified, they are considered "unauthorized" (shown the unauthorized prop content). If the user does not have the permissions specified, they are considered "authorized" (shown the children prop content) */
|
|
10
|
+
negate?: boolean;
|
|
11
|
+
/** The content that renders when the user does not have the permissions required. */
|
|
12
|
+
unauthorized?: ReactNode;
|
|
13
|
+
} & UseAuthorizeProps;
|
|
14
|
+
/** Component for showing content based on the user's permissions. Wrap this component around content you only want specific users to see.
|
|
15
|
+
*
|
|
16
|
+
* The `useAuthorize` hook utilizes [react-query](https://tanstack.com/query/v4/docs/framework/react/overview) to handle data fetching.
|
|
17
|
+
* This means you must add a [QueryClientProvider](https://tanstack.com/query/v4/docs/framework/react/reference/QueryClientProvider)
|
|
18
|
+
* to your app if you do not already have one.
|
|
19
|
+
*
|
|
20
|
+
* The default setup should cover most use-cases. However, there are 2 query options we recommend looking into
|
|
21
|
+
* in order to determine what is correct for your app. These settings are `refetchOnWindowFocus` and
|
|
22
|
+
* `staleTime`. The first option sets whether the to refetch the query when the window is focused, and
|
|
23
|
+
* the other is the default marker for how long the query is valid.
|
|
24
|
+
*/
|
|
25
|
+
declare const Authorize: ({ loader, negate, children, unauthorized, permissions, parameters, queryOptions }: AuthorizeProps) => JSX.Element | null;
|
|
26
|
+
|
|
27
|
+
export { Authorize, type AuthorizeProps };
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { ReactNode } from 'react';
|
|
2
|
+
import { UseAuthorizeProps } from '@availity/authorize';
|
|
3
|
+
|
|
4
|
+
type AuthorizeProps = {
|
|
5
|
+
/** The content that renders when the user does have the permissions required. */
|
|
6
|
+
children?: ReactNode;
|
|
7
|
+
/** When true, BlockUi is used when loading the permissions. When a node, that node is rendered instead of BlockUi when loading the permissions. When false, nothing is rendered when loading the permissions. Default: true. */
|
|
8
|
+
loader?: boolean | ReactNode;
|
|
9
|
+
/** Negate the authorization. If the user does have the permissions specified, they are considered "unauthorized" (shown the unauthorized prop content). If the user does not have the permissions specified, they are considered "authorized" (shown the children prop content) */
|
|
10
|
+
negate?: boolean;
|
|
11
|
+
/** The content that renders when the user does not have the permissions required. */
|
|
12
|
+
unauthorized?: ReactNode;
|
|
13
|
+
} & UseAuthorizeProps;
|
|
14
|
+
/** Component for showing content based on the user's permissions. Wrap this component around content you only want specific users to see.
|
|
15
|
+
*
|
|
16
|
+
* The `useAuthorize` hook utilizes [react-query](https://tanstack.com/query/v4/docs/framework/react/overview) to handle data fetching.
|
|
17
|
+
* This means you must add a [QueryClientProvider](https://tanstack.com/query/v4/docs/framework/react/reference/QueryClientProvider)
|
|
18
|
+
* to your app if you do not already have one.
|
|
19
|
+
*
|
|
20
|
+
* The default setup should cover most use-cases. However, there are 2 query options we recommend looking into
|
|
21
|
+
* in order to determine what is correct for your app. These settings are `refetchOnWindowFocus` and
|
|
22
|
+
* `staleTime`. The first option sets whether the to refetch the query when the window is focused, and
|
|
23
|
+
* the other is the default marker for how long the query is valid.
|
|
24
|
+
*/
|
|
25
|
+
declare const Authorize: ({ loader, negate, children, unauthorized, permissions, parameters, queryOptions }: AuthorizeProps) => JSX.Element | null;
|
|
26
|
+
|
|
27
|
+
export { Authorize, type AuthorizeProps };
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __defProp = Object.defineProperty;
|
|
3
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
4
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
5
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
6
|
+
var __export = (target, all) => {
|
|
7
|
+
for (var name in all)
|
|
8
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
9
|
+
};
|
|
10
|
+
var __copyProps = (to, from, except, desc) => {
|
|
11
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
12
|
+
for (let key of __getOwnPropNames(from))
|
|
13
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
14
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
15
|
+
}
|
|
16
|
+
return to;
|
|
17
|
+
};
|
|
18
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
19
|
+
|
|
20
|
+
// src/index.ts
|
|
21
|
+
var src_exports = {};
|
|
22
|
+
__export(src_exports, {
|
|
23
|
+
Authorize: () => Authorize
|
|
24
|
+
});
|
|
25
|
+
module.exports = __toCommonJS(src_exports);
|
|
26
|
+
|
|
27
|
+
// src/lib/Authorize.tsx
|
|
28
|
+
var import_mui_block_ui = require("@availity/mui-block-ui");
|
|
29
|
+
var import_authorize = require("@availity/authorize");
|
|
30
|
+
var import_jsx_runtime = require("react/jsx-runtime");
|
|
31
|
+
var Authorize = ({
|
|
32
|
+
loader = true,
|
|
33
|
+
negate = false,
|
|
34
|
+
children = null,
|
|
35
|
+
unauthorized = null,
|
|
36
|
+
permissions,
|
|
37
|
+
parameters = {},
|
|
38
|
+
queryOptions
|
|
39
|
+
}) => {
|
|
40
|
+
const { authorized, isLoading } = (0, import_authorize.useAuthorize)(permissions, parameters, queryOptions);
|
|
41
|
+
if (isLoading) {
|
|
42
|
+
if (loader)
|
|
43
|
+
return loader === true ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_mui_block_ui.BlockUi, { blocking: true }) : /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_jsx_runtime.Fragment, { children: loader });
|
|
44
|
+
return null;
|
|
45
|
+
}
|
|
46
|
+
if ((authorized || negate) && !(authorized && negate)) {
|
|
47
|
+
return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_jsx_runtime.Fragment, { children });
|
|
48
|
+
}
|
|
49
|
+
return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_jsx_runtime.Fragment, { children: unauthorized });
|
|
50
|
+
};
|
|
51
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
52
|
+
0 && (module.exports = {
|
|
53
|
+
Authorize
|
|
54
|
+
});
|
package/dist/index.mjs
ADDED
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
// src/lib/Authorize.tsx
|
|
2
|
+
import { BlockUi } from "@availity/mui-block-ui";
|
|
3
|
+
import { useAuthorize } from "@availity/authorize";
|
|
4
|
+
import { Fragment, jsx } from "react/jsx-runtime";
|
|
5
|
+
var Authorize = ({
|
|
6
|
+
loader = true,
|
|
7
|
+
negate = false,
|
|
8
|
+
children = null,
|
|
9
|
+
unauthorized = null,
|
|
10
|
+
permissions,
|
|
11
|
+
parameters = {},
|
|
12
|
+
queryOptions
|
|
13
|
+
}) => {
|
|
14
|
+
const { authorized, isLoading } = useAuthorize(permissions, parameters, queryOptions);
|
|
15
|
+
if (isLoading) {
|
|
16
|
+
if (loader)
|
|
17
|
+
return loader === true ? /* @__PURE__ */ jsx(BlockUi, { blocking: true }) : /* @__PURE__ */ jsx(Fragment, { children: loader });
|
|
18
|
+
return null;
|
|
19
|
+
}
|
|
20
|
+
if ((authorized || negate) && !(authorized && negate)) {
|
|
21
|
+
return /* @__PURE__ */ jsx(Fragment, { children });
|
|
22
|
+
}
|
|
23
|
+
return /* @__PURE__ */ jsx(Fragment, { children: unauthorized });
|
|
24
|
+
};
|
|
25
|
+
export {
|
|
26
|
+
Authorize
|
|
27
|
+
};
|
package/jest.config.js
ADDED
package/package.json
ADDED
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@availity/mui-authorize",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Availity Authorize Component - part of the @availity/element design system",
|
|
5
|
+
"keywords": [
|
|
6
|
+
"react",
|
|
7
|
+
"typescript",
|
|
8
|
+
"availity",
|
|
9
|
+
"mui"
|
|
10
|
+
],
|
|
11
|
+
"homepage": "https://availity.github.io/element/?path=/docs/components-authorize-introduction--docs",
|
|
12
|
+
"bugs": {
|
|
13
|
+
"url": "https://github.com/Availity/element/issues"
|
|
14
|
+
},
|
|
15
|
+
"repository": {
|
|
16
|
+
"type": "git",
|
|
17
|
+
"url": "https://github.com/Availity/element.git",
|
|
18
|
+
"directory": "packages/authorize"
|
|
19
|
+
},
|
|
20
|
+
"license": "MIT",
|
|
21
|
+
"author": "Availity Developers <AVOSS@availity.com>",
|
|
22
|
+
"browser": "./dist/index.js",
|
|
23
|
+
"main": "./dist/index.js",
|
|
24
|
+
"module": "./dist/index.mjs",
|
|
25
|
+
"types": "./dist/index.d.ts",
|
|
26
|
+
"scripts": {
|
|
27
|
+
"build": "tsup src/index.ts --format esm,cjs --dts",
|
|
28
|
+
"dev": "tsup src/index.ts --format esm,cjs --watch --dts",
|
|
29
|
+
"clean": "rm -rf dist",
|
|
30
|
+
"clean:nm": "rm -rf node_modules",
|
|
31
|
+
"publish": "yarn npm publish --tolerate-republish --access public",
|
|
32
|
+
"publish:canary": "yarn npm publish --access public --tag canary"
|
|
33
|
+
},
|
|
34
|
+
"dependencies": {
|
|
35
|
+
"@availity/authorize": "^4.1.0",
|
|
36
|
+
"@availity/mui-block-ui": "^0.1.10",
|
|
37
|
+
"@tanstack/react-query": "^4.36.1"
|
|
38
|
+
},
|
|
39
|
+
"devDependencies": {
|
|
40
|
+
"@mui/material": "^5.15.15",
|
|
41
|
+
"react": "18.2.0",
|
|
42
|
+
"react-dom": "18.2.0",
|
|
43
|
+
"tsup": "^8.0.2",
|
|
44
|
+
"typescript": "^5.4.5"
|
|
45
|
+
},
|
|
46
|
+
"peerDependencies": {
|
|
47
|
+
"@mui/material": "^5.11.9",
|
|
48
|
+
"react": ">=16.3.0"
|
|
49
|
+
},
|
|
50
|
+
"publishConfig": {
|
|
51
|
+
"access": "public"
|
|
52
|
+
}
|
|
53
|
+
}
|
package/project.json
ADDED
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "mui-authorize",
|
|
3
|
+
"$schema": "../../node_modules/nx/schemas/project-schema.json",
|
|
4
|
+
"sourceRoot": "packages/authorize/src",
|
|
5
|
+
"projectType": "library",
|
|
6
|
+
"tags": [],
|
|
7
|
+
"targets": {
|
|
8
|
+
"lint": {
|
|
9
|
+
"executor": "@nx/eslint:lint",
|
|
10
|
+
"options": {
|
|
11
|
+
"eslintConfig": ".eslintrc.json",
|
|
12
|
+
"lintFilePatterns": ["packages/authorize/**/*.{js,ts}"],
|
|
13
|
+
"silent": false,
|
|
14
|
+
"fix": false,
|
|
15
|
+
"cache": true,
|
|
16
|
+
"cacheLocation": "./node_modules/.cache/authorize/.eslintcache",
|
|
17
|
+
"maxWarnings": -1,
|
|
18
|
+
"quiet": false,
|
|
19
|
+
"noEslintrc": false,
|
|
20
|
+
"hasTypeAwareRules": true,
|
|
21
|
+
"cacheStrategy": "metadata"
|
|
22
|
+
}
|
|
23
|
+
},
|
|
24
|
+
"test": {
|
|
25
|
+
"executor": "@nx/jest:jest",
|
|
26
|
+
"outputs": ["{workspaceRoot}/coverage/authorize"],
|
|
27
|
+
"options": {
|
|
28
|
+
"jestConfig": "packages/authorize/jest.config.js",
|
|
29
|
+
"passWithNoTests": true
|
|
30
|
+
}
|
|
31
|
+
},
|
|
32
|
+
"version": {
|
|
33
|
+
"executor": "@jscutlery/semver:version",
|
|
34
|
+
"options": {
|
|
35
|
+
"preset": "conventional",
|
|
36
|
+
"commitMessageFormat": "chore({projectName}): release version ${version} [skip ci]",
|
|
37
|
+
"tagPrefix": "@availity/{projectName}@",
|
|
38
|
+
"trackDeps": true,
|
|
39
|
+
"skipCommitTypes": ["docs"]
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
}
|
package/src/index.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from './lib/Authorize';
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
// Each exported component in the package should have its own stories file
|
|
2
|
+
|
|
3
|
+
import type { Meta, StoryObj } from '@storybook/react';
|
|
4
|
+
import { Authorize, AuthorizeProps } from '..';
|
|
5
|
+
import { QueryClientProvider, QueryClient } from '@tanstack/react-query';
|
|
6
|
+
import Alert from '@mui/material/Alert';
|
|
7
|
+
|
|
8
|
+
const meta: Meta<typeof Authorize> = {
|
|
9
|
+
title: 'Components/Authorize/Authorize',
|
|
10
|
+
component: Authorize,
|
|
11
|
+
tags: ['autodocs'],
|
|
12
|
+
decorators: [
|
|
13
|
+
(Story: () => JSX.Element) => (
|
|
14
|
+
<QueryClientProvider client={new QueryClient()}>
|
|
15
|
+
<Story />
|
|
16
|
+
</QueryClientProvider>
|
|
17
|
+
),
|
|
18
|
+
],
|
|
19
|
+
args: {
|
|
20
|
+
permissions: ['1234'],
|
|
21
|
+
parameters: {
|
|
22
|
+
organizationId: '1111',
|
|
23
|
+
region: true,
|
|
24
|
+
},
|
|
25
|
+
queryOptions: {
|
|
26
|
+
refetchOnWindowFocus: false,
|
|
27
|
+
},
|
|
28
|
+
unauthorized: 'You are not authorized to see this content.',
|
|
29
|
+
children: 'You are authorized to see this content.',
|
|
30
|
+
negate: false,
|
|
31
|
+
loader: true,
|
|
32
|
+
},
|
|
33
|
+
argTypes: {
|
|
34
|
+
children: {
|
|
35
|
+
control: 'text',
|
|
36
|
+
},
|
|
37
|
+
unauthorized: {
|
|
38
|
+
control: 'text',
|
|
39
|
+
},
|
|
40
|
+
},
|
|
41
|
+
};
|
|
42
|
+
|
|
43
|
+
export default meta;
|
|
44
|
+
|
|
45
|
+
export const _Authorize: StoryObj<typeof Authorize> = {
|
|
46
|
+
render: ({children, unauthorized, ...args}: AuthorizeProps) => (
|
|
47
|
+
<div>
|
|
48
|
+
<p>
|
|
49
|
+
For this demo, the following permissions are granted: 1234, 2345, 3456, 4567, 5678, 6789. You can use the knobs
|
|
50
|
+
to see what the component will do when you set the required permissions to various things.
|
|
51
|
+
</p>
|
|
52
|
+
<hr />
|
|
53
|
+
<Authorize
|
|
54
|
+
unauthorized={<Alert severity="error">{unauthorized}</Alert>}
|
|
55
|
+
{...args}
|
|
56
|
+
>
|
|
57
|
+
<Alert severity="success">{children}</Alert>
|
|
58
|
+
</Authorize>
|
|
59
|
+
</div>
|
|
60
|
+
),
|
|
61
|
+
};
|
|
@@ -0,0 +1,196 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { render, screen, waitFor } from '@testing-library/react';
|
|
3
|
+
import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
|
|
4
|
+
// eslint-disable-next-line @nx/enforce-module-boundaries
|
|
5
|
+
import { server } from '@availity/mock/src/lib/server';
|
|
6
|
+
import { Authorize } from './Authorize';
|
|
7
|
+
|
|
8
|
+
describe('Authorize', () => {
|
|
9
|
+
const queryClient = new QueryClient();
|
|
10
|
+
|
|
11
|
+
// start msw server
|
|
12
|
+
beforeAll(() => server.listen());
|
|
13
|
+
|
|
14
|
+
// clear cache and reset msw handlers
|
|
15
|
+
afterEach(() => {
|
|
16
|
+
queryClient.clear();
|
|
17
|
+
server.resetHandlers();
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
// terminate the server
|
|
21
|
+
afterAll(() => server.close());
|
|
22
|
+
|
|
23
|
+
test('should render authorized content', async () => {
|
|
24
|
+
render(
|
|
25
|
+
<QueryClientProvider client={queryClient}>
|
|
26
|
+
<Authorize permissions={['1234']} loader>
|
|
27
|
+
You have permission to see this
|
|
28
|
+
</Authorize>
|
|
29
|
+
</QueryClientProvider>
|
|
30
|
+
);
|
|
31
|
+
|
|
32
|
+
await waitFor(() => {
|
|
33
|
+
expect(screen.getByText('You have permission to see this')).toBeDefined();
|
|
34
|
+
});
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
test('should render unauthorized content', async () => {
|
|
38
|
+
render(
|
|
39
|
+
<QueryClientProvider client={queryClient}>
|
|
40
|
+
<Authorize permissions={['12345']} unauthorized="You do not have permission to see this" />
|
|
41
|
+
</QueryClientProvider>
|
|
42
|
+
);
|
|
43
|
+
|
|
44
|
+
await waitFor(() => {
|
|
45
|
+
expect(screen.getByText('You do not have permission to see this')).toBeDefined();
|
|
46
|
+
});
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
test('should render authorized with array of permissions', async () => {
|
|
50
|
+
render(
|
|
51
|
+
<QueryClientProvider client={queryClient}>
|
|
52
|
+
<Authorize
|
|
53
|
+
permissions={['1234', '2345', ['3456', '4567']]}
|
|
54
|
+
unauthorized="You do not have permission to see this"
|
|
55
|
+
>
|
|
56
|
+
You have permission to see this
|
|
57
|
+
</Authorize>
|
|
58
|
+
</QueryClientProvider>
|
|
59
|
+
);
|
|
60
|
+
|
|
61
|
+
await waitFor(() => {
|
|
62
|
+
expect(screen.getByText('You have permission to see this')).toBeDefined();
|
|
63
|
+
});
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
test('should render negate permissions', async () => {
|
|
67
|
+
render(
|
|
68
|
+
<QueryClientProvider client={queryClient}>
|
|
69
|
+
<Authorize permissions={['1234']} negate unauthorized="You do not have permission to see this">
|
|
70
|
+
You have permission to see this
|
|
71
|
+
</Authorize>
|
|
72
|
+
</QueryClientProvider>
|
|
73
|
+
);
|
|
74
|
+
|
|
75
|
+
await waitFor(() => {
|
|
76
|
+
expect(screen.getByText('You do not have permission to see this')).toBeDefined();
|
|
77
|
+
});
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
test('should render authorized with correct organizationId', async () => {
|
|
81
|
+
render(
|
|
82
|
+
<QueryClientProvider client={queryClient}>
|
|
83
|
+
<Authorize permissions={['1234']} parameters={{organizationId:"1111"}} unauthorized="You do not have permission to see this">
|
|
84
|
+
You have permission to see this
|
|
85
|
+
</Authorize>
|
|
86
|
+
</QueryClientProvider>
|
|
87
|
+
);
|
|
88
|
+
|
|
89
|
+
await waitFor(() => {
|
|
90
|
+
expect(screen.getByText('You have permission to see this')).toBeDefined();
|
|
91
|
+
});
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
test('should render unauthorized with incorrect organizationId', async () => {
|
|
95
|
+
render(
|
|
96
|
+
<QueryClientProvider client={queryClient}>
|
|
97
|
+
<Authorize permissions={['1234']} parameters={{organizationId:"1112"}} unauthorized="You do not have permission to see this">
|
|
98
|
+
You have permission to see this
|
|
99
|
+
</Authorize>
|
|
100
|
+
</QueryClientProvider>
|
|
101
|
+
);
|
|
102
|
+
|
|
103
|
+
await waitFor(() => {
|
|
104
|
+
expect(screen.getByText('You do not have permission to see this')).toBeDefined();
|
|
105
|
+
});
|
|
106
|
+
});
|
|
107
|
+
|
|
108
|
+
test('should render authorized with correct customerId', async () => {
|
|
109
|
+
render(
|
|
110
|
+
<QueryClientProvider client={queryClient}>
|
|
111
|
+
<Authorize permissions={['1234']} parameters={{customerId:"1194"}} unauthorized="You do not have permission to see this">
|
|
112
|
+
You have permission to see this
|
|
113
|
+
</Authorize>
|
|
114
|
+
</QueryClientProvider>
|
|
115
|
+
);
|
|
116
|
+
|
|
117
|
+
await waitFor(() => {
|
|
118
|
+
expect(screen.getByText('You have permission to see this')).toBeDefined();
|
|
119
|
+
});
|
|
120
|
+
});
|
|
121
|
+
|
|
122
|
+
test('should render unauthorized with incorrect customerId', async () => {
|
|
123
|
+
render(
|
|
124
|
+
<QueryClientProvider client={queryClient}>
|
|
125
|
+
<Authorize permissions={['1234']} parameters={{customerId:"1193"}} unauthorized="You do not have permission to see this">
|
|
126
|
+
You have permission to see this
|
|
127
|
+
</Authorize>
|
|
128
|
+
</QueryClientProvider>
|
|
129
|
+
);
|
|
130
|
+
|
|
131
|
+
await waitFor(() => {
|
|
132
|
+
expect(screen.getByText('You do not have permission to see this')).toBeDefined();
|
|
133
|
+
});
|
|
134
|
+
});
|
|
135
|
+
|
|
136
|
+
test('should render authorized with correct resources', async () => {
|
|
137
|
+
render(
|
|
138
|
+
<QueryClientProvider client={queryClient}>
|
|
139
|
+
<Authorize
|
|
140
|
+
permissions={['1234']}
|
|
141
|
+
parameters={{customerId:"1194", resources: ['2']}}
|
|
142
|
+
unauthorized="You do not have permission to see this"
|
|
143
|
+
>
|
|
144
|
+
You have permission to see this
|
|
145
|
+
</Authorize>
|
|
146
|
+
</QueryClientProvider>
|
|
147
|
+
);
|
|
148
|
+
|
|
149
|
+
await waitFor(() => {
|
|
150
|
+
expect(screen.getByText('You have permission to see this')).toBeDefined();
|
|
151
|
+
});
|
|
152
|
+
});
|
|
153
|
+
|
|
154
|
+
test('should render unauthorized with incorrect resources', async () => {
|
|
155
|
+
render(
|
|
156
|
+
<QueryClientProvider client={queryClient}>
|
|
157
|
+
<Authorize
|
|
158
|
+
permissions={['1234']}
|
|
159
|
+
parameters={{customerId:"1194", resources: ['5']}}
|
|
160
|
+
unauthorized="You do not have permission to see this"
|
|
161
|
+
>
|
|
162
|
+
You have permission to see this
|
|
163
|
+
</Authorize>
|
|
164
|
+
</QueryClientProvider>
|
|
165
|
+
);
|
|
166
|
+
|
|
167
|
+
await waitFor(() => {
|
|
168
|
+
expect(screen.getByText('You do not have permission to see this')).toBeDefined();
|
|
169
|
+
});
|
|
170
|
+
});
|
|
171
|
+
|
|
172
|
+
test('shows BlockUi while fetching permissions', async () => {
|
|
173
|
+
render(
|
|
174
|
+
<QueryClientProvider client={queryClient}>
|
|
175
|
+
<Authorize permissions={['1234']} loader>
|
|
176
|
+
You have permission to see this
|
|
177
|
+
</Authorize>
|
|
178
|
+
</QueryClientProvider>
|
|
179
|
+
);
|
|
180
|
+
const loadingElements = screen.getAllByText('loading');
|
|
181
|
+
|
|
182
|
+
// Filter out the visible one
|
|
183
|
+
const visibleLoadingElement = loadingElements.find((element) => {
|
|
184
|
+
// Get computed style of the element
|
|
185
|
+
const style = window.getComputedStyle(element);
|
|
186
|
+
|
|
187
|
+
// Return true if the element is visible
|
|
188
|
+
return style && style.display !== 'none' && style.visibility !== 'hidden' && style.opacity !== '0';
|
|
189
|
+
});
|
|
190
|
+
expect(visibleLoadingElement).toBeInTheDocument();
|
|
191
|
+
|
|
192
|
+
// Once data has been fetched, the BlockUi component should no longer be visible
|
|
193
|
+
await waitFor(() => expect(visibleLoadingElement).not.toBeVisible());
|
|
194
|
+
await waitFor(() => expect(screen.getByText('You have permission to see this')).toBeInTheDocument());
|
|
195
|
+
});
|
|
196
|
+
});
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import type { ReactNode } from 'react';
|
|
3
|
+
import { BlockUi } from '@availity/mui-block-ui';
|
|
4
|
+
|
|
5
|
+
import { useAuthorize, UseAuthorizeProps } from '@availity/authorize';
|
|
6
|
+
|
|
7
|
+
export type AuthorizeProps = {
|
|
8
|
+
/** The content that renders when the user does have the permissions required. */
|
|
9
|
+
children?: ReactNode;
|
|
10
|
+
/** When true, BlockUi is used when loading the permissions. When a node, that node is rendered instead of BlockUi when loading the permissions. When false, nothing is rendered when loading the permissions. Default: true. */
|
|
11
|
+
loader?: boolean | ReactNode;
|
|
12
|
+
/** Negate the authorization. If the user does have the permissions specified, they are considered "unauthorized" (shown the unauthorized prop content). If the user does not have the permissions specified, they are considered "authorized" (shown the children prop content) */
|
|
13
|
+
negate?: boolean;
|
|
14
|
+
/** The content that renders when the user does not have the permissions required. */
|
|
15
|
+
unauthorized?: ReactNode;
|
|
16
|
+
// } & Pick<UseAuthorizeProps, 'permissions' | 'queryOptions'> & UseAuthorizeProps['parameters'];
|
|
17
|
+
} & UseAuthorizeProps;
|
|
18
|
+
|
|
19
|
+
/** Component for showing content based on the user's permissions. Wrap this component around content you only want specific users to see.
|
|
20
|
+
*
|
|
21
|
+
* The `useAuthorize` hook utilizes [react-query](https://tanstack.com/query/v4/docs/framework/react/overview) to handle data fetching.
|
|
22
|
+
* This means you must add a [QueryClientProvider](https://tanstack.com/query/v4/docs/framework/react/reference/QueryClientProvider)
|
|
23
|
+
* to your app if you do not already have one.
|
|
24
|
+
*
|
|
25
|
+
* The default setup should cover most use-cases. However, there are 2 query options we recommend looking into
|
|
26
|
+
* in order to determine what is correct for your app. These settings are `refetchOnWindowFocus` and
|
|
27
|
+
* `staleTime`. The first option sets whether the to refetch the query when the window is focused, and
|
|
28
|
+
* the other is the default marker for how long the query is valid.
|
|
29
|
+
*/
|
|
30
|
+
export const Authorize = ({
|
|
31
|
+
loader = true,
|
|
32
|
+
negate = false,
|
|
33
|
+
children = null,
|
|
34
|
+
unauthorized = null,
|
|
35
|
+
permissions,
|
|
36
|
+
parameters = {},
|
|
37
|
+
queryOptions
|
|
38
|
+
}: AuthorizeProps): JSX.Element | null => {
|
|
39
|
+
const { authorized, isLoading } = useAuthorize(permissions, parameters, queryOptions);
|
|
40
|
+
|
|
41
|
+
if (isLoading) {
|
|
42
|
+
if (loader) return loader === true ? <BlockUi blocking /> : <>{loader}</>;
|
|
43
|
+
return null;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
// show children when authorized or negate is exclusively true
|
|
47
|
+
if ((authorized || negate) && !(authorized && negate)) {
|
|
48
|
+
return <>{children}</>;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
return <>{unauthorized}</>;
|
|
52
|
+
};
|
package/tsconfig.json
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
{
|
|
2
|
+
"extends": "./tsconfig.json",
|
|
3
|
+
"compilerOptions": {
|
|
4
|
+
"outDir": "../../dist/out-tsc",
|
|
5
|
+
"module": "commonjs",
|
|
6
|
+
"types": ["jest", "node", "@testing-library/jest-dom"],
|
|
7
|
+
"allowJs": true
|
|
8
|
+
},
|
|
9
|
+
"include": ["**/*.test.js", "**/*.test.ts", "**/*.test.tsx", "**/*.d.ts"]
|
|
10
|
+
}
|