UNPKG

@compugit/react-rbac

Version:

A comprehensive Role-Based Access Control (RBAC) library for React applications with support for groups, roles, permissions, and protected components

408 lines (397 loc) 16 kB
'use strict'; var jsxRuntime = require('react/jsx-runtime'); var react = require('react'); const RBACContext = react.createContext(undefined); const rbacReducer = (state, action) => { switch (action.type) { case "SET_USER": return { ...state, user: action.payload, error: null, initialized: true, }; case "SET_LOADING": return { ...state, loading: action.payload, }; case "SET_ERROR": return { ...state, error: action.payload, loading: false, }; case "SET_INITIALIZED": return { ...state, initialized: action.payload, }; case "UPDATE_USER_PERMISSIONS": return { ...state, user: state.user ? { ...state.user, permissions: action.payload, } : null, }; case "UPDATE_USER_ROLES": return { ...state, user: state.user ? { ...state.user, roles: action.payload, } : null, }; case "UPDATE_USER_GROUPS": return { ...state, user: state.user ? { ...state.user, groups: action.payload, } : null, }; default: return state; } }; const RBACProvider = ({ children, onUserLoad, onRefreshUser }) => { const [state, dispatch] = react.useReducer(rbacReducer, { user: null, loading: true, error: null, initialized: false, }); const setUser = react.useCallback((user) => { dispatch({ type: "SET_USER", payload: user }); }, []); const setLoading = react.useCallback((loading) => { dispatch({ type: "SET_LOADING", payload: loading }); }, []); const setError = react.useCallback((error) => { dispatch({ type: "SET_ERROR", payload: error }); }, []); const updateUserPermissions = react.useCallback((permissions) => { dispatch({ type: "UPDATE_USER_PERMISSIONS", payload: permissions }); }, []); const updateUserRoles = react.useCallback((roles) => { dispatch({ type: "UPDATE_USER_ROLES", payload: roles }); }, []); const updateUserGroups = react.useCallback((groups) => { dispatch({ type: "UPDATE_USER_GROUPS", payload: groups }); }, []); const clearAuth = react.useCallback(() => { dispatch({ type: "SET_USER", payload: null }); dispatch({ type: "SET_ERROR", payload: null }); dispatch({ type: "SET_LOADING", payload: false }); }, []); const refreshUser = react.useCallback(async () => { if (!onRefreshUser) return; try { setLoading(true); const user = await onRefreshUser(); setUser(user); } catch (error) { setError(error instanceof Error ? error.message : "Failed to refresh user"); } finally { setLoading(false); } }, [onRefreshUser, setLoading, setUser, setError]); react.useEffect(() => { const loadUser = async () => { if (!onUserLoad) { dispatch({ type: "SET_INITIALIZED", payload: true }); setLoading(false); return; } try { setLoading(true); const user = await onUserLoad(); setUser(user); } catch (error) { setError(error instanceof Error ? error.message : "Failed to load user"); } finally { setLoading(false); } }; loadUser(); }, [onUserLoad, setUser, setError, setLoading]); const value = { ...state, setUser, setLoading, setError, updateUserPermissions, updateUserRoles, updateUserGroups, clearAuth, refreshUser, }; return jsxRuntime.jsx(RBACContext.Provider, { value: value, children: children }); }; const useRBACContext = () => { const context = react.useContext(RBACContext); if (context === undefined) { throw new Error("useRBACContext must be used within a RBACProvider"); } return context; }; const useRBAC = () => { const context = useRBACContext(); const userPermissions = react.useMemo(() => { var _a, _b; if (!context.user) return []; const directPermissions = context.user.permissions || []; const rolePermissions = ((_a = context.user.roles) === null || _a === void 0 ? void 0 : _a.flatMap((role) => role.permissions)) || []; const groupPermissions = ((_b = context.user.groups) === null || _b === void 0 ? void 0 : _b.flatMap((group) => { var _a; return [ ...(group.permissions || []), ...(((_a = group.roles) === null || _a === void 0 ? void 0 : _a.flatMap((role) => role.permissions)) || []), ]; })) || []; const allPermissions = [...directPermissions, ...rolePermissions, ...groupPermissions]; return allPermissions.filter((permission, index, self) => index === self.findIndex((p) => p.id === permission.id)); }, [context.user]); const userRoles = react.useMemo(() => { var _a; if (!context.user) return []; const directRoles = context.user.roles || []; const groupRoles = ((_a = context.user.groups) === null || _a === void 0 ? void 0 : _a.flatMap((group) => group.roles)) || []; const allRoles = [...directRoles, ...groupRoles]; return allRoles.filter((role, index, self) => index === self.findIndex((r) => r.id === role.id)); }, [context.user]); const userGroups = react.useMemo(() => { var _a; return ((_a = context.user) === null || _a === void 0 ? void 0 : _a.groups) || []; }, [context.user]); const hasPermission = (permissionName, mode = "any") => { if (!context.user || userPermissions.length === 0) return false; const permissions = Array.isArray(permissionName) ? permissionName : [permissionName]; const userPermissionNames = userPermissions.map((p) => p.name); if (mode === "all") { return permissions.every((permission) => userPermissionNames.includes(permission)); } return permissions.some((permission) => userPermissionNames.includes(permission)); }; const hasRole = (roleName, mode = "any") => { if (!context.user || userRoles.length === 0) return false; const roles = Array.isArray(roleName) ? roleName : [roleName]; const userRoleNames = userRoles.map((r) => r.name); if (mode === "all") { return roles.every((role) => userRoleNames.includes(role)); } return roles.some((role) => userRoleNames.includes(role)); }; const hasGroup = (groupName, mode = "any") => { if (!context.user || userGroups.length === 0) return false; const groups = Array.isArray(groupName) ? groupName : [groupName]; const userGroupNames = userGroups.map((g) => g.name); if (mode === "all") { return groups.every((group) => userGroupNames.includes(group)); } return groups.some((group) => userGroupNames.includes(group)); }; const hasAccess = (options) => { const { permissions = [], roles = [], groups = [], mode = "any" } = options; const checks = []; if (permissions.length > 0) { checks.push(hasPermission(permissions, mode)); } if (roles.length > 0) { checks.push(hasRole(roles, mode)); } if (groups.length > 0) { checks.push(hasGroup(groups, mode)); } if (checks.length === 0) return true; return mode === "all" ? checks.every(Boolean) : checks.some(Boolean); }; const canAccess = (resource, action) => { if (!context.user) return false; return userPermissions.some((permission) => permission.resource === resource && permission.action === action); }; const isAuthenticated = () => { return !!context.user; }; const isInRole = (roleName) => { return hasRole(roleName); }; const isInGroup = (groupName) => { return hasGroup(groupName); }; return { user: context.user, loading: context.loading, error: context.error, initialized: context.initialized, userPermissions, userRoles, userGroups, hasPermission, hasRole, hasGroup, hasAccess, canAccess, isAuthenticated, isInRole, isInGroup, setUser: context.setUser, setLoading: context.setLoading, setError: context.setError, updateUserPermissions: context.updateUserPermissions, updateUserRoles: context.updateUserRoles, updateUserGroups: context.updateUserGroups, clearAuth: context.clearAuth, refreshUser: context.refreshUser, }; }; const useAuth = () => { const rbac = useRBAC(); return { user: rbac.user, loading: rbac.loading, error: rbac.error, initialized: rbac.initialized, isAuthenticated: rbac.isAuthenticated, login: rbac.setUser, logout: rbac.clearAuth, refresh: rbac.refreshUser, }; }; const ProtectedRoute = ({ children, fallback, loadingComponent, unauthorizedComponent, roles = [], permissions = [], groups = [], mode = "any", requireAuth = true, }) => { const { loading, initialized, hasAccess, isAuthenticated } = useRBAC(); if (loading || !initialized) { return loadingComponent || jsxRuntime.jsx("div", { children: "Loading..." }); } if (requireAuth && !isAuthenticated()) { return unauthorizedComponent || fallback || jsxRuntime.jsx("div", { children: "Access Denied: Authentication required" }); } const authorized = hasAccess({ roles, permissions, groups, mode }); if (!authorized) { return unauthorizedComponent || fallback || jsxRuntime.jsx("div", { children: "Access Denied: Insufficient permissions" }); } return jsxRuntime.jsx(jsxRuntime.Fragment, { children: children }); }; const ProtectedElement = ({ children, fallback = null, roles = [], permissions = [], groups = [], mode = "any", }) => { const { hasAccess, isAuthenticated } = useRBAC(); if (roles.length === 0 && permissions.length === 0 && groups.length === 0) { return isAuthenticated() ? jsxRuntime.jsx(jsxRuntime.Fragment, { children: children }) : jsxRuntime.jsx(jsxRuntime.Fragment, { children: fallback }); } const authorized = hasAccess({ roles, permissions, groups, mode }); return authorized ? jsxRuntime.jsx(jsxRuntime.Fragment, { children: children }) : jsxRuntime.jsx(jsxRuntime.Fragment, { children: fallback }); }; const ConditionalRender = ({ children, show, hide, fallback = null }) => { const { hasAccess } = useRBAC(); let shouldShow = true; if (show) { shouldShow = hasAccess({ roles: show.roles || [], permissions: show.permissions || [], groups: show.groups || [], mode: show.mode || "any", }); } if (hide && shouldShow) { const shouldHide = hasAccess({ roles: hide.roles || [], permissions: hide.permissions || [], groups: hide.groups || [], mode: hide.mode || "any", }); shouldShow = !shouldHide; } return shouldShow ? jsxRuntime.jsx(jsxRuntime.Fragment, { children: children }) : jsxRuntime.jsx(jsxRuntime.Fragment, { children: fallback }); }; function withAuthorization(WrappedComponent, options = {}) { const { roles = [], permissions = [], groups = [], mode = "any", fallback: FallbackComponent, loading: LoadingComponent, } = options; const AuthorizedComponent = (props) => { const { hasAccess, loading, initialized } = useRBAC(); if (loading || !initialized) { return LoadingComponent ? jsxRuntime.jsx(LoadingComponent, {}) : jsxRuntime.jsx("div", { children: "Loading..." }); } const authorized = hasAccess({ roles, permissions, groups, mode }); if (!authorized) { return FallbackComponent ? jsxRuntime.jsx(FallbackComponent, {}) : jsxRuntime.jsx("div", { children: "Access Denied" }); } return jsxRuntime.jsx(WrappedComponent, { ...props }); }; AuthorizedComponent.displayName = `withAuthorization(${WrappedComponent.displayName || WrappedComponent.name})`; return AuthorizedComponent; } class RBACUtils { static getAllUserPermissions(user) { var _a, _b; const directPermissions = user.permissions || []; const rolePermissions = ((_a = user.roles) === null || _a === void 0 ? void 0 : _a.flatMap((role) => role.permissions)) || []; const groupPermissions = ((_b = user.groups) === null || _b === void 0 ? void 0 : _b.flatMap((group) => { var _a; return [ ...(group.permissions || []), ...(((_a = group.roles) === null || _a === void 0 ? void 0 : _a.flatMap((role) => role.permissions)) || []), ]; })) || []; const allPermissions = [...directPermissions, ...rolePermissions, ...groupPermissions]; return allPermissions.filter((permission, index, self) => index === self.findIndex((p) => p.id === permission.id)); } static getAllUserRoles(user) { var _a; const directRoles = user.roles || []; const groupRoles = ((_a = user.groups) === null || _a === void 0 ? void 0 : _a.flatMap((group) => group.roles)) || []; const allRoles = [...directRoles, ...groupRoles]; return allRoles.filter((role, index, self) => index === self.findIndex((r) => r.id === role.id)); } static hasPermission(user, permissionName) { const permissions = this.getAllUserPermissions(user); return permissions.some((p) => p.name === permissionName); } static hasRole(user, roleName) { const roles = this.getAllUserRoles(user); return roles.some((r) => r.name === roleName); } static hasGroup(user, groupName) { var _a; return ((_a = user.groups) === null || _a === void 0 ? void 0 : _a.some((g) => g.name === groupName)) || false; } static canAccessResource(user, resource, action) { const permissions = this.getAllUserPermissions(user); return permissions.some((p) => p.resource === resource && p.action === action); } static createPermission(id, name, resource, action, description) { return { id, name, resource, action, description }; } static createRole(id, name, permissions, description) { return { id, name, permissions, description }; } static createGroup(id, name, roles, permissions, description) { return { id, name, roles, permissions, description }; } } exports.ConditionalRender = ConditionalRender; exports.ProtectedElement = ProtectedElement; exports.ProtectedRoute = ProtectedRoute; exports.RBACProvider = RBACProvider; exports.RBACUtils = RBACUtils; exports.useAuth = useAuth; exports.useRBAC = useRBAC; exports.useRBACContext = useRBACContext; exports.withAuthorization = withAuthorization; //# sourceMappingURL=index.js.map