import React from 'react';
import PropTypes from 'prop-types';
import { Navigate } from 'react-router-dom';
import { hasAllPermissions, hasPermission, hasAnyPermissions } from '../helpers/helpers.js';
import { useAuth } from '../hooks/useAuth';
import { CircularProgress } from '@mui/material';
import { AUTH_STATES } from '../constants.js';
/**
* @description Component that checks if the user has the required permissions
* Should set ONE of the following properties permission, any, OR all
* @param {object} props
* @param {string} [props.permission] A single required permission
* @param {array<string>} [props.any] Passes if ANY of the permissions are met
* @param {array<string>} [props.all] Passes if ALL of the permissions are met
* @param {boolean} [props.isRoute] If true, the component will render a Navigate component instead of null if the user does not have the required permissions
* @param {Route401Props} [props.noAuthOptions] If isRoute is true, this component will be rendered instead of the Navigate component if the user does not have the required permissions
* @param {boolean} [props.showLoggingIn] - If true, the component will render a CircularProgress component if the user is in the LOGGING_IN state
* @param {React.ReactNode} [props.children] - The children to render if the user has the required permissions
* @returns {React.ReactElement} - The children to render if the user has the required permissions OR null if the user does not have the required permissions
*
* @example
SampleUsage:
// Show the children if the user has the admin permission
<PermissionFilter permission="admin">
<div>Admin</div>
</PermissionFilter>
// Show the children if the user has the admin OR user permission
<PermissionFilter any={["admin", "user"]}>
<div>Admin or User</div>
</PermissionFilter>
// Show the children if the user has the admin AND user permission
<PermissionFilter all={["admin", "user"]}>
<div>Admin and User</div>
</PermissionFilter>
// Route example. If the user does not have the required permissions, they will be redirected to the root
<Route path="applications/addfd" element={
<PermissionFilter isRoute={true} permission={ACLS.CAN_ADD_APPLICATION}>
<ApplicationForm type={GRANT_APP_TYPES.FD} />
</PermissionFilter>
}
/>;
*/
const PermissionFilter = ({ permission, any, all, isRoute, showLoggingIn, children, ...props }) => {
// Get the user acls from the auth hook authState
const { authState } = useAuth();
// As this is under the authProvider context it will render on every authState change
// props.acl is a backdoor to allow storybook stories to pass in acls
const acl = authState?.user?.acl || props.acl;
// If the permissionFilter is a route, return a Navigate component to the root.
const returned = isRoute === true ? (<Navigate to="/" />) : null;
// There is not component to render OR there are no acls to check
if ((!children || !acl || !acl.length) && !showLoggingIn) {
return returned;
}
if(authState?.state === AUTH_STATES.LOGGING_IN && showLoggingIn) {
return <CircularProgress color="accent" />;
}
// Check if the user has the singular permission
if (hasPermission(permission, acl)) {
return children;
} else if (any) {
// Check if the user has any of the permissions
if (hasAnyPermissions(any, acl)) {
return children;
}
} else if (all) {
// Check if the user has all of the permissions
return hasAllPermissions(all, acl) ? children : returned;
}
// User has failed all the checks, return a null component (nothing rendered) or a Navigate component to the root.
return returned;
};
PermissionFilter.propTypes = {
// ACL is a list of strings
acl: PropTypes.array,
permission: PropTypes.string,
any: PropTypes.array,
all: PropTypes.array,
isRoute: PropTypes.bool,
showLoggingIn: PropTypes.bool,
children: PropTypes.node,
};
PermissionFilter.defaultProps = {
acl: [],
permission: null,
any: null,
all: null,
isRoute: false,
};
export default PermissionFilter;
Source