=Z&&(c=l,f=!1,e=new A(e));t:for(;++a0&&r(c)?e>1?F(c,e-1,r,n,o):u(o,c):n||(o[o.length]=c)}return o}function V(t){return!(!Q(t)||z(t))&&(H(t)||y(t)?_t:ut).test(U(t))}function D(t,e){var r=t.__data__;return W(e)?r["string"==typeof e?"string":"hash"]:r.map}function q(t,e){var r=p(t,e);return V(r)?r:void 0}function L(t){return xt(t)||J(t)||!!(Ot&&t&&t[Ot])}function W(t){var e=typeof t;return"string"==e||"number"==e||"symbol"==e||"boolean"==e?"__proto__"!==t:null===t}function z(t){return!!dt&&dt in t}function U(t){if(null!=t){try{return ht.call(t)}catch(t){}try{return t+""}catch(t){}}return""}function Y(t,e){return t===e||t!==t&&e!==e}function J(t){return G(t)&&vt.call(t,"callee")&&(!gt.call(t,"callee")||bt.call(t)==rt)}function B(t){return null!=t&&K(t.length)&&!H(t)}function G(t){return X(t)&&B(t)}function H(t){var e=Q(t)?bt.call(t):"";return e==nt||e==ot}function K(t){return"number"==typeof t&&t>-1&&t%1==0&&t<=et}function Q(t){var e=typeof t;return!!t&&("object"==e||"function"==e)}function X(t){return!!t&&"object"==typeof t}var Z=200,tt="__lodash_hash_undefined__",et=9007199254740991,rt="[object Arguments]",nt="[object Function]",ot="[object GeneratorFunction]",it=/[\\^$.*+?()[\]{}|]/g,ut=/^\[object .+?Constructor\]$/,at="object"==typeof e&&e&&e.Object===Object&&e,ct="object"==typeof self&&self&&self.Object===Object&&self,ft=at||ct||Function("return this")(),st=Array.prototype,lt=Function.prototype,pt=Object.prototype,yt=ft["__core-js_shared__"],dt=function(){var t=/[^.]+$/.exec(yt&&yt.keys&&yt.keys.IE_PROTO||"");return t?"Symbol(src)_1."+t:""}(),ht=lt.toString,vt=pt.hasOwnProperty,bt=pt.toString,_t=RegExp("^"+ht.call(vt).replace(it,"\\$&").replace(/hasOwnProperty|(function).*?(?=\\\()| for .+?(?=\\\])/g,"$1.*?")+"$"),mt=ft.Symbol,gt=pt.propertyIsEnumerable,wt=st.splice,Ot=mt?mt.isConcatSpreadable:void 0,jt=Math.max,Pt=q(ft,"Map"),Et=q(Object,"create");d.prototype.clear=h,d.prototype.delete=v,d.prototype.get=b,d.prototype.has=_,d.prototype.set=m,g.prototype.clear=w,g.prototype.delete=O,g.prototype.get=j,g.prototype.has=P,g.prototype.set=E,S.prototype.clear=x,S.prototype.delete=T,S.prototype.get=$,S.prototype.has=k,S.prototype.set=C,A.prototype.add=A.prototype.push=R,A.prototype.has=M;var St=function(t,e){return e=jt(void 0===e?t.length-1:e,0),function(){for(var n=arguments,o=-1,i=jt(n.length-e,0),u=Array(i);++o1?e-1:0),n=1;n2?r-2:0),o=2;o1?"Invalid arguments supplied to oneOf, expected an array, got "+arguments.length+" arguments. A common mistake is to write oneOf(x, y, z) instead of oneOf([x, y, z]).":"Invalid argument supplied to oneOf, expected an array."),n)}function _(t){function e(e,r,n,o,i){if("function"!=typeof t)return new p("Property `"+i+"` of component `"+n+"` has invalid PropType notation inside objectOf.");var a=e[r],f=P(a);if("object"!==f)return new p("Invalid "+o+" `"+i+"` of type `"+f+"` supplied to `"+n+"`, expected an object.");for(var s in a)if(c(a,s)){var l=t(a,s,n,o,i+"."+s,u);if(l instanceof Error)return l}return null}return y(e)}function m(t){function r(e,r,n,o,i){for(var a=0;a>",C={array:d("array"),bool:d("boolean"),func:d("function"),number:d("number"),object:d("object"),string:d("string"),symbol:d("symbol"),any:function(){return y(n)}(),arrayOf:h,element:function(){function e(e,r,n,o,i){var u=e[r];if(!t(u)){return new p("Invalid "+o+" `"+i+"` of type `"+P(u)+"` supplied to `"+n+"`, expected a single ReactElement.")}return null}return y(e)}(),elementType:function(){function t(t,e,r,n,i){var u=t[e];if(!o.isValidElementType(u)){return new p("Invalid "+n+" `"+i+"` of type `"+P(u)+"` supplied to `"+r+"`, expected a single ReactElement type.")}return null}return y(t)}(),instanceOf:v,node:function(){function t(t,e,r,n,o){return O(t[e])?null:new p("Invalid "+n+" `"+o+"` supplied to `"+r+"`, expected a ReactNode.")}return y(t)}(),objectOf:_,oneOf:b,oneOfType:m,shape:g,exact:w};return p.prototype=Error.prototype,C.checkPropTypes=a,C.resetWarningCache=a.resetWarningCache,C.PropTypes=C,C}}).call(e,r(0))},function(t,e,r){"use strict";function n(t){if(null===t||void 0===t)throw new TypeError("Object.assign cannot be called with null or undefined");return Object(t)}/*
18 | object-assign
19 | (c) Sindre Sorhus
20 | @license MIT
21 | */
22 | var o=Object.getOwnPropertySymbols,i=Object.prototype.hasOwnProperty,u=Object.prototype.propertyIsEnumerable;t.exports=function(){try{if(!Object.assign)return!1;var t=new String("abc");if(t[5]="de","5"===Object.getOwnPropertyNames(t)[0])return!1;for(var e={},r=0;r<10;r++)e["_"+String.fromCharCode(r)]=r;if("0123456789"!==Object.getOwnPropertyNames(e).map(function(t){return e[t]}).join(""))return!1;var n={};return"abcdefghijklmnopqrst".split("").forEach(function(t){n[t]=t}),"abcdefghijklmnopqrst"===Object.keys(Object.assign({},n)).join("")}catch(t){return!1}}()?Object.assign:function(t,e){for(var r,a,c=n(t),f=1;f=6.14.5"
7 | },
8 | "scripts": {
9 | "lint": "esw webpack.config.* src tools test --color",
10 | "lint:types": "esw 'types/*.{ts,tsx}' 'types/tests/*.{ts,tsx}' --ext ts,tsx --color --format codeframe --config ./types/.eslintrc",
11 | "lint:types:watch": "npm run --silent lint:types -- --watch",
12 | "lint:types:fix": "npm run --silent lint:types -- --fix",
13 | "clean-dist": "rm -rf ./lib && mkdir lib",
14 | "prebuild": "npm run clean-dist",
15 | "build": "node tools/build.js",
16 | "test": "mocha tools/testSetup.js \"./test/**/*.js\"",
17 | "test:coverage": "NODE_PATH=example babel-node ./node_modules/.bin/isparta cover _mocha -- --require ./tools/testSetup.js \"./test/**/*.js\" && open coverage/lcov-report/index.html",
18 | "test:coverage:ci": "NODE_PATH=example babel-node ./node_modules/.bin/isparta cover _mocha --report lcovonly -- --require ./tools/testSetup.js \"./test/**/*.js\"",
19 | "test:typescript": "cd ./types/tests && npm install-test --no-audit",
20 | "precommit": "npm run lint && npm run lint:types"
21 | },
22 | "author": "Krystian Kościelniak",
23 | "license": "MIT",
24 | "dependencies": {
25 | "gatsby-plugin-google-analytics": "2.3.2",
26 | "lodash.difference": "^4.5.0",
27 | "lodash.intersection": "^4.4.0"
28 | },
29 | "devDependencies": {
30 | "autoprefixer": "^9.8.4",
31 | "babel-cli": "^6.26.0",
32 | "babel-core": "^6.26.3",
33 | "babel-eslint": "^10.1.0",
34 | "babel-loader": "^8.1.0",
35 | "babel-plugin-react-display-name": "^2.0.0",
36 | "babel-plugin-transform-decorators-legacy": "^1.3.5",
37 | "babel-plugin-transform-react-constant-elements": "^6.23.0",
38 | "babel-plugin-transform-react-remove-prop-types": "^0.4.24",
39 | "babel-polyfill": "^6.26.0",
40 | "babel-preset-env": "^1.7.0",
41 | "babel-preset-es2015": "^6.24.1",
42 | "babel-preset-latest": "^6.24.1",
43 | "babel-preset-react": "^6.24.1",
44 | "babel-preset-react-hmre": "^1.1.1",
45 | "babel-preset-stage-1": "^6.24.1",
46 | "babel-register": "^6.26.0",
47 | "chai": "^4.2.0",
48 | "chai-as-promised": "^7.1.1",
49 | "chai-enzyme": "^1.0.0-beta.1",
50 | "chalk": "^4.1.0",
51 | "coveralls": "^3.1.0",
52 | "enzyme": "^3.11.0",
53 | "enzyme-adapter-react-16": "^1.15.2",
54 | "eslint": "^7.3.1",
55 | "eslint-config-brainhub": "^1.13.0",
56 | "eslint-plugin-typescript": "^0.14.0",
57 | "eslint-watch": "^7.0.0",
58 | "html-webpack-plugin": "^4.3.0",
59 | "husky": "^4.2.5",
60 | "isparta": "^4.1.1",
61 | "jsdom": "^16.2.2",
62 | "mocha": "^8.0.1",
63 | "prop-types": "^15.7.2",
64 | "react": "^16.13.1",
65 | "react-dom": "^16.13.1",
66 | "react-test-renderer": "^16.13.1",
67 | "sinon": "^9.0.2",
68 | "sinon-chai": "^3.5.0",
69 | "typescript": "^3.9.6",
70 | "typescript-eslint-parser": "^21.0.2",
71 | "uglifyjs-webpack-plugin": "^2.2.0",
72 | "webpack": "^4.43.0"
73 | },
74 | "peerDependencies": {
75 | "react": ">0.14.0 || >15.0.0",
76 | "react-dom": ">0.14.0 || >15.0.0"
77 | },
78 | "keywords": [
79 | "react",
80 | "react-permissions",
81 | "permissions",
82 | "permission-manager",
83 | "props",
84 | "callback",
85 | "users",
86 | "authentication",
87 | "optional-props",
88 | "authorization",
89 | "access-control"
90 | ],
91 | "repository": {
92 | "type": "git",
93 | "url": "https://github.com/brainhubeu/react-permissible"
94 | },
95 | "main": "./lib/react-permissible.js",
96 | "types": "./types/react-permissible.d.ts"
97 | }
98 |
--------------------------------------------------------------------------------
/renovate.json:
--------------------------------------------------------------------------------
1 | {
2 | "bumpVersion": "patch",
3 | "commitMessagePrefix": "[renovate] ",
4 | "groupName": "NPM dependencies",
5 | "labels": ["renovate"],
6 | "rangeStrategy": "bump",
7 | "branchPrefix": "fix/renovate/",
8 | "docker": {
9 | "major": {
10 | "enabled": true
11 | }
12 | },
13 | "packageRules": [
14 | {
15 | "groupName": "Docker dependencies",
16 | "managers": [
17 | "circleci"
18 | ]
19 | },
20 | {
21 | "groupName": "gatsby",
22 | "managerBranchPrefix": "gatsby",
23 | "packagePatterns": [
24 | "gatsby"
25 | ],
26 | "rangeStrategy": "pin"
27 | }
28 | ],
29 | "schedule": [
30 | "before 3am on the first day of the month"
31 | ]
32 | }
33 |
--------------------------------------------------------------------------------
/src/components/permissible.js:
--------------------------------------------------------------------------------
1 | import React, { Component } from 'react';
2 | import PropTypes from 'prop-types';
3 | import intersection from 'lodash.intersection';
4 | import difference from 'lodash.difference';
5 |
6 | export function Permissible(
7 | RestrictedComponent,
8 | userPermissions,
9 | requiredPermissions,
10 | callbackFunction,
11 | oneperm,
12 | ) {
13 | const permissionsStatus = oneperm
14 | ? intersection(userPermissions, requiredPermissions).length
15 | : difference(requiredPermissions, userPermissions).length === 0;
16 |
17 | class PermissibleHOC extends Component {
18 | static propTypes = {
19 | oneperm: PropTypes.bool,
20 | history: PropTypes.object, // eslint-disable-line react/forbid-prop-types
21 | };
22 |
23 | constructor(props) {
24 | super(props);
25 |
26 | if (!permissionsStatus) {
27 | this.runCallback();
28 | }
29 | }
30 |
31 | runCallback() {
32 | if (callbackFunction) {
33 | return callbackFunction({
34 | userPermissions,
35 | requiredPermissions,
36 | },
37 | this.props.history);
38 | }
39 | return;
40 | }
41 |
42 | render() {
43 | if (permissionsStatus) {
44 | return ;
45 | }
46 | return null;
47 | }
48 | }
49 | return PermissibleHOC;
50 | }
51 |
--------------------------------------------------------------------------------
/src/components/permissibleRender.js:
--------------------------------------------------------------------------------
1 | import { Component } from 'react';
2 | import PropTypes from 'prop-types';
3 | import intersection from 'lodash.intersection';
4 | import difference from 'lodash.difference';
5 |
6 | export class PermissibleRender extends Component {
7 | static propTypes = {
8 | oneperm: PropTypes.bool,
9 | userPermissions: PropTypes.arrayOf(PropTypes.string).isRequired,
10 | requiredPermissions: PropTypes.arrayOf(PropTypes.string).isRequired,
11 | children: PropTypes.element.isRequired,
12 | renderOtherwise: PropTypes.element,
13 | };
14 |
15 | checkPermissions() {
16 | const { userPermissions, requiredPermissions, oneperm } = this.props;
17 |
18 | if (oneperm) {
19 | return intersection(userPermissions, requiredPermissions).length;
20 | }
21 |
22 | return difference(requiredPermissions, userPermissions).length === 0;
23 | }
24 |
25 | render() {
26 | const { children, userPermissions, requiredPermissions, renderOtherwise } = this.props;
27 |
28 | if (!children || !userPermissions || !requiredPermissions) {
29 | return null;
30 | }
31 |
32 | if (this.checkPermissions()) {
33 | return children;
34 | } else if (renderOtherwise) {
35 | return renderOtherwise;
36 | }
37 | return null;
38 | }
39 | }
40 |
--------------------------------------------------------------------------------
/src/index.js:
--------------------------------------------------------------------------------
1 | import { Permissible } from './components/permissible';
2 | import { PermissibleRender } from './components/permissibleRender';
3 |
4 | export {
5 | Permissible,
6 | PermissibleRender,
7 | };
8 |
--------------------------------------------------------------------------------
/test/.eslintrc:
--------------------------------------------------------------------------------
1 | {
2 | env: {
3 | es6: true,
4 | browser: true,
5 | node: true,
6 | mocha: true,
7 | },
8 | }
9 |
--------------------------------------------------------------------------------
/test/accessible.component.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import PropTypes from 'prop-types';
3 |
4 | const Accessible = ({ permission, oneperm }) => (
5 |
6 |
7 | {oneperm ? 'One of' : 'Whole set of'} {permission}
is necessary to see this component.
8 |
9 |
10 | );
11 |
12 | Accessible.propTypes = {
13 | permission: PropTypes.string,
14 | oneperm: PropTypes.bool,
15 | };
16 |
17 | export default Accessible;
18 |
--------------------------------------------------------------------------------
/test/permissible.test.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { shallow } from 'enzyme';
3 | import chai from 'chai';
4 | import chaiEnzyme from 'chai-enzyme';
5 | import { JSDOM } from 'jsdom';
6 |
7 | import { Permissible } from '../src/components/permissible';
8 |
9 | import AccessedComponent from './accessible.component';
10 |
11 | const { document } = (new JSDOM('')).window;
12 | global.document = document;
13 | global.window = document.defaultView;
14 |
15 | chai.use(chaiEnzyme());
16 | chai.should();
17 |
18 | describe('Permissible HOC', () => {
19 | it('doesn\'t run a callback function if the permissions are right', done => {
20 | let err = null;
21 |
22 | const AccessibleRoute = Permissible(
23 | () => ,
24 | ['MATCHING_PERMISSIONS'],
25 | ['MATCHING_PERMISSIONS'],
26 | () => {
27 | err = new Error('Callback function called.');
28 | },
29 | );
30 |
31 | shallow(
32 | ,
33 | );
34 |
35 | done(err);
36 | });
37 |
38 | it('doesn\'t run a callback function if `requiredPermissions` and `userPermissions` are both empty', done => {
39 | let err = null;
40 |
41 | const AccessibleRoute = Permissible(
42 | () => ,
43 | [],
44 | [],
45 | () => {
46 | err = new Error('Callback function called.');
47 | },
48 | );
49 |
50 | shallow(
51 | ,
52 | );
53 |
54 | done(err);
55 | });
56 |
57 | it('doesn\'t run a callback function if only `requiredPermissions` are empty', done => {
58 | let err = null;
59 |
60 | const AccessibleRoute = Permissible(
61 | () => ,
62 | ['SOME_PERMISSION'],
63 | [],
64 | () => {
65 | err = new Error('Callback function called.');
66 | },
67 | );
68 |
69 | shallow(
70 | ,
71 | );
72 |
73 | done(err);
74 | });
75 |
76 | it('runs a callback function if the permissions don\'t match', done => {
77 | const AccessibleRoute = Permissible(
78 | () => ,
79 | ['MATCHING_PERMISSIONS'],
80 | ['UNMATCHING_PERMISSIONS'],
81 | () => {
82 | done();
83 | },
84 | );
85 |
86 | shallow(
87 | ,
88 | );
89 | });
90 |
91 | it('doesn\'t run a callback function if the user has one of necessary permissions and `oneperm` is `true`', done => {
92 | let err = null;
93 |
94 | const AccessibleRoute = Permissible(
95 | () => ,
96 | ['REQUIRED_PERMISSION'],
97 | ['REQUIRED_PERMISSION', 'ANOTHER_PERMISSION'],
98 | () => {
99 | err = new Error('Callback function called.');
100 | },
101 | true,
102 | );
103 |
104 | shallow(
105 | ,
106 | );
107 |
108 | done(err);
109 | });
110 |
111 | it('runs a callback function if the user has one of necessary permissions and `oneperm` is `false`', done => {
112 | const AccessibleRoute = Permissible(
113 | () => ,
114 | ['REQUIRED_PERMISSION'],
115 | ['REQUIRED_PERMISSION', 'ANOTHER_PERMISSION'],
116 | () => {
117 | done();
118 | },
119 | false,
120 | );
121 |
122 | shallow(
123 | ,
124 | );
125 | });
126 |
127 | it('doesn\'t run a callback function if it is not defined', done => {
128 | const AccessibleRoute = Permissible(
129 | () => ,
130 | ['REQUIRED_PERMISSION'],
131 | ['REQUIRED_PERMISSION', 'ANOTHER_PERMISSION'],
132 | null,
133 | false,
134 | );
135 |
136 | shallow(
137 | ,
138 | );
139 |
140 | done();
141 | });
142 | });
143 |
--------------------------------------------------------------------------------
/test/permissibleRender.test.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { mount } from 'enzyme';
3 | import chai from 'chai';
4 | import chaiEnzyme from 'chai-enzyme';
5 | import { JSDOM } from 'jsdom';
6 |
7 | import { PermissibleRender } from '../src/components/permissibleRender';
8 |
9 | const should = chai.should();
10 |
11 | const { document } = (new JSDOM('')).window;
12 | global.document = document;
13 | global.window = document.defaultView;
14 |
15 | chai.use(chaiEnzyme());
16 | chai.should();
17 |
18 | describe('PermissibleRender', () => {
19 | const ChildComponent = () => (
20 |
21 | Child component
22 |
23 | );
24 |
25 | const NotAllowedComponent = () => (
26 |
27 | Not allowed component
28 |
29 | );
30 |
31 | it('doesn\'t render a if `userPermissions` prop is not set', () => {
32 | const props = {
33 | requiredPermissions: [],
34 | };
35 |
36 | const mountedComponent = mount(
37 |
38 |
39 | ,
40 | );
41 |
42 | const searchedElement = mountedComponent.find('ChildComponent');
43 | searchedElement.length.should.be.equal(0);
44 | });
45 |
46 | it('doesn\'t render a if `requiredPermissions` prop is not set', () => {
47 | const props = {
48 | userPermissions: [],
49 | };
50 |
51 | const mountedComponent = mount(
52 |
53 |
54 | ,
55 | );
56 |
57 | const searchedElement = mountedComponent.find('ChildComponent');
58 | searchedElement.length.should.be.equal(0);
59 | });
60 |
61 | it('renders nothing if `children` prop is not set', () => {
62 | const props = {
63 | userPermissions: [],
64 | };
65 |
66 | const mountedComponent = mount(
67 | ,
68 | );
69 |
70 | should.not.exist(mountedComponent.find('PermissibleRender').html());
71 | });
72 |
73 | it('renders a if user permissions and required permissions are both empty', () => {
74 | const props = {
75 | userPermissions: [],
76 | requiredPermissions: [],
77 | };
78 |
79 | const mountedComponent = mount(
80 |
81 |
82 | ,
83 | );
84 |
85 | const searchedElement = mountedComponent.find('ChildComponent');
86 | searchedElement.length.should.be.equal(1);
87 | });
88 |
89 | it('renders a if only required permissions are empty', () => {
90 | const props = {
91 | userPermissions: ['SOME_PERMISSION'],
92 | requiredPermissions: [],
93 | };
94 |
95 | const mountedComponent = mount(
96 |
97 |
98 | ,
99 | );
100 |
101 | const searchedElement = mountedComponent.find('ChildComponent');
102 | searchedElement.length.should.be.equal(1);
103 | });
104 |
105 | it('doesn\'t render a if there is a permission mismatch', () => {
106 | const props = {
107 | userPermissions: ['REQUIRED_PERMISSION'],
108 | requiredPermissions: ['ANOTHER_PERMISSION'],
109 | };
110 |
111 | const mountedComponent = mount(
112 |
113 |
114 | ,
115 | );
116 |
117 | const searchedElement = mountedComponent.find('ChildComponent');
118 | searchedElement.length.should.be.equal(0);
119 | });
120 |
121 | it('renders a if the user has required permission', () => {
122 | const props = {
123 | userPermissions: ['REQUIRED_PERMISSION'],
124 | requiredPermissions: ['REQUIRED_PERMISSION'],
125 | };
126 |
127 | const mountedComponent = mount(
128 |
129 |
130 | ,
131 | );
132 |
133 | const searchedElement = mountedComponent.find('ChildComponent');
134 | searchedElement.length.should.be.greaterThan(0);
135 | });
136 |
137 | it('renders a if the user doesn\'t have required permissions and renderOtherwise is given', () => {
138 | const props = {
139 | userPermissions: ['REQUIRED_PERMISSION'],
140 | requiredPermissions: ['NOT_REQUIRED_PERMISSION'],
141 | renderOtherwise: ,
142 | };
143 |
144 | const mountedComponent = mount(
145 |
146 |
147 | ,
148 | );
149 |
150 | const searchedElement = mountedComponent.find('NotAllowedComponent');
151 | searchedElement.length.should.be.greaterThan(0);
152 | });
153 |
154 | it('renders a if the user has one of necessary conditions when `oneperm` prop is defined', () => {
155 | const props = {
156 | userPermissions: ['ANOTHER_PERMISSION'],
157 | requiredPermissions: ['REQUIRED_PERMISSION', 'ANOTHER_PERMISSION'],
158 | oneperm: true,
159 | };
160 |
161 | const mountedComponent = mount(
162 |
163 |
164 | ,
165 | );
166 |
167 | const searchedElement = mountedComponent.find('ChildComponent');
168 | searchedElement.length.should.be.greaterThan(0);
169 | });
170 |
171 | it('doesn\'t render a if the user doesn\'t have all of necessary permissions when `oneperm` prop is explicitly set to false', () => {
172 | const props = {
173 | userPermissions: ['REQUIRED_PERMISSION'],
174 | requiredPermissions: ['REQUIRED_PERMISSION', 'ANOTHER_PERMISSION'],
175 | oneperm: false,
176 | };
177 |
178 | const mountedComponent = mount(
179 |
180 |
181 | ,
182 | );
183 |
184 | const searchedElement = mountedComponent.find('ChildComponent');
185 | searchedElement.length.should.be.equal(0);
186 | });
187 | });
188 |
--------------------------------------------------------------------------------
/tools/build.js:
--------------------------------------------------------------------------------
1 | // More info on Webpack's Node API here: https://webpack.github.io/docs/node.js-api.html
2 | // Allowing console calls below since this is a build file.
3 | /* eslint-disable no-console */
4 | const chalk = require('chalk');
5 | const webpack = require('webpack');
6 |
7 | const config = require('../webpack.config.prod');
8 |
9 | const chalkConfig = {
10 | chalkError: chalk.red,
11 | chalkSuccess: chalk.green,
12 | chalkWarning: chalk.yellow,
13 | chalkProcessing: chalk.blue,
14 | };
15 |
16 | const { chalkError, chalkSuccess, chalkWarning, chalkProcessing } = chalkConfig;
17 |
18 | process.env.NODE_ENV = 'production'; // this assures React is built in prod mode and that the Babel dev config doesn't apply.
19 |
20 | console.log(chalkProcessing('Generating minified bundle. This will take a moment...'));
21 |
22 | webpack(config).run((error, stats) => {
23 | if (error) { // so a fatal error occurred. Stop here.
24 | console.log(chalkError(error));
25 | return 1;
26 | }
27 |
28 | const jsonStats = stats.toJson();
29 |
30 | if (jsonStats.hasErrors) {
31 | return jsonStats.errors.map(error => console.log(chalkError(error)));
32 | }
33 |
34 | if (jsonStats.hasWarnings) {
35 | console.log(chalkWarning('Webpack generated the following warnings: '));
36 | jsonStats.warnings.map(warning => console.log(chalkWarning(warning)));
37 | }
38 |
39 | console.log(`Webpack stats: ${stats}`);
40 |
41 | // if we got this far, the build succeeded.
42 | console.log(chalkSuccess('Your app is compiled in production mode in /dist. It\'s ready to roll!'));
43 |
44 | return 0;
45 | });
46 |
--------------------------------------------------------------------------------
/tools/testSetup.js:
--------------------------------------------------------------------------------
1 | process.env.NODE_ENV = 'test';
2 |
3 | // Disable webpack-specific features for tests since
4 | // Mocha doesn't know what to do with them.
5 | /* istanbul ignore next */
6 | ['.css', '.scss', '.png', '.jpg'].forEach(ext => {
7 | require.extensions[ext] = () => null;
8 | });
9 |
10 | // add required globals
11 | /* eslint-disable no-empty-function */
12 | /* istanbul ignore next */
13 | global.logger = function() {};
14 | /* istanbul ignore next */
15 | global.logger.info = function() {};
16 | /* istanbul ignore next */
17 | global.logger.apiSuccess = function() {};
18 | /* istanbul ignore next */
19 | global.logger.apiError = function() {};
20 | /* istanbul ignore next */
21 | global.logger.warn = function() {};
22 | /* eslint-enable */
23 |
24 | // Register babel so that it will transpile ES6 to ES5
25 | // before our tests run.
26 | require('babel-register')();
27 | require('babel-polyfill');
28 |
29 | const chai = require('chai');
30 | const sinonChai = require('sinon-chai');
31 | const chaiAsPromised = require('chai-as-promised');
32 | const enzyme = require('enzyme');
33 | const Adapter = require('enzyme-adapter-react-16');
34 |
35 | enzyme.configure({ adapter: new Adapter() });
36 |
37 | chai.use(sinonChai);
38 | chai.use(chaiAsPromised);
39 |
--------------------------------------------------------------------------------
/types/.eslintrc:
--------------------------------------------------------------------------------
1 | {
2 | "parser": "typescript-eslint-parser",
3 | "plugins": ["eslint-plugin-typescript"],
4 | "extends": "../.eslintrc",
5 | "rules": {
6 | "comma-dangle":"off",
7 | "no-unused-vars": "off",
8 | "import/no-namespace": "off",
9 | "typescript/no-unused-vars": "error"
10 | }
11 | }
12 |
--------------------------------------------------------------------------------
/types/react-permissible.d.ts:
--------------------------------------------------------------------------------
1 | import { Component, ComponentState, ComponentType, ReactNode, ReactNodeArray, StaticLifecycle, ValidationMap, } from 'react';
2 |
3 | declare module '@brainhubeu/react-permissible' {
4 | type Permissions = string[];
5 |
6 | type Children = ReactNode | ReactNodeArray;
7 |
8 | export interface UserAndRequiredPermissions {
9 | userPermissions: Permissions
10 | requiredPermissions: Permissions
11 | }
12 |
13 | export interface PermissibleRenderProps extends UserAndRequiredPermissions {
14 | oneperm?: boolean
15 | children: Children
16 | renderOtherwise?: Children
17 | }
18 |
19 | export class PermissibleRender extends Component {
20 | checkPermissions(): boolean
21 | }
22 |
23 | export function Permissible(
24 | RestrictedComponent: ComponentType,
25 | userPermissions: Permissions,
26 | requiredPermissions: Permissions,
27 | callbackFunction?: ({ userPermissions, requiredPermissions, }: UserAndRequiredPermissions) => void,
28 | oneperm?: boolean,
29 | ): PermissibleHOC
30 |
31 | interface PermissibleHOC extends StaticLifecycle {
32 | new (props: Props, context?: any): Component & TE
33 | propTypes?: ValidationMap
34 | contextTypes?: ValidationMap
35 | childContextTypes?: ValidationMap
36 | defaultProps?: Partial
37 | displayName?: string
38 | }
39 | }
40 |
--------------------------------------------------------------------------------
/types/tests/.gitignore:
--------------------------------------------------------------------------------
1 | brainhubeu-react-permissible-*.tgz
2 | node_modules/
3 |
--------------------------------------------------------------------------------
/types/tests/.npmrc:
--------------------------------------------------------------------------------
1 | update-notifier=false
2 | audit=false
3 | prefer-offline=true
4 |
--------------------------------------------------------------------------------
/types/tests/Permissible.test.tsx:
--------------------------------------------------------------------------------
1 | import * as React from 'react';
2 | import { render } from 'react-dom';
3 | // eslint-disable-next-line import/no-unresolved, import/no-extraneous-dependencies, /* this is what we're testing */
4 | import { Permissible, UserAndRequiredPermissions } from '@brainhubeu/react-permissible';
5 |
6 | function callbackFunction({ userPermissions, requiredPermissions }: UserAndRequiredPermissions) {
7 | // eslint-disable-next-line no-console
8 | console.info(`
9 | react-permissible: Access Denied
10 | userPermissions: ${userPermissions}
11 | requiredPermissions: ${requiredPermissions}
12 | `);
13 | }
14 |
15 | const AccessGranted = ({ message }: { message: string, }) => <>AccessGranted {message}>;
16 |
17 | const RestrictedComponentWithCallback = Permissible(
18 | AccessGranted,
19 | ['ACCESS_DASHBOARD'], // userPermissions
20 | ['ACCESS_ADMIN'], // requiredPermissions
21 | callbackFunction,
22 | );
23 |
24 | render(, document.createElement('div'));
25 |
--------------------------------------------------------------------------------
/types/tests/PermissibleRender.test.tsx:
--------------------------------------------------------------------------------
1 | import * as React from 'react';
2 | import { render } from 'react-dom';
3 | // eslint-disable-next-line import/no-unresolved, import/no-extraneous-dependencies, /* this is what we're testing */
4 | import { PermissibleRender, PermissibleRenderProps } from '@brainhubeu/react-permissible';
5 |
6 | const VIEW_PERMISSION = 'VIEW';
7 |
8 | const permissibleRenderTestProps: PermissibleRenderProps = {
9 | oneperm: false,
10 | children: 'restricted content',
11 | userPermissions: [VIEW_PERMISSION],
12 | requiredPermissions: [VIEW_PERMISSION],
13 | renderOtherwise: 'ACCESS DENIED',
14 | };
15 |
16 | render(, document.createElement('div'));
17 |
--------------------------------------------------------------------------------
/types/tests/package-lock.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "react-permissible-typescript-tests",
3 | "version": "1.0.27",
4 | "lockfileVersion": 1,
5 | "requires": true,
6 | "dependencies": {
7 | "@types/prop-types": {
8 | "version": "15.7.3",
9 | "resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.3.tgz",
10 | "integrity": "sha512-KfRL3PuHmqQLOG+2tGpRO26Ctg+Cq1E01D2DMriKEATHgWLfeNDmq9e29Q9WIky0dQ3NPkd1mzYH8Lm936Z9qw=="
11 | },
12 | "@types/react": {
13 | "version": "16.9.41",
14 | "resolved": "https://registry.npmjs.org/@types/react/-/react-16.9.41.tgz",
15 | "integrity": "sha512-6cFei7F7L4wwuM+IND/Q2cV1koQUvJ8iSV+Gwn0c3kvABZ691g7sp3hfEQHOUBJtccl1gPi+EyNjMIl9nGA0ug==",
16 | "requires": {
17 | "@types/prop-types": "*",
18 | "csstype": "^2.2.0"
19 | }
20 | },
21 | "@types/react-dom": {
22 | "version": "16.9.8",
23 | "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-16.9.8.tgz",
24 | "integrity": "sha512-ykkPQ+5nFknnlU6lDd947WbQ6TE3NNzbQAkInC2EKY1qeYdTKp7onFusmYZb+ityzx2YviqT6BXSu+LyWWJwcA==",
25 | "requires": {
26 | "@types/react": "*"
27 | }
28 | },
29 | "csstype": {
30 | "version": "2.6.10",
31 | "resolved": "https://registry.npmjs.org/csstype/-/csstype-2.6.10.tgz",
32 | "integrity": "sha512-D34BqZU4cIlMCY93rZHbrq9pjTAQJ3U8S8rfBqjwHxkGPThWFjzZDQpgMJY0QViLxth6ZKYiwFBo14RdN44U/w=="
33 | },
34 | "js-tokens": {
35 | "version": "4.0.0",
36 | "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz",
37 | "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ=="
38 | },
39 | "loose-envify": {
40 | "version": "1.4.0",
41 | "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz",
42 | "integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==",
43 | "requires": {
44 | "js-tokens": "^3.0.0 || ^4.0.0"
45 | }
46 | },
47 | "object-assign": {
48 | "version": "4.1.1",
49 | "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz",
50 | "integrity": "sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM="
51 | },
52 | "prop-types": {
53 | "version": "15.7.2",
54 | "resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.7.2.tgz",
55 | "integrity": "sha512-8QQikdH7//R2vurIJSutZ1smHYTcLpRWEOlHnzcWHmBYrOGUysKwSsrC89BCiFj3CbrfJ/nXFdJepOVrY1GCHQ==",
56 | "requires": {
57 | "loose-envify": "^1.4.0",
58 | "object-assign": "^4.1.1",
59 | "react-is": "^16.8.1"
60 | }
61 | },
62 | "react": {
63 | "version": "16.13.1",
64 | "resolved": "https://registry.npmjs.org/react/-/react-16.13.1.tgz",
65 | "integrity": "sha512-YMZQQq32xHLX0bz5Mnibv1/LHb3Sqzngu7xstSM+vrkE5Kzr9xE0yMByK5kMoTK30YVJE61WfbxIFFvfeDKT1w==",
66 | "requires": {
67 | "loose-envify": "^1.1.0",
68 | "object-assign": "^4.1.1",
69 | "prop-types": "^15.6.2"
70 | }
71 | },
72 | "react-dom": {
73 | "version": "16.13.1",
74 | "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-16.13.1.tgz",
75 | "integrity": "sha512-81PIMmVLnCNLO/fFOQxdQkvEq/+Hfpv24XNJfpyZhTRfO0QcmQIF/PgCa1zCOj2w1hrn12MFLyaJ/G0+Mxtfag==",
76 | "requires": {
77 | "loose-envify": "^1.1.0",
78 | "object-assign": "^4.1.1",
79 | "prop-types": "^15.6.2",
80 | "scheduler": "^0.19.1"
81 | }
82 | },
83 | "react-is": {
84 | "version": "16.13.1",
85 | "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz",
86 | "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ=="
87 | },
88 | "scheduler": {
89 | "version": "0.19.1",
90 | "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.19.1.tgz",
91 | "integrity": "sha512-n/zwRWRYSUj0/3g/otKDRPMh6qv2SYMWNq85IEa8iZyAv8od9zDYpGSnpBEjNgcMNq6Scbu5KfIPxNF72R/2EA==",
92 | "requires": {
93 | "loose-envify": "^1.1.0",
94 | "object-assign": "^4.1.1"
95 | }
96 | },
97 | "typescript": {
98 | "version": "3.9.6",
99 | "resolved": "https://registry.npmjs.org/typescript/-/typescript-3.9.6.tgz",
100 | "integrity": "sha512-Pspx3oKAPJtjNwE92YS05HQoY7z2SFyOpHo9MqJor3BXAGNaPUs83CuVp9VISFkSjyRfiTpmKuAYGJB7S7hOxw=="
101 | }
102 | }
103 | }
104 |
--------------------------------------------------------------------------------
/types/tests/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "react-permissible-typescript-tests",
3 | "version": "1.0.27",
4 | "description": "Test publishing the typings of react-permissible package",
5 | "scripts": {
6 | "clean:packed-package": "rm ./brainhubeu-react-permissible-*.tgz",
7 | "clean:node-modules": "rm -rf ./node_modules/",
8 | "preinstall": "npm run clean:packed-package && npm run clean:node-modules",
9 | "pack-parent-package-get-filename": "npm pack ../.. | grep \\.tgz",
10 | "postinstall": "npm install --no-save --no-audit `npm run --silent pack-parent-package-get-filename`",
11 | "test": "tsc --project .",
12 | "posttest": "echo 'TypeScript typings successfuly compiled!'"
13 | },
14 | "repository": {
15 | "type": "git",
16 | "url": "https://github.com/brainhubeu/react-permissible"
17 | },
18 | "author": "Nick Ribal (https://about.me/nickribal)",
19 | "license": "MIT",
20 | "dependencies": {
21 | "@types/react": "^16.9.41",
22 | "@types/react-dom": "^16.9.8",
23 | "react": "^16.13.1",
24 | "react-dom": "^16.13.1",
25 | "typescript": "^3.9.6"
26 | }
27 | }
28 |
--------------------------------------------------------------------------------
/types/tests/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "target": "es6",
4 | "module": "es2015",
5 | "noEmit": true,
6 | "jsx": "react",
7 | "strict": true,
8 | "noUnusedLocals": true,
9 | "noUnusedParameters": true,
10 | "noImplicitReturns": true,
11 | "noFallthroughCasesInSwitch": true,
12 | "moduleResolution": "node"
13 | }
14 | }
15 |
--------------------------------------------------------------------------------
/webpack.config.prod.js:
--------------------------------------------------------------------------------
1 | const path = require('path');
2 |
3 | const UglifyJsPlugin = require('uglifyjs-webpack-plugin');
4 |
5 | module.exports = {
6 | externals: [
7 | {
8 | 'react-dom': {
9 | root: 'ReactDOM',
10 | commonjs2: 'react-dom',
11 | commonjs: 'react-dom',
12 | amd: 'react-dom',
13 | },
14 | },
15 | {
16 | react: {
17 | root: 'React',
18 | commonjs2: 'react',
19 | commonjs: 'react',
20 | amd: 'react',
21 | },
22 | },
23 | ],
24 | resolve: {
25 | extensions: ['.js', '.jsx', '.json'],
26 | modules: [
27 | 'node_modules',
28 | path.join(__dirname, 'src'),
29 | ],
30 | },
31 | devtool: 'source-map',
32 | entry: './src/index.js',
33 | output: {
34 | filename: 'react-permissible.js',
35 | library: 'react-permissible',
36 | libraryTarget: 'umd',
37 | path: path.resolve(__dirname, 'lib'),
38 | umdNamedDefine: true,
39 | },
40 | optimization: {
41 | minimizer: [
42 | new UglifyJsPlugin({
43 | sourceMap: false,
44 | }),
45 | ],
46 | },
47 | module: {
48 | rules: [
49 | {
50 | test: /\.jsx?$/,
51 | exclude: /node_modules/,
52 | use: ['babel-loader'],
53 | },
54 | ],
55 | },
56 | };
57 |
--------------------------------------------------------------------------------