@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
JavaScript
;
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