diff --git a/README.md b/README.md index 727f169..6d0637d 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,5 @@ # React Role + [![npm](https://img.shields.io/npm/v/@permify/react-role?style=flat-square)](https://www.npmjs.com/package/@permify/react-role) [![Twitter Follow](https://img.shields.io/twitter/follow/GetPermify?style=social)](https://twitter.com/GetPermify) [![Discord](https://img.shields.io/discord/950799928047833088.svg?label=&logo=discord&logoColor=ffffff&color=7389D8&labelColor=6A7EC2)](https://discord.gg/MJbUjwskdH) @@ -6,11 +7,13 @@ React Role is lightweight role based access management solution which provides components, hooks, and helper methods for controlling access checks and user permissions throughout your entire React application. # Installation -Use npm to install: + +Use npm to install: ```shell npm install @permify/react-role ``` + Use yarn to install: ```shell @@ -37,11 +40,12 @@ const App = () => { export default App; ``` + ### User Identification -In order to check user roles or permissions, you should set logged in user with ```setUser``` function. Our advice is call this method in your login functions promise. +In order to check user roles or permissions, you should set logged in user with `setUser` function. Our advice is call this method in your login functions promise. -Set the user using the ```usePermify``` hook: +Set the user using the `usePermify` hook: ```javascript @@ -62,11 +66,11 @@ const login = async (e) => { // // Continue authentication flow - // + // }; ``` -Or using ```PermifyContext```: +Or using `PermifyContext`: ```javascript import React from "react"; @@ -79,13 +83,13 @@ const AuthComponent = () => { setUser({ id: "2", - roles: ["admin", "manager"], + roles: ["admin", "manager"], permissions: ["post-create", "user-delete", "content-show"] }) // // Continue authentication flow - // + // }; }; @@ -96,16 +100,16 @@ const AuthComponent = () => { {/* form layer */} )} - ; + ; ) }; export default AuthComponent; ``` -## `HasAccess` +## `HasAccess` -HasAccess is a wrapper component that you can wrap around components or UI Layers that should only be accessible to users have authorization. +HasAccess is a wrapper component that you can wrap around components or UI Layers that should only be accessible to users have authorization. You can check roles and permissions of the user with giving them as props. @@ -119,8 +123,8 @@ const AnyComponent = () => { .. You are not authorized to access!

} isLoading={} > @@ -137,7 +141,7 @@ export default App; ## `isAuthorized(roleNames, permissionNames)` -isAuthorized is a helper function that returns a Promise which resolves with true if the user is authorized for action with the given parameters, if not it resolves with false. +isAuthorized is a helper function that returns a Promise which resolves with true if the user is authorized for action with the given parameters, if not it resolves with false. You should call it inside a conditional logic structure; for ex. if check for fetching protected information. @@ -163,7 +167,7 @@ const AnyComponent = () => { },[]); return ( - <> + <> {isLoading && Loading...} {dataFetched && //render protected component, UI Layers etc. @@ -175,8 +179,7 @@ const AnyComponent = () => { export default AnyComponent; ``` -Stargazers ------------ +## Stargazers [![Stargazers repo roster for @Permify/react-role](https://reporoster.com/stars/Permify/react-role)](https://github.com/Permify/react-role/stargazers) @@ -192,4 +195,5 @@ Stargazers

