secure-role-guard 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (41) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +813 -0
  3. package/dist/adapters/express.d.mts +109 -0
  4. package/dist/adapters/express.d.ts +109 -0
  5. package/dist/adapters/express.js +122 -0
  6. package/dist/adapters/express.js.map +1 -0
  7. package/dist/adapters/express.mjs +118 -0
  8. package/dist/adapters/express.mjs.map +1 -0
  9. package/dist/adapters/index.d.mts +3 -0
  10. package/dist/adapters/index.d.ts +3 -0
  11. package/dist/adapters/index.js +181 -0
  12. package/dist/adapters/index.js.map +1 -0
  13. package/dist/adapters/index.mjs +171 -0
  14. package/dist/adapters/index.mjs.map +1 -0
  15. package/dist/adapters/nextjs.d.mts +140 -0
  16. package/dist/adapters/nextjs.d.ts +140 -0
  17. package/dist/adapters/nextjs.js +138 -0
  18. package/dist/adapters/nextjs.js.map +1 -0
  19. package/dist/adapters/nextjs.mjs +131 -0
  20. package/dist/adapters/nextjs.mjs.map +1 -0
  21. package/dist/core/index.d.mts +100 -0
  22. package/dist/core/index.d.ts +100 -0
  23. package/dist/core/index.js +132 -0
  24. package/dist/core/index.js.map +1 -0
  25. package/dist/core/index.mjs +125 -0
  26. package/dist/core/index.mjs.map +1 -0
  27. package/dist/index.d.mts +4 -0
  28. package/dist/index.d.ts +4 -0
  29. package/dist/index.js +238 -0
  30. package/dist/index.js.map +1 -0
  31. package/dist/index.mjs +222 -0
  32. package/dist/index.mjs.map +1 -0
  33. package/dist/react/index.d.mts +237 -0
  34. package/dist/react/index.d.ts +237 -0
  35. package/dist/react/index.js +177 -0
  36. package/dist/react/index.js.map +1 -0
  37. package/dist/react/index.mjs +167 -0
  38. package/dist/react/index.mjs.map +1 -0
  39. package/dist/types-CSUpaGsY.d.mts +76 -0
  40. package/dist/types-CSUpaGsY.d.ts +76 -0
  41. package/package.json +99 -0
