@zudojs/permissions 0.1.0 → 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 +345 -14
- package/dist/ability/ability.core.d.ts +2 -1
- package/dist/ability/ability.core.js +34 -9
- package/dist/cache/cache.core.d.ts +24 -5
- package/dist/cache/cache.core.js +62 -20
- package/dist/cache/index.d.ts +1 -1
- package/dist/conditions/conditions.core.d.ts +18 -2
- package/dist/conditions/conditions.core.js +46 -6
- package/dist/conditions/index.d.ts +1 -1
- package/dist/conditions/index.js +1 -1
- package/dist/evaluator/authorizationEngine.d.ts +48 -6
- package/dist/evaluator/authorizationEngine.js +146 -17
- package/dist/evaluator/evaluator.core.d.ts +28 -4
- package/dist/evaluator/evaluator.core.js +246 -39
- package/dist/evaluator/evaluator.explain.d.ts +6 -3
- package/dist/evaluator/evaluator.explain.js +8 -71
- package/dist/evaluator/evaluator.pipeline.d.ts +70 -9
- package/dist/evaluator/evaluator.pipeline.js +194 -49
- package/dist/evaluator/index.d.ts +4 -2
- package/dist/evaluator/index.js +4 -1
- package/dist/http/httpHelpers.d.ts +28 -12
- package/dist/http/httpHelpers.js +33 -6
- package/dist/http/httpMiddleware.core.d.ts +54 -13
- package/dist/http/httpMiddleware.core.js +72 -28
- package/dist/http/index.d.ts +7 -3
- package/dist/http/index.js +7 -3
- package/dist/index.d.ts +3 -2
- package/dist/index.js +2 -2
- package/dist/observability/index.d.ts +1 -1
- package/dist/observability/observability.core.d.ts +32 -12
- package/dist/observability/observability.core.js +41 -20
- package/dist/permission/index.d.ts +2 -2
- package/dist/permission/index.js +1 -1
- package/dist/permission/permission.core.d.ts +13 -2
- package/dist/permission/permission.core.js +29 -21
- package/dist/permission/permissionRegistry.d.ts +27 -33
- package/dist/permission/permissionRegistry.js +59 -41
- package/dist/permissionErrors/index.d.ts +1 -1
- package/dist/permissionErrors/index.js +1 -1
- package/dist/permissionErrors/permissionError.access.d.ts +24 -0
- package/dist/permissionErrors/permissionError.access.js +25 -5
- package/dist/permissionErrors/permissionError.types.d.ts +1 -1
- package/dist/permissionErrors/permissionError.types.js +1 -1
- package/dist/permissionTypes/index.d.ts +2 -2
- package/dist/permissionTypes/index.js +1 -1
- package/dist/permissionTypes/policyTypes.d.ts +35 -2
- package/dist/permissionTypes/ruleTypes.d.ts +38 -5
- package/dist/policy/index.d.ts +1 -1
- package/dist/policy/index.js +1 -1
- package/dist/policy/policyRegistry.d.ts +16 -23
- package/dist/policy/policyRegistry.js +26 -23
- package/dist/role/index.d.ts +2 -2
- package/dist/role/index.js +1 -1
- package/dist/role/roleHierarchy.d.ts +34 -4
- package/dist/role/roleHierarchy.js +48 -23
- package/dist/role/roleRegistry.d.ts +16 -25
- package/dist/role/roleRegistry.js +27 -23
- package/dist/rule/index.d.ts +2 -2
- package/dist/rule/index.js +2 -2
- package/dist/rule/rule.core.d.ts +39 -10
- package/dist/rule/rule.core.js +95 -38
- package/dist/rule/ruleCompiler.d.ts +14 -5
- package/dist/rule/ruleCompiler.js +46 -30
- package/dist/utils/utils.helper.d.ts +8 -0
- package/dist/utils/utils.helper.js +16 -14
- package/package.json +23 -16
- package/dist/.tsbuildinfo +0 -1
- package/dist/ability/ability.core.d.ts.map +0 -1
- package/dist/ability/ability.core.js.map +0 -1
- package/dist/ability/index.d.ts.map +0 -1
- package/dist/ability/index.js.map +0 -1
- package/dist/actor/actor.core.d.ts.map +0 -1
- package/dist/actor/actor.core.js.map +0 -1
- package/dist/actor/index.d.ts.map +0 -1
- package/dist/actor/index.js.map +0 -1
- package/dist/cache/cache.core.d.ts.map +0 -1
- package/dist/cache/cache.core.js.map +0 -1
- package/dist/cache/index.d.ts.map +0 -1
- package/dist/cache/index.js.map +0 -1
- package/dist/conditions/conditions.core.d.ts.map +0 -1
- package/dist/conditions/conditions.core.js.map +0 -1
- package/dist/conditions/index.d.ts.map +0 -1
- package/dist/conditions/index.js.map +0 -1
- package/dist/evaluator/authorizationEngine.d.ts.map +0 -1
- package/dist/evaluator/authorizationEngine.js.map +0 -1
- package/dist/evaluator/evaluator.core.d.ts.map +0 -1
- package/dist/evaluator/evaluator.core.js.map +0 -1
- package/dist/evaluator/evaluator.explain.d.ts.map +0 -1
- package/dist/evaluator/evaluator.explain.js.map +0 -1
- package/dist/evaluator/evaluator.pipeline.d.ts.map +0 -1
- package/dist/evaluator/evaluator.pipeline.js.map +0 -1
- package/dist/evaluator/index.d.ts.map +0 -1
- package/dist/evaluator/index.js.map +0 -1
- package/dist/http/httpHelpers.d.ts.map +0 -1
- package/dist/http/httpHelpers.js.map +0 -1
- package/dist/http/httpMiddleware.core.d.ts.map +0 -1
- package/dist/http/httpMiddleware.core.js.map +0 -1
- package/dist/http/httpTypes.d.ts.map +0 -1
- package/dist/http/httpTypes.js.map +0 -1
- package/dist/http/index.d.ts.map +0 -1
- package/dist/http/index.js.map +0 -1
- package/dist/index.d.ts.map +0 -1
- package/dist/index.js.map +0 -1
- package/dist/observability/index.d.ts.map +0 -1
- package/dist/observability/index.js.map +0 -1
- package/dist/observability/observability.core.d.ts.map +0 -1
- package/dist/observability/observability.core.js.map +0 -1
- package/dist/permission/index.d.ts.map +0 -1
- package/dist/permission/index.js.map +0 -1
- package/dist/permission/permission.core.d.ts.map +0 -1
- package/dist/permission/permission.core.js.map +0 -1
- package/dist/permission/permissionRegistry.d.ts.map +0 -1
- package/dist/permission/permissionRegistry.js.map +0 -1
- package/dist/permissionErrors/index.d.ts.map +0 -1
- package/dist/permissionErrors/index.js.map +0 -1
- package/dist/permissionErrors/permissionError.access.d.ts.map +0 -1
- package/dist/permissionErrors/permissionError.access.js.map +0 -1
- package/dist/permissionErrors/permissionError.base.d.ts.map +0 -1
- package/dist/permissionErrors/permissionError.base.js.map +0 -1
- package/dist/permissionErrors/permissionError.types.d.ts.map +0 -1
- package/dist/permissionErrors/permissionError.types.js.map +0 -1
- package/dist/permissionErrors/permissionError.validation.d.ts.map +0 -1
- package/dist/permissionErrors/permissionError.validation.js.map +0 -1
- package/dist/permissionTypes/index.d.ts.map +0 -1
- package/dist/permissionTypes/index.js.map +0 -1
- package/dist/permissionTypes/permissionActor.d.ts.map +0 -1
- package/dist/permissionTypes/permissionActor.js.map +0 -1
- package/dist/permissionTypes/policyTypes.d.ts.map +0 -1
- package/dist/permissionTypes/policyTypes.js.map +0 -1
- package/dist/permissionTypes/ruleTypes.d.ts.map +0 -1
- package/dist/permissionTypes/ruleTypes.js.map +0 -1
- package/dist/policy/index.d.ts.map +0 -1
- package/dist/policy/index.js.map +0 -1
- package/dist/policy/policyRegistry.d.ts.map +0 -1
- package/dist/policy/policyRegistry.js.map +0 -1
- package/dist/resolvers/index.d.ts.map +0 -1
- package/dist/resolvers/index.js.map +0 -1
- package/dist/resolvers/resolvers.core.d.ts.map +0 -1
- package/dist/resolvers/resolvers.core.js.map +0 -1
- package/dist/role/index.d.ts.map +0 -1
- package/dist/role/index.js.map +0 -1
- package/dist/role/roleHierarchy.d.ts.map +0 -1
- package/dist/role/roleHierarchy.js.map +0 -1
- package/dist/role/roleRegistry.d.ts.map +0 -1
- package/dist/role/roleRegistry.js.map +0 -1
- package/dist/rule/index.d.ts.map +0 -1
- package/dist/rule/index.js.map +0 -1
- package/dist/rule/rule.core.d.ts.map +0 -1
- package/dist/rule/rule.core.js.map +0 -1
- package/dist/rule/ruleCompiler.d.ts.map +0 -1
- package/dist/rule/ruleCompiler.js.map +0 -1
- package/dist/utils/index.d.ts.map +0 -1
- package/dist/utils/index.js.map +0 -1
- package/dist/utils/utils.helper.d.ts.map +0 -1
- package/dist/utils/utils.helper.js.map +0 -1
package/dist/http/index.d.ts
CHANGED
|
@@ -1,11 +1,15 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* HTTP middleware adapter for @zudojs/permissions.
|
|
3
3
|
*
|
|
4
|
-
*
|
|
4
|
+
* The HTTP types are mirrored locally in `httpTypes.ts` so this package has no
|
|
5
|
+
* hard dependency on @zudojs/http, which is an optional peer. The mirror is
|
|
6
|
+
* structural: anything satisfying the real `HttpMiddlewareContext` satisfies
|
|
7
|
+
* the local one, so the middleware composes with the real pipeline. Keep the
|
|
8
|
+
* two in step when @zudojs/http changes — nothing here can check it for you.
|
|
5
9
|
*
|
|
6
10
|
* @module http
|
|
7
11
|
*/
|
|
8
|
-
export { createActorMiddleware, createRequirePermissionMiddleware, authorize, createRequirePermissionsMiddleware, ACTOR_STATE_KEY, DECISION_STATE_KEY, type AuthorizeMiddlewareOptions, type RequirePermissionMiddlewareOptions, } from "./httpMiddleware.core.js";
|
|
9
|
-
export { createForbiddenResponse, createJsonResponse, type DeniedResponseOptions, } from "./httpHelpers.js";
|
|
12
|
+
export { createActorMiddleware, createRequirePermissionMiddleware, authorize, createRequirePermissionsMiddleware, ACTOR_STATE_KEY, DECISION_STATE_KEY, DECISIONS_STATE_KEY, type AuthorizeMiddlewareOptions, type ActorMiddlewareOptions, type RequirePermissionMiddlewareOptions, type RequirePermissionsMiddlewareOptions, } from "./httpMiddleware.core.js";
|
|
13
|
+
export { createForbiddenResponse, createUnauthorizedResponse, createJsonResponse, type DeniedResponseOptions, type PermissionHttpResponse, } from "./httpHelpers.js";
|
|
10
14
|
export type { HttpMiddleware, HttpMiddlewareContext, HttpRequestContext, HttpResponseContext, HttpMiddlewareState, } from "./httpTypes.js";
|
|
11
15
|
//# sourceMappingURL=index.d.ts.map
|
package/dist/http/index.js
CHANGED
|
@@ -1,10 +1,14 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* HTTP middleware adapter for @zudojs/permissions.
|
|
3
3
|
*
|
|
4
|
-
*
|
|
4
|
+
* The HTTP types are mirrored locally in `httpTypes.ts` so this package has no
|
|
5
|
+
* hard dependency on @zudojs/http, which is an optional peer. The mirror is
|
|
6
|
+
* structural: anything satisfying the real `HttpMiddlewareContext` satisfies
|
|
7
|
+
* the local one, so the middleware composes with the real pipeline. Keep the
|
|
8
|
+
* two in step when @zudojs/http changes — nothing here can check it for you.
|
|
5
9
|
*
|
|
6
10
|
* @module http
|
|
7
11
|
*/
|
|
8
|
-
export { createActorMiddleware, createRequirePermissionMiddleware, authorize, createRequirePermissionsMiddleware, ACTOR_STATE_KEY, DECISION_STATE_KEY, } from "./httpMiddleware.core.js";
|
|
9
|
-
export { createForbiddenResponse, createJsonResponse, } from "./httpHelpers.js";
|
|
12
|
+
export { createActorMiddleware, createRequirePermissionMiddleware, authorize, createRequirePermissionsMiddleware, ACTOR_STATE_KEY, DECISION_STATE_KEY, DECISIONS_STATE_KEY, } from "./httpMiddleware.core.js";
|
|
13
|
+
export { createForbiddenResponse, createUnauthorizedResponse, createJsonResponse, } from "./httpHelpers.js";
|
|
10
14
|
//# sourceMappingURL=index.js.map
|
package/dist/index.d.ts
CHANGED
|
@@ -4,7 +4,8 @@
|
|
|
4
4
|
* Authorization engine for the Zudojs framework.
|
|
5
5
|
*
|
|
6
6
|
* Supports RBAC, ABAC, resource policies, wildcards, role hierarchy,
|
|
7
|
-
* condition combinators, ability compilation,
|
|
7
|
+
* condition combinators, ability compilation, decision caching, audit events
|
|
8
|
+
* and explain mode.
|
|
8
9
|
*
|
|
9
10
|
* @module @zudojs/permissions
|
|
10
11
|
*/
|
|
@@ -23,5 +24,5 @@ export * from "./cache/index.js";
|
|
|
23
24
|
export * from "./observability/index.js";
|
|
24
25
|
export * from "./utils/index.js";
|
|
25
26
|
export * from "./http/index.js";
|
|
26
|
-
export {
|
|
27
|
+
export type { RoleSource, PolicySource, } from "./evaluator/authorizationEngine.js";
|
|
27
28
|
//# sourceMappingURL=index.d.ts.map
|
package/dist/index.js
CHANGED
|
@@ -4,7 +4,8 @@
|
|
|
4
4
|
* Authorization engine for the Zudojs framework.
|
|
5
5
|
*
|
|
6
6
|
* Supports RBAC, ABAC, resource policies, wildcards, role hierarchy,
|
|
7
|
-
* condition combinators, ability compilation,
|
|
7
|
+
* condition combinators, ability compilation, decision caching, audit events
|
|
8
|
+
* and explain mode.
|
|
8
9
|
*
|
|
9
10
|
* @module @zudojs/permissions
|
|
10
11
|
*/
|
|
@@ -23,5 +24,4 @@ export * from "./cache/index.js";
|
|
|
23
24
|
export * from "./observability/index.js";
|
|
24
25
|
export * from "./utils/index.js";
|
|
25
26
|
export * from "./http/index.js";
|
|
26
|
-
export { createPermissionEngine, } from "./evaluator/authorizationEngine.js";
|
|
27
27
|
//# sourceMappingURL=index.js.map
|
|
@@ -3,5 +3,5 @@
|
|
|
3
3
|
*
|
|
4
4
|
* @module observability
|
|
5
5
|
*/
|
|
6
|
-
export { createPermissionEventEmitter, withObservability, type PermissionCheckEvent, type PermissionEventHandler, } from "./observability.core.js";
|
|
6
|
+
export { createPermissionEventEmitter, withObservability, type PermissionCheckEvent, type PermissionEventHandler, type PermissionEventEmitter, type PermissionEventEmitterOptions, } from "./observability.core.js";
|
|
7
7
|
//# sourceMappingURL=index.d.ts.map
|
|
@@ -12,30 +12,50 @@ export interface PermissionCheckEvent {
|
|
|
12
12
|
readonly permission: string;
|
|
13
13
|
/** Resource type (if available). */
|
|
14
14
|
readonly resourceType?: string;
|
|
15
|
-
/** Whether allowed. */
|
|
15
|
+
/** Whether allowed. A check that threw is recorded as not allowed. */
|
|
16
16
|
readonly allowed: boolean;
|
|
17
|
-
/** Decision reason. */
|
|
17
|
+
/** Decision reason, or `error:<Name>` when the check threw. */
|
|
18
18
|
readonly reason?: string;
|
|
19
19
|
/** Evaluation duration in ms. */
|
|
20
20
|
readonly durationMs: number;
|
|
21
|
+
/** Whether the check ended in an exception rather than a decision. */
|
|
22
|
+
readonly errored?: boolean;
|
|
21
23
|
}
|
|
22
24
|
/** Handler for permission events. */
|
|
23
25
|
export type PermissionEventHandler = (event: PermissionCheckEvent) => void;
|
|
24
|
-
/**
|
|
25
|
-
|
|
26
|
-
*/
|
|
27
|
-
export declare function createPermissionEventEmitter(): {
|
|
28
|
-
/** Register an event handler. */
|
|
26
|
+
/** Emits authorization audit events. */
|
|
27
|
+
export interface PermissionEventEmitter {
|
|
28
|
+
/** Register a handler. Returns an unsubscribe function. */
|
|
29
29
|
on(handler: PermissionEventHandler): () => void;
|
|
30
30
|
/** Emit a permission check event. */
|
|
31
31
|
emit(event: PermissionCheckEvent): void;
|
|
32
|
-
|
|
32
|
+
/** Number of registered handlers. */
|
|
33
|
+
readonly size: number;
|
|
34
|
+
}
|
|
35
|
+
/** Options for {@link createPermissionEventEmitter}. */
|
|
36
|
+
export interface PermissionEventEmitterOptions {
|
|
37
|
+
/**
|
|
38
|
+
* Reports a handler that threw.
|
|
39
|
+
*
|
|
40
|
+
* Handler errors are swallowed so a broken audit sink cannot break
|
|
41
|
+
* authorization — but a sink that is silently failing is worse than one
|
|
42
|
+
* that is loudly failing, so this is where it surfaces.
|
|
43
|
+
*/
|
|
44
|
+
readonly onHandlerError?: (error: unknown, event: PermissionCheckEvent) => void;
|
|
45
|
+
}
|
|
46
|
+
/**
|
|
47
|
+
* Create a permission event emitter.
|
|
48
|
+
*/
|
|
49
|
+
export declare function createPermissionEventEmitter(options?: PermissionEventEmitterOptions): PermissionEventEmitter;
|
|
33
50
|
/**
|
|
34
51
|
* Wrap a permission check with observability.
|
|
35
52
|
*
|
|
36
|
-
*
|
|
37
|
-
*
|
|
38
|
-
*
|
|
53
|
+
* The event is emitted from a `finally`, so a check that throws is recorded
|
|
54
|
+
* too — the exception paths are exactly the ones an audit trail must not
|
|
55
|
+
* miss.
|
|
56
|
+
*
|
|
57
|
+
* Prefer passing an emitter to `createPermissionEngine({ emitter })`, which
|
|
58
|
+
* instruments every check the engine makes.
|
|
39
59
|
*/
|
|
40
|
-
export declare function withObservability<
|
|
60
|
+
export declare function withObservability<TArgs extends readonly unknown[]>(emitter: PermissionEventEmitter, fn: (...args: TArgs) => Promise<PermissionDecision>): (...args: TArgs) => Promise<PermissionDecision>;
|
|
41
61
|
//# sourceMappingURL=observability.core.d.ts.map
|
|
@@ -6,49 +6,70 @@
|
|
|
6
6
|
/**
|
|
7
7
|
* Create a permission event emitter.
|
|
8
8
|
*/
|
|
9
|
-
export function createPermissionEventEmitter() {
|
|
9
|
+
export function createPermissionEventEmitter(options) {
|
|
10
10
|
const handlers = new Set();
|
|
11
11
|
return {
|
|
12
|
-
/** Register an event handler. */
|
|
13
12
|
on(handler) {
|
|
14
13
|
handlers.add(handler);
|
|
15
14
|
return () => {
|
|
16
15
|
handlers.delete(handler);
|
|
17
16
|
};
|
|
18
17
|
},
|
|
19
|
-
/** Emit a permission check event. */
|
|
20
18
|
emit(event) {
|
|
21
19
|
for (const handler of handlers) {
|
|
22
20
|
try {
|
|
23
21
|
handler(event);
|
|
24
22
|
}
|
|
25
|
-
catch {
|
|
26
|
-
|
|
23
|
+
catch (error) {
|
|
24
|
+
options?.onHandlerError?.(error, event);
|
|
27
25
|
}
|
|
28
26
|
}
|
|
29
27
|
},
|
|
28
|
+
get size() {
|
|
29
|
+
return handlers.size;
|
|
30
|
+
},
|
|
30
31
|
};
|
|
31
32
|
}
|
|
32
33
|
/**
|
|
33
34
|
* Wrap a permission check with observability.
|
|
34
35
|
*
|
|
35
|
-
*
|
|
36
|
-
*
|
|
37
|
-
*
|
|
36
|
+
* The event is emitted from a `finally`, so a check that throws is recorded
|
|
37
|
+
* too — the exception paths are exactly the ones an audit trail must not
|
|
38
|
+
* miss.
|
|
39
|
+
*
|
|
40
|
+
* Prefer passing an emitter to `createPermissionEngine({ emitter })`, which
|
|
41
|
+
* instruments every check the engine makes.
|
|
38
42
|
*/
|
|
39
43
|
export function withObservability(emitter, fn) {
|
|
40
|
-
return
|
|
44
|
+
return async (...args) => {
|
|
41
45
|
const start = performance.now();
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
46
|
+
let decision;
|
|
47
|
+
let failure;
|
|
48
|
+
try {
|
|
49
|
+
decision = await fn(...args);
|
|
50
|
+
return decision;
|
|
51
|
+
}
|
|
52
|
+
catch (error) {
|
|
53
|
+
failure = error;
|
|
54
|
+
throw error;
|
|
55
|
+
}
|
|
56
|
+
finally {
|
|
57
|
+
const actor = args[0];
|
|
58
|
+
const resource = args[2];
|
|
59
|
+
emitter.emit({
|
|
60
|
+
actorId: actor?.id ?? "unknown",
|
|
61
|
+
permission: typeof args[1] === "string" ? args[1] : "unknown",
|
|
62
|
+
resourceType: typeof resource === "object" && resource !== null
|
|
63
|
+
? (resource.type ??
|
|
64
|
+
resource.constructor?.name)
|
|
65
|
+
: undefined,
|
|
66
|
+
allowed: decision?.allowed ?? false,
|
|
67
|
+
reason: decision?.reason ??
|
|
68
|
+
(failure instanceof Error ? `error:${failure.name}` : undefined),
|
|
69
|
+
durationMs: performance.now() - start,
|
|
70
|
+
errored: failure !== undefined,
|
|
71
|
+
});
|
|
72
|
+
}
|
|
73
|
+
};
|
|
53
74
|
}
|
|
54
75
|
//# sourceMappingURL=observability.core.js.map
|
|
@@ -3,6 +3,6 @@
|
|
|
3
3
|
*
|
|
4
4
|
* @module permission
|
|
5
5
|
*/
|
|
6
|
-
export { parsePermission, isValidPermission, matches, matchesPermission, } from "./permission.core.js";
|
|
7
|
-
export { createPermissionRegistry, type PermissionRegistryOptions, } from "./permissionRegistry.js";
|
|
6
|
+
export { parsePermission, parsePermissionSafe, isValidPermission, matches, matchesPermission, formatPermission, } from "./permission.core.js";
|
|
7
|
+
export { createPermissionRegistry, type PermissionRegistry, type PermissionRegistryOptions, type RegisteredPermission, } from "./permissionRegistry.js";
|
|
8
8
|
//# sourceMappingURL=index.d.ts.map
|
package/dist/permission/index.js
CHANGED
|
@@ -3,6 +3,6 @@
|
|
|
3
3
|
*
|
|
4
4
|
* @module permission
|
|
5
5
|
*/
|
|
6
|
-
export { parsePermission, isValidPermission, matches, matchesPermission, } from "./permission.core.js";
|
|
6
|
+
export { parsePermission, parsePermissionSafe, isValidPermission, matches, matchesPermission, formatPermission, } from "./permission.core.js";
|
|
7
7
|
export { createPermissionRegistry, } from "./permissionRegistry.js";
|
|
8
8
|
//# sourceMappingURL=index.js.map
|
|
@@ -9,6 +9,7 @@ import type { Permission } from "../permissionTypes/index.js";
|
|
|
9
9
|
*
|
|
10
10
|
* @example parsePermission("post:update") → { resource: "post", action: "update" }
|
|
11
11
|
* @example parsePermission("billing.invoice:refund") → { resource: "billing.invoice", action: "refund" }
|
|
12
|
+
* @throws InvalidPermissionError if the string is not a valid permission
|
|
12
13
|
*/
|
|
13
14
|
export declare function parsePermission(permission: string): Permission;
|
|
14
15
|
/**
|
|
@@ -16,15 +17,25 @@ export declare function parsePermission(permission: string): Permission;
|
|
|
16
17
|
*/
|
|
17
18
|
export declare function isValidPermission(permission: string): boolean;
|
|
18
19
|
/**
|
|
19
|
-
* Check if a permission matches a target permission
|
|
20
|
+
* Check if a permission pattern matches a target permission.
|
|
21
|
+
*
|
|
22
|
+
* Uses the same wildcard rules as the rule engine, so `billing.*:read`
|
|
23
|
+
* means the same thing whether it is written as a grant, a deny, or a rule.
|
|
20
24
|
*
|
|
21
25
|
* @example matches("post:*", "post:update") → true
|
|
22
26
|
* @example matches("*:*", "anything:goes") → true
|
|
27
|
+
* @example matches("billing.*:read", "billing.invoice:read") → true
|
|
23
28
|
* @example matches("post:read", "post:update") → false
|
|
24
29
|
*/
|
|
25
30
|
export declare function matches(pattern: string, target: string): boolean;
|
|
26
31
|
/**
|
|
27
|
-
* Check if a permission
|
|
32
|
+
* Check if a permission pattern matches a structured Permission object.
|
|
28
33
|
*/
|
|
29
34
|
export declare function matchesPermission(pattern: string, permission: Permission): boolean;
|
|
35
|
+
/**
|
|
36
|
+
* Parse a permission string, returning null on invalid format instead of throwing.
|
|
37
|
+
*/
|
|
38
|
+
export declare function parsePermissionSafe(permission: string): Permission | null;
|
|
39
|
+
/** Build a permission string from a resource and an action. */
|
|
40
|
+
export declare function formatPermission(resource: string, action: string): string;
|
|
30
41
|
//# sourceMappingURL=permission.core.d.ts.map
|
|
@@ -4,22 +4,29 @@
|
|
|
4
4
|
* @module permission/permission
|
|
5
5
|
*/
|
|
6
6
|
import { InvalidPermissionError } from "../permissionErrors/index.js";
|
|
7
|
-
|
|
8
|
-
|
|
7
|
+
import { patternStrMatches } from "../rule/rule.core.js";
|
|
8
|
+
/**
|
|
9
|
+
* Valid permission format: `resource:action`.
|
|
10
|
+
*
|
|
11
|
+
* A segment is either a bare `*`, a namespace wildcard such as `billing.*`,
|
|
12
|
+
* or a literal name. A partial wildcard like `post*` is rejected: the matcher
|
|
13
|
+
* has no meaning for it, so accepting one produced a grant that silently
|
|
14
|
+
* never matched anything.
|
|
15
|
+
*/
|
|
16
|
+
const SEGMENT = String.raw `(?:\*|[a-zA-Z0-9._-]+(?:\.\*)?)`;
|
|
17
|
+
const PERMISSION_REGEX = new RegExp(`^${SEGMENT}:${SEGMENT}$`);
|
|
9
18
|
/**
|
|
10
19
|
* Parse a permission string into a structured Permission.
|
|
11
20
|
*
|
|
12
21
|
* @example parsePermission("post:update") → { resource: "post", action: "update" }
|
|
13
22
|
* @example parsePermission("billing.invoice:refund") → { resource: "billing.invoice", action: "refund" }
|
|
23
|
+
* @throws InvalidPermissionError if the string is not a valid permission
|
|
14
24
|
*/
|
|
15
25
|
export function parsePermission(permission) {
|
|
16
|
-
|
|
26
|
+
const parsed = parsePermissionSafe(permission);
|
|
27
|
+
if (!parsed)
|
|
17
28
|
throw new InvalidPermissionError(permission);
|
|
18
|
-
|
|
19
|
-
const lastColon = permission.lastIndexOf(":");
|
|
20
|
-
const resource = permission.slice(0, lastColon);
|
|
21
|
-
const action = permission.slice(lastColon + 1);
|
|
22
|
-
return Object.freeze({ resource, action });
|
|
29
|
+
return Object.freeze(parsed);
|
|
23
30
|
}
|
|
24
31
|
/**
|
|
25
32
|
* Check if a permission string is valid without throwing.
|
|
@@ -28,40 +35,37 @@ export function isValidPermission(permission) {
|
|
|
28
35
|
return PERMISSION_REGEX.test(permission);
|
|
29
36
|
}
|
|
30
37
|
/**
|
|
31
|
-
* Check if a permission matches a target permission
|
|
38
|
+
* Check if a permission pattern matches a target permission.
|
|
39
|
+
*
|
|
40
|
+
* Uses the same wildcard rules as the rule engine, so `billing.*:read`
|
|
41
|
+
* means the same thing whether it is written as a grant, a deny, or a rule.
|
|
32
42
|
*
|
|
33
43
|
* @example matches("post:*", "post:update") → true
|
|
34
44
|
* @example matches("*:*", "anything:goes") → true
|
|
45
|
+
* @example matches("billing.*:read", "billing.invoice:read") → true
|
|
35
46
|
* @example matches("post:read", "post:update") → false
|
|
36
47
|
*/
|
|
37
48
|
export function matches(pattern, target) {
|
|
38
|
-
if (pattern === "*:*")
|
|
39
|
-
return true;
|
|
40
49
|
const patternParsed = parsePermissionSafe(pattern);
|
|
41
50
|
const targetParsed = parsePermissionSafe(target);
|
|
42
51
|
if (!patternParsed || !targetParsed)
|
|
43
52
|
return false;
|
|
44
|
-
|
|
45
|
-
patternParsed.resource === targetParsed.resource;
|
|
46
|
-
const actionMatch = patternParsed.action === "*" ||
|
|
47
|
-
patternParsed.action === targetParsed.action;
|
|
48
|
-
return resourceMatch && actionMatch;
|
|
53
|
+
return matchesPermission(pattern, targetParsed);
|
|
49
54
|
}
|
|
50
55
|
/**
|
|
51
|
-
* Check if a permission
|
|
56
|
+
* Check if a permission pattern matches a structured Permission object.
|
|
52
57
|
*/
|
|
53
58
|
export function matchesPermission(pattern, permission) {
|
|
54
59
|
const parsed = parsePermissionSafe(pattern);
|
|
55
60
|
if (!parsed)
|
|
56
61
|
return false;
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
return resourceMatch && actionMatch;
|
|
62
|
+
return (patternStrMatches(parsed.resource, permission.resource) &&
|
|
63
|
+
patternStrMatches(parsed.action, permission.action));
|
|
60
64
|
}
|
|
61
65
|
/**
|
|
62
66
|
* Parse a permission string, returning null on invalid format instead of throwing.
|
|
63
67
|
*/
|
|
64
|
-
function parsePermissionSafe(permission) {
|
|
68
|
+
export function parsePermissionSafe(permission) {
|
|
65
69
|
if (!PERMISSION_REGEX.test(permission))
|
|
66
70
|
return null;
|
|
67
71
|
const lastColon = permission.lastIndexOf(":");
|
|
@@ -69,4 +73,8 @@ function parsePermissionSafe(permission) {
|
|
|
69
73
|
const action = permission.slice(lastColon + 1);
|
|
70
74
|
return { resource, action };
|
|
71
75
|
}
|
|
76
|
+
/** Build a permission string from a resource and an action. */
|
|
77
|
+
export function formatPermission(resource, action) {
|
|
78
|
+
return `${resource}:${action}`;
|
|
79
|
+
}
|
|
72
80
|
//# sourceMappingURL=permission.core.js.map
|
|
@@ -5,7 +5,8 @@
|
|
|
5
5
|
*/
|
|
6
6
|
import type { Permission } from "../permissionTypes/index.js";
|
|
7
7
|
/** A registered permission with optional metadata. */
|
|
8
|
-
interface RegisteredPermission {
|
|
8
|
+
export interface RegisteredPermission {
|
|
9
|
+
readonly key: string;
|
|
9
10
|
readonly parsed: Permission;
|
|
10
11
|
readonly description?: string;
|
|
11
12
|
readonly implies?: readonly string[];
|
|
@@ -15,49 +16,42 @@ export interface PermissionRegistryOptions {
|
|
|
15
16
|
/** Allow overwriting existing permissions. Defaults to false. */
|
|
16
17
|
readonly allowOverride?: boolean;
|
|
17
18
|
}
|
|
18
|
-
/**
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
export declare function createPermissionRegistry(options?: PermissionRegistryOptions): {
|
|
22
|
-
/**
|
|
23
|
-
* Register a permission by string or structured object.
|
|
24
|
-
*/
|
|
25
|
-
define(permission: string | Permission, opts?: {
|
|
19
|
+
/** A registry of the permissions an application defines. */
|
|
20
|
+
export interface PermissionRegistry {
|
|
21
|
+
define(permission: string | Permission, options?: {
|
|
26
22
|
readonly description?: string;
|
|
27
23
|
readonly implies?: readonly string[];
|
|
28
24
|
}): void;
|
|
29
|
-
/**
|
|
30
|
-
* Look up a registered permission by string.
|
|
31
|
-
*/
|
|
32
25
|
get(permission: string): Permission | undefined;
|
|
33
|
-
/**
|
|
34
|
-
|
|
35
|
-
*/
|
|
26
|
+
/** Like {@link PermissionRegistry.get}, but throws when unregistered. */
|
|
27
|
+
require(permission: string): Permission;
|
|
36
28
|
getEntry(permission: string): RegisteredPermission | undefined;
|
|
37
|
-
/**
|
|
38
|
-
* Check if a permission is registered.
|
|
39
|
-
*/
|
|
40
29
|
has(permission: string): boolean;
|
|
41
|
-
/**
|
|
42
|
-
* Get all registered permission strings.
|
|
43
|
-
*/
|
|
44
30
|
all(): readonly string[];
|
|
45
|
-
/**
|
|
46
|
-
* Get permissions that match a pattern.
|
|
47
|
-
*/
|
|
48
31
|
match(pattern: string): readonly Permission[];
|
|
49
|
-
/**
|
|
50
|
-
* Get permissions implied by a given permission.
|
|
51
|
-
*/
|
|
52
32
|
getImplied(permission: string): readonly string[];
|
|
53
33
|
/**
|
|
54
|
-
*
|
|
34
|
+
* Every permission implied by `permission`, following chains and stopping
|
|
35
|
+
* at cycles. This is what `createPermissionEngine({ expandImplied })`
|
|
36
|
+
* wants: `post:admin implies post:read` only affects a decision if
|
|
37
|
+
* something expands it.
|
|
55
38
|
*/
|
|
39
|
+
expandImplied(permission: string): readonly string[];
|
|
56
40
|
remove(permission: string): boolean;
|
|
57
|
-
/**
|
|
58
|
-
* Clear all registered permissions.
|
|
59
|
-
*/
|
|
60
41
|
clear(): void;
|
|
61
|
-
}
|
|
62
|
-
|
|
42
|
+
}
|
|
43
|
+
/**
|
|
44
|
+
* Create a permission registry.
|
|
45
|
+
*
|
|
46
|
+
* ```ts
|
|
47
|
+
* const permissions = createPermissionRegistry();
|
|
48
|
+
* permissions.define("post:admin", { implies: ["post:read", "post:write"] });
|
|
49
|
+
*
|
|
50
|
+
* const engine = createPermissionEngine({
|
|
51
|
+
* roles,
|
|
52
|
+
* expandImplied: (permission) => permissions.expandImplied(permission),
|
|
53
|
+
* });
|
|
54
|
+
* ```
|
|
55
|
+
*/
|
|
56
|
+
export declare function createPermissionRegistry(options?: PermissionRegistryOptions): PermissionRegistry;
|
|
63
57
|
//# sourceMappingURL=permissionRegistry.d.ts.map
|
|
@@ -3,83 +3,101 @@
|
|
|
3
3
|
*
|
|
4
4
|
* @module permission/permissionRegistry
|
|
5
5
|
*/
|
|
6
|
-
import { DuplicatePermissionError, PermissionNotFoundError, } from "../permissionErrors/index.js";
|
|
7
|
-
import {
|
|
6
|
+
import { DuplicatePermissionError, InvalidPermissionError, PermissionNotFoundError, } from "../permissionErrors/index.js";
|
|
7
|
+
import { formatPermission, isValidPermission, matchesPermission, parsePermission, } from "./permission.core.js";
|
|
8
8
|
/**
|
|
9
9
|
* Create a permission registry.
|
|
10
|
+
*
|
|
11
|
+
* ```ts
|
|
12
|
+
* const permissions = createPermissionRegistry();
|
|
13
|
+
* permissions.define("post:admin", { implies: ["post:read", "post:write"] });
|
|
14
|
+
*
|
|
15
|
+
* const engine = createPermissionEngine({
|
|
16
|
+
* roles,
|
|
17
|
+
* expandImplied: (permission) => permissions.expandImplied(permission),
|
|
18
|
+
* });
|
|
19
|
+
* ```
|
|
10
20
|
*/
|
|
11
21
|
export function createPermissionRegistry(options) {
|
|
12
22
|
const permissions = new Map();
|
|
13
23
|
const allowOverride = options?.allowOverride ?? false;
|
|
24
|
+
function keyOf(permission) {
|
|
25
|
+
if (typeof permission === "string") {
|
|
26
|
+
// Validate strings and structured values alike; a structured value
|
|
27
|
+
// skipping validation let `{ resource: "a b", action: "" }` in.
|
|
28
|
+
parsePermission(permission);
|
|
29
|
+
return permission;
|
|
30
|
+
}
|
|
31
|
+
const key = formatPermission(permission.resource, permission.action);
|
|
32
|
+
if (!isValidPermission(key))
|
|
33
|
+
throw new InvalidPermissionError(key);
|
|
34
|
+
return key;
|
|
35
|
+
}
|
|
14
36
|
return {
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
define(permission, opts) {
|
|
19
|
-
const parsed = typeof permission === "string"
|
|
20
|
-
? parsePermission(permission)
|
|
21
|
-
: permission;
|
|
22
|
-
const key = `${parsed.resource}:${parsed.action}`;
|
|
37
|
+
define(permission, defineOptions) {
|
|
38
|
+
const key = keyOf(permission);
|
|
39
|
+
const parsed = parsePermission(key);
|
|
23
40
|
if (permissions.has(key) && !allowOverride) {
|
|
24
41
|
throw new DuplicatePermissionError(key);
|
|
25
42
|
}
|
|
43
|
+
for (const implied of defineOptions?.implies ?? []) {
|
|
44
|
+
if (!isValidPermission(implied)) {
|
|
45
|
+
throw new InvalidPermissionError(implied);
|
|
46
|
+
}
|
|
47
|
+
}
|
|
26
48
|
permissions.set(key, {
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
49
|
+
key,
|
|
50
|
+
parsed,
|
|
51
|
+
description: defineOptions?.description,
|
|
52
|
+
implies: defineOptions?.implies
|
|
53
|
+
? Object.freeze([...defineOptions.implies])
|
|
54
|
+
: undefined,
|
|
30
55
|
});
|
|
31
56
|
},
|
|
32
|
-
/**
|
|
33
|
-
* Look up a registered permission by string.
|
|
34
|
-
*/
|
|
35
57
|
get(permission) {
|
|
36
58
|
return permissions.get(permission)?.parsed;
|
|
37
59
|
},
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
60
|
+
require(permission) {
|
|
61
|
+
const entry = permissions.get(permission);
|
|
62
|
+
if (!entry)
|
|
63
|
+
throw new PermissionNotFoundError(permission);
|
|
64
|
+
return entry.parsed;
|
|
65
|
+
},
|
|
41
66
|
getEntry(permission) {
|
|
42
67
|
return permissions.get(permission);
|
|
43
68
|
},
|
|
44
|
-
/**
|
|
45
|
-
* Check if a permission is registered.
|
|
46
|
-
*/
|
|
47
69
|
has(permission) {
|
|
48
70
|
return permissions.has(permission);
|
|
49
71
|
},
|
|
50
|
-
/**
|
|
51
|
-
* Get all registered permission strings.
|
|
52
|
-
*/
|
|
53
72
|
all() {
|
|
54
|
-
return
|
|
73
|
+
return [...permissions.keys()];
|
|
55
74
|
},
|
|
56
|
-
/**
|
|
57
|
-
* Get permissions that match a pattern.
|
|
58
|
-
*/
|
|
59
75
|
match(pattern) {
|
|
60
76
|
const results = [];
|
|
61
|
-
for (const
|
|
62
|
-
if (matchesPermission(pattern, entry.parsed))
|
|
77
|
+
for (const entry of permissions.values()) {
|
|
78
|
+
if (matchesPermission(pattern, entry.parsed))
|
|
63
79
|
results.push(entry.parsed);
|
|
64
|
-
}
|
|
65
80
|
}
|
|
66
81
|
return results;
|
|
67
82
|
},
|
|
68
|
-
/**
|
|
69
|
-
* Get permissions implied by a given permission.
|
|
70
|
-
*/
|
|
71
83
|
getImplied(permission) {
|
|
72
84
|
return permissions.get(permission)?.implies ?? [];
|
|
73
85
|
},
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
86
|
+
expandImplied(permission) {
|
|
87
|
+
const expanded = new Set();
|
|
88
|
+
const queue = [...(permissions.get(permission)?.implies ?? [])];
|
|
89
|
+
while (queue.length > 0) {
|
|
90
|
+
const next = queue.shift();
|
|
91
|
+
if (next === permission || expanded.has(next))
|
|
92
|
+
continue;
|
|
93
|
+
expanded.add(next);
|
|
94
|
+
queue.push(...(permissions.get(next)?.implies ?? []));
|
|
95
|
+
}
|
|
96
|
+
return [...expanded];
|
|
97
|
+
},
|
|
77
98
|
remove(permission) {
|
|
78
99
|
return permissions.delete(permission);
|
|
79
100
|
},
|
|
80
|
-
/**
|
|
81
|
-
* Clear all registered permissions.
|
|
82
|
-
*/
|
|
83
101
|
clear() {
|
|
84
102
|
permissions.clear();
|
|
85
103
|
},
|
|
@@ -4,5 +4,5 @@
|
|
|
4
4
|
* @module permissionErrors
|
|
5
5
|
*/
|
|
6
6
|
export { PermissionError } from "./permissionError.base.js";
|
|
7
|
-
export { PermissionDeniedError, PermissionNotFoundError, DuplicatePermissionError, RoleNotFoundError, DuplicateRoleError, InvalidPermissionError, InvalidRoleError, CircularRoleInheritanceError, PolicyError, PolicyTimeoutError, PermissionResolverError, AuthorizationAbortedError, } from "./permissionError.types.js";
|
|
7
|
+
export { PermissionDeniedError, PermissionNotFoundError, DuplicatePermissionError, RoleNotFoundError, DuplicateRoleError, DuplicatePolicyError, InvalidPermissionError, InvalidRoleError, CircularRoleInheritanceError, PolicyError, PolicyTimeoutError, PermissionResolverError, AuthorizationAbortedError, } from "./permissionError.types.js";
|
|
8
8
|
//# sourceMappingURL=index.d.ts.map
|
|
@@ -4,5 +4,5 @@
|
|
|
4
4
|
* @module permissionErrors
|
|
5
5
|
*/
|
|
6
6
|
export { PermissionError } from "./permissionError.base.js";
|
|
7
|
-
export { PermissionDeniedError, PermissionNotFoundError, DuplicatePermissionError, RoleNotFoundError, DuplicateRoleError, InvalidPermissionError, InvalidRoleError, CircularRoleInheritanceError, PolicyError, PolicyTimeoutError, PermissionResolverError, AuthorizationAbortedError, } from "./permissionError.types.js";
|
|
7
|
+
export { PermissionDeniedError, PermissionNotFoundError, DuplicatePermissionError, RoleNotFoundError, DuplicateRoleError, DuplicatePolicyError, InvalidPermissionError, InvalidRoleError, CircularRoleInheritanceError, PolicyError, PolicyTimeoutError, PermissionResolverError, AuthorizationAbortedError, } from "./permissionError.types.js";
|
|
8
8
|
//# sourceMappingURL=index.js.map
|