role-permission-engine 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.
@@ -0,0 +1,389 @@
1
+ 'use strict';
2
+
3
+ /**
4
+ * @fileoverview Core permission-checking utility functions.
5
+ *
6
+ * These are pure, framework-agnostic functions that form the heart of
7
+ * the role-permission-engine. They have zero side effects and can be
8
+ * used independently of any React component.
9
+ *
10
+ * @module utils/checkPermission
11
+ */
12
+
13
+ // ─── Type Definitions ────────────────────────────────────────────────────────
14
+
15
+ /**
16
+ * The logical operator used when evaluating multiple roles or permissions.
17
+ *
18
+ * - `"any"` – The user must satisfy **at least one** of the required items (OR logic).
19
+ * - `"all"` – The user must satisfy **every** required item (AND logic).
20
+ *
21
+ * @typedef {"any" | "all"} LogicOperator
22
+ */
23
+
24
+ /**
25
+ * A single role string, e.g. `"admin"`, `"editor"`, `"viewer"`.
26
+ *
27
+ * @typedef {string} Role
28
+ */
29
+
30
+ /**
31
+ * A single permission string, typically in `"action:resource"` format,
32
+ * e.g. `"read:users"`, `"write:posts"`, `"delete:*"`.
33
+ *
34
+ * @typedef {string} Permission
35
+ */
36
+
37
+ /**
38
+ * The result returned by permission-check functions.
39
+ *
40
+ * @typedef {Object} PermissionResult
41
+ * @property {boolean} allowed - Whether the user is allowed access.
42
+ * @property {string} reason - A human-readable explanation of the result.
43
+ */
44
+
45
+ // ─── Role Checking ────────────────────────────────────────────────────────────
46
+
47
+ /**
48
+ * Checks whether a user's roles satisfy the required roles using
49
+ * the specified logic operator.
50
+ *
51
+ * This is a **pure utility** — it accepts any string as a role.
52
+ * You define what roles mean in your own application. Examples:
53
+ * `"admin"`, `"manager"`, `"superuser"`, `"read-only"`, `"tenant-owner"`, etc.
54
+ *
55
+ * @param {string[]} userRoles
56
+ * Array of role strings currently assigned to the user.
57
+ * - Comparison is **case-insensitive** and **trims whitespace**.
58
+ * - Example: `['Admin', ' editor ']` is treated as `['admin', 'editor']`.
59
+ * - Pass an empty array `[]` when the user has no roles.
60
+ *
61
+ * @param {string[]} requiredRoles
62
+ * Array of role strings required to pass this check.
63
+ * - If this array is **empty** (`[]`) or not provided, the check
64
+ * is skipped and `allowed: true` is returned immediately.
65
+ * - Example: `['admin', 'superuser']`
66
+ *
67
+ * @param {"any" | "all"} [logic="any"]
68
+ * Controls how multiple `requiredRoles` are evaluated.
69
+ *
70
+ * | Value | Alias | Behaviour | Example |
71
+ * |---------|-------|-----------------------------------------------------|----------------------------------------------|
72
+ * | `"any"` | OR | User needs **at least one** of the required roles | `required: ['admin','editor']` → admin **or** editor is enough |
73
+ * | `"all"` | AND | User needs **every single** required role | `required: ['admin','editor']` → must have **both** |
74
+ *
75
+ * **Default:** `"any"` (OR logic).
76
+ *
77
+ * ```js
78
+ * // Default — no third argument needed for OR logic
79
+ * hasRole(['editor'], ['admin', 'editor']);
80
+ * // same as:
81
+ * hasRole(['editor'], ['admin', 'editor'], 'any');
82
+ * ```
83
+ *
84
+ * @returns {PermissionResult}
85
+ * `{ allowed: boolean, reason: string }`
86
+ *
87
+ * @example <caption>OR logic (default) — user has at least one required role</caption>
88
+ * hasRole(['editor'], ['admin', 'editor']);
89
+ * // => { allowed: true, reason: 'User has at least one required role.' }
90
+ *
91
+ * @example <caption>OR logic — user has none of the required roles</caption>
92
+ * hasRole(['viewer'], ['admin', 'editor'], 'any');
93
+ * // => { allowed: false, reason: 'User does not have any of the required roles: admin, editor' }
94
+ *
95
+ * @example <caption>AND logic — user must have every required role</caption>
96
+ * hasRole(['admin', 'editor'], ['admin', 'editor'], 'all');
97
+ * // => { allowed: true, reason: 'User has all required roles.' }
98
+ *
99
+ * @example <caption>AND logic — user is missing one role</caption>
100
+ * hasRole(['editor'], ['admin', 'editor'], 'all');
101
+ * // => { allowed: false, reason: 'User is missing required roles: admin' }
102
+ *
103
+ * @example <caption>Empty required roles — always allowed</caption>
104
+ * hasRole([], []);
105
+ * // => { allowed: true, reason: 'No roles required — access granted.' }
106
+ */
107
+ function hasRole(userRoles, requiredRoles, logic = "any") {
108
+ if (!Array.isArray(requiredRoles) || requiredRoles.length === 0) {
109
+ return {
110
+ allowed: true,
111
+ reason: "No roles required — access granted."
112
+ };
113
+ }
114
+ if (!Array.isArray(userRoles) || userRoles.length === 0) {
115
+ return {
116
+ allowed: false,
117
+ reason: "User has no roles assigned."
118
+ };
119
+ }
120
+ const normalizedUser = userRoles.map(r => r.toLowerCase().trim());
121
+ const normalizedRequired = requiredRoles.map(r => r.toLowerCase().trim());
122
+ if (logic === "all") {
123
+ const missing = normalizedRequired.filter(r => !normalizedUser.includes(r));
124
+ if (missing.length > 0) {
125
+ return {
126
+ allowed: false,
127
+ reason: `User is missing required roles: ${missing.join(", ")}`
128
+ };
129
+ }
130
+ return {
131
+ allowed: true,
132
+ reason: "User has all required roles."
133
+ };
134
+ }
135
+
136
+ // Default: "any" (OR logic)
137
+ const matched = normalizedRequired.filter(r => normalizedUser.includes(r));
138
+ if (matched.length === 0) {
139
+ return {
140
+ allowed: false,
141
+ reason: `User does not have any of the required roles: ${normalizedRequired.join(", ")}`
142
+ };
143
+ }
144
+ return {
145
+ allowed: true,
146
+ reason: "User has at least one required role."
147
+ };
148
+ }
149
+
150
+ // ─── Permission Checking ──────────────────────────────────────────────────────
151
+
152
+ /**
153
+ * Checks whether a user's permissions satisfy the required permissions
154
+ * using the specified logic operator. Supports wildcard `"*"` permissions.
155
+ *
156
+ * This is a **pure utility** — it accepts any string as a permission.
157
+ * You define what permissions mean in your own application. A common
158
+ * convention is `"action:resource"` format (e.g. `"read:invoices"`,
159
+ * `"export:reports"`, `"delete:accounts"`), but any string works.
160
+ *
161
+ * **Wildcard rules:**
162
+ * | User permission | Matches |
163
+ * |-----------------|----------------------------------------------|
164
+ * | `"*"` | Every possible permission — full super-access |
165
+ * | `"read:*"` | Any permission starting with `"read:"` |
166
+ * | `"write:posts"` | Only `"write:posts"` exactly |
167
+ *
168
+ * @param {string[]} userPermissions
169
+ * Array of permission strings the user currently holds.
170
+ * - Comparison is **case-insensitive** and **trims whitespace**.
171
+ * - Pass `['*']` to grant superuser access to all checks.
172
+ * - Pass an empty array `[]` when the user has no permissions.
173
+ *
174
+ * @param {string[]} requiredPermissions
175
+ * Array of permission strings required to pass this check.
176
+ * - If this array is **empty** (`[]`) or not provided, the check
177
+ * is skipped and `allowed: true` is returned immediately.
178
+ *
179
+ * @param {"any" | "all"} [logic="any"]
180
+ * Controls how multiple `requiredPermissions` are evaluated.
181
+ *
182
+ * | Value | Alias | Behaviour | Example |
183
+ * |---------|-------|-----------------------------------------------------------|------------------------------------------------------|
184
+ * | `"any"` | OR | User needs **at least one** of the required permissions | `required: ['read:x','write:x']` → read **or** write is enough |
185
+ * | `"all"` | AND | User needs **every single** required permission | `required: ['read:x','write:x']` → must have **both** |
186
+ *
187
+ * **Default:** `"any"` (OR logic).
188
+ *
189
+ * ```js
190
+ * // Default — OR logic, no third argument needed
191
+ * hasPermission(['read:reports'], ['read:reports', 'write:reports']);
192
+ * // same as:
193
+ * hasPermission(['read:reports'], ['read:reports', 'write:reports'], 'any');
194
+ * ```
195
+ *
196
+ * @returns {PermissionResult}
197
+ * `{ allowed: boolean, reason: string }`
198
+ *
199
+ * @example <caption>OR logic (default) — user has one of the required permissions</caption>
200
+ * hasPermission(['read:invoices'], ['read:invoices', 'write:invoices']);
201
+ * // => { allowed: true, reason: 'User has at least one required permission.' }
202
+ *
203
+ * @example <caption>OR logic — user has none</caption>
204
+ * hasPermission(['read:posts'], ['write:posts', 'delete:posts'], 'any');
205
+ * // => { allowed: false, reason: 'User does not have any of the required permissions: ...' }
206
+ *
207
+ * @example <caption>AND logic — must have all required permissions</caption>
208
+ * hasPermission(['read:users', 'write:users'], ['read:users', 'write:users'], 'all');
209
+ * // => { allowed: true, reason: 'User has all required permissions.' }
210
+ *
211
+ * @example <caption>Full wildcard — grants everything</caption>
212
+ * hasPermission(['*'], ['read:users', 'delete:posts'], 'all');
213
+ * // => { allowed: true, reason: 'User has wildcard permission (*) — full access granted.' }
214
+ *
215
+ * @example <caption>Namespace wildcard — "read:*" satisfies any "read:" permission</caption>
216
+ * hasPermission(['read:*'], ['read:invoices', 'read:reports'], 'all');
217
+ * // => { allowed: true, reason: 'User has all required permissions.' }
218
+ */
219
+ function hasPermission(userPermissions, requiredPermissions, logic = "any") {
220
+ if (!Array.isArray(requiredPermissions) || requiredPermissions.length === 0) {
221
+ return {
222
+ allowed: true,
223
+ reason: 'No permissions required — access granted.'
224
+ };
225
+ }
226
+ if (!Array.isArray(userPermissions) || userPermissions.length === 0) {
227
+ return {
228
+ allowed: false,
229
+ reason: 'User has no permissions assigned.'
230
+ };
231
+ }
232
+ const normalizedUser = userPermissions.map(p => p.toLowerCase().trim());
233
+ const normalizedRequired = requiredPermissions.map(p => p.toLowerCase().trim());
234
+
235
+ // Full wildcard check
236
+ if (normalizedUser.includes("*")) {
237
+ return {
238
+ allowed: true,
239
+ reason: "User has wildcard permission (*) — full access granted."
240
+ };
241
+ }
242
+
243
+ /**
244
+ * Checks if a single required permission is satisfied by the user's permission set,
245
+ * including namespace wildcard matching (e.g. "read:*" matches "read:users").
246
+ *
247
+ * @param {string} required - The required permission to satisfy.
248
+ * @returns {boolean} Whether the user satisfies this specific permission.
249
+ */
250
+ function isSatisfied(required) {
251
+ if (normalizedUser.includes(required)) return true;
252
+
253
+ // Namespace wildcard: "read:*" should satisfy "read:users"
254
+ const [requiredNamespace] = required.split(":");
255
+ return normalizedUser.includes(`${requiredNamespace}:*`);
256
+ }
257
+ if (logic === "all") {
258
+ const missing = normalizedRequired.filter(p => !isSatisfied(p));
259
+ if (missing.length > 0) {
260
+ return {
261
+ allowed: false,
262
+ reason: `User is missing required permissions: ${missing.join(", ")}`
263
+ };
264
+ }
265
+ return {
266
+ allowed: true,
267
+ reason: "User has all required permissions."
268
+ };
269
+ }
270
+
271
+ // Default: "any" (OR logic)
272
+ const matched = normalizedRequired.filter(p => isSatisfied(p));
273
+ if (matched.length === 0) {
274
+ return {
275
+ allowed: false,
276
+ reason: `User does not have any of the required permissions: ${normalizedRequired.join(", ")}`
277
+ };
278
+ }
279
+ return {
280
+ allowed: true,
281
+ reason: "User has at least one required permission."
282
+ };
283
+ }
284
+
285
+ // ─── Combined Check ───────────────────────────────────────────────────────────
286
+
287
+ /**
288
+ * Performs a **combined** role AND permission check in a single call.
289
+ *
290
+ * Both the role check and the permission check must independently pass
291
+ * for `allowed` to be `true`. If a constraint array is empty or not
292
+ * provided, that constraint is automatically satisfied (open access).
293
+ *
294
+ * @param {Object} options
295
+ *
296
+ * @param {string[]} [options.userRoles=[]]
297
+ * The roles currently held by the user. Any strings are accepted;
298
+ * you define what roles mean in your app.
299
+ *
300
+ * @param {string[]} [options.userPermissions=[]]
301
+ * The permissions currently held by the user. Supports wildcards:
302
+ * `"*"` (all access) and `"namespace:*"` (all in namespace).
303
+ *
304
+ * @param {string[]} [options.requiredRoles=[]]
305
+ * Roles the user must have to pass the role check.
306
+ * Pass `[]` (default) to skip the role check entirely.
307
+ *
308
+ * @param {string[]} [options.requiredPermissions=[]]
309
+ * Permissions the user must have to pass the permission check.
310
+ * Pass `[]` (default) to skip the permission check entirely.
311
+ *
312
+ * @param {"any" | "all"} [options.roleLogic="any"]
313
+ * Controls how `requiredRoles` are evaluated.
314
+ *
315
+ * | Value | Behaviour | Default? |
316
+ * |---------|--------------------------------------------------|----------|
317
+ * | `"any"` | User needs **at least one** required role (OR) | ✅ Yes |
318
+ * | `"all"` | User needs **every** required role (AND) | No |
319
+ *
320
+ * ```js
321
+ * // 'any' is the default — no need to specify it explicitly for OR logic
322
+ * checkAccess({ requiredRoles: ['admin', 'editor'], roleLogic: 'any' });
323
+ * ```
324
+ *
325
+ * @param {"any" | "all"} [options.permissionLogic="any"]
326
+ * Controls how `requiredPermissions` are evaluated.
327
+ *
328
+ * | Value | Behaviour | Default? |
329
+ * |---------|-------------------------------------------------------|----------|
330
+ * | `"any"` | User needs **at least one** required permission (OR) | ✅ Yes |
331
+ * | `"all"` | User needs **every** required permission (AND) | No |
332
+ *
333
+ * ```js
334
+ * // Require the user to have ALL listed permissions (AND logic)
335
+ * checkAccess({
336
+ * requiredPermissions: ['read:report', 'export:report'],
337
+ * permissionLogic: 'all',
338
+ * });
339
+ * ```
340
+ *
341
+ * @returns {PermissionResult}
342
+ * `{ allowed: boolean, reason: string }` — `allowed` is `true` only
343
+ * when **both** role and permission checks pass.
344
+ *
345
+ * @example <caption>Combined check — OR role logic + OR permission logic (both defaults)</caption>
346
+ * checkAccess({
347
+ * userRoles: ['editor'],
348
+ * userPermissions: ['write:posts'],
349
+ * requiredRoles: ['editor', 'admin'], // editor OR admin
350
+ * requiredPermissions: ['write:posts'], // write:posts required
351
+ * });
352
+ * // => { allowed: true, reason: 'Access granted.' }
353
+ *
354
+ * @example <caption>AND logic for permissions — user must have ALL listed permissions</caption>
355
+ * checkAccess({
356
+ * userRoles: ['manager'],
357
+ * userPermissions: ['approve:leaves'],
358
+ * requiredRoles: ['manager'],
359
+ * requiredPermissions: ['approve:leaves', 'view:team'],
360
+ * permissionLogic: 'all', // must have BOTH permissions
361
+ * });
362
+ * // => { allowed: false, reason: 'User is missing required permissions: view:team' }
363
+ *
364
+ * @example <caption>No constraints — always allowed</caption>
365
+ * checkAccess({ userRoles: ['guest'], userPermissions: [] });
366
+ * // => { allowed: true, reason: 'Access granted.' }
367
+ */
368
+ function checkAccess({
369
+ userRoles = [],
370
+ userPermissions = [],
371
+ requiredRoles = [],
372
+ requiredPermissions = [],
373
+ roleLogic = "any",
374
+ permissionLogic = "any"
375
+ }) {
376
+ const roleResult = hasRole(userRoles, requiredRoles, roleLogic);
377
+ if (!roleResult.allowed) return roleResult;
378
+ const permResult = hasPermission(userPermissions, requiredPermissions, permissionLogic);
379
+ if (!permResult.allowed) return permResult;
380
+ return {
381
+ allowed: true,
382
+ reason: "Access granted."
383
+ };
384
+ }
385
+
386
+ exports.checkAccess = checkAccess;
387
+ exports.hasPermission = hasPermission;
388
+ exports.hasRole = hasRole;
389
+ //# sourceMappingURL=utils.cjs.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"utils.cjs.js","sources":["../src/utils/checkPermission.js"],"sourcesContent":["/**\n * @fileoverview Core permission-checking utility functions.\n *\n * These are pure, framework-agnostic functions that form the heart of\n * the role-permission-engine. They have zero side effects and can be\n * used independently of any React component.\n *\n * @module utils/checkPermission\n */\n\n// ─── Type Definitions ────────────────────────────────────────────────────────\n\n/**\n * The logical operator used when evaluating multiple roles or permissions.\n *\n * - `\"any\"` – The user must satisfy **at least one** of the required items (OR logic).\n * - `\"all\"` – The user must satisfy **every** required item (AND logic).\n *\n * @typedef {\"any\" | \"all\"} LogicOperator\n */\n\n/**\n * A single role string, e.g. `\"admin\"`, `\"editor\"`, `\"viewer\"`.\n *\n * @typedef {string} Role\n */\n\n/**\n * A single permission string, typically in `\"action:resource\"` format,\n * e.g. `\"read:users\"`, `\"write:posts\"`, `\"delete:*\"`.\n *\n * @typedef {string} Permission\n */\n\n/**\n * The result returned by permission-check functions.\n *\n * @typedef {Object} PermissionResult\n * @property {boolean} allowed - Whether the user is allowed access.\n * @property {string} reason - A human-readable explanation of the result.\n */\n\n// ─── Role Checking ────────────────────────────────────────────────────────────\n\n/**\n * Checks whether a user's roles satisfy the required roles using\n * the specified logic operator.\n *\n * This is a **pure utility** — it accepts any string as a role.\n * You define what roles mean in your own application. Examples:\n * `\"admin\"`, `\"manager\"`, `\"superuser\"`, `\"read-only\"`, `\"tenant-owner\"`, etc.\n *\n * @param {string[]} userRoles\n * Array of role strings currently assigned to the user.\n * - Comparison is **case-insensitive** and **trims whitespace**.\n * - Example: `['Admin', ' editor ']` is treated as `['admin', 'editor']`.\n * - Pass an empty array `[]` when the user has no roles.\n *\n * @param {string[]} requiredRoles\n * Array of role strings required to pass this check.\n * - If this array is **empty** (`[]`) or not provided, the check\n * is skipped and `allowed: true` is returned immediately.\n * - Example: `['admin', 'superuser']`\n *\n * @param {\"any\" | \"all\"} [logic=\"any\"]\n * Controls how multiple `requiredRoles` are evaluated.\n *\n * | Value | Alias | Behaviour | Example |\n * |---------|-------|-----------------------------------------------------|----------------------------------------------|\n * | `\"any\"` | OR | User needs **at least one** of the required roles | `required: ['admin','editor']` → admin **or** editor is enough |\n * | `\"all\"` | AND | User needs **every single** required role | `required: ['admin','editor']` → must have **both** |\n *\n * **Default:** `\"any\"` (OR logic).\n *\n * ```js\n * // Default — no third argument needed for OR logic\n * hasRole(['editor'], ['admin', 'editor']);\n * // same as:\n * hasRole(['editor'], ['admin', 'editor'], 'any');\n * ```\n *\n * @returns {PermissionResult}\n * `{ allowed: boolean, reason: string }`\n *\n * @example <caption>OR logic (default) — user has at least one required role</caption>\n * hasRole(['editor'], ['admin', 'editor']);\n * // => { allowed: true, reason: 'User has at least one required role.' }\n *\n * @example <caption>OR logic — user has none of the required roles</caption>\n * hasRole(['viewer'], ['admin', 'editor'], 'any');\n * // => { allowed: false, reason: 'User does not have any of the required roles: admin, editor' }\n *\n * @example <caption>AND logic — user must have every required role</caption>\n * hasRole(['admin', 'editor'], ['admin', 'editor'], 'all');\n * // => { allowed: true, reason: 'User has all required roles.' }\n *\n * @example <caption>AND logic — user is missing one role</caption>\n * hasRole(['editor'], ['admin', 'editor'], 'all');\n * // => { allowed: false, reason: 'User is missing required roles: admin' }\n *\n * @example <caption>Empty required roles — always allowed</caption>\n * hasRole([], []);\n * // => { allowed: true, reason: 'No roles required — access granted.' }\n */\nexport function hasRole(userRoles, requiredRoles, logic = \"any\") {\n if (!Array.isArray(requiredRoles) || requiredRoles.length === 0) {\n return { allowed: true, reason: \"No roles required — access granted.\" };\n }\n\n if (!Array.isArray(userRoles) || userRoles.length === 0) {\n return { allowed: false, reason: \"User has no roles assigned.\" };\n }\n\n const normalizedUser = userRoles.map((r) => r.toLowerCase().trim());\n const normalizedRequired = requiredRoles.map((r) => r.toLowerCase().trim());\n\n if (logic === \"all\") {\n const missing = normalizedRequired.filter(\n (r) => !normalizedUser.includes(r),\n );\n if (missing.length > 0) {\n return {\n allowed: false,\n reason: `User is missing required roles: ${missing.join(\", \")}`,\n };\n }\n return { allowed: true, reason: \"User has all required roles.\" };\n }\n\n // Default: \"any\" (OR logic)\n const matched = normalizedRequired.filter((r) => normalizedUser.includes(r));\n if (matched.length === 0) {\n return {\n allowed: false,\n reason: `User does not have any of the required roles: ${normalizedRequired.join(\", \")}`,\n };\n }\n return { allowed: true, reason: \"User has at least one required role.\" };\n}\n\n// ─── Permission Checking ──────────────────────────────────────────────────────\n\n/**\n * Checks whether a user's permissions satisfy the required permissions\n * using the specified logic operator. Supports wildcard `\"*\"` permissions.\n *\n * This is a **pure utility** — it accepts any string as a permission.\n * You define what permissions mean in your own application. A common\n * convention is `\"action:resource\"` format (e.g. `\"read:invoices\"`,\n * `\"export:reports\"`, `\"delete:accounts\"`), but any string works.\n *\n * **Wildcard rules:**\n * | User permission | Matches |\n * |-----------------|----------------------------------------------|\n * | `\"*\"` | Every possible permission — full super-access |\n * | `\"read:*\"` | Any permission starting with `\"read:\"` |\n * | `\"write:posts\"` | Only `\"write:posts\"` exactly |\n *\n * @param {string[]} userPermissions\n * Array of permission strings the user currently holds.\n * - Comparison is **case-insensitive** and **trims whitespace**.\n * - Pass `['*']` to grant superuser access to all checks.\n * - Pass an empty array `[]` when the user has no permissions.\n *\n * @param {string[]} requiredPermissions\n * Array of permission strings required to pass this check.\n * - If this array is **empty** (`[]`) or not provided, the check\n * is skipped and `allowed: true` is returned immediately.\n *\n * @param {\"any\" | \"all\"} [logic=\"any\"]\n * Controls how multiple `requiredPermissions` are evaluated.\n *\n * | Value | Alias | Behaviour | Example |\n * |---------|-------|-----------------------------------------------------------|------------------------------------------------------|\n * | `\"any\"` | OR | User needs **at least one** of the required permissions | `required: ['read:x','write:x']` → read **or** write is enough |\n * | `\"all\"` | AND | User needs **every single** required permission | `required: ['read:x','write:x']` → must have **both** |\n *\n * **Default:** `\"any\"` (OR logic).\n *\n * ```js\n * // Default — OR logic, no third argument needed\n * hasPermission(['read:reports'], ['read:reports', 'write:reports']);\n * // same as:\n * hasPermission(['read:reports'], ['read:reports', 'write:reports'], 'any');\n * ```\n *\n * @returns {PermissionResult}\n * `{ allowed: boolean, reason: string }`\n *\n * @example <caption>OR logic (default) — user has one of the required permissions</caption>\n * hasPermission(['read:invoices'], ['read:invoices', 'write:invoices']);\n * // => { allowed: true, reason: 'User has at least one required permission.' }\n *\n * @example <caption>OR logic — user has none</caption>\n * hasPermission(['read:posts'], ['write:posts', 'delete:posts'], 'any');\n * // => { allowed: false, reason: 'User does not have any of the required permissions: ...' }\n *\n * @example <caption>AND logic — must have all required permissions</caption>\n * hasPermission(['read:users', 'write:users'], ['read:users', 'write:users'], 'all');\n * // => { allowed: true, reason: 'User has all required permissions.' }\n *\n * @example <caption>Full wildcard — grants everything</caption>\n * hasPermission(['*'], ['read:users', 'delete:posts'], 'all');\n * // => { allowed: true, reason: 'User has wildcard permission (*) — full access granted.' }\n *\n * @example <caption>Namespace wildcard — \"read:*\" satisfies any \"read:\" permission</caption>\n * hasPermission(['read:*'], ['read:invoices', 'read:reports'], 'all');\n * // => { allowed: true, reason: 'User has all required permissions.' }\n */\nexport function hasPermission(\n userPermissions,\n requiredPermissions,\n logic = \"any\",\n) {\n if (!Array.isArray(requiredPermissions) || requiredPermissions.length === 0) {\n return {\n allowed: true,\n reason: 'No permissions required — access granted.',\n };\n }\n\n if (!Array.isArray(userPermissions) || userPermissions.length === 0) {\n return { allowed: false, reason: 'User has no permissions assigned.' };\n }\n\n\n const normalizedUser = userPermissions.map((p) => p.toLowerCase().trim());\n const normalizedRequired = requiredPermissions.map((p) =>\n p.toLowerCase().trim(),\n );\n\n // Full wildcard check\n if (normalizedUser.includes(\"*\")) {\n return {\n allowed: true,\n reason: \"User has wildcard permission (*) — full access granted.\",\n };\n }\n\n /**\n * Checks if a single required permission is satisfied by the user's permission set,\n * including namespace wildcard matching (e.g. \"read:*\" matches \"read:users\").\n *\n * @param {string} required - The required permission to satisfy.\n * @returns {boolean} Whether the user satisfies this specific permission.\n */\n function isSatisfied(required) {\n if (normalizedUser.includes(required)) return true;\n\n // Namespace wildcard: \"read:*\" should satisfy \"read:users\"\n const [requiredNamespace] = required.split(\":\");\n return normalizedUser.includes(`${requiredNamespace}:*`);\n }\n\n if (logic === \"all\") {\n const missing = normalizedRequired.filter((p) => !isSatisfied(p));\n if (missing.length > 0) {\n return {\n allowed: false,\n reason: `User is missing required permissions: ${missing.join(\", \")}`,\n };\n }\n return { allowed: true, reason: \"User has all required permissions.\" };\n }\n\n // Default: \"any\" (OR logic)\n const matched = normalizedRequired.filter((p) => isSatisfied(p));\n if (matched.length === 0) {\n return {\n allowed: false,\n reason: `User does not have any of the required permissions: ${normalizedRequired.join(\", \")}`,\n };\n }\n return {\n allowed: true,\n reason: \"User has at least one required permission.\",\n };\n}\n\n// ─── Combined Check ───────────────────────────────────────────────────────────\n\n/**\n * Performs a **combined** role AND permission check in a single call.\n *\n * Both the role check and the permission check must independently pass\n * for `allowed` to be `true`. If a constraint array is empty or not\n * provided, that constraint is automatically satisfied (open access).\n *\n * @param {Object} options\n *\n * @param {string[]} [options.userRoles=[]]\n * The roles currently held by the user. Any strings are accepted;\n * you define what roles mean in your app.\n *\n * @param {string[]} [options.userPermissions=[]]\n * The permissions currently held by the user. Supports wildcards:\n * `\"*\"` (all access) and `\"namespace:*\"` (all in namespace).\n *\n * @param {string[]} [options.requiredRoles=[]]\n * Roles the user must have to pass the role check.\n * Pass `[]` (default) to skip the role check entirely.\n *\n * @param {string[]} [options.requiredPermissions=[]]\n * Permissions the user must have to pass the permission check.\n * Pass `[]` (default) to skip the permission check entirely.\n *\n * @param {\"any\" | \"all\"} [options.roleLogic=\"any\"]\n * Controls how `requiredRoles` are evaluated.\n *\n * | Value | Behaviour | Default? |\n * |---------|--------------------------------------------------|----------|\n * | `\"any\"` | User needs **at least one** required role (OR) | ✅ Yes |\n * | `\"all\"` | User needs **every** required role (AND) | No |\n *\n * ```js\n * // 'any' is the default — no need to specify it explicitly for OR logic\n * checkAccess({ requiredRoles: ['admin', 'editor'], roleLogic: 'any' });\n * ```\n *\n * @param {\"any\" | \"all\"} [options.permissionLogic=\"any\"]\n * Controls how `requiredPermissions` are evaluated.\n *\n * | Value | Behaviour | Default? |\n * |---------|-------------------------------------------------------|----------|\n * | `\"any\"` | User needs **at least one** required permission (OR) | ✅ Yes |\n * | `\"all\"` | User needs **every** required permission (AND) | No |\n *\n * ```js\n * // Require the user to have ALL listed permissions (AND logic)\n * checkAccess({\n * requiredPermissions: ['read:report', 'export:report'],\n * permissionLogic: 'all',\n * });\n * ```\n *\n * @returns {PermissionResult}\n * `{ allowed: boolean, reason: string }` — `allowed` is `true` only\n * when **both** role and permission checks pass.\n *\n * @example <caption>Combined check — OR role logic + OR permission logic (both defaults)</caption>\n * checkAccess({\n * userRoles: ['editor'],\n * userPermissions: ['write:posts'],\n * requiredRoles: ['editor', 'admin'], // editor OR admin\n * requiredPermissions: ['write:posts'], // write:posts required\n * });\n * // => { allowed: true, reason: 'Access granted.' }\n *\n * @example <caption>AND logic for permissions — user must have ALL listed permissions</caption>\n * checkAccess({\n * userRoles: ['manager'],\n * userPermissions: ['approve:leaves'],\n * requiredRoles: ['manager'],\n * requiredPermissions: ['approve:leaves', 'view:team'],\n * permissionLogic: 'all', // must have BOTH permissions\n * });\n * // => { allowed: false, reason: 'User is missing required permissions: view:team' }\n *\n * @example <caption>No constraints — always allowed</caption>\n * checkAccess({ userRoles: ['guest'], userPermissions: [] });\n * // => { allowed: true, reason: 'Access granted.' }\n */\nexport function checkAccess({\n userRoles = [],\n userPermissions = [],\n requiredRoles = [],\n requiredPermissions = [],\n roleLogic = \"any\",\n permissionLogic = \"any\",\n}) {\n const roleResult = hasRole(userRoles, requiredRoles, roleLogic);\n if (!roleResult.allowed) return roleResult;\n\n const permResult = hasPermission(\n userPermissions,\n requiredPermissions,\n permissionLogic,\n );\n if (!permResult.allowed) return permResult;\n\n return { allowed: true, reason: \"Access granted.\" };\n}\n"],"names":["hasRole","userRoles","requiredRoles","logic","Array","isArray","length","allowed","reason","normalizedUser","map","r","toLowerCase","trim","normalizedRequired","missing","filter","includes","join","matched","hasPermission","userPermissions","requiredPermissions","p","isSatisfied","required","requiredNamespace","split","checkAccess","roleLogic","permissionLogic","roleResult","permResult"],"mappings":"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"}