@shield-acl/react
Version:
Sistema ACL (Access Control List) inteligente e granular para aplicações React
535 lines (521 loc) • 15.9 kB
JavaScript
;
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