# License + Licensed under the Apache License, Version 2.0: http://www.apache.org/licenses/LICENSE-2.0 diff --git a/src/PermifyProvider.tsx b/src/PermifyProvider.tsx index fc34a9c..b71fdad 100644 --- a/src/PermifyProvider.tsx +++ b/src/PermifyProvider.tsx @@ -1,9 +1,9 @@ -import React, { PropsWithChildren, useCallback, useState } from "react"; +import React, { ReactNode, useCallback, useState } from "react"; export interface UserPayload { - id: string, - roles: string[], - permissions: string[] + id: string; + roles: string[]; + permissions: string[]; } //context @@ -11,58 +11,82 @@ import PermifyContext from "./PermifyContext"; const LOCAL_STORAGE_KEY_USER = "__permifyUser"; -const PermifyProvider = ({ - children -}: PropsWithChildren) => { - const [isLoading, setIsLoading] = useState(false); - - const updateUser = (newUser: UserPayload) => { - localStorage.setItem(LOCAL_STORAGE_KEY_USER, JSON.stringify(newUser)); - }; - - const isAuthorized = useCallback(async (roleNames: string[], permissionNames?:string[]): Promise => { - - let hasAuthorization: boolean = false; - const storedUser = JSON.parse(localStorage.getItem(LOCAL_STORAGE_KEY_USER)); - - setIsLoading(true) - if(storedUser) { - hasAuthorization = await CheckUserHasRolesOrPermissions(storedUser, roleNames, permissionNames) - } - setIsLoading(false) - - return hasAuthorization - }, []); - - const CheckUserHasRolesOrPermissions = async (storedUser: UserPayload, roleNames?: string[], permissionNames?:string[]): Promise => { - let hasRoles: boolean = false; - let hasPermissions: boolean = false; - - // role checking - if(storedUser.roles && roleNames && storedUser.roles.length > 0) { - const userRoles = storedUser.roles; - - const intersection = userRoles.filter(role => roleNames.includes(role)); - hasRoles = intersection.length > 0 - } - - // permission checking - if(storedUser.permissions && permissionNames && storedUser.permissions.length > 0) { - const userPermissions = storedUser.permissions; - - const intersection = userPermissions.filter(permission => permissionNames.includes(permission)); - hasPermissions = intersection.length > 0 - } - - return hasRoles || hasPermissions - }; - - return { + const [isLoading, setIsLoading] = useState(false); + + const updateUser = (newUser: UserPayload) => { + localStorage.setItem(LOCAL_STORAGE_KEY_USER, JSON.stringify(newUser)); + }; + + const isAuthorized = useCallback( + async ( + roleNames: string[], + permissionNames?: string[] + ): Promise => { + let hasAuthorization: boolean = false; + const storedUser = JSON.parse( + localStorage.getItem(LOCAL_STORAGE_KEY_USER) + ); + + setIsLoading(true); + if (storedUser) { + hasAuthorization = await CheckUserHasRolesOrPermissions( + storedUser, + roleNames, + permissionNames + ); + } + setIsLoading(false); + + return hasAuthorization; + }, + [] + ); + + const CheckUserHasRolesOrPermissions = async ( + storedUser: UserPayload, + roleNames?: string[], + permissionNames?: string[] + ): Promise => { + let hasRoles: boolean = false; + let hasPermissions: boolean = false; + + // role checking + if (storedUser.roles && roleNames && storedUser.roles.length > 0) { + const userRoles = storedUser.roles; + + const intersection = userRoles.filter((role) => roleNames.includes(role)); + hasRoles = intersection.length > 0; + } + + // permission checking + if ( + storedUser.permissions && + permissionNames && + storedUser.permissions.length > 0 + ) { + const userPermissions = storedUser.permissions; + + const intersection = userPermissions.filter((permission) => + permissionNames.includes(permission) + ); + hasPermissions = intersection.length > 0; + } + + return hasRoles || hasPermissions; + }; + + return ( + {children} - ; + isLoading, + }} + > + {children} + + ); }; -export default PermifyProvider; \ No newline at end of file +export default PermifyProvider; diff --git a/src/components/HasAccess.tsx b/src/components/HasAccess.tsx index 5370c9b..07c4e9b 100644 --- a/src/components/HasAccess.tsx +++ b/src/components/HasAccess.tsx @@ -1,19 +1,19 @@ -import * as React from 'react'; -import { useEffect, useState, useContext, PropsWithChildren } from 'react' +import * as React from "react"; +import { useEffect, useState, useContext, PropsWithChildren } from "react"; export interface UserPayload { - id: string, - roles: string[], - permissions: string[] + id: string; + roles: string[]; + permissions: string[]; } const LOCAL_STORAGE_KEY_USER = "__permifyUser"; export interface HasAccessProps { - roles?: string[], - permissions?: string[] - isLoading?: React.ReactElement, - renderAuthFailed?: React.ReactElement + roles?: string[]; + permissions?: string[]; + isLoading?: React.ReactElement; + renderAuthFailed?: React.ReactElement; } const HasAccess = ({ @@ -21,53 +21,59 @@ const HasAccess = ({ permissions, isLoading, renderAuthFailed, - children + children, }: PropsWithChildren) => { - const [hasAccess, setHasAccess] = useState(false) - const [checking, setChecking] = useState(false) + const [hasAccess, setHasAccess] = useState(false); + const [checking, setChecking] = useState(false); useEffect(() => { const storedUser = JSON.parse(localStorage.getItem(LOCAL_STORAGE_KEY_USER)); - if(!storedUser) { - console.log('No user provided to Permify! You should set user to perfom access check') + if (!storedUser) { + console.log( + "No user provided to Permify! You should set user to perfom access check" + ); return; - } + } - setChecking(true) + setChecking(true); // role check if (roles && storedUser.roles && storedUser.roles.length > 0) { - const intersection = storedUser.roles.filter((role: string) => roles.includes(role)); - if (intersection.length > 0) setHasAccess(true) + const intersection = storedUser.roles.filter((role: string) => + roles.includes(role) + ); + if (intersection.length > 0) setHasAccess(true); } // permission check - if (permissions && storedUser.permissions && storedUser.permissions.length > 0) { - const intersection = storedUser.permissions.filter((permission: string) => permissions.includes(permission)); - if (intersection.length > 0) setHasAccess(true) + if ( + permissions && + storedUser.permissions && + storedUser.permissions.length > 0 + ) { + const intersection = storedUser.permissions.filter((permission: string) => + permissions.includes(permission) + ); + if (intersection.length > 0) setHasAccess(true); } - setChecking(false) - - }, [roles, permissions]) + setChecking(false); + }, [roles, permissions]); if (!hasAccess && checking) { - return isLoading + return isLoading; } - + if (hasAccess) { - return ( - // children is of type ReactNode which already includes ReactFragment - {children} - ) + return <>{children}; } if (renderAuthFailed) { - return renderAuthFailed + return renderAuthFailed; } - return null -} + return null; +}; export default HasAccess;