UNPKG

@shield-acl/react

Version:

Sistema ACL (Access Control List) inteligente e granular para aplicações React

535 lines (521 loc) 15.9 kB
"use strict"; var __defProp = Object.defineProperty; var __getOwnPropDesc = Object.getOwnPropertyDescriptor; var __getOwnPropNames = Object.getOwnPropertyNames; var __hasOwnProp = Object.prototype.hasOwnProperty; var __export = (target, all) => { for (var name in all) __defProp(target, name, { get: all[name], enumerable: true }); }; var __copyProps = (to, from, except, desc) => { if (from && typeof from === "object" || typeof from === "function") { for (let key of __getOwnPropNames(from)) if (!__hasOwnProp.call(to, key) && key !== except) __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable }); } return to; }; var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod); // src/index.ts var src_exports = {}; __export(src_exports, { ACLProvider: () => ACLProvider, Can: () => Can, Cannot: () => Cannot, allOf: () => allOf, anyOf: () => anyOf, useACLContext: () => useACLContext, useAcl: () => useAcl, useCan: () => useCan, useCanAsync: () => useCanAsync, useCannot: () => useCannot, useChecks: () => useChecks, useEvaluate: () => useEvaluate, useGrantedRoles: () => useGrantedRoles, usePermissionEffect: () => usePermissionEffect, usePermissions: () => usePermissions, useResource: () => useResource, useRoleHierarchy: () => useRoleHierarchy, useRolesSync: () => useRolesSync, useUserSync: () => useUserSync }); module.exports = __toCommonJS(src_exports); // src/context.tsx var import_react = require("react"); // src/hooks/internal.ts function stableKey(value) { var _a; try { return (_a = JSON.stringify(value)) != null ? _a : "undefined"; } catch (e) { return "unserializable"; } } function mergeEnv(base, override) { if (!base && !override) return void 0; return { ...base, ...override }; } // src/context.tsx var import_jsx_runtime = require("react/jsx-runtime"); var ACLContext = (0, import_react.createContext)(void 0); function ACLProvider({ engine, user: userProp, scope = "*", environment, children }) { const [user, setUser] = (0, import_react.useState)(userProp != null ? userProp : null); (0, import_react.useEffect)(() => { if (userProp !== void 0) setUser(userProp); }, [userProp]); const revision = (0, import_react.useSyncExternalStore)( engine.subscribe, () => engine.revision, () => engine.revision ); const can = (0, import_react.useCallback)( (action, resource, opts) => { var _a; if (!user) return false; return engine.can( user, (_a = opts == null ? void 0 : opts.scope) != null ? _a : scope, action, resource, toEval(opts, environment) ); }, // revision garante reavaliação quando o catálogo de roles muda [engine, user, scope, environment, revision] ); const evaluate = (0, import_react.useCallback)( (action, resource, opts) => { var _a; const activeScope = (_a = opts == null ? void 0 : opts.scope) != null ? _a : scope; if (!user) { return { allowed: false, reason: "No user authenticated", scope: activeScope }; } return engine.evaluate( user, activeScope, action, resource, toEval(opts, environment) ); }, [engine, user, scope, environment, revision] ); const canAsync = (0, import_react.useCallback)( (action, resource, opts) => { var _a; const activeScope = (_a = opts == null ? void 0 : opts.scope) != null ? _a : scope; if (!user) { return Promise.resolve({ allowed: false, reason: "No user authenticated", scope: activeScope }); } return engine.evaluateAsync( user, activeScope, action, resource, toEval(opts, environment) ); }, [engine, user, scope, environment, revision] ); const value = (0, import_react.useMemo)( () => ({ engine, scope, environment, user, setUser, revision, can, evaluate, canAsync }), [engine, scope, environment, user, revision, can, evaluate, canAsync] ); return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(ACLContext.Provider, { value, children }); } function toEval(opts, providerEnv) { return { resource: opts == null ? void 0 : opts.record, environment: mergeEnv(providerEnv, opts == null ? void 0 : opts.environment) }; } function useACLContext() { const context = (0, import_react.useContext)(ACLContext); if (!context) { throw new Error("useACLContext must be used within an ACLProvider"); } return context; } // src/hooks/useAcl.ts var import_react2 = require("react"); function useAcl() { const ctx = useACLContext(); const cannot = (0, import_react2.useCallback)( (action, resource, opts) => !ctx.can(action, resource, opts), [ctx] ); const clearCache = (0, import_react2.useCallback)(() => ctx.engine.clearCache(), [ctx.engine]); return { ...ctx, cannot, clearCache }; } // src/hooks/useCan.ts var import_react3 = require("react"); function useCan(action, resource, opts) { const { can, user, revision } = useACLContext(); const key = stableKey(opts); return (0, import_react3.useMemo)( () => can(action, resource, opts), // eslint-disable-next-line react-hooks/exhaustive-deps [can, action, resource, key, user, revision] ); } function useCannot(action, resource, opts) { return !useCan(action, resource, opts); } // src/hooks/useEvaluate.ts var import_react4 = require("react"); function useEvaluate(action, resource, opts) { const { evaluate, user, revision } = useACLContext(); const key = stableKey(opts); return (0, import_react4.useMemo)( () => evaluate(action, resource, opts), // eslint-disable-next-line react-hooks/exhaustive-deps [evaluate, action, resource, key, user, revision] ); } // src/hooks/useCanAsync.ts var import_react5 = require("react"); function useCanAsync(action, resource, opts) { const { canAsync, user, revision } = useACLContext(); const [state, setState] = (0, import_react5.useState)({ allowed: void 0, loading: true, error: null }); const [nonce, setNonce] = (0, import_react5.useState)(0); const refetch = (0, import_react5.useCallback)(() => setNonce((n) => n + 1), []); const key = stableKey(opts); (0, import_react5.useEffect)(() => { let alive = true; setState((s) => s.loading ? s : { ...s, loading: true }); canAsync(action, resource, opts).then((r) => { if (alive) setState({ allowed: r.allowed, loading: false, error: null }); }).catch((e) => { if (alive) setState({ allowed: void 0, loading: false, error: e }); }); return () => { alive = false; }; }, [canAsync, action, resource, key, user, revision, nonce]); return { ...state, refetch }; } // src/hooks/useChecks.ts var import_react6 = require("react"); function useChecks(map) { const { can, user, revision } = useACLContext(); const key = stableKey(map); return (0, import_react6.useMemo)(() => { const out = {}; Object.keys(map).forEach((k) => { const [action, resource, opts] = map[k]; out[k] = can(action, resource, opts); }); return out; }, [can, key, user, revision]); } var anyOf = (result) => Object.values(result).some(Boolean); var allOf = (result) => Object.values(result).every(Boolean); // src/hooks/useResource.ts var import_react7 = require("react"); function useResource(type, record, opts) { const { can, evaluate, user, revision } = useACLContext(); const scope = opts == null ? void 0 : opts.scope; const environment = opts == null ? void 0 : opts.environment; return (0, import_react7.useMemo)(() => { const base = { record, scope, environment }; const check = (action, extra) => can(action, type, { ...base, ...extra }); return { resource: type, record, can: check, evaluate: (action) => evaluate(action, type, base), canRead: () => check("read") || check(`${type}.read`), canCreate: () => check("create") || check(`${type}.create`), canUpdate: () => check("update") || check(`${type}.update`), canDelete: () => check("delete") || check(`${type}.delete`), canManage: () => check("manage") || check("*") }; }, [ can, evaluate, type, stableKey(record), scope, stableKey(environment), user, revision ]); } // src/hooks/useRoles.ts var import_react8 = require("react"); function useGrantedRoles(opts) { var _a; const { engine, user, scope, revision } = useACLContext(); const active = (_a = opts == null ? void 0 : opts.scope) != null ? _a : scope; return (0, import_react8.useMemo)( () => user ? engine.getGrantedRoles(user, active) : [], // eslint-disable-next-line react-hooks/exhaustive-deps [engine, user, active, revision] ); } function useRoleHierarchy(opts) { var _a; const { engine, user, scope, revision } = useACLContext(); const active = (_a = opts == null ? void 0 : opts.scope) != null ? _a : scope; const { allRoles, directRoles, inheritedRoles } = (0, import_react8.useMemo)(() => { const direct = user ? engine.getGrantedRoles(user, active) : []; const all = new Set(direct); const walk = (name, seen = /* @__PURE__ */ new Set()) => { var _a2, _b; if (seen.has(name)) return; seen.add(name); (_b = (_a2 = engine.getRole(name)) == null ? void 0 : _a2.inherits) == null ? void 0 : _b.forEach((inherited) => { all.add(inherited); walk(inherited, seen); }); }; direct.forEach((d) => walk(d)); const allArr = [...all]; return { allRoles: allArr, directRoles: direct, inheritedRoles: allArr.filter((r) => !direct.includes(r)) }; }, [engine, user, active, revision]); const hasRole = (0, import_react8.useCallback)( (n) => allRoles.includes(n), [allRoles] ); const hasAnyRole = (0, import_react8.useCallback)( (ns) => ns.some((n) => allRoles.includes(n)), [allRoles] ); const hasAllRoles = (0, import_react8.useCallback)( (ns) => ns.every((n) => allRoles.includes(n)), [allRoles] ); const getRole = (0, import_react8.useCallback)( (n) => engine.getRole(n), // eslint-disable-next-line react-hooks/exhaustive-deps [engine, revision] ); return { allRoles, directRoles, inheritedRoles, hasRole, hasAnyRole, hasAllRoles, getRole }; } // src/hooks/usePermissions.ts var import_react9 = require("react"); var EMPTY = { all: [], direct: [], byRole: {}, denials: [], allowances: [], actions: [], resources: [], count: 0 }; function usePermissions(opts) { var _a; const { engine, user, scope, revision } = useACLContext(); const active = (_a = opts == null ? void 0 : opts.scope) != null ? _a : scope; return (0, import_react9.useMemo)(() => { if (!user) return EMPTY; const all = engine.getUserPermissions(user, active); const direct = user.grants.filter((g) => g.scope === active).flatMap((g) => { var _a2; return (_a2 = g.permissions) != null ? _a2 : []; }); const byRole = {}; engine.getGrantedRoles(user, active).forEach((roleName) => { const role = engine.getRole(roleName); if (role) byRole[roleName] = role.permissions; }); const actions = /* @__PURE__ */ new Set(); const resources = /* @__PURE__ */ new Set(); for (const p of all) { (Array.isArray(p.action) ? p.action : [p.action]).forEach( (a) => actions.add(a) ); if (p.resource !== void 0) { (Array.isArray(p.resource) ? p.resource : [p.resource]).forEach( (r) => resources.add(r) ); } } return { all, direct, byRole, denials: all.filter((p) => p.deny === true), allowances: all.filter((p) => p.deny !== true), actions: [...actions].sort(), resources: [...resources].sort(), count: all.length }; }, [engine, user, active, revision]); } // src/hooks/useSync.ts var import_react10 = require("react"); function useUserSync(subscribe) { const { setUser } = useACLContext(); const ref = (0, import_react10.useRef)(subscribe); ref.current = subscribe; (0, import_react10.useEffect)(() => { const cleanup = ref.current(setUser); return () => { if (typeof cleanup === "function") cleanup(); }; }, [setUser]); } function useRolesSync(subscribe) { const { engine } = useACLContext(); const reload = (0, import_react10.useCallback)( (roles) => engine.setRoles(roles), [engine] ); const ref = (0, import_react10.useRef)(subscribe); ref.current = subscribe; (0, import_react10.useEffect)(() => { const cleanup = ref.current(reload); return () => { if (typeof cleanup === "function") cleanup(); }; }, [reload]); } function usePermissionEffect(action, resource, handlers, opts) { const allowed = useCan(action, resource, opts); const prev = (0, import_react10.useRef)(null); (0, import_react10.useEffect)(() => { var _a, _b; if (prev.current !== null) { if (allowed && !prev.current) (_a = handlers.onGain) == null ? void 0 : _a.call(handlers); else if (!allowed && prev.current) (_b = handlers.onLose) == null ? void 0 : _b.call(handlers); } prev.current = allowed; }, [allowed]); } // src/components.tsx var import_jsx_runtime2 = require("react/jsx-runtime"); function CanBase({ action, resource, record, scope, environment, fallback = null, children }) { const allowed = useCan(action, resource, { record, scope, environment }); return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_jsx_runtime2.Fragment, { children: allowed ? children : fallback }); } function Cannot({ action, resource, record, scope, environment, children }) { const allowed = useCan(action, resource, { record, scope, environment }); return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_jsx_runtime2.Fragment, { children: allowed ? null : children }); } function toMap(checks) { const map = {}; checks.forEach((c, i) => { map[String(i)] = c; }); return map; } function CanAny({ checks, fallback = null, children }) { const result = useChecks(toMap(checks)); return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_jsx_runtime2.Fragment, { children: anyOf(result) ? children : fallback }); } function CanAll({ checks, fallback = null, children }) { const result = useChecks(toMap(checks)); return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_jsx_runtime2.Fragment, { children: allOf(result) ? children : fallback }); } function CanAsync({ action, resource, record, scope, environment, pending = null, fallback = null, children }) { const { allowed, loading } = useCanAsync(action, resource, { record, scope, environment }); if (loading) return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_jsx_runtime2.Fragment, { children: pending }); return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_jsx_runtime2.Fragment, { children: allowed ? children : fallback }); } var Can = Object.assign(CanBase, { Any: CanAny, All: CanAll, Async: CanAsync }); // Annotate the CommonJS export names for ESM import in node: 0 && (module.exports = { ACLProvider, Can, Cannot, allOf, anyOf, useACLContext, useAcl, useCan, useCanAsync, useCannot, useChecks, useEvaluate, useGrantedRoles, usePermissionEffect, usePermissions, useResource, useRoleHierarchy, useRolesSync, useUserSync }); //# sourceMappingURL=index.js.map