@cullet/erp-core 1.3.0 → 1.4.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/KIT_CONTEXT.md +5 -3
- package/README.md +160 -0
- package/dist/abac/index.cjs +7 -0
- package/dist/abac/index.d.cts +2 -0
- package/dist/abac/index.d.ts +2 -0
- package/dist/abac/index.js +2 -0
- package/dist/app-error.cjs +193 -0
- package/dist/app-error.cjs.map +1 -0
- package/dist/app-error.js +170 -0
- package/dist/app-error.js.map +1 -0
- package/dist/application/index.cjs +2 -1
- package/dist/application/index.d.cts +2 -1
- package/dist/application/index.d.ts +2 -1
- package/dist/application/index.js +2 -1
- package/dist/authorization-error.cjs +157 -0
- package/dist/authorization-error.cjs.map +1 -0
- package/dist/authorization-error.d.cts +113 -0
- package/dist/authorization-error.d.ts +113 -0
- package/dist/authorization-error.js +152 -0
- package/dist/authorization-error.js.map +1 -0
- package/dist/authorizer.port.d.cts +130 -0
- package/dist/authorizer.port.d.ts +130 -0
- package/dist/composite-authorizer.d.cts +216 -0
- package/dist/composite-authorizer.d.ts +216 -0
- package/dist/condition-evaluator.cjs +565 -0
- package/dist/condition-evaluator.cjs.map +1 -0
- package/dist/condition-evaluator.js +536 -0
- package/dist/condition-evaluator.js.map +1 -0
- package/dist/{gate-types.d.ts → core-config.d.cts} +78 -77
- package/dist/{gate-types.d.cts → core-config.d.ts} +78 -77
- package/dist/domain/index.cjs +2 -1
- package/dist/domain/index.d.cts +2 -1
- package/dist/domain/index.d.ts +2 -1
- package/dist/domain/index.js +2 -1
- package/dist/domain-event-contracts.cjs +3 -2
- package/dist/domain-event-contracts.cjs.map +1 -1
- package/dist/domain-event-contracts.js +2 -1
- package/dist/domain-event-contracts.js.map +1 -1
- package/dist/entity.cjs +126 -0
- package/dist/entity.cjs.map +1 -0
- package/dist/entity.js +115 -0
- package/dist/entity.js.map +1 -0
- package/dist/errors/index.cjs +8 -6
- package/dist/errors/index.d.cts +2 -1
- package/dist/errors/index.d.ts +2 -1
- package/dist/errors/index.js +4 -2
- package/dist/gate-engine-registry.cjs +5 -4
- package/dist/gate-engine-registry.cjs.map +1 -1
- package/dist/gate-engine-registry.d.cts +3 -2
- package/dist/gate-engine-registry.d.ts +3 -2
- package/dist/gate-engine-registry.js +2 -1
- package/dist/gate-engine-registry.js.map +1 -1
- package/dist/gate-v1-payload.schema.cjs +0 -562
- package/dist/gate-v1-payload.schema.cjs.map +1 -1
- package/dist/gate-v1-payload.schema.js +1 -533
- package/dist/gate-v1-payload.schema.js.map +1 -1
- package/dist/index.cjs +34 -16
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +19 -12
- package/dist/index.d.ts +19 -12
- package/dist/index.js +15 -9
- package/dist/index.js.map +1 -1
- package/dist/not-found-error.cjs +5 -5
- package/dist/not-found-error.cjs.map +1 -1
- package/dist/not-found-error.js +1 -1
- package/dist/outcome.d.cts +1 -83
- package/dist/outcome.d.ts +1 -83
- package/dist/parse-gate-payload.d.cts +2 -2
- package/dist/parse-gate-payload.d.ts +2 -2
- package/dist/path.d.cts +2 -2
- package/dist/path.d.ts +2 -2
- package/dist/policies/engines/index.d.cts +1 -1
- package/dist/policies/engines/index.d.ts +1 -1
- package/dist/policies/engines/v1/gate/index.cjs +5 -4
- package/dist/policies/engines/v1/gate/index.cjs.map +1 -1
- package/dist/policies/engines/v1/gate/index.d.cts +2 -2
- package/dist/policies/engines/v1/gate/index.d.ts +2 -2
- package/dist/policies/engines/v1/gate/index.js +2 -1
- package/dist/policies/engines/v1/gate/index.js.map +1 -1
- package/dist/policies/index.cjs +5 -5
- package/dist/policies/index.d.cts +3 -2
- package/dist/policies/index.d.ts +3 -2
- package/dist/policies/index.js +2 -2
- package/dist/policy-bridge.cjs +437 -0
- package/dist/policy-bridge.cjs.map +1 -0
- package/dist/policy-bridge.d.cts +185 -0
- package/dist/policy-bridge.d.ts +185 -0
- package/dist/policy-bridge.js +396 -0
- package/dist/policy-bridge.js.map +1 -0
- package/dist/policy-service.cjs +239 -239
- package/dist/policy-service.cjs.map +1 -1
- package/dist/policy-service.d.cts +2 -2
- package/dist/policy-service.d.ts +2 -2
- package/dist/policy-service.js +239 -239
- package/dist/policy-service.js.map +1 -1
- package/dist/rbac/index.cjs +9 -0
- package/dist/rbac/index.d.cts +3 -0
- package/dist/rbac/index.d.ts +3 -0
- package/dist/rbac/index.js +2 -0
- package/dist/requested-by.cjs +54 -0
- package/dist/requested-by.cjs.map +1 -0
- package/dist/requested-by.d.cts +25 -0
- package/dist/requested-by.d.ts +25 -0
- package/dist/requested-by.js +49 -0
- package/dist/requested-by.js.map +1 -0
- package/dist/result/index.d.cts +2 -1
- package/dist/result/index.d.ts +2 -1
- package/dist/result.d.cts +84 -0
- package/dist/result.d.ts +84 -0
- package/dist/rule.cjs +327 -0
- package/dist/rule.cjs.map +1 -0
- package/dist/rule.js +298 -0
- package/dist/rule.js.map +1 -0
- package/dist/temporal-use-case.cjs +1 -53
- package/dist/temporal-use-case.cjs.map +1 -1
- package/dist/temporal-use-case.d.cts +5 -27
- package/dist/temporal-use-case.d.ts +5 -27
- package/dist/temporal-use-case.js +2 -48
- package/dist/temporal-use-case.js.map +1 -1
- package/dist/unexpected-error.cjs +3 -192
- package/dist/unexpected-error.cjs.map +1 -1
- package/dist/unexpected-error.js +2 -167
- package/dist/unexpected-error.js.map +1 -1
- package/dist/uuid-identifier.d.cts +2 -85
- package/dist/uuid-identifier.d.ts +2 -85
- package/dist/validation-error.cjs +33 -166
- package/dist/validation-error.cjs.map +1 -1
- package/dist/validation-error.d.cts +2 -98
- package/dist/validation-error.d.ts +2 -98
- package/dist/validation-error.js +7 -134
- package/dist/validation-error.js.map +1 -1
- package/dist/value-object.cjs +0 -123
- package/dist/value-object.cjs.map +1 -1
- package/dist/value-object.d.cts +89 -0
- package/dist/value-object.d.ts +89 -0
- package/dist/value-object.js +1 -112
- package/dist/value-object.js.map +1 -1
- package/meta.json +18 -4
- package/package.json +27 -1
- package/src/abac/index.ts +1 -0
- package/src/core/abac/abac-request.ts +29 -0
- package/src/core/abac/attributes.ts +39 -0
- package/src/core/abac/authorizer.ts +108 -0
- package/src/core/abac/combining.ts +63 -0
- package/src/core/abac/composite-authorizer.ts +45 -0
- package/src/core/abac/domain/policy-set.ts +52 -0
- package/src/core/abac/domain/rule.ts +197 -0
- package/src/core/abac/index.ts +21 -0
- package/src/core/application/ports/abac-authorizer.port.ts +20 -0
- package/src/core/application/ports/authorizer.port.ts +22 -0
- package/src/core/errors/authorization-error.ts +26 -0
- package/src/core/errors/error-codes.ts +1 -0
- package/src/core/index.ts +7 -0
- package/src/core/rbac/access-request.ts +32 -0
- package/src/core/rbac/authorizer.ts +91 -0
- package/src/core/rbac/domain/grant.ts +96 -0
- package/src/core/rbac/domain/permission-set.ts +67 -0
- package/src/core/rbac/domain/permission.ts +119 -0
- package/src/core/rbac/domain/role.ts +101 -0
- package/src/core/rbac/domain/scope.ts +84 -0
- package/src/core/rbac/index.ts +15 -0
- package/src/core/rbac/policy-bridge.ts +44 -0
- package/src/examples/application/authorize-cancel-order-abac.example.ts +107 -0
- package/src/examples/application/authorize-cancel-order.example.ts +101 -0
- package/src/rbac/index.ts +1 -0
- package/src/version.ts +1 -1
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
import { t as RequestedBy } from "./requested-by.cjs";
|
|
2
|
+
import { t as AuthorizationError } from "./authorization-error.cjs";
|
|
3
|
+
import { r as Result } from "./result.cjs";
|
|
4
|
+
import { t as ValueObject } from "./value-object.cjs";
|
|
5
|
+
|
|
6
|
+
//#region src/core/rbac/domain/permission.d.ts
|
|
7
|
+
/** The two halves of a permission: what is acted on, and what is done to it. */
|
|
8
|
+
type PermissionProps = {
|
|
9
|
+
readonly resource: string;
|
|
10
|
+
readonly action: string;
|
|
11
|
+
};
|
|
12
|
+
/**
|
|
13
|
+
* A `"<resource>:<action>"` capability such as `"orders:cancel"`, with a
|
|
14
|
+
* single-level wildcard. Either segment may be the whole-segment wildcard `*`
|
|
15
|
+
* (`"orders:*"`, `"*:cancel"`, `"*:*"`), which is what lets a broad grant cover
|
|
16
|
+
* a narrower requirement — see {@link implies}.
|
|
17
|
+
*
|
|
18
|
+
* A zod-free value object: it validates its own format on construction
|
|
19
|
+
* (throwing {@link InvalidValueException}) and is frozen thereafter. Build one
|
|
20
|
+
* through {@link of} (from a raw `"resource:action"` string) or {@link for}
|
|
21
|
+
* (from already-separated parts); the constructor is private.
|
|
22
|
+
*/
|
|
23
|
+
declare class Permission extends ValueObject<PermissionProps, string> {
|
|
24
|
+
private constructor();
|
|
25
|
+
/**
|
|
26
|
+
* Parses a `"resource:action"` string. Throws {@link InvalidValueException}
|
|
27
|
+
* when the string is blank, lacks exactly one `:`, or carries an invalid
|
|
28
|
+
* segment.
|
|
29
|
+
*/
|
|
30
|
+
static of(raw: string): Permission;
|
|
31
|
+
/**
|
|
32
|
+
* Builds from already-separated `resource` and `action` parts. Each must be
|
|
33
|
+
* a valid segment (`[a-z0-9_-]+` or the whole-segment wildcard `*`).
|
|
34
|
+
*/
|
|
35
|
+
static for(resource: string, action: string): Permission;
|
|
36
|
+
private static assertSegment;
|
|
37
|
+
get resource(): string;
|
|
38
|
+
get action(): string;
|
|
39
|
+
/**
|
|
40
|
+
* Whether holding this permission grants `other`. A `*` segment matches any
|
|
41
|
+
* value in the same position, so `"orders:*"` implies `"orders:cancel"` and
|
|
42
|
+
* `"*:*"` implies everything. Concrete segments must match exactly.
|
|
43
|
+
*/
|
|
44
|
+
implies(other: Permission): boolean;
|
|
45
|
+
/**
|
|
46
|
+
* Whether either segment is the whole-segment wildcard `*`. A *granted*
|
|
47
|
+
* permission may wildcard (see {@link implies}); a *required* one may not —
|
|
48
|
+
* `RbacAuthorizer` rejects a wildcard requirement as caller misuse.
|
|
49
|
+
*/
|
|
50
|
+
hasWildcard(): boolean;
|
|
51
|
+
toPrimitive(): string;
|
|
52
|
+
}
|
|
53
|
+
//#endregion
|
|
54
|
+
//#region src/core/rbac/domain/scope.d.ts
|
|
55
|
+
/** The wrapped, already-validated scope string (`"type:id"` or `"*"`). */
|
|
56
|
+
type ScopeProps = {
|
|
57
|
+
readonly raw: string;
|
|
58
|
+
};
|
|
59
|
+
/**
|
|
60
|
+
* Where a grant (or a target resource) lives, aligned with
|
|
61
|
+
* `AuthorizationRequirement.scope` and the policy `PolicyScope`
|
|
62
|
+
* (`tenantId`/`schoolId`): `"tenant:{id}"`, `"school:{id}"`, or the global
|
|
63
|
+
* `"*"`.
|
|
64
|
+
*
|
|
65
|
+
* v1 keeps containment ({@link includes}) flat — the global scope contains
|
|
66
|
+
* everything, and any other scope contains only itself. A real hierarchy
|
|
67
|
+
* (`tenant` ⊇ `school`) is a deliberately deferred extension point.
|
|
68
|
+
*/
|
|
69
|
+
declare class Scope extends ValueObject<ScopeProps, string> {
|
|
70
|
+
private constructor();
|
|
71
|
+
/** Validates `"type:id"` or the global `"*"`, throwing on anything else. */
|
|
72
|
+
static of(raw: string): Scope;
|
|
73
|
+
/** The global scope `"*"`, which {@link includes} every other scope. */
|
|
74
|
+
static global(): Scope;
|
|
75
|
+
/** The type segment, or `"*"` for the global scope. */
|
|
76
|
+
get type(): string;
|
|
77
|
+
/** The id segment, or `undefined` for the global scope. */
|
|
78
|
+
get id(): string | undefined;
|
|
79
|
+
/**
|
|
80
|
+
* Whether this scope contains `target`. The global scope contains
|
|
81
|
+
* everything; any other scope contains only an exact match of itself.
|
|
82
|
+
*/
|
|
83
|
+
includes(target: Scope): boolean;
|
|
84
|
+
toPrimitive(): string;
|
|
85
|
+
}
|
|
86
|
+
//#endregion
|
|
87
|
+
//#region src/core/rbac/access-request.d.ts
|
|
88
|
+
/**
|
|
89
|
+
* The question put to the {@link RbacAuthorizer}: *may this actor perform this
|
|
90
|
+
* action on this resource, in this scope?* Carries the audit-friendly business
|
|
91
|
+
* `action` label alongside the concrete {@link Permission} it requires, and
|
|
92
|
+
* references the target by type/id only (never the full payload).
|
|
93
|
+
*/
|
|
94
|
+
interface AccessRequest {
|
|
95
|
+
/** Who is acting — reused as the RBAC subject, no separate `Actor` type. */
|
|
96
|
+
readonly actor: RequestedBy;
|
|
97
|
+
/** Stable business action for auditing, e.g. `"order.cancel"`. */
|
|
98
|
+
readonly action: string;
|
|
99
|
+
/**
|
|
100
|
+
* The permission the action requires, e.g. `Permission.of("orders:cancel")`.
|
|
101
|
+
* Never a wildcard — only the *granted* permissions on roles may wildcard.
|
|
102
|
+
*/
|
|
103
|
+
readonly required: Permission;
|
|
104
|
+
/** The target resource, identified without leaking its payload. */
|
|
105
|
+
readonly resource?: {
|
|
106
|
+
readonly type: string;
|
|
107
|
+
readonly id?: string;
|
|
108
|
+
};
|
|
109
|
+
/** The scope the resource lives in, e.g. `Scope.of("school:123")`. */
|
|
110
|
+
readonly scope: Scope;
|
|
111
|
+
}
|
|
112
|
+
//#endregion
|
|
113
|
+
//#region src/core/application/ports/authorizer.port.d.ts
|
|
114
|
+
/**
|
|
115
|
+
* The seam an application layer injects to authorize an action — symmetric to
|
|
116
|
+
* {@link PolicyPort}. The use case depends only on this contract; an adapter in
|
|
117
|
+
* the consumer's stack loads the actor's grants (from its own store) and
|
|
118
|
+
* delegates the decision to the pure `RbacAuthorizer`.
|
|
119
|
+
*
|
|
120
|
+
* Returns the decision as a {@link Result}: `ok(void)` when allowed, or
|
|
121
|
+
* `err(AuthorizationError)` carrying the discriminated `reason` the HTTP
|
|
122
|
+
* boundary maps to a 403. Promised here under `[extension-points]` in
|
|
123
|
+
* KIT_CONTEXT.md.
|
|
124
|
+
*/
|
|
125
|
+
interface AuthorizerPort {
|
|
126
|
+
authorize(request: AccessRequest): Promise<Result<void, AuthorizationError>>;
|
|
127
|
+
}
|
|
128
|
+
//#endregion
|
|
129
|
+
export { Permission as a, ScopeProps as i, AccessRequest as n, PermissionProps as o, Scope as r, AuthorizerPort as t };
|
|
130
|
+
//# sourceMappingURL=authorizer.port.d.cts.map
|
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
import { t as RequestedBy } from "./requested-by.js";
|
|
2
|
+
import { t as AuthorizationError } from "./authorization-error.js";
|
|
3
|
+
import { r as Result } from "./result.js";
|
|
4
|
+
import { t as ValueObject } from "./value-object.js";
|
|
5
|
+
|
|
6
|
+
//#region src/core/rbac/domain/permission.d.ts
|
|
7
|
+
/** The two halves of a permission: what is acted on, and what is done to it. */
|
|
8
|
+
type PermissionProps = {
|
|
9
|
+
readonly resource: string;
|
|
10
|
+
readonly action: string;
|
|
11
|
+
};
|
|
12
|
+
/**
|
|
13
|
+
* A `"<resource>:<action>"` capability such as `"orders:cancel"`, with a
|
|
14
|
+
* single-level wildcard. Either segment may be the whole-segment wildcard `*`
|
|
15
|
+
* (`"orders:*"`, `"*:cancel"`, `"*:*"`), which is what lets a broad grant cover
|
|
16
|
+
* a narrower requirement — see {@link implies}.
|
|
17
|
+
*
|
|
18
|
+
* A zod-free value object: it validates its own format on construction
|
|
19
|
+
* (throwing {@link InvalidValueException}) and is frozen thereafter. Build one
|
|
20
|
+
* through {@link of} (from a raw `"resource:action"` string) or {@link for}
|
|
21
|
+
* (from already-separated parts); the constructor is private.
|
|
22
|
+
*/
|
|
23
|
+
declare class Permission extends ValueObject<PermissionProps, string> {
|
|
24
|
+
private constructor();
|
|
25
|
+
/**
|
|
26
|
+
* Parses a `"resource:action"` string. Throws {@link InvalidValueException}
|
|
27
|
+
* when the string is blank, lacks exactly one `:`, or carries an invalid
|
|
28
|
+
* segment.
|
|
29
|
+
*/
|
|
30
|
+
static of(raw: string): Permission;
|
|
31
|
+
/**
|
|
32
|
+
* Builds from already-separated `resource` and `action` parts. Each must be
|
|
33
|
+
* a valid segment (`[a-z0-9_-]+` or the whole-segment wildcard `*`).
|
|
34
|
+
*/
|
|
35
|
+
static for(resource: string, action: string): Permission;
|
|
36
|
+
private static assertSegment;
|
|
37
|
+
get resource(): string;
|
|
38
|
+
get action(): string;
|
|
39
|
+
/**
|
|
40
|
+
* Whether holding this permission grants `other`. A `*` segment matches any
|
|
41
|
+
* value in the same position, so `"orders:*"` implies `"orders:cancel"` and
|
|
42
|
+
* `"*:*"` implies everything. Concrete segments must match exactly.
|
|
43
|
+
*/
|
|
44
|
+
implies(other: Permission): boolean;
|
|
45
|
+
/**
|
|
46
|
+
* Whether either segment is the whole-segment wildcard `*`. A *granted*
|
|
47
|
+
* permission may wildcard (see {@link implies}); a *required* one may not —
|
|
48
|
+
* `RbacAuthorizer` rejects a wildcard requirement as caller misuse.
|
|
49
|
+
*/
|
|
50
|
+
hasWildcard(): boolean;
|
|
51
|
+
toPrimitive(): string;
|
|
52
|
+
}
|
|
53
|
+
//#endregion
|
|
54
|
+
//#region src/core/rbac/domain/scope.d.ts
|
|
55
|
+
/** The wrapped, already-validated scope string (`"type:id"` or `"*"`). */
|
|
56
|
+
type ScopeProps = {
|
|
57
|
+
readonly raw: string;
|
|
58
|
+
};
|
|
59
|
+
/**
|
|
60
|
+
* Where a grant (or a target resource) lives, aligned with
|
|
61
|
+
* `AuthorizationRequirement.scope` and the policy `PolicyScope`
|
|
62
|
+
* (`tenantId`/`schoolId`): `"tenant:{id}"`, `"school:{id}"`, or the global
|
|
63
|
+
* `"*"`.
|
|
64
|
+
*
|
|
65
|
+
* v1 keeps containment ({@link includes}) flat — the global scope contains
|
|
66
|
+
* everything, and any other scope contains only itself. A real hierarchy
|
|
67
|
+
* (`tenant` ⊇ `school`) is a deliberately deferred extension point.
|
|
68
|
+
*/
|
|
69
|
+
declare class Scope extends ValueObject<ScopeProps, string> {
|
|
70
|
+
private constructor();
|
|
71
|
+
/** Validates `"type:id"` or the global `"*"`, throwing on anything else. */
|
|
72
|
+
static of(raw: string): Scope;
|
|
73
|
+
/** The global scope `"*"`, which {@link includes} every other scope. */
|
|
74
|
+
static global(): Scope;
|
|
75
|
+
/** The type segment, or `"*"` for the global scope. */
|
|
76
|
+
get type(): string;
|
|
77
|
+
/** The id segment, or `undefined` for the global scope. */
|
|
78
|
+
get id(): string | undefined;
|
|
79
|
+
/**
|
|
80
|
+
* Whether this scope contains `target`. The global scope contains
|
|
81
|
+
* everything; any other scope contains only an exact match of itself.
|
|
82
|
+
*/
|
|
83
|
+
includes(target: Scope): boolean;
|
|
84
|
+
toPrimitive(): string;
|
|
85
|
+
}
|
|
86
|
+
//#endregion
|
|
87
|
+
//#region src/core/rbac/access-request.d.ts
|
|
88
|
+
/**
|
|
89
|
+
* The question put to the {@link RbacAuthorizer}: *may this actor perform this
|
|
90
|
+
* action on this resource, in this scope?* Carries the audit-friendly business
|
|
91
|
+
* `action` label alongside the concrete {@link Permission} it requires, and
|
|
92
|
+
* references the target by type/id only (never the full payload).
|
|
93
|
+
*/
|
|
94
|
+
interface AccessRequest {
|
|
95
|
+
/** Who is acting — reused as the RBAC subject, no separate `Actor` type. */
|
|
96
|
+
readonly actor: RequestedBy;
|
|
97
|
+
/** Stable business action for auditing, e.g. `"order.cancel"`. */
|
|
98
|
+
readonly action: string;
|
|
99
|
+
/**
|
|
100
|
+
* The permission the action requires, e.g. `Permission.of("orders:cancel")`.
|
|
101
|
+
* Never a wildcard — only the *granted* permissions on roles may wildcard.
|
|
102
|
+
*/
|
|
103
|
+
readonly required: Permission;
|
|
104
|
+
/** The target resource, identified without leaking its payload. */
|
|
105
|
+
readonly resource?: {
|
|
106
|
+
readonly type: string;
|
|
107
|
+
readonly id?: string;
|
|
108
|
+
};
|
|
109
|
+
/** The scope the resource lives in, e.g. `Scope.of("school:123")`. */
|
|
110
|
+
readonly scope: Scope;
|
|
111
|
+
}
|
|
112
|
+
//#endregion
|
|
113
|
+
//#region src/core/application/ports/authorizer.port.d.ts
|
|
114
|
+
/**
|
|
115
|
+
* The seam an application layer injects to authorize an action — symmetric to
|
|
116
|
+
* {@link PolicyPort}. The use case depends only on this contract; an adapter in
|
|
117
|
+
* the consumer's stack loads the actor's grants (from its own store) and
|
|
118
|
+
* delegates the decision to the pure `RbacAuthorizer`.
|
|
119
|
+
*
|
|
120
|
+
* Returns the decision as a {@link Result}: `ok(void)` when allowed, or
|
|
121
|
+
* `err(AuthorizationError)` carrying the discriminated `reason` the HTTP
|
|
122
|
+
* boundary maps to a 403. Promised here under `[extension-points]` in
|
|
123
|
+
* KIT_CONTEXT.md.
|
|
124
|
+
*/
|
|
125
|
+
interface AuthorizerPort {
|
|
126
|
+
authorize(request: AccessRequest): Promise<Result<void, AuthorizationError>>;
|
|
127
|
+
}
|
|
128
|
+
//#endregion
|
|
129
|
+
export { Permission as a, ScopeProps as i, AccessRequest as n, PermissionProps as o, Scope as r, AuthorizerPort as t };
|
|
130
|
+
//# sourceMappingURL=authorizer.port.d.ts.map
|
|
@@ -0,0 +1,216 @@
|
|
|
1
|
+
import { t as RequestedBy } from "./requested-by.cjs";
|
|
2
|
+
import { t as AuthorizationError } from "./authorization-error.cjs";
|
|
3
|
+
import { r as Result } from "./result.cjs";
|
|
4
|
+
import { T as PolicyContext, j as ConditionNode, t as CoreConfig } from "./core-config.cjs";
|
|
5
|
+
import { t as ValueObject } from "./value-object.cjs";
|
|
6
|
+
import { n as AccessRequest, t as AuthorizerPort } from "./authorizer.port.cjs";
|
|
7
|
+
|
|
8
|
+
//#region src/core/abac/abac-request.d.ts
|
|
9
|
+
/**
|
|
10
|
+
* The attribute bags an ABAC decision reasons over, grouped by the four standard
|
|
11
|
+
* categories. Each is a flat record; {@link abacContext} nests them under
|
|
12
|
+
* `subject` / `resource` / `action` / `env` so a rule's condition can reference
|
|
13
|
+
* fields like `resource.status` or `env.businessHours`.
|
|
14
|
+
*/
|
|
15
|
+
type AbacAttributes = {
|
|
16
|
+
readonly subject?: Record<string, unknown>;
|
|
17
|
+
readonly resource?: Record<string, unknown>;
|
|
18
|
+
readonly action?: Record<string, unknown>;
|
|
19
|
+
readonly environment?: Record<string, unknown>;
|
|
20
|
+
};
|
|
21
|
+
/**
|
|
22
|
+
* The question put to the {@link AbacAuthorizer}: *do these attributes authorize
|
|
23
|
+
* this action?* Carries the acting {@link RequestedBy} (reused as the subject),
|
|
24
|
+
* an audit-friendly `action` label, an optional type/id resource reference, and
|
|
25
|
+
* the {@link AbacAttributes} the rules evaluate.
|
|
26
|
+
*/
|
|
27
|
+
interface AbacRequest {
|
|
28
|
+
readonly actor: RequestedBy;
|
|
29
|
+
readonly action: string;
|
|
30
|
+
readonly resource?: {
|
|
31
|
+
readonly type: string;
|
|
32
|
+
readonly id?: string;
|
|
33
|
+
};
|
|
34
|
+
readonly attributes: AbacAttributes;
|
|
35
|
+
}
|
|
36
|
+
//#endregion
|
|
37
|
+
//#region src/core/application/ports/abac-authorizer.port.d.ts
|
|
38
|
+
/**
|
|
39
|
+
* The seam an application layer injects to authorize an action by attributes —
|
|
40
|
+
* symmetric to {@link AuthorizerPort} (RBAC). The use case depends only on this
|
|
41
|
+
* contract; an adapter in the consumer's stack resolves the dynamic attributes
|
|
42
|
+
* (resource state, environment, RBAC facts) and delegates the decision to the
|
|
43
|
+
* pure `AbacAuthorizer`.
|
|
44
|
+
*
|
|
45
|
+
* Returns the decision as a {@link Result}: `ok(void)` when permitted, or
|
|
46
|
+
* `err(AuthorizationError)` carrying `reason: "policy_denied"` (or `"forbidden"`
|
|
47
|
+
* on a fail-closed evaluation error) that the HTTP boundary maps to a 403.
|
|
48
|
+
*/
|
|
49
|
+
interface AbacAuthorizerPort {
|
|
50
|
+
authorize(request: AbacRequest): Promise<Result<void, AuthorizationError>>;
|
|
51
|
+
}
|
|
52
|
+
//#endregion
|
|
53
|
+
//#region src/core/abac/attributes.d.ts
|
|
54
|
+
/**
|
|
55
|
+
* Flattens an {@link AbacRequest} into the nested {@link PolicyContext} the
|
|
56
|
+
* condition evaluator reads (it resolves dotted fields like `resource.status` by
|
|
57
|
+
* walking nested objects). The actor lands as `subject.id`; the four attribute
|
|
58
|
+
* bags nest under `subject` / `resource` / `action` / `env`; a `resource`
|
|
59
|
+
* reference adds `resource.type` / `resource.id`. The actor id and the resource
|
|
60
|
+
* reference win over any same-named keys in the attribute bags.
|
|
61
|
+
*
|
|
62
|
+
* Consumers fold dynamic facts (e.g. RBAC roles/permissions derived from grants,
|
|
63
|
+
* or a computed `ownerIsActor` flag) into `attributes.subject` / `resource` so
|
|
64
|
+
* rules can reference `subject.roles`, `resource.ownerIsActor`, and the like.
|
|
65
|
+
*/
|
|
66
|
+
declare function abacContext(request: AbacRequest): PolicyContext;
|
|
67
|
+
//#endregion
|
|
68
|
+
//#region src/core/abac/domain/rule.d.ts
|
|
69
|
+
/** Whether a matching {@link AbacRule} permits or denies the action. */
|
|
70
|
+
type RuleEffect = "PERMIT" | "DENY";
|
|
71
|
+
/**
|
|
72
|
+
* The serializable shape of a rule: a stable `id`/`version` (surfaced on a
|
|
73
|
+
* denial as `policyId`/`policyVersion`), the {@link RuleEffect}, and the
|
|
74
|
+
* {@link ConditionNode} that decides whether the rule applies to a request's
|
|
75
|
+
* flattened attributes.
|
|
76
|
+
*/
|
|
77
|
+
type AbacRuleProps = {
|
|
78
|
+
readonly id: string;
|
|
79
|
+
readonly version: number;
|
|
80
|
+
readonly effect: RuleEffect;
|
|
81
|
+
readonly condition: ConditionNode;
|
|
82
|
+
readonly description?: string;
|
|
83
|
+
};
|
|
84
|
+
/**
|
|
85
|
+
* A single attribute-based rule: an {@link RuleEffect} that takes hold when its
|
|
86
|
+
* {@link ConditionNode} matches the request's flattened attributes. The
|
|
87
|
+
* condition reuses the gate/compute condition DSL (`{ field, op, value }` leaves
|
|
88
|
+
* combined with `and`/`or`/`not`), so a consumer learns one condition language
|
|
89
|
+
* across the whole kit.
|
|
90
|
+
*
|
|
91
|
+
* A zod-free value object: it validates its own shape on construction (throwing
|
|
92
|
+
* {@link InvalidValueException}) and is deep-frozen thereafter. Because rules are
|
|
93
|
+
* authored in TypeScript — trusted data, not untrusted JSON — the structural
|
|
94
|
+
* check is a hand-rolled walk of the condition tree rather than a schema parse.
|
|
95
|
+
* Build one through {@link of}; the constructor is private.
|
|
96
|
+
*/
|
|
97
|
+
declare class AbacRule extends ValueObject<AbacRuleProps, AbacRuleProps> {
|
|
98
|
+
private constructor();
|
|
99
|
+
/**
|
|
100
|
+
* Builds a rule, validating that `id` is non-blank, `version` is an integer
|
|
101
|
+
* ≥ 1, `effect` is `"PERMIT"`/`"DENY"`, and `condition` is a well-formed
|
|
102
|
+
* condition node. Throws {@link InvalidValueException} otherwise.
|
|
103
|
+
*/
|
|
104
|
+
static of(props: AbacRuleProps): AbacRule;
|
|
105
|
+
private static assertConditionShape;
|
|
106
|
+
private static rejectCondition;
|
|
107
|
+
get id(): string;
|
|
108
|
+
get version(): number;
|
|
109
|
+
get effect(): RuleEffect;
|
|
110
|
+
/** The condition tree evaluated against a request's flattened attributes. */
|
|
111
|
+
get condition(): ConditionNode;
|
|
112
|
+
get description(): string | undefined;
|
|
113
|
+
toPrimitive(): AbacRuleProps;
|
|
114
|
+
}
|
|
115
|
+
//#endregion
|
|
116
|
+
//#region src/core/abac/combining.d.ts
|
|
117
|
+
/**
|
|
118
|
+
* How an {@link AbacPolicySet} resolves several applicable rules into one effect.
|
|
119
|
+
*
|
|
120
|
+
* - `deny-overrides` (default, secure): any applicable DENY wins; otherwise the
|
|
121
|
+
* first PERMIT; otherwise the set's default effect.
|
|
122
|
+
* - `permit-overrides`: any applicable PERMIT wins; otherwise the first DENY;
|
|
123
|
+
* otherwise the default effect.
|
|
124
|
+
* - `first-applicable`: the effect of the first rule that matches; otherwise the
|
|
125
|
+
* default effect.
|
|
126
|
+
*/
|
|
127
|
+
type CombiningAlgorithm = "deny-overrides" | "permit-overrides" | "first-applicable";
|
|
128
|
+
/** The resolved decision plus the rule (if any) that produced it. */
|
|
129
|
+
//#endregion
|
|
130
|
+
//#region src/core/abac/domain/policy-set.d.ts
|
|
131
|
+
/**
|
|
132
|
+
* An ordered collection of {@link AbacRule}s plus how to combine them
|
|
133
|
+
* ({@link CombiningAlgorithm}) and what to decide when no rule applies
|
|
134
|
+
* ({@link defaultEffect}). This is where ABAC goes beyond a single gate
|
|
135
|
+
* condition: several PERMIT/DENY rules resolved by an explicit algorithm.
|
|
136
|
+
*
|
|
137
|
+
* Closed by default — the combining algorithm defaults to `"deny-overrides"` and
|
|
138
|
+
* the default effect to `"DENY"`, so a request matching nothing is denied. A
|
|
139
|
+
* plain holder (not a value object); build through {@link of}.
|
|
140
|
+
*/
|
|
141
|
+
declare class AbacPolicySet {
|
|
142
|
+
private readonly _rules;
|
|
143
|
+
private readonly _algorithm;
|
|
144
|
+
private readonly _defaultEffect;
|
|
145
|
+
private constructor();
|
|
146
|
+
static of(rules: readonly AbacRule[], options?: {
|
|
147
|
+
readonly algorithm?: CombiningAlgorithm;
|
|
148
|
+
readonly defaultEffect?: RuleEffect;
|
|
149
|
+
}): AbacPolicySet;
|
|
150
|
+
get rules(): readonly AbacRule[];
|
|
151
|
+
get algorithm(): CombiningAlgorithm;
|
|
152
|
+
get defaultEffect(): RuleEffect;
|
|
153
|
+
}
|
|
154
|
+
//#endregion
|
|
155
|
+
//#region src/core/abac/authorizer.d.ts
|
|
156
|
+
/** The successful decision returned by {@link AbacAuthorizer.authorize}. */
|
|
157
|
+
interface AbacDecision {
|
|
158
|
+
/** The audited business action that was allowed. */
|
|
159
|
+
readonly action: string;
|
|
160
|
+
readonly effect: "PERMIT";
|
|
161
|
+
/** The id of the rule that permitted, or `"<default>"` when the set's default did. */
|
|
162
|
+
readonly matchedRule: string;
|
|
163
|
+
readonly algorithm: CombiningAlgorithm;
|
|
164
|
+
/** When the decision was made, ISO-8601. */
|
|
165
|
+
readonly grantedAtIso: string;
|
|
166
|
+
}
|
|
167
|
+
/**
|
|
168
|
+
* The pure ABAC decisor. Given an {@link AbacRequest} and an
|
|
169
|
+
* {@link AbacPolicySet}, it flattens the request's attributes into a context,
|
|
170
|
+
* matches each rule's condition with the gate engine's pure condition evaluator
|
|
171
|
+
* (reused, not reimplemented), resolves the effects through the set's combining
|
|
172
|
+
* algorithm, and returns a {@link Result} — never throwing, mirroring the
|
|
173
|
+
* "errors as values" contract of `RbacAuthorizer`/`GateEngineV1`.
|
|
174
|
+
*
|
|
175
|
+
* A denial maps to {@link AuthorizationError.policyDenied}, attributed to the
|
|
176
|
+
* deciding rule's `id`/`version`; a condition-evaluation failure (a missing
|
|
177
|
+
* attribute, a wrong-typed operand) fails closed as
|
|
178
|
+
* {@link AuthorizationError.forbidden}. Loading the dynamic attributes is the
|
|
179
|
+
* consumer's job (behind an `AbacAuthorizerPort` adapter); this class only decides.
|
|
180
|
+
*/
|
|
181
|
+
declare class AbacAuthorizer {
|
|
182
|
+
private readonly coreConfig;
|
|
183
|
+
constructor(params?: {
|
|
184
|
+
readonly coreConfig?: CoreConfig;
|
|
185
|
+
});
|
|
186
|
+
authorize(request: AbacRequest, policies: AbacPolicySet): Result<AbacDecision, AuthorizationError>;
|
|
187
|
+
}
|
|
188
|
+
//#endregion
|
|
189
|
+
//#region src/core/abac/composite-authorizer.d.ts
|
|
190
|
+
/**
|
|
191
|
+
* The request a {@link CompositeAuthorizer} accepts — one object that satisfies
|
|
192
|
+
* both the RBAC {@link AccessRequest} (its `required`/`scope`) and the ABAC
|
|
193
|
+
* request (its `attributes`).
|
|
194
|
+
*/
|
|
195
|
+
type CompositeAccessRequest = AccessRequest & {
|
|
196
|
+
readonly attributes: AbacAttributes;
|
|
197
|
+
};
|
|
198
|
+
/**
|
|
199
|
+
* Runs a coarse RBAC check first, then refines with ABAC — the standard hybrid
|
|
200
|
+
* "does the actor hold the capability, *and* do the attributes allow it here?"
|
|
201
|
+
* flow. Short-circuits with the RBAC denial when the actor is not even capable,
|
|
202
|
+
* so the boundary sees the most specific 403.
|
|
203
|
+
*
|
|
204
|
+
* Only the two port interfaces are referenced (both erased at runtime), so this
|
|
205
|
+
* pulls in no RBAC/ABAC engine code of its own — it just sequences the seams the
|
|
206
|
+
* consumer wires up.
|
|
207
|
+
*/
|
|
208
|
+
declare class CompositeAuthorizer {
|
|
209
|
+
private readonly rbac;
|
|
210
|
+
private readonly abac;
|
|
211
|
+
constructor(rbac: AuthorizerPort, abac: AbacAuthorizerPort);
|
|
212
|
+
authorize(request: CompositeAccessRequest): Promise<Result<void, AuthorizationError>>;
|
|
213
|
+
}
|
|
214
|
+
//#endregion
|
|
215
|
+
export { AbacPolicySet as a, AbacRuleProps as c, AbacAuthorizerPort as d, AbacAttributes as f, AbacDecision as i, RuleEffect as l, CompositeAuthorizer as n, CombiningAlgorithm as o, AbacRequest as p, AbacAuthorizer as r, AbacRule as s, CompositeAccessRequest as t, abacContext as u };
|
|
216
|
+
//# sourceMappingURL=composite-authorizer.d.cts.map
|