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,140 @@
1
+ import { U as UserContext, a as RoleRegistry } from '../types-CSUpaGsY.js';
2
+
3
+ /**
4
+ * Secure Role Guard - Next.js Adapter
5
+ *
6
+ * Thin wrapper for Next.js API routes and Route Handlers.
7
+ * Delegates all authorization logic to the core permission engine.
8
+ *
9
+ * IMPORTANT: This adapter does NOT handle authentication.
10
+ * You must provide the user context from your auth system.
11
+ */
12
+
13
+ /**
14
+ * Next.js Request type (minimal interface for App Router).
15
+ * Using minimal interface to avoid requiring next as a dependency.
16
+ */
17
+ interface NextRequest {
18
+ /** Headers from the request */
19
+ headers: {
20
+ get(name: string): string | null;
21
+ };
22
+ }
23
+ /**
24
+ * Next.js Response helper for App Router.
25
+ */
26
+ interface NextResponse {
27
+ json(body: unknown, init?: {
28
+ status?: number;
29
+ }): Response;
30
+ }
31
+ /**
32
+ * Result of a permission check in Next.js context.
33
+ */
34
+ type PermissionResult = {
35
+ readonly allowed: boolean;
36
+ readonly user: UserContext | null;
37
+ };
38
+ /**
39
+ * Options for Next.js permission wrappers.
40
+ */
41
+ type NextPermissionOptions = {
42
+ /** Function to extract user context from request */
43
+ readonly getUser: (request: NextRequest) => UserContext | null | Promise<UserContext | null>;
44
+ /** HTTP status code on denial (default: 403) */
45
+ readonly statusCode?: number;
46
+ /** Error message on denial (default: 'Forbidden') */
47
+ readonly message?: string;
48
+ };
49
+ /**
50
+ * Checks if a user has permission in a Next.js API context.
51
+ *
52
+ * This is a pure check function - you handle the response.
53
+ *
54
+ * @param user - User context
55
+ * @param permission - Required permission
56
+ * @param registry - Role registry
57
+ * @returns PermissionResult
58
+ *
59
+ * @example
60
+ * ```ts
61
+ * // app/api/users/route.ts
62
+ * import { defineRoles } from 'secure-role-guard/core';
63
+ * import { checkNextPermission } from 'secure-role-guard/adapters/nextjs';
64
+ *
65
+ * const registry = defineRoles({ admin: ['user.update'] });
66
+ *
67
+ * export async function PUT(request: NextRequest) {
68
+ * const user = await getUser(request); // Your auth function
69
+ * const result = checkNextPermission(user, 'user.update', registry);
70
+ *
71
+ * if (!result.allowed) {
72
+ * return NextResponse.json({ error: 'Forbidden' }, { status: 403 });
73
+ * }
74
+ *
75
+ * // Handle the request...
76
+ * }
77
+ * ```
78
+ */
79
+ declare function checkNextPermission(user: UserContext | null | undefined, permission: string, registry: RoleRegistry): PermissionResult;
80
+ /**
81
+ * Checks if a user has ALL specified permissions.
82
+ *
83
+ * @param user - User context
84
+ * @param permissions - Required permissions
85
+ * @param registry - Role registry
86
+ * @returns PermissionResult
87
+ */
88
+ declare function checkNextPermissionAll(user: UserContext | null | undefined, permissions: readonly string[], registry: RoleRegistry): PermissionResult;
89
+ /**
90
+ * Checks if a user has ANY of the specified permissions.
91
+ *
92
+ * @param user - User context
93
+ * @param permissions - Permissions to check
94
+ * @param registry - Role registry
95
+ * @returns PermissionResult
96
+ */
97
+ declare function checkNextPermissionAny(user: UserContext | null | undefined, permissions: readonly string[], registry: RoleRegistry): PermissionResult;
98
+ /**
99
+ * Higher-order function that wraps a Next.js Route Handler with permission check.
100
+ *
101
+ * @param permission - Required permission
102
+ * @param registry - Role registry
103
+ * @param options - Configuration options
104
+ * @param handler - The route handler to wrap
105
+ * @returns Wrapped route handler
106
+ *
107
+ * @example
108
+ * ```ts
109
+ * // app/api/admin/route.ts
110
+ * import { defineRoles } from 'secure-role-guard/core';
111
+ * import { withPermission } from 'secure-role-guard/adapters/nextjs';
112
+ *
113
+ * const registry = defineRoles({ admin: ['admin.access'] });
114
+ *
115
+ * async function getUser(req: NextRequest) {
116
+ * // Your auth logic to extract user from session/token
117
+ * return { roles: ['admin'] };
118
+ * }
119
+ *
120
+ * export const GET = withPermission(
121
+ * 'admin.access',
122
+ * registry,
123
+ * { getUser },
124
+ * async (request, user) => {
125
+ * return Response.json({ message: 'Welcome, admin!' });
126
+ * }
127
+ * );
128
+ * ```
129
+ */
130
+ declare function withPermission<T extends NextRequest>(permission: string, registry: RoleRegistry, options: NextPermissionOptions, handler: (request: T, user: UserContext) => Response | Promise<Response>): (request: T) => Promise<Response>;
131
+ /**
132
+ * Higher-order function that wraps a handler requiring ALL permissions.
133
+ */
134
+ declare function withAllPermissions<T extends NextRequest>(permissions: readonly string[], registry: RoleRegistry, options: NextPermissionOptions, handler: (request: T, user: UserContext) => Response | Promise<Response>): (request: T) => Promise<Response>;
135
+ /**
136
+ * Higher-order function that wraps a handler requiring ANY permission.
137
+ */
138
+ declare function withAnyPermission<T extends NextRequest>(permissions: readonly string[], registry: RoleRegistry, options: NextPermissionOptions, handler: (request: T, user: UserContext) => Response | Promise<Response>): (request: T) => Promise<Response>;
139
+
140
+ export { type NextPermissionOptions, type NextRequest, type NextResponse, type PermissionResult, checkNextPermission, checkNextPermissionAll, checkNextPermissionAny, withAllPermissions, withAnyPermission, withPermission };
@@ -0,0 +1,138 @@
1
+ 'use strict';
2
+
3
+ // src/core/permission-engine.ts
4
+ var WILDCARD_PERMISSION = "*";
5
+ function collectUserPermissions(user, registry) {
6
+ const permissions = /* @__PURE__ */ new Set();
7
+ if (user.permissions !== void 0) {
8
+ for (const permission of user.permissions) {
9
+ permissions.add(permission);
10
+ }
11
+ }
12
+ if (user.roles !== void 0) {
13
+ for (const role of user.roles) {
14
+ const rolePermissions = registry.getPermissions(role);
15
+ for (const permission of rolePermissions) {
16
+ permissions.add(permission);
17
+ }
18
+ }
19
+ }
20
+ return permissions;
21
+ }
22
+ function hasWildcardAccess(permissions, requested) {
23
+ if (permissions.has(WILDCARD_PERMISSION)) {
24
+ return true;
25
+ }
26
+ const parts = requested.split(".");
27
+ let namespace = "";
28
+ for (let i = 0; i < parts.length - 1; i++) {
29
+ const part = parts[i];
30
+ if (part !== void 0) {
31
+ namespace = namespace === "" ? part : `${namespace}.${part}`;
32
+ if (permissions.has(`${namespace}.*`)) {
33
+ return true;
34
+ }
35
+ }
36
+ }
37
+ return false;
38
+ }
39
+ function canUser(user, permission, registry) {
40
+ if (user === null || user === void 0) {
41
+ return false;
42
+ }
43
+ if (permission === "" || permission.trim() === "") {
44
+ return false;
45
+ }
46
+ const userPermissions = collectUserPermissions(user, registry);
47
+ if (userPermissions.has(permission)) {
48
+ return true;
49
+ }
50
+ if (hasWildcardAccess(userPermissions, permission)) {
51
+ return true;
52
+ }
53
+ return false;
54
+ }
55
+ function canUserAll(user, permissions, registry) {
56
+ if (permissions.length === 0) {
57
+ return false;
58
+ }
59
+ for (const permission of permissions) {
60
+ if (!canUser(user, permission, registry)) {
61
+ return false;
62
+ }
63
+ }
64
+ return true;
65
+ }
66
+ function canUserAny(user, permissions, registry) {
67
+ if (permissions.length === 0) {
68
+ return false;
69
+ }
70
+ for (const permission of permissions) {
71
+ if (canUser(user, permission, registry)) {
72
+ return true;
73
+ }
74
+ }
75
+ return false;
76
+ }
77
+
78
+ // src/adapters/nextjs.ts
79
+ function checkNextPermission(user, permission, registry) {
80
+ return {
81
+ allowed: canUser(user, permission, registry),
82
+ user: user ?? null
83
+ };
84
+ }
85
+ function checkNextPermissionAll(user, permissions, registry) {
86
+ return {
87
+ allowed: canUserAll(user, permissions, registry),
88
+ user: user ?? null
89
+ };
90
+ }
91
+ function checkNextPermissionAny(user, permissions, registry) {
92
+ return {
93
+ allowed: canUserAny(user, permissions, registry),
94
+ user: user ?? null
95
+ };
96
+ }
97
+ function withPermission(permission, registry, options, handler) {
98
+ const statusCode = options.statusCode ?? 403;
99
+ const message = options.message ?? "Forbidden";
100
+ return async (request) => {
101
+ const user = await options.getUser(request);
102
+ if (user === null || !canUser(user, permission, registry)) {
103
+ return Response.json({ error: message }, { status: statusCode });
104
+ }
105
+ return handler(request, user);
106
+ };
107
+ }
108
+ function withAllPermissions(permissions, registry, options, handler) {
109
+ const statusCode = options.statusCode ?? 403;
110
+ const message = options.message ?? "Forbidden";
111
+ return async (request) => {
112
+ const user = await options.getUser(request);
113
+ if (user === null || !canUserAll(user, permissions, registry)) {
114
+ return Response.json({ error: message }, { status: statusCode });
115
+ }
116
+ return handler(request, user);
117
+ };
118
+ }
119
+ function withAnyPermission(permissions, registry, options, handler) {
120
+ const statusCode = options.statusCode ?? 403;
121
+ const message = options.message ?? "Forbidden";
122
+ return async (request) => {
123
+ const user = await options.getUser(request);
124
+ if (user === null || !canUserAny(user, permissions, registry)) {
125
+ return Response.json({ error: message }, { status: statusCode });
126
+ }
127
+ return handler(request, user);
128
+ };
129
+ }
130
+
131
+ exports.checkNextPermission = checkNextPermission;
132
+ exports.checkNextPermissionAll = checkNextPermissionAll;
133
+ exports.checkNextPermissionAny = checkNextPermissionAny;
134
+ exports.withAllPermissions = withAllPermissions;
135
+ exports.withAnyPermission = withAnyPermission;
136
+ exports.withPermission = withPermission;
137
+ //# sourceMappingURL=nextjs.js.map
138
+ //# sourceMappingURL=nextjs.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/core/permission-engine.ts","../../src/adapters/nextjs.ts"],"names":[],"mappings":"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","file":"nextjs.js","sourcesContent":["/**\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","/**\r\n * Secure Role Guard - Next.js Adapter\r\n *\r\n * Thin wrapper for Next.js API routes and Route Handlers.\r\n * Delegates all authorization logic to the core permission engine.\r\n *\r\n * IMPORTANT: This adapter does NOT handle authentication.\r\n * You must provide the user context from your auth system.\r\n */\r\n\r\nimport type { UserContext, RoleRegistry } from \"../core/types\";\r\nimport { canUser, canUserAll, canUserAny } from \"../core/permission-engine\";\r\n\r\n/**\r\n * Next.js Request type (minimal interface for App Router).\r\n * Using minimal interface to avoid requiring next as a dependency.\r\n */\r\nexport interface NextRequest {\r\n /** Headers from the request */\r\n headers: {\r\n get(name: string): string | null;\r\n };\r\n}\r\n\r\n/**\r\n * Next.js Response helper for App Router.\r\n */\r\nexport interface NextResponse {\r\n json(body: unknown, init?: { status?: number }): Response;\r\n}\r\n\r\n/**\r\n * Result of a permission check in Next.js context.\r\n */\r\nexport type PermissionResult = {\r\n readonly allowed: boolean;\r\n readonly user: UserContext | null;\r\n};\r\n\r\n/**\r\n * Options for Next.js permission wrappers.\r\n */\r\nexport type NextPermissionOptions = {\r\n /** Function to extract user context from request */\r\n readonly getUser: (\r\n request: NextRequest\r\n ) => UserContext | null | Promise<UserContext | null>;\r\n /** HTTP status code on denial (default: 403) */\r\n readonly statusCode?: number;\r\n /** Error message on denial (default: 'Forbidden') */\r\n readonly message?: string;\r\n};\r\n\r\n/**\r\n * Checks if a user has permission in a Next.js API context.\r\n *\r\n * This is a pure check function - you handle the response.\r\n *\r\n * @param user - User context\r\n * @param permission - Required permission\r\n * @param registry - Role registry\r\n * @returns PermissionResult\r\n *\r\n * @example\r\n * ```ts\r\n * // app/api/users/route.ts\r\n * import { defineRoles } from 'secure-role-guard/core';\r\n * import { checkNextPermission } from 'secure-role-guard/adapters/nextjs';\r\n *\r\n * const registry = defineRoles({ admin: ['user.update'] });\r\n *\r\n * export async function PUT(request: NextRequest) {\r\n * const user = await getUser(request); // Your auth function\r\n * const result = checkNextPermission(user, 'user.update', registry);\r\n *\r\n * if (!result.allowed) {\r\n * return NextResponse.json({ error: 'Forbidden' }, { status: 403 });\r\n * }\r\n *\r\n * // Handle the request...\r\n * }\r\n * ```\r\n */\r\nexport function checkNextPermission(\r\n user: UserContext | null | undefined,\r\n permission: string,\r\n registry: RoleRegistry\r\n): PermissionResult {\r\n return {\r\n allowed: canUser(user, permission, registry),\r\n user: user ?? null,\r\n };\r\n}\r\n\r\n/**\r\n * Checks if a user has ALL specified permissions.\r\n *\r\n * @param user - User context\r\n * @param permissions - Required permissions\r\n * @param registry - Role registry\r\n * @returns PermissionResult\r\n */\r\nexport function checkNextPermissionAll(\r\n user: UserContext | null | undefined,\r\n permissions: readonly string[],\r\n registry: RoleRegistry\r\n): PermissionResult {\r\n return {\r\n allowed: canUserAll(user, permissions, registry),\r\n user: user ?? null,\r\n };\r\n}\r\n\r\n/**\r\n * Checks if a user has ANY of the specified permissions.\r\n *\r\n * @param user - User context\r\n * @param permissions - Permissions to check\r\n * @param registry - Role registry\r\n * @returns PermissionResult\r\n */\r\nexport function checkNextPermissionAny(\r\n user: UserContext | null | undefined,\r\n permissions: readonly string[],\r\n registry: RoleRegistry\r\n): PermissionResult {\r\n return {\r\n allowed: canUserAny(user, permissions, registry),\r\n user: user ?? null,\r\n };\r\n}\r\n\r\n/**\r\n * Higher-order function that wraps a Next.js Route Handler with permission check.\r\n *\r\n * @param permission - Required permission\r\n * @param registry - Role registry\r\n * @param options - Configuration options\r\n * @param handler - The route handler to wrap\r\n * @returns Wrapped route handler\r\n *\r\n * @example\r\n * ```ts\r\n * // app/api/admin/route.ts\r\n * import { defineRoles } from 'secure-role-guard/core';\r\n * import { withPermission } from 'secure-role-guard/adapters/nextjs';\r\n *\r\n * const registry = defineRoles({ admin: ['admin.access'] });\r\n *\r\n * async function getUser(req: NextRequest) {\r\n * // Your auth logic to extract user from session/token\r\n * return { roles: ['admin'] };\r\n * }\r\n *\r\n * export const GET = withPermission(\r\n * 'admin.access',\r\n * registry,\r\n * { getUser },\r\n * async (request, user) => {\r\n * return Response.json({ message: 'Welcome, admin!' });\r\n * }\r\n * );\r\n * ```\r\n */\r\nexport function withPermission<T extends NextRequest>(\r\n permission: string,\r\n registry: RoleRegistry,\r\n options: NextPermissionOptions,\r\n handler: (request: T, user: UserContext) => Response | Promise<Response>\r\n): (request: T) => Promise<Response> {\r\n const statusCode = options.statusCode ?? 403;\r\n const message = options.message ?? \"Forbidden\";\r\n\r\n return async (request: T): Promise<Response> => {\r\n const user = await options.getUser(request);\r\n\r\n if (user === null || !canUser(user, permission, registry)) {\r\n return Response.json({ error: message }, { status: statusCode });\r\n }\r\n\r\n return handler(request, user);\r\n };\r\n}\r\n\r\n/**\r\n * Higher-order function that wraps a handler requiring ALL permissions.\r\n */\r\nexport function withAllPermissions<T extends NextRequest>(\r\n permissions: readonly string[],\r\n registry: RoleRegistry,\r\n options: NextPermissionOptions,\r\n handler: (request: T, user: UserContext) => Response | Promise<Response>\r\n): (request: T) => Promise<Response> {\r\n const statusCode = options.statusCode ?? 403;\r\n const message = options.message ?? \"Forbidden\";\r\n\r\n return async (request: T): Promise<Response> => {\r\n const user = await options.getUser(request);\r\n\r\n if (user === null || !canUserAll(user, permissions, registry)) {\r\n return Response.json({ error: message }, { status: statusCode });\r\n }\r\n\r\n return handler(request, user);\r\n };\r\n}\r\n\r\n/**\r\n * Higher-order function that wraps a handler requiring ANY permission.\r\n */\r\nexport function withAnyPermission<T extends NextRequest>(\r\n permissions: readonly string[],\r\n registry: RoleRegistry,\r\n options: NextPermissionOptions,\r\n handler: (request: T, user: UserContext) => Response | Promise<Response>\r\n): (request: T) => Promise<Response> {\r\n const statusCode = options.statusCode ?? 403;\r\n const message = options.message ?? \"Forbidden\";\r\n\r\n return async (request: T): Promise<Response> => {\r\n const user = await options.getUser(request);\r\n\r\n if (user === null || !canUserAny(user, permissions, registry)) {\r\n return Response.json({ error: message }, { status: statusCode });\r\n }\r\n\r\n return handler(request, user);\r\n };\r\n}\r\n"]}
@@ -0,0 +1,131 @@
1
+ // src/core/permission-engine.ts
2
+ var WILDCARD_PERMISSION = "*";
3
+ function collectUserPermissions(user, registry) {
4
+ const permissions = /* @__PURE__ */ new Set();
5
+ if (user.permissions !== void 0) {
6
+ for (const permission of user.permissions) {
7
+ permissions.add(permission);
8
+ }
9
+ }
10
+ if (user.roles !== void 0) {
11
+ for (const role of user.roles) {
12
+ const rolePermissions = registry.getPermissions(role);
13
+ for (const permission of rolePermissions) {
14
+ permissions.add(permission);
15
+ }
16
+ }
17
+ }
18
+ return permissions;
19
+ }
20
+ function hasWildcardAccess(permissions, requested) {
21
+ if (permissions.has(WILDCARD_PERMISSION)) {
22
+ return true;
23
+ }
24
+ const parts = requested.split(".");
25
+ let namespace = "";
26
+ for (let i = 0; i < parts.length - 1; i++) {
27
+ const part = parts[i];
28
+ if (part !== void 0) {
29
+ namespace = namespace === "" ? part : `${namespace}.${part}`;
30
+ if (permissions.has(`${namespace}.*`)) {
31
+ return true;
32
+ }
33
+ }
34
+ }
35
+ return false;
36
+ }
37
+ function canUser(user, permission, registry) {
38
+ if (user === null || user === void 0) {
39
+ return false;
40
+ }
41
+ if (permission === "" || permission.trim() === "") {
42
+ return false;
43
+ }
44
+ const userPermissions = collectUserPermissions(user, registry);
45
+ if (userPermissions.has(permission)) {
46
+ return true;
47
+ }
48
+ if (hasWildcardAccess(userPermissions, permission)) {
49
+ return true;
50
+ }
51
+ return false;
52
+ }
53
+ function canUserAll(user, permissions, registry) {
54
+ if (permissions.length === 0) {
55
+ return false;
56
+ }
57
+ for (const permission of permissions) {
58
+ if (!canUser(user, permission, registry)) {
59
+ return false;
60
+ }
61
+ }
62
+ return true;
63
+ }
64
+ function canUserAny(user, permissions, registry) {
65
+ if (permissions.length === 0) {
66
+ return false;
67
+ }
68
+ for (const permission of permissions) {
69
+ if (canUser(user, permission, registry)) {
70
+ return true;
71
+ }
72
+ }
73
+ return false;
74
+ }
75
+
76
+ // src/adapters/nextjs.ts
77
+ function checkNextPermission(user, permission, registry) {
78
+ return {
79
+ allowed: canUser(user, permission, registry),
80
+ user: user ?? null
81
+ };
82
+ }
83
+ function checkNextPermissionAll(user, permissions, registry) {
84
+ return {
85
+ allowed: canUserAll(user, permissions, registry),
86
+ user: user ?? null
87
+ };
88
+ }
89
+ function checkNextPermissionAny(user, permissions, registry) {
90
+ return {
91
+ allowed: canUserAny(user, permissions, registry),
92
+ user: user ?? null
93
+ };
94
+ }
95
+ function withPermission(permission, registry, options, handler) {
96
+ const statusCode = options.statusCode ?? 403;
97
+ const message = options.message ?? "Forbidden";
98
+ return async (request) => {
99
+ const user = await options.getUser(request);
100
+ if (user === null || !canUser(user, permission, registry)) {
101
+ return Response.json({ error: message }, { status: statusCode });
102
+ }
103
+ return handler(request, user);
104
+ };
105
+ }
106
+ function withAllPermissions(permissions, registry, options, handler) {
107
+ const statusCode = options.statusCode ?? 403;
108
+ const message = options.message ?? "Forbidden";
109
+ return async (request) => {
110
+ const user = await options.getUser(request);
111
+ if (user === null || !canUserAll(user, permissions, registry)) {
112
+ return Response.json({ error: message }, { status: statusCode });
113
+ }
114
+ return handler(request, user);
115
+ };
116
+ }
117
+ function withAnyPermission(permissions, registry, options, handler) {
118
+ const statusCode = options.statusCode ?? 403;
119
+ const message = options.message ?? "Forbidden";
120
+ return async (request) => {
121
+ const user = await options.getUser(request);
122
+ if (user === null || !canUserAny(user, permissions, registry)) {
123
+ return Response.json({ error: message }, { status: statusCode });
124
+ }
125
+ return handler(request, user);
126
+ };
127
+ }
128
+
129
+ export { checkNextPermission, checkNextPermissionAll, checkNextPermissionAny, withAllPermissions, withAnyPermission, withPermission };
130
+ //# sourceMappingURL=nextjs.mjs.map
131
+ //# sourceMappingURL=nextjs.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/core/permission-engine.ts","../../src/adapters/nextjs.ts"],"names":[],"mappings":"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","file":"nextjs.mjs","sourcesContent":["/**\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","/**\r\n * Secure Role Guard - Next.js Adapter\r\n *\r\n * Thin wrapper for Next.js API routes and Route Handlers.\r\n * Delegates all authorization logic to the core permission engine.\r\n *\r\n * IMPORTANT: This adapter does NOT handle authentication.\r\n * You must provide the user context from your auth system.\r\n */\r\n\r\nimport type { UserContext, RoleRegistry } from \"../core/types\";\r\nimport { canUser, canUserAll, canUserAny } from \"../core/permission-engine\";\r\n\r\n/**\r\n * Next.js Request type (minimal interface for App Router).\r\n * Using minimal interface to avoid requiring next as a dependency.\r\n */\r\nexport interface NextRequest {\r\n /** Headers from the request */\r\n headers: {\r\n get(name: string): string | null;\r\n };\r\n}\r\n\r\n/**\r\n * Next.js Response helper for App Router.\r\n */\r\nexport interface NextResponse {\r\n json(body: unknown, init?: { status?: number }): Response;\r\n}\r\n\r\n/**\r\n * Result of a permission check in Next.js context.\r\n */\r\nexport type PermissionResult = {\r\n readonly allowed: boolean;\r\n readonly user: UserContext | null;\r\n};\r\n\r\n/**\r\n * Options for Next.js permission wrappers.\r\n */\r\nexport type NextPermissionOptions = {\r\n /** Function to extract user context from request */\r\n readonly getUser: (\r\n request: NextRequest\r\n ) => UserContext | null | Promise<UserContext | null>;\r\n /** HTTP status code on denial (default: 403) */\r\n readonly statusCode?: number;\r\n /** Error message on denial (default: 'Forbidden') */\r\n readonly message?: string;\r\n};\r\n\r\n/**\r\n * Checks if a user has permission in a Next.js API context.\r\n *\r\n * This is a pure check function - you handle the response.\r\n *\r\n * @param user - User context\r\n * @param permission - Required permission\r\n * @param registry - Role registry\r\n * @returns PermissionResult\r\n *\r\n * @example\r\n * ```ts\r\n * // app/api/users/route.ts\r\n * import { defineRoles } from 'secure-role-guard/core';\r\n * import { checkNextPermission } from 'secure-role-guard/adapters/nextjs';\r\n *\r\n * const registry = defineRoles({ admin: ['user.update'] });\r\n *\r\n * export async function PUT(request: NextRequest) {\r\n * const user = await getUser(request); // Your auth function\r\n * const result = checkNextPermission(user, 'user.update', registry);\r\n *\r\n * if (!result.allowed) {\r\n * return NextResponse.json({ error: 'Forbidden' }, { status: 403 });\r\n * }\r\n *\r\n * // Handle the request...\r\n * }\r\n * ```\r\n */\r\nexport function checkNextPermission(\r\n user: UserContext | null | undefined,\r\n permission: string,\r\n registry: RoleRegistry\r\n): PermissionResult {\r\n return {\r\n allowed: canUser(user, permission, registry),\r\n user: user ?? null,\r\n };\r\n}\r\n\r\n/**\r\n * Checks if a user has ALL specified permissions.\r\n *\r\n * @param user - User context\r\n * @param permissions - Required permissions\r\n * @param registry - Role registry\r\n * @returns PermissionResult\r\n */\r\nexport function checkNextPermissionAll(\r\n user: UserContext | null | undefined,\r\n permissions: readonly string[],\r\n registry: RoleRegistry\r\n): PermissionResult {\r\n return {\r\n allowed: canUserAll(user, permissions, registry),\r\n user: user ?? null,\r\n };\r\n}\r\n\r\n/**\r\n * Checks if a user has ANY of the specified permissions.\r\n *\r\n * @param user - User context\r\n * @param permissions - Permissions to check\r\n * @param registry - Role registry\r\n * @returns PermissionResult\r\n */\r\nexport function checkNextPermissionAny(\r\n user: UserContext | null | undefined,\r\n permissions: readonly string[],\r\n registry: RoleRegistry\r\n): PermissionResult {\r\n return {\r\n allowed: canUserAny(user, permissions, registry),\r\n user: user ?? null,\r\n };\r\n}\r\n\r\n/**\r\n * Higher-order function that wraps a Next.js Route Handler with permission check.\r\n *\r\n * @param permission - Required permission\r\n * @param registry - Role registry\r\n * @param options - Configuration options\r\n * @param handler - The route handler to wrap\r\n * @returns Wrapped route handler\r\n *\r\n * @example\r\n * ```ts\r\n * // app/api/admin/route.ts\r\n * import { defineRoles } from 'secure-role-guard/core';\r\n * import { withPermission } from 'secure-role-guard/adapters/nextjs';\r\n *\r\n * const registry = defineRoles({ admin: ['admin.access'] });\r\n *\r\n * async function getUser(req: NextRequest) {\r\n * // Your auth logic to extract user from session/token\r\n * return { roles: ['admin'] };\r\n * }\r\n *\r\n * export const GET = withPermission(\r\n * 'admin.access',\r\n * registry,\r\n * { getUser },\r\n * async (request, user) => {\r\n * return Response.json({ message: 'Welcome, admin!' });\r\n * }\r\n * );\r\n * ```\r\n */\r\nexport function withPermission<T extends NextRequest>(\r\n permission: string,\r\n registry: RoleRegistry,\r\n options: NextPermissionOptions,\r\n handler: (request: T, user: UserContext) => Response | Promise<Response>\r\n): (request: T) => Promise<Response> {\r\n const statusCode = options.statusCode ?? 403;\r\n const message = options.message ?? \"Forbidden\";\r\n\r\n return async (request: T): Promise<Response> => {\r\n const user = await options.getUser(request);\r\n\r\n if (user === null || !canUser(user, permission, registry)) {\r\n return Response.json({ error: message }, { status: statusCode });\r\n }\r\n\r\n return handler(request, user);\r\n };\r\n}\r\n\r\n/**\r\n * Higher-order function that wraps a handler requiring ALL permissions.\r\n */\r\nexport function withAllPermissions<T extends NextRequest>(\r\n permissions: readonly string[],\r\n registry: RoleRegistry,\r\n options: NextPermissionOptions,\r\n handler: (request: T, user: UserContext) => Response | Promise<Response>\r\n): (request: T) => Promise<Response> {\r\n const statusCode = options.statusCode ?? 403;\r\n const message = options.message ?? \"Forbidden\";\r\n\r\n return async (request: T): Promise<Response> => {\r\n const user = await options.getUser(request);\r\n\r\n if (user === null || !canUserAll(user, permissions, registry)) {\r\n return Response.json({ error: message }, { status: statusCode });\r\n }\r\n\r\n return handler(request, user);\r\n };\r\n}\r\n\r\n/**\r\n * Higher-order function that wraps a handler requiring ANY permission.\r\n */\r\nexport function withAnyPermission<T extends NextRequest>(\r\n permissions: readonly string[],\r\n registry: RoleRegistry,\r\n options: NextPermissionOptions,\r\n handler: (request: T, user: UserContext) => Response | Promise<Response>\r\n): (request: T) => Promise<Response> {\r\n const statusCode = options.statusCode ?? 403;\r\n const message = options.message ?? \"Forbidden\";\r\n\r\n return async (request: T): Promise<Response> => {\r\n const user = await options.getUser(request);\r\n\r\n if (user === null || !canUserAny(user, permissions, registry)) {\r\n return Response.json({ error: message }, { status: statusCode });\r\n }\r\n\r\n return handler(request, user);\r\n };\r\n}\r\n"]}
@@ -0,0 +1,100 @@
1
+ import { R as RoleDefinition, a as RoleRegistry, U as UserContext, b as PermissionCheckResult } from '../types-CSUpaGsY.mjs';
2
+ export { c as PermissionCheckOptions, P as PermissionEngineConfig } from '../types-CSUpaGsY.mjs';
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 };