@@ -0,0 +1,100 @@
1
+ import { R as RoleDefinition, a as RoleRegistry, U as UserContext, b as PermissionCheckResult } from '../types-CSUpaGsY.js';
2
+ export { c as PermissionCheckOptions, P as PermissionEngineConfig } from '../types-CSUpaGsY.js';
3
+
4
+ /**
5
+ * Secure Role Guard - Role Registry
6
+ *
7
+ * Creates an immutable registry of role definitions.
8
+ * Pure functions only, zero side effects.
9
+ */
10
+
11
+ /**
12
+ * Creates an immutable role registry from role definitions.
13
+ *
14
+ * Security guarantees:
15
+ * - Returns a frozen, immutable object
16
+ * - No mutations possible after creation
17
+ * - Unknown roles return empty permissions (deny by default)
18
+ *
19
+ * @param definitions - Object mapping role names to permission arrays
20
+ * @returns Frozen RoleRegistry object
21
+ *
22
+ * @example
23
+ * const registry = defineRoles({
24
+ * superadmin: ['*'],
25
+ * admin: ['user.read', 'user.update'],
26
+ * support: ['ticket.read', 'ticket.reply']
27
+ * });
28
+ *
29
+ * registry.getPermissions('admin'); // ['user.read', 'user.update']
30
+ * registry.getPermissions('unknown'); // []
31
+ */
32
+ declare function defineRoles(definitions: RoleDefinition): RoleRegistry;
33
+ /**
34
+ * Creates an empty role registry.
35
+ * Useful for testing or when no roles are defined.
36
+ *
37
+ * @returns Empty frozen RoleRegistry
38
+ */
39
+ declare function createEmptyRegistry(): RoleRegistry;
40
+
41
+ /**
42
+ * Secure Role Guard - Permission Engine
43
+ *
44
+ * Pure functions for permission checking.
45
+ * Zero side effects, zero dependencies, zero mutations.
46
+ */
47
+
48
+ /**
49
+ * Checks if a user has a specific permission.
50
+ *
51
+ * SECURITY GUARANTEES:
52
+ * - Deny by default: undefined/null/empty always returns false
53
+ * - Pure function: no side effects, no mutations
54
+ * - Deterministic: same inputs always produce same output
55
+ *
56
+ * @param user - The user context to check
57
+ * @param permission - The permission required
58
+ * @param registry - The role registry for resolving roles
59
+ * @returns True if user has the permission, false otherwise
60
+ *
61
+ * @example
62
+ * const canEdit = canUser(currentUser, 'user.update', roleRegistry);
63
+ */
64
+ declare function canUser(user: UserContext | null | undefined, permission: string, registry: RoleRegistry): boolean;
65
+ /**
66
+ * Checks if a user has ALL of the specified permissions.
67
+ *
68
+ * @param user - The user context to check
69
+ * @param permissions - Array of permissions required
70
+ * @param registry - The role registry
71
+ * @returns True if user has ALL permissions
72
+ *
73
+ * @example
74
+ * const canManage = canUserAll(user, ['user.read', 'user.update'], registry);
75
+ */
76
+ declare function canUserAll(user: UserContext | null | undefined, permissions: readonly string[], registry: RoleRegistry): boolean;
77
+ /**
78
+ * Checks if a user has ANY of the specified permissions.
79
+ *
80
+ * @param user - The user context to check
81
+ * @param permissions - Array of permissions to check
82
+ * @param registry - The role registry
83
+ * @returns True if user has at least one permission
84
+ *
85
+ * @example
86
+ * const canView = canUserAny(user, ['report.view', 'report.admin'], registry);
87
+ */
88
+ declare function canUserAny(user: UserContext | null | undefined, permissions: readonly string[], registry: RoleRegistry): boolean;
89
+ /**
90
+ * Extended permission check with detailed result.
91
+ * Useful for debugging and logging.
92
+ *
93
+ * @param user - The user context
94
+ * @param permission - Permission to check
95
+ * @param registry - The role registry
96
+ * @returns PermissionCheckResult with allowed status and reason
97
+ */
98
+ declare function checkPermission(user: UserContext | null | undefined, permission: string, registry: RoleRegistry): PermissionCheckResult;
99
+
100
+ export { PermissionCheckResult, RoleDefinition, RoleRegistry, UserContext, canUser, canUserAll, canUserAny, checkPermission, createEmptyRegistry, defineRoles };
@@ -0,0 +1,132 @@
1
+ 'use strict';
2
+
3
+ // src/core/role-registry.ts
4
+ function defineRoles(definitions) {
5
+ const frozenDefinitions = {};
6
+ for (const role of Object.keys(definitions)) {
7
+ const permissions = definitions[role];
8
+ if (permissions !== void 0) {
9
+ frozenDefinitions[role] = Object.freeze([...permissions]);
10
+ }
11
+ }
12
+ Object.freeze(frozenDefinitions);
13
+ const registry = {
14
+ getPermissions(role) {
15
+ const permissions = frozenDefinitions[role];
16
+ return permissions !== void 0 ? permissions : Object.freeze([]);
17
+ },
18
+ hasRole(role) {
19
+ return Object.prototype.hasOwnProperty.call(frozenDefinitions, role);
20
+ },
21
+ getRoleNames() {
22
+ return Object.freeze(Object.keys(frozenDefinitions));
23
+ }
24
+ };
25
+ return Object.freeze(registry);
26
+ }
27
+ function createEmptyRegistry() {
28
+ return defineRoles({});
29
+ }
30
+
31
+ // src/core/permission-engine.ts
32
+ var WILDCARD_PERMISSION = "*";
33
+ function collectUserPermissions(user, registry) {
34
+ const permissions = /* @__PURE__ */ new Set();
35
+ if (user.permissions !== void 0) {
36
+ for (const permission of user.permissions) {
37
+ permissions.add(permission);
38
+ }
39
+ }
40
+ if (user.roles !== void 0) {
41
+ for (const role of user.roles) {
42
+ const rolePermissions = registry.getPermissions(role);
43
+ for (const permission of rolePermissions) {
44
+ permissions.add(permission);
45
+ }
46
+ }
47
+ }
48
+ return permissions;
49
+ }
50
+ function hasWildcardAccess(permissions, requested) {
51
+ if (permissions.has(WILDCARD_PERMISSION)) {
52
+ return true;
53
+ }
54
+ const parts = requested.split(".");
55
+ let namespace = "";
56
+ for (let i = 0; i < parts.length - 1; i++) {
57
+ const part = parts[i];
58
+ if (part !== void 0) {
59
+ namespace = namespace === "" ? part : `${namespace}.${part}`;
60
+ if (permissions.has(`${namespace}.*`)) {
61
+ return true;
62
+ }
63
+ }
64
+ }
65
+ return false;
66
+ }
67
+ function canUser(user, permission, registry) {
68
+ if (user === null || user === void 0) {
69
+ return false;
70
+ }
71
+ if (permission === "" || permission.trim() === "") {
72
+ return false;
73
+ }
74
+ const userPermissions = collectUserPermissions(user, registry);
75
+ if (userPermissions.has(permission)) {
76
+ return true;
77
+ }
78
+ if (hasWildcardAccess(userPermissions, permission)) {
79
+ return true;
80
+ }
81
+ return false;
82
+ }
83
+ function canUserAll(user, permissions, registry) {
84
+ if (permissions.length === 0) {
85
+ return false;
86
+ }
87
+ for (const permission of permissions) {
88
+ if (!canUser(user, permission, registry)) {
89
+ return false;
90
+ }
91
+ }
92
+ return true;
93
+ }
94
+ function canUserAny(user, permissions, registry) {
95
+ if (permissions.length === 0) {
96
+ return false;
97
+ }
98
+ for (const permission of permissions) {
99
+ if (canUser(user, permission, registry)) {
100
+ return true;
101
+ }
102
+ }
103
+ return false;
104
+ }
105
+ function checkPermission(user, permission, registry) {
106
+ if (user === null || user === void 0) {
107
+ return Object.freeze({
108
+ allowed: false,
109
+ reason: "No user context provided"
110
+ });
111
+ }
112
+ if (permission === "" || permission.trim() === "") {
113
+ return Object.freeze({
114
+ allowed: false,
115
+ reason: "Empty permission requested"
116
+ });
117
+ }
118
+ const allowed = canUser(user, permission, registry);
119
+ return Object.freeze({
120
+ allowed,
121
+ reason: allowed ? `Permission "${permission}" granted` : `Permission "${permission}" denied`
122
+ });
123
+ }
124
+
125
+ exports.canUser = canUser;
126
+ exports.canUserAll = canUserAll;
127
+ exports.canUserAny = canUserAny;
128
+ exports.checkPermission = checkPermission;
129
+ exports.createEmptyRegistry = createEmptyRegistry;
130
+ exports.defineRoles = defineRoles;
131
+ //# sourceMappingURL=index.js.map
132
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/core/role-registry.ts","../../src/core/permission-engine.ts"],"names":[],"mappings":"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","file":"index.js","sourcesContent":["/**\r\n * Secure Role Guard - Role Registry\r\n *\r\n * Creates an immutable registry of role definitions.\r\n * Pure functions only, zero side effects.\r\n */\r\n\r\nimport type { RoleDefinition, RoleRegistry } from \"./types\";\r\n\r\n/**\r\n * Creates an immutable role registry from role definitions.\r\n *\r\n * Security guarantees:\r\n * - Returns a frozen, immutable object\r\n * - No mutations possible after creation\r\n * - Unknown roles return empty permissions (deny by default)\r\n *\r\n * @param definitions - Object mapping role names to permission arrays\r\n * @returns Frozen RoleRegistry object\r\n *\r\n * @example\r\n * const registry = defineRoles({\r\n * superadmin: ['*'],\r\n * admin: ['user.read', 'user.update'],\r\n * support: ['ticket.read', 'ticket.reply']\r\n * });\r\n *\r\n * registry.getPermissions('admin'); // ['user.read', 'user.update']\r\n * registry.getPermissions('unknown'); // []\r\n */\r\nexport function defineRoles(definitions: RoleDefinition): RoleRegistry {\r\n // Create a deep-frozen copy to prevent external mutations\r\n const frozenDefinitions: Record<string, readonly string[]> = {};\r\n\r\n for (const role of Object.keys(definitions)) {\r\n const permissions = definitions[role];\r\n if (permissions !== undefined) {\r\n // Freeze the permissions array\r\n frozenDefinitions[role] = Object.freeze([...permissions]);\r\n }\r\n }\r\n\r\n // Freeze the definitions object itself\r\n Object.freeze(frozenDefinitions);\r\n\r\n const registry: RoleRegistry = {\r\n getPermissions(role: string): readonly string[] {\r\n const permissions = frozenDefinitions[role];\r\n // Deny by default: unknown roles get empty permissions\r\n return permissions !== undefined ? permissions : Object.freeze([]);\r\n },\r\n\r\n hasRole(role: string): boolean {\r\n return Object.prototype.hasOwnProperty.call(frozenDefinitions, role);\r\n },\r\n\r\n getRoleNames(): readonly string[] {\r\n return Object.freeze(Object.keys(frozenDefinitions));\r\n },\r\n };\r\n\r\n // Freeze the registry object to prevent modifications\r\n return Object.freeze(registry);\r\n}\r\n\r\n/**\r\n * Creates an empty role registry.\r\n * Useful for testing or when no roles are defined.\r\n *\r\n * @returns Empty frozen RoleRegistry\r\n */\r\nexport function createEmptyRegistry(): RoleRegistry {\r\n return defineRoles({});\r\n}\r\n","/**\r\n * Secure Role Guard - Permission Engine\r\n *\r\n * Pure functions for permission checking.\r\n * Zero side effects, zero dependencies, zero mutations.\r\n */\r\n\r\nimport type { UserContext, RoleRegistry, PermissionCheckResult } from \"./types\";\r\n\r\n/** Wildcard permission that grants all access */\r\nconst WILDCARD_PERMISSION = \"*\";\r\n\r\n/**\r\n * Collects all permissions for a user from their roles and direct permissions.\r\n *\r\n * @param user - The user context\r\n * @param registry - The role registry to resolve role permissions\r\n * @returns Set of all permissions (for efficient lookup)\r\n */\r\nfunction collectUserPermissions(\r\n user: UserContext,\r\n registry: RoleRegistry\r\n): ReadonlySet<string> {\r\n const permissions = new Set<string>();\r\n\r\n // Add direct user permissions\r\n if (user.permissions !== undefined) {\r\n for (const permission of user.permissions) {\r\n permissions.add(permission);\r\n }\r\n }\r\n\r\n // Add permissions from all user roles\r\n if (user.roles !== undefined) {\r\n for (const role of user.roles) {\r\n const rolePermissions = registry.getPermissions(role);\r\n for (const permission of rolePermissions) {\r\n permissions.add(permission);\r\n }\r\n }\r\n }\r\n\r\n return permissions;\r\n}\r\n\r\n/**\r\n * Checks if a permission set contains a wildcard that grants the requested permission.\r\n *\r\n * Supports:\r\n * - Exact wildcard (*) - grants everything\r\n * - Namespace wildcards (user.*) - grants all under namespace\r\n *\r\n * @param permissions - Set of permissions to check\r\n * @param requested - The permission being requested\r\n * @returns True if wildcard grants access\r\n */\r\nfunction hasWildcardAccess(\r\n permissions: ReadonlySet<string>,\r\n requested: string\r\n): boolean {\r\n // Check for global wildcard\r\n if (permissions.has(WILDCARD_PERMISSION)) {\r\n return true;\r\n }\r\n\r\n // Check for namespace wildcards (e.g., user.* grants user.read)\r\n const parts = requested.split(\".\");\r\n let namespace = \"\";\r\n\r\n for (let i = 0; i < parts.length - 1; i++) {\r\n const part = parts[i];\r\n if (part !== undefined) {\r\n namespace = namespace === \"\" ? part : `${namespace}.${part}`;\r\n if (permissions.has(`${namespace}.*`)) {\r\n return true;\r\n }\r\n }\r\n }\r\n\r\n return false;\r\n}\r\n\r\n/**\r\n * Checks if a user has a specific permission.\r\n *\r\n * SECURITY GUARANTEES:\r\n * - Deny by default: undefined/null/empty always returns false\r\n * - Pure function: no side effects, no mutations\r\n * - Deterministic: same inputs always produce same output\r\n *\r\n * @param user - The user context to check\r\n * @param permission - The permission required\r\n * @param registry - The role registry for resolving roles\r\n * @returns True if user has the permission, false otherwise\r\n *\r\n * @example\r\n * const canEdit = canUser(currentUser, 'user.update', roleRegistry);\r\n */\r\nexport function canUser(\r\n user: UserContext | null | undefined,\r\n permission: string,\r\n registry: RoleRegistry\r\n): boolean {\r\n // DENY BY DEFAULT: No user context means no access\r\n if (user === null || user === undefined) {\r\n return false;\r\n }\r\n\r\n // DENY BY DEFAULT: Empty permission request is invalid\r\n if (permission === \"\" || permission.trim() === \"\") {\r\n return false;\r\n }\r\n\r\n const userPermissions = collectUserPermissions(user, registry);\r\n\r\n // Check for exact permission match\r\n if (userPermissions.has(permission)) {\r\n return true;\r\n }\r\n\r\n // Check for wildcard access\r\n if (hasWildcardAccess(userPermissions, permission)) {\r\n return true;\r\n }\r\n\r\n // DENY BY DEFAULT\r\n return false;\r\n}\r\n\r\n/**\r\n * Checks if a user has ALL of the specified permissions.\r\n *\r\n * @param user - The user context to check\r\n * @param permissions - Array of permissions required\r\n * @param registry - The role registry\r\n * @returns True if user has ALL permissions\r\n *\r\n * @example\r\n * const canManage = canUserAll(user, ['user.read', 'user.update'], registry);\r\n */\r\nexport function canUserAll(\r\n user: UserContext | null | undefined,\r\n permissions: readonly string[],\r\n registry: RoleRegistry\r\n): boolean {\r\n // DENY BY DEFAULT: Empty permissions array\r\n if (permissions.length === 0) {\r\n return false;\r\n }\r\n\r\n for (const permission of permissions) {\r\n if (!canUser(user, permission, registry)) {\r\n return false;\r\n }\r\n }\r\n\r\n return true;\r\n}\r\n\r\n/**\r\n * Checks if a user has ANY of the specified permissions.\r\n *\r\n * @param user - The user context to check\r\n * @param permissions - Array of permissions to check\r\n * @param registry - The role registry\r\n * @returns True if user has at least one permission\r\n *\r\n * @example\r\n * const canView = canUserAny(user, ['report.view', 'report.admin'], registry);\r\n */\r\nexport function canUserAny(\r\n user: UserContext | null | undefined,\r\n permissions: readonly string[],\r\n registry: RoleRegistry\r\n): boolean {\r\n // DENY BY DEFAULT: Empty permissions array\r\n if (permissions.length === 0) {\r\n return false;\r\n }\r\n\r\n for (const permission of permissions) {\r\n if (canUser(user, permission, registry)) {\r\n return true;\r\n }\r\n }\r\n\r\n return false;\r\n}\r\n\r\n/**\r\n * Extended permission check with detailed result.\r\n * Useful for debugging and logging.\r\n *\r\n * @param user - The user context\r\n * @param permission - Permission to check\r\n * @param registry - The role registry\r\n * @returns PermissionCheckResult with allowed status and reason\r\n */\r\nexport function checkPermission(\r\n user: UserContext | null | undefined,\r\n permission: string,\r\n registry: RoleRegistry\r\n): PermissionCheckResult {\r\n if (user === null || user === undefined) {\r\n return Object.freeze({\r\n allowed: false,\r\n reason: \"No user context provided\",\r\n });\r\n }\r\n\r\n if (permission === \"\" || permission.trim() === \"\") {\r\n return Object.freeze({\r\n allowed: false,\r\n reason: \"Empty permission requested\",\r\n });\r\n }\r\n\r\n const allowed = canUser(user, permission, registry);\r\n\r\n return Object.freeze({\r\n allowed,\r\n reason: allowed\r\n ? `Permission \"${permission}\" granted`\r\n : `Permission \"${permission}\" denied`,\r\n });\r\n}\r\n"]}
@@ -0,0 +1,125 @@
1
+ // src/core/role-registry.ts
2
+ function defineRoles(definitions) {
3
+ const frozenDefinitions = {};
4
+ for (const role of Object.keys(definitions)) {
5
+ const permissions = definitions[role];
6
+ if (permissions !== void 0) {
7
+ frozenDefinitions[role] = Object.freeze([...permissions]);
8
+ }
9
+ }
10
+ Object.freeze(frozenDefinitions);
11
+ const registry = {
12
+ getPermissions(role) {
13
+ const permissions = frozenDefinitions[role];
14
+ return permissions !== void 0 ? permissions : Object.freeze([]);
15
+ },
16
+ hasRole(role) {
17
+ return Object.prototype.hasOwnProperty.call(frozenDefinitions, role);
18
+ },
19
+ getRoleNames() {
20
+ return Object.freeze(Object.keys(frozenDefinitions));
21
+ }
22
+ };
23
+ return Object.freeze(registry);
24
+ }
25
+ function createEmptyRegistry() {
26
+ return defineRoles({});
27
+ }
28
+
29
+ // src/core/permission-engine.ts
30
+ var WILDCARD_PERMISSION = "*";
31
+ function collectUserPermissions(user, registry) {
32
+ const permissions = /* @__PURE__ */ new Set();
33
+ if (user.permissions !== void 0) {
34
+ for (const permission of user.permissions) {
35
+ permissions.add(permission);
36
+ }
37
+ }
38
+ if (user.roles !== void 0) {
39
+ for (const role of user.roles) {
40
+ const rolePermissions = registry.getPermissions(role);
41
+ for (const permission of rolePermissions) {
42
+ permissions.add(permission);
43
+ }
44
+ }
45
+ }
46
+ return permissions;
47
+ }
48
+ function hasWildcardAccess(permissions, requested) {
49
+ if (permissions.has(WILDCARD_PERMISSION)) {
50
+ return true;
51
+ }
52
+ const parts = requested.split(".");
53
+ let namespace = "";
54
+ for (let i = 0; i < parts.length - 1; i++) {
55
+ const part = parts[i];
56
+ if (part !== void 0) {
57
+ namespace = namespace === "" ? part : `${namespace}.${part}`;
58
+ if (permissions.has(`${namespace}.*`)) {
59
+ return true;
60
+ }
61
+ }
62
+ }
63
+ return false;
64
+ }
65
+ function canUser(user, permission, registry) {
66
+ if (user === null || user === void 0) {
67
+ return false;
68
+ }
69
+ if (permission === "" || permission.trim() === "") {
70
+ return false;
71
+ }
72
+ const userPermissions = collectUserPermissions(user, registry);
73
+ if (userPermissions.has(permission)) {
74
+ return true;
75
+ }
76
+ if (hasWildcardAccess(userPermissions, permission)) {
77
+ return true;
78
+ }
79
+ return false;
80
+ }
81
+ function canUserAll(user, permissions, registry) {
82
+ if (permissions.length === 0) {
83
+ return false;
84
+ }
85
+ for (const permission of permissions) {
86
+ if (!canUser(user, permission, registry)) {
87
+ return false;
88
+ }
89
+ }
90
+ return true;
91
+ }
92
+ function canUserAny(user, permissions, registry) {
93
+ if (permissions.length === 0) {
94
+ return false;
95
+ }
96
+ for (const permission of permissions) {
97
+ if (canUser(user, permission, registry)) {
98
+ return true;
99
+ }
100
+ }
101
+ return false;
102
+ }
103
+ function checkPermission(user, permission, registry) {
104
+ if (user === null || user === void 0) {
105
+ return Object.freeze({
106
+ allowed: false,
107
+ reason: "No user context provided"
108
+ });
109
+ }
110
+ if (permission === "" || permission.trim() === "") {
111
+ return Object.freeze({
112
+ allowed: false,
113
+ reason: "Empty permission requested"
114
+ });
115
+ }
116
+ const allowed = canUser(user, permission, registry);
117
+ return Object.freeze({
118
+ allowed,
119
+ reason: allowed ? `Permission "${permission}" granted` : `Permission "${permission}" denied`
120
+ });
121
+ }
122
+
123
+ export { canUser, canUserAll, canUserAny, checkPermission, createEmptyRegistry, defineRoles };
124
+ //# sourceMappingURL=index.mjs.map
125
+ //# sourceMappingURL=index.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/core/role-registry.ts","../../src/core/permission-engine.ts"],"names":[],"mappings":";AA8BO,SAAS,YAAY,WAAA,EAA2C;AAErE,EAAA,MAAM,oBAAuD,EAAC;AAE9D,EAAA,KAAA,MAAW,IAAA,IAAQ,MAAA,CAAO,IAAA,CAAK,WAAW,CAAA,EAAG;AAC3C,IAAA,MAAM,WAAA,GAAc,YAAY,IAAI,CAAA;AACpC,IAAA,IAAI,gBAAgB,MAAA,EAAW;AAE7B,MAAA,iBAAA,CAAkB,IAAI,CAAA,GAAI,MAAA,CAAO,OAAO,CAAC,GAAG,WAAW,CAAC,CAAA;AAAA,IAC1D;AAAA,EACF;AAGA,EAAA,MAAA,CAAO,OAAO,iBAAiB,CAAA;AAE/B,EAAA,MAAM,QAAA,GAAyB;AAAA,IAC7B,eAAe,IAAA,EAAiC;AAC9C,MAAA,MAAM,WAAA,GAAc,kBAAkB,IAAI,CAAA;AAE1C,MAAA,OAAO,gBAAgB,MAAA,GAAY,WAAA,GAAc,MAAA,CAAO,MAAA,CAAO,EAAE,CAAA;AAAA,IACnE,CAAA;AAAA,IAEA,QAAQ,IAAA,EAAuB;AAC7B,MAAA,OAAO,MAAA,CAAO,SAAA,CAAU,cAAA,CAAe,IAAA,CAAK,mBAAmB,IAAI,CAAA;AAAA,IACrE,CAAA;AAAA,IAEA,YAAA,GAAkC;AAChC,MAAA,OAAO,MAAA,CAAO,MAAA,CAAO,MAAA,CAAO,IAAA,CAAK,iBAAiB,CAAC,CAAA;AAAA,IACrD;AAAA,GACF;AAGA,EAAA,OAAO,MAAA,CAAO,OAAO,QAAQ,CAAA;AAC/B;AAQO,SAAS,mBAAA,GAAoC;AAClD,EAAA,OAAO,WAAA,CAAY,EAAE,CAAA;AACvB;;;AC/DA,IAAM,mBAAA,GAAsB,GAAA;AAS5B,SAAS,sBAAA,CACP,MACA,QAAA,EACqB;AACrB,EAAA,MAAM,WAAA,uBAAkB,GAAA,EAAY;AAGpC,EAAA,IAAI,IAAA,CAAK,gBAAgB,MAAA,EAAW;AAClC,IAAA,KAAA,MAAW,UAAA,IAAc,KAAK,WAAA,EAAa;AACzC,MAAA,WAAA,CAAY,IAAI,UAAU,CAAA;AAAA,IAC5B;AAAA,EACF;AAGA,EAAA,IAAI,IAAA,CAAK,UAAU,MAAA,EAAW;AAC5B,IAAA,KAAA,MAAW,IAAA,IAAQ,KAAK,KAAA,EAAO;AAC7B,MAAA,MAAM,eAAA,GAAkB,QAAA,CAAS,cAAA,CAAe,IAAI,CAAA;AACpD,MAAA,KAAA,MAAW,cAAc,eAAA,EAAiB;AACxC,QAAA,WAAA,CAAY,IAAI,UAAU,CAAA;AAAA,MAC5B;AAAA,IACF;AAAA,EACF;AAEA,EAAA,OAAO,WAAA;AACT;AAaA,SAAS,iBAAA,CACP,aACA,SAAA,EACS;AAET,EAAA,IAAI,WAAA,CAAY,GAAA,CAAI,mBAAmB,CAAA,EAAG;AACxC,IAAA,OAAO,IAAA;AAAA,EACT;AAGA,EAAA,MAAM,KAAA,GAAQ,SAAA,CAAU,KAAA,CAAM,GAAG,CAAA;AACjC,EAAA,IAAI,SAAA,GAAY,EAAA;AAEhB,EAAA,KAAA,IAAS,IAAI,CAAA,EAAG,CAAA,GAAI,KAAA,CAAM,MAAA,GAAS,GAAG,CAAA,EAAA,EAAK;AACzC,IAAA,MAAM,IAAA,GAAO,MAAM,CAAC,CAAA;AACpB,IAAA,IAAI,SAAS,MAAA,EAAW;AACtB,MAAA,SAAA,GAAY,cAAc,EAAA,GAAK,IAAA,GAAO,CAAA,EAAG,SAAS,IAAI,IAAI,CAAA,CAAA;AAC1D,MAAA,IAAI,WAAA,CAAY,GAAA,CAAI,CAAA,EAAG,SAAS,IAAI,CAAA,EAAG;AACrC,QAAA,OAAO,IAAA;AAAA,MACT;AAAA,IACF;AAAA,EACF;AAEA,EAAA,OAAO,KAAA;AACT;AAkBO,SAAS,OAAA,CACd,IAAA,EACA,UAAA,EACA,QAAA,EACS;AAET,EAAA,IAAI,IAAA,KAAS,IAAA,IAAQ,IAAA,KAAS,MAAA,EAAW;AACvC,IAAA,OAAO,KAAA;AAAA,EACT;AAGA,EAAA,IAAI,UAAA,KAAe,EAAA,IAAM,UAAA,CAAW,IAAA,OAAW,EAAA,EAAI;AACjD,IAAA,OAAO,KAAA;AAAA,EACT;AAEA,EAAA,MAAM,eAAA,GAAkB,sBAAA,CAAuB,IAAA,EAAM,QAAQ,CAAA;AAG7D,EAAA,IAAI,eAAA,CAAgB,GAAA,CAAI,UAAU,CAAA,EAAG;AACnC,IAAA,OAAO,IAAA;AAAA,EACT;AAGA,EAAA,IAAI,iBAAA,CAAkB,eAAA,EAAiB,UAAU,CAAA,EAAG;AAClD,IAAA,OAAO,IAAA;AAAA,EACT;AAGA,EAAA,OAAO,KAAA;AACT;AAaO,SAAS,UAAA,CACd,IAAA,EACA,WAAA,EACA,QAAA,EACS;AAET,EAAA,IAAI,WAAA,CAAY,WAAW,CAAA,EAAG;AAC5B,IAAA,OAAO,KAAA;AAAA,EACT;AAEA,EAAA,KAAA,MAAW,cAAc,WAAA,EAAa;AACpC,IAAA,IAAI,CAAC,OAAA,CAAQ,IAAA,EAAM,UAAA,EAAY,QAAQ,CAAA,EAAG;AACxC,MAAA,OAAO,KAAA;AAAA,IACT;AAAA,EACF;AAEA,EAAA,OAAO,IAAA;AACT;AAaO,SAAS,UAAA,CACd,IAAA,EACA,WAAA,EACA,QAAA,EACS;AAET,EAAA,IAAI,WAAA,CAAY,WAAW,CAAA,EAAG;AAC5B,IAAA,OAAO,KAAA;AAAA,EACT;AAEA,EAAA,KAAA,MAAW,cAAc,WAAA,EAAa;AACpC,IAAA,IAAI,OAAA,CAAQ,IAAA,EAAM,UAAA,EAAY,QAAQ,CAAA,EAAG;AACvC,MAAA,OAAO,IAAA;AAAA,IACT;AAAA,EACF;AAEA,EAAA,OAAO,KAAA;AACT;AAWO,SAAS,eAAA,CACd,IAAA,EACA,UAAA,EACA,QAAA,EACuB;AACvB,EAAA,IAAI,IAAA,KAAS,IAAA,IAAQ,IAAA,KAAS,MAAA,EAAW;AACvC,IAAA,OAAO,OAAO,MAAA,CAAO;AAAA,MACnB,OAAA,EAAS,KAAA;AAAA,MACT,MAAA,EAAQ;AAAA,KACT,CAAA;AAAA,EACH;AAEA,EAAA,IAAI,UAAA,KAAe,EAAA,IAAM,UAAA,CAAW,IAAA,OAAW,EAAA,EAAI;AACjD,IAAA,OAAO,OAAO,MAAA,CAAO;AAAA,MACnB,OAAA,EAAS,KAAA;AAAA,MACT,MAAA,EAAQ;AAAA,KACT,CAAA;AAAA,EACH;AAEA,EAAA,MAAM,OAAA,GAAU,OAAA,CAAQ,IAAA,EAAM,UAAA,EAAY,QAAQ,CAAA;AAElD,EAAA,OAAO,OAAO,MAAA,CAAO;AAAA,IACnB,OAAA;AAAA,IACA,QAAQ,OAAA,GACJ,CAAA,YAAA,EAAe,UAAU,CAAA,SAAA,CAAA,GACzB,eAAe,UAAU,CAAA,QAAA;AAAA,GAC9B,CAAA;AACH","file":"index.mjs","sourcesContent":["/**\r\n * Secure Role Guard - Role Registry\r\n *\r\n * Creates an immutable registry of role definitions.\r\n * Pure functions only, zero side effects.\r\n */\r\n\r\nimport type { RoleDefinition, RoleRegistry } from \"./types\";\r\n\r\n/**\r\n * Creates an immutable role registry from role definitions.\r\n *\r\n * Security guarantees:\r\n * - Returns a frozen, immutable object\r\n * - No mutations possible after creation\r\n * - Unknown roles return empty permissions (deny by default)\r\n *\r\n * @param definitions - Object mapping role names to permission arrays\r\n * @returns Frozen RoleRegistry object\r\n *\r\n * @example\r\n * const registry = defineRoles({\r\n * superadmin: ['*'],\r\n * admin: ['user.read', 'user.update'],\r\n * support: ['ticket.read', 'ticket.reply']\r\n * });\r\n *\r\n * registry.getPermissions('admin'); // ['user.read', 'user.update']\r\n * registry.getPermissions('unknown'); // []\r\n */\r\nexport function defineRoles(definitions: RoleDefinition): RoleRegistry {\r\n // Create a deep-frozen copy to prevent external mutations\r\n const frozenDefinitions: Record<string, readonly string[]> = {};\r\n\r\n for (const role of Object.keys(definitions)) {\r\n const permissions = definitions[role];\r\n if (permissions !== undefined) {\r\n // Freeze the permissions array\r\n frozenDefinitions[role] = Object.freeze([...permissions]);\r\n }\r\n }\r\n\r\n // Freeze the definitions object itself\r\n Object.freeze(frozenDefinitions);\r\n\r\n const registry: RoleRegistry = {\r\n getPermissions(role: string): readonly string[] {\r\n const permissions = frozenDefinitions[role];\r\n // Deny by default: unknown roles get empty permissions\r\n return permissions !== undefined ? permissions : Object.freeze([]);\r\n },\r\n\r\n hasRole(role: string): boolean {\r\n return Object.prototype.hasOwnProperty.call(frozenDefinitions, role);\r\n },\r\n\r\n getRoleNames(): readonly string[] {\r\n return Object.freeze(Object.keys(frozenDefinitions));\r\n },\r\n };\r\n\r\n // Freeze the registry object to prevent modifications\r\n return Object.freeze(registry);\r\n}\r\n\r\n/**\r\n * Creates an empty role registry.\r\n * Useful for testing or when no roles are defined.\r\n *\r\n * @returns Empty frozen RoleRegistry\r\n */\r\nexport function createEmptyRegistry(): RoleRegistry {\r\n return defineRoles({});\r\n}\r\n","/**\r\n * Secure Role Guard - Permission Engine\r\n *\r\n * Pure functions for permission checking.\r\n * Zero side effects, zero dependencies, zero mutations.\r\n */\r\n\r\nimport type { UserContext, RoleRegistry, PermissionCheckResult } from \"./types\";\r\n\r\n/** Wildcard permission that grants all access */\r\nconst WILDCARD_PERMISSION = \"*\";\r\n\r\n/**\r\n * Collects all permissions for a user from their roles and direct permissions.\r\n *\r\n * @param user - The user context\r\n * @param registry - The role registry to resolve role permissions\r\n * @returns Set of all permissions (for efficient lookup)\r\n */\r\nfunction collectUserPermissions(\r\n user: UserContext,\r\n registry: RoleRegistry\r\n): ReadonlySet<string> {\r\n const permissions = new Set<string>();\r\n\r\n // Add direct user permissions\r\n if (user.permissions !== undefined) {\r\n for (const permission of user.permissions) {\r\n permissions.add(permission);\r\n }\r\n }\r\n\r\n // Add permissions from all user roles\r\n if (user.roles !== undefined) {\r\n for (const role of user.roles) {\r\n const rolePermissions = registry.getPermissions(role);\r\n for (const permission of rolePermissions) {\r\n permissions.add(permission);\r\n }\r\n }\r\n }\r\n\r\n return permissions;\r\n}\r\n\r\n/**\r\n * Checks if a permission set contains a wildcard that grants the requested permission.\r\n *\r\n * Supports:\r\n * - Exact wildcard (*) - grants everything\r\n * - Namespace wildcards (user.*) - grants all under namespace\r\n *\r\n * @param permissions - Set of permissions to check\r\n * @param requested - The permission being requested\r\n * @returns True if wildcard grants access\r\n */\r\nfunction hasWildcardAccess(\r\n permissions: ReadonlySet<string>,\r\n requested: string\r\n): boolean {\r\n // Check for global wildcard\r\n if (permissions.has(WILDCARD_PERMISSION)) {\r\n return true;\r\n }\r\n\r\n // Check for namespace wildcards (e.g., user.* grants user.read)\r\n const parts = requested.split(\".\");\r\n let namespace = \"\";\r\n\r\n for (let i = 0; i < parts.length - 1; i++) {\r\n const part = parts[i];\r\n if (part !== undefined) {\r\n namespace = namespace === \"\" ? part : `${namespace}.${part}`;\r\n if (permissions.has(`${namespace}.*`)) {\r\n return true;\r\n }\r\n }\r\n }\r\n\r\n return false;\r\n}\r\n\r\n/**\r\n * Checks if a user has a specific permission.\r\n *\r\n * SECURITY GUARANTEES:\r\n * - Deny by default: undefined/null/empty always returns false\r\n * - Pure function: no side effects, no mutations\r\n * - Deterministic: same inputs always produce same output\r\n *\r\n * @param user - The user context to check\r\n * @param permission - The permission required\r\n * @param registry - The role registry for resolving roles\r\n * @returns True if user has the permission, false otherwise\r\n *\r\n * @example\r\n * const canEdit = canUser(currentUser, 'user.update', roleRegistry);\r\n */\r\nexport function canUser(\r\n user: UserContext | null | undefined,\r\n permission: string,\r\n registry: RoleRegistry\r\n): boolean {\r\n // DENY BY DEFAULT: No user context means no access\r\n if (user === null || user === undefined) {\r\n return false;\r\n }\r\n\r\n // DENY BY DEFAULT: Empty permission request is invalid\r\n if (permission === \"\" || permission.trim() === \"\") {\r\n return false;\r\n }\r\n\r\n const userPermissions = collectUserPermissions(user, registry);\r\n\r\n // Check for exact permission match\r\n if (userPermissions.has(permission)) {\r\n return true;\r\n }\r\n\r\n // Check for wildcard access\r\n if (hasWildcardAccess(userPermissions, permission)) {\r\n return true;\r\n }\r\n\r\n // DENY BY DEFAULT\r\n return false;\r\n}\r\n\r\n/**\r\n * Checks if a user has ALL of the specified permissions.\r\n *\r\n * @param user - The user context to check\r\n * @param permissions - Array of permissions required\r\n * @param registry - The role registry\r\n * @returns True if user has ALL permissions\r\n *\r\n * @example\r\n * const canManage = canUserAll(user, ['user.read', 'user.update'], registry);\r\n */\r\nexport function canUserAll(\r\n user: UserContext | null | undefined,\r\n permissions: readonly string[],\r\n registry: RoleRegistry\r\n): boolean {\r\n // DENY BY DEFAULT: Empty permissions array\r\n if (permissions.length === 0) {\r\n return false;\r\n }\r\n\r\n for (const permission of permissions) {\r\n if (!canUser(user, permission, registry)) {\r\n return false;\r\n }\r\n }\r\n\r\n return true;\r\n}\r\n\r\n/**\r\n * Checks if a user has ANY of the specified permissions.\r\n *\r\n * @param user - The user context to check\r\n * @param permissions - Array of permissions to check\r\n * @param registry - The role registry\r\n * @returns True if user has at least one permission\r\n *\r\n * @example\r\n * const canView = canUserAny(user, ['report.view', 'report.admin'], registry);\r\n */\r\nexport function canUserAny(\r\n user: UserContext | null | undefined,\r\n permissions: readonly string[],\r\n registry: RoleRegistry\r\n): boolean {\r\n // DENY BY DEFAULT: Empty permissions array\r\n if (permissions.length === 0) {\r\n return false;\r\n }\r\n\r\n for (const permission of permissions) {\r\n if (canUser(user, permission, registry)) {\r\n return true;\r\n }\r\n }\r\n\r\n return false;\r\n}\r\n\r\n/**\r\n * Extended permission check with detailed result.\r\n * Useful for debugging and logging.\r\n *\r\n * @param user - The user context\r\n * @param permission - Permission to check\r\n * @param registry - The role registry\r\n * @returns PermissionCheckResult with allowed status and reason\r\n */\r\nexport function checkPermission(\r\n user: UserContext | null | undefined,\r\n permission: string,\r\n registry: RoleRegistry\r\n): PermissionCheckResult {\r\n if (user === null || user === undefined) {\r\n return Object.freeze({\r\n allowed: false,\r\n reason: \"No user context provided\",\r\n });\r\n }\r\n\r\n if (permission === \"\" || permission.trim() === \"\") {\r\n return Object.freeze({\r\n allowed: false,\r\n reason: \"Empty permission requested\",\r\n });\r\n }\r\n\r\n const allowed = canUser(user, permission, registry);\r\n\r\n return Object.freeze({\r\n allowed,\r\n reason: allowed\r\n ? `Permission \"${permission}\" granted`\r\n : `Permission \"${permission}\" denied`,\r\n });\r\n}\r\n"]}
@@ -0,0 +1,4 @@
1
+ export { c as PermissionCheckOptions, b as PermissionCheckResult, P as PermissionEngineConfig, R as RoleDefinition, a as RoleRegistry, U as UserContext } from './types-CSUpaGsY.mjs';
2
+ export { canUser, canUserAll, canUserAny, checkPermission, createEmptyRegistry, defineRoles } from './core/index.mjs';
3
+ export { Can, CanProps, Cannot, CannotProps, PermissionContext, PermissionContextValue, PermissionProvider, PermissionProviderProps, useCan, useCanAll, useCanAny, usePermissions, useUser } from './react/index.mjs';
4
+ import 'react';
@@ -0,0 +1,4 @@
1
+ export { c as PermissionCheckOptions, b as PermissionCheckResult, P as PermissionEngineConfig, R as RoleDefinition, a as RoleRegistry, U as UserContext } from './types-CSUpaGsY.js';
2
+ export { canUser, canUserAll, canUserAny, checkPermission, createEmptyRegistry, defineRoles } from './core/index.js';
3
+ export { Can, CanProps, Cannot, CannotProps, PermissionContext, PermissionContextValue, PermissionProvider, PermissionProviderProps, useCan, useCanAll, useCanAny, usePermissions, useUser } from './react/index.js';
4
+ import 'react';
package/dist/index.js ADDED
@@ -0,0 +1,238 @@
1
+ 'use strict';
2
+
3
+ var react = require('react');
4
+ var jsxRuntime = require('react/jsx-runtime');
5
+
6
+ // src/core/role-registry.ts
7
+ function defineRoles(definitions) {
8
+ const frozenDefinitions = {};
9
+ for (const role of Object.keys(definitions)) {
10
+ const permissions = definitions[role];
11
+ if (permissions !== void 0) {
12
+ frozenDefinitions[role] = Object.freeze([...permissions]);
13
+ }
14
+ }
15
+ Object.freeze(frozenDefinitions);
16
+ const registry = {
17
+ getPermissions(role) {
18
+ const permissions = frozenDefinitions[role];
19
+ return permissions !== void 0 ? permissions : Object.freeze([]);
20
+ },
21
+ hasRole(role) {
22
+ return Object.prototype.hasOwnProperty.call(frozenDefinitions, role);
23
+ },
24
+ getRoleNames() {
25
+ return Object.freeze(Object.keys(frozenDefinitions));
26
+ }
27
+ };
28
+ return Object.freeze(registry);
29
+ }
30
+ function createEmptyRegistry() {
31
+ return defineRoles({});
32
+ }
33
+
34
+ // src/core/permission-engine.ts
35
+ var WILDCARD_PERMISSION = "*";
36
+ function collectUserPermissions(user, registry) {
37
+ const permissions = /* @__PURE__ */ new Set();
38
+ if (user.permissions !== void 0) {
39
+ for (const permission of user.permissions) {
40
+ permissions.add(permission);
41
+ }
42
+ }
43
+ if (user.roles !== void 0) {
44
+ for (const role of user.roles) {
45
+ const rolePermissions = registry.getPermissions(role);
46
+ for (const permission of rolePermissions) {
47
+ permissions.add(permission);
48
+ }
49
+ }
50
+ }
51
+ return permissions;
52
+ }
53
+ function hasWildcardAccess(permissions, requested) {
54
+ if (permissions.has(WILDCARD_PERMISSION)) {
55
+ return true;
56
+ }
57
+ const parts = requested.split(".");
58
+ let namespace = "";
59
+ for (let i = 0; i < parts.length - 1; i++) {
60
+ const part = parts[i];
61
+ if (part !== void 0) {
62
+ namespace = namespace === "" ? part : `${namespace}.${part}`;
63
+ if (permissions.has(`${namespace}.*`)) {
64
+ return true;
65
+ }
66
+ }
67
+ }
68
+ return false;
69
+ }
70
+ function canUser(user, permission, registry) {
71
+ if (user === null || user === void 0) {
72
+ return false;
73
+ }
74
+ if (permission === "" || permission.trim() === "") {
75
+ return false;
76
+ }
77
+ const userPermissions = collectUserPermissions(user, registry);
78
+ if (userPermissions.has(permission)) {
79
+ return true;
80
+ }
81
+ if (hasWildcardAccess(userPermissions, permission)) {
82
+ return true;
83
+ }
84
+ return false;
85
+ }
86
+ function canUserAll(user, permissions, registry) {
87
+ if (permissions.length === 0) {
88
+ return false;
89
+ }
90
+ for (const permission of permissions) {
91
+ if (!canUser(user, permission, registry)) {
92
+ return false;
93
+ }
94
+ }
95
+ return true;
96
+ }
97
+ function canUserAny(user, permissions, registry) {
98
+ if (permissions.length === 0) {
99
+ return false;
100
+ }
101
+ for (const permission of permissions) {
102
+ if (canUser(user, permission, registry)) {
103
+ return true;
104
+ }
105
+ }
106
+ return false;
107
+ }
108
+ function checkPermission(user, permission, registry) {
109
+ if (user === null || user === void 0) {
110
+ return Object.freeze({
111
+ allowed: false,
112
+ reason: "No user context provided"
113
+ });
114
+ }
115
+ if (permission === "" || permission.trim() === "") {
116
+ return Object.freeze({
117
+ allowed: false,
118
+ reason: "Empty permission requested"
119
+ });
120
+ }
121
+ const allowed = canUser(user, permission, registry);
122
+ return Object.freeze({
123
+ allowed,
124
+ reason: allowed ? `Permission "${permission}" granted` : `Permission "${permission}" denied`
125
+ });
126
+ }
127
+ var defaultContextValue = {
128
+ user: null,
129
+ registry: {
130
+ getPermissions: () => Object.freeze([]),
131
+ hasRole: () => false,
132
+ getRoleNames: () => Object.freeze([])
133
+ },
134
+ can: () => false,
135
+ canAll: () => false,
136
+ canAny: () => false
137
+ };
138
+ var PermissionContext = react.createContext(defaultContextValue);
139
+ PermissionContext.displayName = "PermissionContext";
140
+ function PermissionProvider({
141
+ user,
142
+ registry,
143
+ children
144
+ }) {
145
+ const contextValue = react.useMemo(
146
+ () => ({
147
+ user,
148
+ registry,
149
+ can: (permission) => canUser(user, permission, registry),
150
+ canAll: (permissions) => canUserAll(user, permissions, registry),
151
+ canAny: (permissions) => canUserAny(user, permissions, registry)
152
+ }),
153
+ [user, registry]
154
+ );
155
+ return /* @__PURE__ */ jsxRuntime.jsx(PermissionContext.Provider, { value: contextValue, children });
156
+ }
157
+ function usePermissions() {
158
+ return react.useContext(PermissionContext);
159
+ }
160
+ function useCan(permission) {
161
+ const { can } = react.useContext(PermissionContext);
162
+ return can(permission);
163
+ }
164
+ function useCanAll(permissions) {
165
+ const { canAll } = react.useContext(PermissionContext);
166
+ return canAll(permissions);
167
+ }
168
+ function useCanAny(permissions) {
169
+ const { canAny } = react.useContext(PermissionContext);
170
+ return canAny(permissions);
171
+ }
172
+ function useUser() {
173
+ const { user } = react.useContext(PermissionContext);
174
+ return user;
175
+ }
176
+
177
+ // src/react/components.tsx
178
+ function Can({
179
+ permission,
180
+ permissions,
181
+ anyOf = false,
182
+ children,
183
+ fallback = null
184
+ }) {
185
+ if (permission !== void 0) {
186
+ const allowed = useCan(permission);
187
+ return allowed ? children : fallback;
188
+ }
189
+ if (permissions !== void 0 && permissions.length > 0) {
190
+ const allowed = anyOf ? useCanAny(permissions) : useCanAll(permissions);
191
+ return allowed ? children : fallback;
192
+ }
193
+ return fallback;
194
+ }
195
+ function Cannot({
196
+ permission,
197
+ permissions,
198
+ anyOf = false,
199
+ children
200
+ }) {
201
+ if (permission !== void 0) {
202
+ const allowed = useCan(permission);
203
+ return allowed ? null : children;
204
+ }
205
+ if (permissions !== void 0 && permissions.length > 0) {
206
+ const allowed = anyOf ? useCanAny(permissions) : useCanAll(permissions);
207
+ return allowed ? null : children;
208
+ }
209
+ return children;
210
+ }
211
+ /**
212
+ * Secure Role Guard
213
+ *
214
+ * Zero-vulnerability, framework-agnostic RBAC authorization library.
215
+ * Works with React, Next.js, Remix, Gatsby, and any React framework.
216
+ *
217
+ * @author Sohel Rahaman
218
+ * @license MIT
219
+ * @see https://github.com/sohelrahaman/secure-role-guard
220
+ */
221
+
222
+ exports.Can = Can;
223
+ exports.Cannot = Cannot;
224
+ exports.PermissionContext = PermissionContext;
225
+ exports.PermissionProvider = PermissionProvider;
226
+ exports.canUser = canUser;
227
+ exports.canUserAll = canUserAll;
228
+ exports.canUserAny = canUserAny;
229
+ exports.checkPermission = checkPermission;
230
+ exports.createEmptyRegistry = createEmptyRegistry;
231
+ exports.defineRoles = defineRoles;
232
+ exports.useCan = useCan;
233
+ exports.useCanAll = useCanAll;
234
+ exports.useCanAny = useCanAny;
235
+ exports.usePermissions = usePermissions;
236
+ exports.useUser = useUser;
237
+ //# sourceMappingURL=index.js.map
238
+ //# sourceMappingURL=index.js.map