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.
- package/LICENSE +21 -0
- package/README.md +813 -0
- package/dist/adapters/express.d.mts +109 -0
- package/dist/adapters/express.d.ts +109 -0
- package/dist/adapters/express.js +122 -0
- package/dist/adapters/express.js.map +1 -0
- package/dist/adapters/express.mjs +118 -0
- package/dist/adapters/express.mjs.map +1 -0
- package/dist/adapters/index.d.mts +3 -0
- package/dist/adapters/index.d.ts +3 -0
- package/dist/adapters/index.js +181 -0
- package/dist/adapters/index.js.map +1 -0
- package/dist/adapters/index.mjs +171 -0
- package/dist/adapters/index.mjs.map +1 -0
- package/dist/adapters/nextjs.d.mts +140 -0
- package/dist/adapters/nextjs.d.ts +140 -0
- package/dist/adapters/nextjs.js +138 -0
- package/dist/adapters/nextjs.js.map +1 -0
- package/dist/adapters/nextjs.mjs +131 -0
- package/dist/adapters/nextjs.mjs.map +1 -0
- package/dist/core/index.d.mts +100 -0
- package/dist/core/index.d.ts +100 -0
- package/dist/core/index.js +132 -0
- package/dist/core/index.js.map +1 -0
- package/dist/core/index.mjs +125 -0
- package/dist/core/index.mjs.map +1 -0
- package/dist/index.d.mts +4 -0
- package/dist/index.d.ts +4 -0
- package/dist/index.js +238 -0
- package/dist/index.js.map +1 -0
- package/dist/index.mjs +222 -0
- package/dist/index.mjs.map +1 -0
- package/dist/react/index.d.mts +237 -0
- package/dist/react/index.d.ts +237 -0
- package/dist/react/index.js +177 -0
- package/dist/react/index.js.map +1 -0
- package/dist/react/index.mjs +167 -0
- package/dist/react/index.mjs.map +1 -0
- package/dist/types-CSUpaGsY.d.mts +76 -0
- package/dist/types-CSUpaGsY.d.ts +76 -0
- package/package.json +99 -0
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
import { a as RoleRegistry, U as UserContext } from '../types-CSUpaGsY.mjs';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Secure Role Guard - Express Adapter
|
|
5
|
+
*
|
|
6
|
+
* Thin middleware wrapper for Express.js applications.
|
|
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 middleware.
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Express-compatible Request type (minimal interface).
|
|
15
|
+
* We use a minimal interface to avoid requiring express as a dependency.
|
|
16
|
+
*/
|
|
17
|
+
interface ExpressRequest {
|
|
18
|
+
/** User context attached by your auth middleware */
|
|
19
|
+
user?: UserContext;
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* Express-compatible Response type (minimal interface).
|
|
23
|
+
*/
|
|
24
|
+
interface ExpressResponse {
|
|
25
|
+
status(code: number): ExpressResponse;
|
|
26
|
+
json(body: unknown): ExpressResponse;
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* Express-compatible NextFunction type.
|
|
30
|
+
*/
|
|
31
|
+
type ExpressNextFunction = (error?: unknown) => void;
|
|
32
|
+
/**
|
|
33
|
+
* Express middleware signature.
|
|
34
|
+
*/
|
|
35
|
+
type ExpressMiddleware = (req: ExpressRequest, res: ExpressResponse, next: ExpressNextFunction) => void;
|
|
36
|
+
/**
|
|
37
|
+
* Options for the permission middleware.
|
|
38
|
+
*/
|
|
39
|
+
type PermissionMiddlewareOptions = {
|
|
40
|
+
/** HTTP status code to return on denial (default: 403) */
|
|
41
|
+
readonly statusCode?: number;
|
|
42
|
+
/** Custom error message (default: 'Forbidden') */
|
|
43
|
+
readonly message?: string;
|
|
44
|
+
/** Custom function to extract user from request */
|
|
45
|
+
readonly getUser?: (req: ExpressRequest) => UserContext | null | undefined;
|
|
46
|
+
};
|
|
47
|
+
/**
|
|
48
|
+
* Creates Express middleware that requires a specific permission.
|
|
49
|
+
*
|
|
50
|
+
* SECURITY: Denies by default if user is missing or permission not granted.
|
|
51
|
+
*
|
|
52
|
+
* @param permission - Required permission
|
|
53
|
+
* @param registry - Role registry
|
|
54
|
+
* @param options - Optional configuration
|
|
55
|
+
* @returns Express middleware function
|
|
56
|
+
*
|
|
57
|
+
* @example
|
|
58
|
+
* ```ts
|
|
59
|
+
* import { defineRoles } from 'secure-role-guard/core';
|
|
60
|
+
* import { requirePermission } from 'secure-role-guard/adapters/express';
|
|
61
|
+
*
|
|
62
|
+
* const registry = defineRoles({ admin: ['user.update'] });
|
|
63
|
+
*
|
|
64
|
+
* app.put('/users/:id',
|
|
65
|
+
* authMiddleware, // Your auth middleware sets req.user
|
|
66
|
+
* requirePermission('user.update', registry),
|
|
67
|
+
* updateUserHandler
|
|
68
|
+
* );
|
|
69
|
+
* ```
|
|
70
|
+
*/
|
|
71
|
+
declare function requirePermission(permission: string, registry: RoleRegistry, options?: PermissionMiddlewareOptions): ExpressMiddleware;
|
|
72
|
+
/**
|
|
73
|
+
* Creates Express middleware that requires ALL specified permissions.
|
|
74
|
+
*
|
|
75
|
+
* @param permissions - Array of required permissions
|
|
76
|
+
* @param registry - Role registry
|
|
77
|
+
* @param options - Optional configuration
|
|
78
|
+
* @returns Express middleware function
|
|
79
|
+
*
|
|
80
|
+
* @example
|
|
81
|
+
* ```ts
|
|
82
|
+
* app.delete('/users/:id',
|
|
83
|
+
* authMiddleware,
|
|
84
|
+
* requireAllPermissions(['user.read', 'user.delete'], registry),
|
|
85
|
+
* deleteUserHandler
|
|
86
|
+
* );
|
|
87
|
+
* ```
|
|
88
|
+
*/
|
|
89
|
+
declare function requireAllPermissions(permissions: readonly string[], registry: RoleRegistry, options?: PermissionMiddlewareOptions): ExpressMiddleware;
|
|
90
|
+
/**
|
|
91
|
+
* Creates Express middleware that requires ANY of the specified permissions.
|
|
92
|
+
*
|
|
93
|
+
* @param permissions - Array of permissions (user needs at least one)
|
|
94
|
+
* @param registry - Role registry
|
|
95
|
+
* @param options - Optional configuration
|
|
96
|
+
* @returns Express middleware function
|
|
97
|
+
*
|
|
98
|
+
* @example
|
|
99
|
+
* ```ts
|
|
100
|
+
* app.get('/reports',
|
|
101
|
+
* authMiddleware,
|
|
102
|
+
* requireAnyPermission(['report.admin', 'report.viewer'], registry),
|
|
103
|
+
* getReportsHandler
|
|
104
|
+
* );
|
|
105
|
+
* ```
|
|
106
|
+
*/
|
|
107
|
+
declare function requireAnyPermission(permissions: readonly string[], registry: RoleRegistry, options?: PermissionMiddlewareOptions): ExpressMiddleware;
|
|
108
|
+
|
|
109
|
+
export { type ExpressMiddleware, type ExpressNextFunction, type ExpressRequest, type ExpressResponse, type PermissionMiddlewareOptions, requireAllPermissions, requireAnyPermission, requirePermission };
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
import { a as RoleRegistry, U as UserContext } from '../types-CSUpaGsY.js';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Secure Role Guard - Express Adapter
|
|
5
|
+
*
|
|
6
|
+
* Thin middleware wrapper for Express.js applications.
|
|
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 middleware.
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Express-compatible Request type (minimal interface).
|
|
15
|
+
* We use a minimal interface to avoid requiring express as a dependency.
|
|
16
|
+
*/
|
|
17
|
+
interface ExpressRequest {
|
|
18
|
+
/** User context attached by your auth middleware */
|
|
19
|
+
user?: UserContext;
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* Express-compatible Response type (minimal interface).
|
|
23
|
+
*/
|
|
24
|
+
interface ExpressResponse {
|
|
25
|
+
status(code: number): ExpressResponse;
|
|
26
|
+
json(body: unknown): ExpressResponse;
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* Express-compatible NextFunction type.
|
|
30
|
+
*/
|
|
31
|
+
type ExpressNextFunction = (error?: unknown) => void;
|
|
32
|
+
/**
|
|
33
|
+
* Express middleware signature.
|
|
34
|
+
*/
|
|
35
|
+
type ExpressMiddleware = (req: ExpressRequest, res: ExpressResponse, next: ExpressNextFunction) => void;
|
|
36
|
+
/**
|
|
37
|
+
* Options for the permission middleware.
|
|
38
|
+
*/
|
|
39
|
+
type PermissionMiddlewareOptions = {
|
|
40
|
+
/** HTTP status code to return on denial (default: 403) */
|
|
41
|
+
readonly statusCode?: number;
|
|
42
|
+
/** Custom error message (default: 'Forbidden') */
|
|
43
|
+
readonly message?: string;
|
|
44
|
+
/** Custom function to extract user from request */
|
|
45
|
+
readonly getUser?: (req: ExpressRequest) => UserContext | null | undefined;
|
|
46
|
+
};
|
|
47
|
+
/**
|
|
48
|
+
* Creates Express middleware that requires a specific permission.
|
|
49
|
+
*
|
|
50
|
+
* SECURITY: Denies by default if user is missing or permission not granted.
|
|
51
|
+
*
|
|
52
|
+
* @param permission - Required permission
|
|
53
|
+
* @param registry - Role registry
|
|
54
|
+
* @param options - Optional configuration
|
|
55
|
+
* @returns Express middleware function
|
|
56
|
+
*
|
|
57
|
+
* @example
|
|
58
|
+
* ```ts
|
|
59
|
+
* import { defineRoles } from 'secure-role-guard/core';
|
|
60
|
+
* import { requirePermission } from 'secure-role-guard/adapters/express';
|
|
61
|
+
*
|
|
62
|
+
* const registry = defineRoles({ admin: ['user.update'] });
|
|
63
|
+
*
|
|
64
|
+
* app.put('/users/:id',
|
|
65
|
+
* authMiddleware, // Your auth middleware sets req.user
|
|
66
|
+
* requirePermission('user.update', registry),
|
|
67
|
+
* updateUserHandler
|
|
68
|
+
* );
|
|
69
|
+
* ```
|
|
70
|
+
*/
|
|
71
|
+
declare function requirePermission(permission: string, registry: RoleRegistry, options?: PermissionMiddlewareOptions): ExpressMiddleware;
|
|
72
|
+
/**
|
|
73
|
+
* Creates Express middleware that requires ALL specified permissions.
|
|
74
|
+
*
|
|
75
|
+
* @param permissions - Array of required permissions
|
|
76
|
+
* @param registry - Role registry
|
|
77
|
+
* @param options - Optional configuration
|
|
78
|
+
* @returns Express middleware function
|
|
79
|
+
*
|
|
80
|
+
* @example
|
|
81
|
+
* ```ts
|
|
82
|
+
* app.delete('/users/:id',
|
|
83
|
+
* authMiddleware,
|
|
84
|
+
* requireAllPermissions(['user.read', 'user.delete'], registry),
|
|
85
|
+
* deleteUserHandler
|
|
86
|
+
* );
|
|
87
|
+
* ```
|
|
88
|
+
*/
|
|
89
|
+
declare function requireAllPermissions(permissions: readonly string[], registry: RoleRegistry, options?: PermissionMiddlewareOptions): ExpressMiddleware;
|
|
90
|
+
/**
|
|
91
|
+
* Creates Express middleware that requires ANY of the specified permissions.
|
|
92
|
+
*
|
|
93
|
+
* @param permissions - Array of permissions (user needs at least one)
|
|
94
|
+
* @param registry - Role registry
|
|
95
|
+
* @param options - Optional configuration
|
|
96
|
+
* @returns Express middleware function
|
|
97
|
+
*
|
|
98
|
+
* @example
|
|
99
|
+
* ```ts
|
|
100
|
+
* app.get('/reports',
|
|
101
|
+
* authMiddleware,
|
|
102
|
+
* requireAnyPermission(['report.admin', 'report.viewer'], registry),
|
|
103
|
+
* getReportsHandler
|
|
104
|
+
* );
|
|
105
|
+
* ```
|
|
106
|
+
*/
|
|
107
|
+
declare function requireAnyPermission(permissions: readonly string[], registry: RoleRegistry, options?: PermissionMiddlewareOptions): ExpressMiddleware;
|
|
108
|
+
|
|
109
|
+
export { type ExpressMiddleware, type ExpressNextFunction, type ExpressRequest, type ExpressResponse, type PermissionMiddlewareOptions, requireAllPermissions, requireAnyPermission, requirePermission };
|
|
@@ -0,0 +1,122 @@
|
|
|
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/express.ts
|
|
79
|
+
var DEFAULT_OPTIONS = {
|
|
80
|
+
statusCode: 403,
|
|
81
|
+
message: "Forbidden",
|
|
82
|
+
getUser: (req) => req.user
|
|
83
|
+
};
|
|
84
|
+
function requirePermission(permission, registry, options = {}) {
|
|
85
|
+
const opts = { ...DEFAULT_OPTIONS, ...options };
|
|
86
|
+
return (req, res, next) => {
|
|
87
|
+
const user = opts.getUser(req);
|
|
88
|
+
if (canUser(user, permission, registry)) {
|
|
89
|
+
next();
|
|
90
|
+
} else {
|
|
91
|
+
res.status(opts.statusCode).json({ error: opts.message });
|
|
92
|
+
}
|
|
93
|
+
};
|
|
94
|
+
}
|
|
95
|
+
function requireAllPermissions(permissions, registry, options = {}) {
|
|
96
|
+
const opts = { ...DEFAULT_OPTIONS, ...options };
|
|
97
|
+
return (req, res, next) => {
|
|
98
|
+
const user = opts.getUser(req);
|
|
99
|
+
if (canUserAll(user, permissions, registry)) {
|
|
100
|
+
next();
|
|
101
|
+
} else {
|
|
102
|
+
res.status(opts.statusCode).json({ error: opts.message });
|
|
103
|
+
}
|
|
104
|
+
};
|
|
105
|
+
}
|
|
106
|
+
function requireAnyPermission(permissions, registry, options = {}) {
|
|
107
|
+
const opts = { ...DEFAULT_OPTIONS, ...options };
|
|
108
|
+
return (req, res, next) => {
|
|
109
|
+
const user = opts.getUser(req);
|
|
110
|
+
if (canUserAny(user, permissions, registry)) {
|
|
111
|
+
next();
|
|
112
|
+
} else {
|
|
113
|
+
res.status(opts.statusCode).json({ error: opts.message });
|
|
114
|
+
}
|
|
115
|
+
};
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
exports.requireAllPermissions = requireAllPermissions;
|
|
119
|
+
exports.requireAnyPermission = requireAnyPermission;
|
|
120
|
+
exports.requirePermission = requirePermission;
|
|
121
|
+
//# sourceMappingURL=express.js.map
|
|
122
|
+
//# sourceMappingURL=express.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../src/core/permission-engine.ts","../../src/adapters/express.ts"],"names":[],"mappings":"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","file":"express.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 - Express Adapter\r\n *\r\n * Thin middleware wrapper for Express.js applications.\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 middleware.\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 * Express-compatible Request type (minimal interface).\r\n * We use a minimal interface to avoid requiring express as a dependency.\r\n */\r\nexport interface ExpressRequest {\r\n /** User context attached by your auth middleware */\r\n user?: UserContext;\r\n}\r\n\r\n/**\r\n * Express-compatible Response type (minimal interface).\r\n */\r\nexport interface ExpressResponse {\r\n status(code: number): ExpressResponse;\r\n json(body: unknown): ExpressResponse;\r\n}\r\n\r\n/**\r\n * Express-compatible NextFunction type.\r\n */\r\nexport type ExpressNextFunction = (error?: unknown) => void;\r\n\r\n/**\r\n * Express middleware signature.\r\n */\r\nexport type ExpressMiddleware = (\r\n req: ExpressRequest,\r\n res: ExpressResponse,\r\n next: ExpressNextFunction\r\n) => void;\r\n\r\n/**\r\n * Options for the permission middleware.\r\n */\r\nexport type PermissionMiddlewareOptions = {\r\n /** HTTP status code to return on denial (default: 403) */\r\n readonly statusCode?: number;\r\n /** Custom error message (default: 'Forbidden') */\r\n readonly message?: string;\r\n /** Custom function to extract user from request */\r\n readonly getUser?: (req: ExpressRequest) => UserContext | null | undefined;\r\n};\r\n\r\nconst DEFAULT_OPTIONS: Required<PermissionMiddlewareOptions> = {\r\n statusCode: 403,\r\n message: \"Forbidden\",\r\n getUser: (req) => req.user,\r\n};\r\n\r\n/**\r\n * Creates Express middleware that requires a specific permission.\r\n *\r\n * SECURITY: Denies by default if user is missing or permission not granted.\r\n *\r\n * @param permission - Required permission\r\n * @param registry - Role registry\r\n * @param options - Optional configuration\r\n * @returns Express middleware function\r\n *\r\n * @example\r\n * ```ts\r\n * import { defineRoles } from 'secure-role-guard/core';\r\n * import { requirePermission } from 'secure-role-guard/adapters/express';\r\n *\r\n * const registry = defineRoles({ admin: ['user.update'] });\r\n *\r\n * app.put('/users/:id',\r\n * authMiddleware, // Your auth middleware sets req.user\r\n * requirePermission('user.update', registry),\r\n * updateUserHandler\r\n * );\r\n * ```\r\n */\r\nexport function requirePermission(\r\n permission: string,\r\n registry: RoleRegistry,\r\n options: PermissionMiddlewareOptions = {}\r\n): ExpressMiddleware {\r\n const opts = { ...DEFAULT_OPTIONS, ...options };\r\n\r\n return (req, res, next) => {\r\n const user = opts.getUser(req);\r\n\r\n if (canUser(user, permission, registry)) {\r\n next();\r\n } else {\r\n res.status(opts.statusCode).json({ error: opts.message });\r\n }\r\n };\r\n}\r\n\r\n/**\r\n * Creates Express middleware that requires ALL specified permissions.\r\n *\r\n * @param permissions - Array of required permissions\r\n * @param registry - Role registry\r\n * @param options - Optional configuration\r\n * @returns Express middleware function\r\n *\r\n * @example\r\n * ```ts\r\n * app.delete('/users/:id',\r\n * authMiddleware,\r\n * requireAllPermissions(['user.read', 'user.delete'], registry),\r\n * deleteUserHandler\r\n * );\r\n * ```\r\n */\r\nexport function requireAllPermissions(\r\n permissions: readonly string[],\r\n registry: RoleRegistry,\r\n options: PermissionMiddlewareOptions = {}\r\n): ExpressMiddleware {\r\n const opts = { ...DEFAULT_OPTIONS, ...options };\r\n\r\n return (req, res, next) => {\r\n const user = opts.getUser(req);\r\n\r\n if (canUserAll(user, permissions, registry)) {\r\n next();\r\n } else {\r\n res.status(opts.statusCode).json({ error: opts.message });\r\n }\r\n };\r\n}\r\n\r\n/**\r\n * Creates Express middleware that requires ANY of the specified permissions.\r\n *\r\n * @param permissions - Array of permissions (user needs at least one)\r\n * @param registry - Role registry\r\n * @param options - Optional configuration\r\n * @returns Express middleware function\r\n *\r\n * @example\r\n * ```ts\r\n * app.get('/reports',\r\n * authMiddleware,\r\n * requireAnyPermission(['report.admin', 'report.viewer'], registry),\r\n * getReportsHandler\r\n * );\r\n * ```\r\n */\r\nexport function requireAnyPermission(\r\n permissions: readonly string[],\r\n registry: RoleRegistry,\r\n options: PermissionMiddlewareOptions = {}\r\n): ExpressMiddleware {\r\n const opts = { ...DEFAULT_OPTIONS, ...options };\r\n\r\n return (req, res, next) => {\r\n const user = opts.getUser(req);\r\n\r\n if (canUserAny(user, permissions, registry)) {\r\n next();\r\n } else {\r\n res.status(opts.statusCode).json({ error: opts.message });\r\n }\r\n };\r\n}\r\n"]}
|
|
@@ -0,0 +1,118 @@
|
|
|
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/express.ts
|
|
77
|
+
var DEFAULT_OPTIONS = {
|
|
78
|
+
statusCode: 403,
|
|
79
|
+
message: "Forbidden",
|
|
80
|
+
getUser: (req) => req.user
|
|
81
|
+
};
|
|
82
|
+
function requirePermission(permission, registry, options = {}) {
|
|
83
|
+
const opts = { ...DEFAULT_OPTIONS, ...options };
|
|
84
|
+
return (req, res, next) => {
|
|
85
|
+
const user = opts.getUser(req);
|
|
86
|
+
if (canUser(user, permission, registry)) {
|
|
87
|
+
next();
|
|
88
|
+
} else {
|
|
89
|
+
res.status(opts.statusCode).json({ error: opts.message });
|
|
90
|
+
}
|
|
91
|
+
};
|
|
92
|
+
}
|
|
93
|
+
function requireAllPermissions(permissions, registry, options = {}) {
|
|
94
|
+
const opts = { ...DEFAULT_OPTIONS, ...options };
|
|
95
|
+
return (req, res, next) => {
|
|
96
|
+
const user = opts.getUser(req);
|
|
97
|
+
if (canUserAll(user, permissions, registry)) {
|
|
98
|
+
next();
|
|
99
|
+
} else {
|
|
100
|
+
res.status(opts.statusCode).json({ error: opts.message });
|
|
101
|
+
}
|
|
102
|
+
};
|
|
103
|
+
}
|
|
104
|
+
function requireAnyPermission(permissions, registry, options = {}) {
|
|
105
|
+
const opts = { ...DEFAULT_OPTIONS, ...options };
|
|
106
|
+
return (req, res, next) => {
|
|
107
|
+
const user = opts.getUser(req);
|
|
108
|
+
if (canUserAny(user, permissions, registry)) {
|
|
109
|
+
next();
|
|
110
|
+
} else {
|
|
111
|
+
res.status(opts.statusCode).json({ error: opts.message });
|
|
112
|
+
}
|
|
113
|
+
};
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
export { requireAllPermissions, requireAnyPermission, requirePermission };
|
|
117
|
+
//# sourceMappingURL=express.mjs.map
|
|
118
|
+
//# sourceMappingURL=express.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../src/core/permission-engine.ts","../../src/adapters/express.ts"],"names":[],"mappings":"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","file":"express.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 - Express Adapter\r\n *\r\n * Thin middleware wrapper for Express.js applications.\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 middleware.\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 * Express-compatible Request type (minimal interface).\r\n * We use a minimal interface to avoid requiring express as a dependency.\r\n */\r\nexport interface ExpressRequest {\r\n /** User context attached by your auth middleware */\r\n user?: UserContext;\r\n}\r\n\r\n/**\r\n * Express-compatible Response type (minimal interface).\r\n */\r\nexport interface ExpressResponse {\r\n status(code: number): ExpressResponse;\r\n json(body: unknown): ExpressResponse;\r\n}\r\n\r\n/**\r\n * Express-compatible NextFunction type.\r\n */\r\nexport type ExpressNextFunction = (error?: unknown) => void;\r\n\r\n/**\r\n * Express middleware signature.\r\n */\r\nexport type ExpressMiddleware = (\r\n req: ExpressRequest,\r\n res: ExpressResponse,\r\n next: ExpressNextFunction\r\n) => void;\r\n\r\n/**\r\n * Options for the permission middleware.\r\n */\r\nexport type PermissionMiddlewareOptions = {\r\n /** HTTP status code to return on denial (default: 403) */\r\n readonly statusCode?: number;\r\n /** Custom error message (default: 'Forbidden') */\r\n readonly message?: string;\r\n /** Custom function to extract user from request */\r\n readonly getUser?: (req: ExpressRequest) => UserContext | null | undefined;\r\n};\r\n\r\nconst DEFAULT_OPTIONS: Required<PermissionMiddlewareOptions> = {\r\n statusCode: 403,\r\n message: \"Forbidden\",\r\n getUser: (req) => req.user,\r\n};\r\n\r\n/**\r\n * Creates Express middleware that requires a specific permission.\r\n *\r\n * SECURITY: Denies by default if user is missing or permission not granted.\r\n *\r\n * @param permission - Required permission\r\n * @param registry - Role registry\r\n * @param options - Optional configuration\r\n * @returns Express middleware function\r\n *\r\n * @example\r\n * ```ts\r\n * import { defineRoles } from 'secure-role-guard/core';\r\n * import { requirePermission } from 'secure-role-guard/adapters/express';\r\n *\r\n * const registry = defineRoles({ admin: ['user.update'] });\r\n *\r\n * app.put('/users/:id',\r\n * authMiddleware, // Your auth middleware sets req.user\r\n * requirePermission('user.update', registry),\r\n * updateUserHandler\r\n * );\r\n * ```\r\n */\r\nexport function requirePermission(\r\n permission: string,\r\n registry: RoleRegistry,\r\n options: PermissionMiddlewareOptions = {}\r\n): ExpressMiddleware {\r\n const opts = { ...DEFAULT_OPTIONS, ...options };\r\n\r\n return (req, res, next) => {\r\n const user = opts.getUser(req);\r\n\r\n if (canUser(user, permission, registry)) {\r\n next();\r\n } else {\r\n res.status(opts.statusCode).json({ error: opts.message });\r\n }\r\n };\r\n}\r\n\r\n/**\r\n * Creates Express middleware that requires ALL specified permissions.\r\n *\r\n * @param permissions - Array of required permissions\r\n * @param registry - Role registry\r\n * @param options - Optional configuration\r\n * @returns Express middleware function\r\n *\r\n * @example\r\n * ```ts\r\n * app.delete('/users/:id',\r\n * authMiddleware,\r\n * requireAllPermissions(['user.read', 'user.delete'], registry),\r\n * deleteUserHandler\r\n * );\r\n * ```\r\n */\r\nexport function requireAllPermissions(\r\n permissions: readonly string[],\r\n registry: RoleRegistry,\r\n options: PermissionMiddlewareOptions = {}\r\n): ExpressMiddleware {\r\n const opts = { ...DEFAULT_OPTIONS, ...options };\r\n\r\n return (req, res, next) => {\r\n const user = opts.getUser(req);\r\n\r\n if (canUserAll(user, permissions, registry)) {\r\n next();\r\n } else {\r\n res.status(opts.statusCode).json({ error: opts.message });\r\n }\r\n };\r\n}\r\n\r\n/**\r\n * Creates Express middleware that requires ANY of the specified permissions.\r\n *\r\n * @param permissions - Array of permissions (user needs at least one)\r\n * @param registry - Role registry\r\n * @param options - Optional configuration\r\n * @returns Express middleware function\r\n *\r\n * @example\r\n * ```ts\r\n * app.get('/reports',\r\n * authMiddleware,\r\n * requireAnyPermission(['report.admin', 'report.viewer'], registry),\r\n * getReportsHandler\r\n * );\r\n * ```\r\n */\r\nexport function requireAnyPermission(\r\n permissions: readonly string[],\r\n registry: RoleRegistry,\r\n options: PermissionMiddlewareOptions = {}\r\n): ExpressMiddleware {\r\n const opts = { ...DEFAULT_OPTIONS, ...options };\r\n\r\n return (req, res, next) => {\r\n const user = opts.getUser(req);\r\n\r\n if (canUserAny(user, permissions, registry)) {\r\n next();\r\n } else {\r\n res.status(opts.statusCode).json({ error: opts.message });\r\n }\r\n };\r\n}\r\n"]}
|
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
export { ExpressMiddleware, ExpressNextFunction, ExpressRequest, ExpressResponse, PermissionMiddlewareOptions, requireAllPermissions, requireAnyPermission, requirePermission } from './express.mjs';
|
|
2
|
+
export { NextPermissionOptions, NextRequest, NextResponse, PermissionResult, checkNextPermission, checkNextPermissionAll, checkNextPermissionAny, withAllPermissions, withAnyPermission, withPermission } from './nextjs.mjs';
|
|
3
|
+
import '../types-CSUpaGsY.mjs';
|
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
export { ExpressMiddleware, ExpressNextFunction, ExpressRequest, ExpressResponse, PermissionMiddlewareOptions, requireAllPermissions, requireAnyPermission, requirePermission } from './express.js';
|
|
2
|
+
export { NextPermissionOptions, NextRequest, NextResponse, PermissionResult, checkNextPermission, checkNextPermissionAll, checkNextPermissionAny, withAllPermissions, withAnyPermission, withPermission } from './nextjs.js';
|
|
3
|
+
import '../types-CSUpaGsY.js';
|