@contractspec/lib.contracts 1.49.0 → 1.51.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/dist/app-config/contracts.d.ts +51 -51
- package/dist/app-config/events.d.ts +27 -27
- package/dist/app-config/lifecycle-contracts.d.ts +55 -55
- package/dist/app-config/runtime.d.ts +1 -1
- package/dist/app-config/spec.d.ts +1 -1
- package/dist/capabilities/capabilities.d.ts +64 -5
- package/dist/capabilities/capabilities.js +125 -0
- package/dist/capabilities/context.d.ts +88 -0
- package/dist/capabilities/context.js +87 -0
- package/dist/capabilities/docs/capabilities.docblock.js +191 -2
- package/dist/capabilities/guards.d.ts +110 -0
- package/dist/capabilities/guards.js +146 -0
- package/dist/capabilities/index.d.ts +4 -1
- package/dist/capabilities/index.js +4 -1
- package/dist/capabilities/validation.d.ts +76 -0
- package/dist/capabilities/validation.js +141 -0
- package/dist/client/react/feature-render.d.ts +2 -2
- package/dist/data-views/runtime.d.ts +1 -1
- package/dist/events.d.ts +79 -13
- package/dist/events.js +33 -3
- package/dist/examples/schema.d.ts +10 -10
- package/dist/experiments/spec.d.ts +7 -4
- package/dist/features/install.d.ts +4 -4
- package/dist/features/types.d.ts +28 -32
- package/dist/index.d.ts +21 -12
- package/dist/index.js +12 -3
- package/dist/install.d.ts +1 -1
- package/dist/integrations/openbanking/contracts/accounts.d.ts +67 -67
- package/dist/integrations/openbanking/contracts/balances.d.ts +35 -35
- package/dist/integrations/openbanking/contracts/transactions.d.ts +49 -49
- package/dist/integrations/openbanking/models.d.ts +55 -55
- package/dist/integrations/operations.d.ts +103 -103
- package/dist/integrations/spec.d.ts +1 -1
- package/dist/knowledge/operations.d.ts +67 -67
- package/dist/llm/exporters.d.ts +2 -2
- package/dist/markdown.d.ts +1 -1
- package/dist/onboarding-base.d.ts +29 -29
- package/dist/operations/operation.d.ts +6 -0
- package/dist/ownership.d.ts +133 -8
- package/dist/ownership.js +25 -0
- package/dist/policy/context.d.ts +237 -0
- package/dist/policy/context.js +227 -0
- package/dist/policy/guards.d.ts +145 -0
- package/dist/policy/guards.js +254 -0
- package/dist/policy/index.d.ts +12 -1
- package/dist/policy/index.js +11 -1
- package/dist/policy/spec.d.ts +7 -4
- package/dist/policy/validation.d.ts +67 -0
- package/dist/policy/validation.js +307 -0
- package/dist/presentations/presentations.d.ts +6 -0
- package/dist/tests/spec.d.ts +17 -12
- package/dist/themes.d.ts +7 -4
- package/dist/translations/index.d.ts +6 -0
- package/dist/translations/index.js +5 -0
- package/dist/translations/registry.d.ts +144 -0
- package/dist/translations/registry.js +223 -0
- package/dist/translations/spec.d.ts +126 -0
- package/dist/translations/spec.js +31 -0
- package/dist/translations/validation.d.ts +85 -0
- package/dist/translations/validation.js +328 -0
- package/dist/types.d.ts +140 -14
- package/dist/versioning/index.d.ts +2 -1
- package/dist/versioning/index.js +2 -1
- package/dist/versioning/refs.d.ts +179 -0
- package/dist/versioning/refs.js +161 -0
- package/dist/workflow/context.d.ts +191 -0
- package/dist/workflow/context.js +227 -0
- package/dist/workflow/index.d.ts +4 -2
- package/dist/workflow/index.js +4 -2
- package/dist/workflow/spec.d.ts +1 -1
- package/dist/workflow/validation.d.ts +64 -2
- package/dist/workflow/validation.js +194 -1
- package/package.json +19 -6
|
@@ -0,0 +1,227 @@
|
|
|
1
|
+
//#region src/policy/context.ts
|
|
2
|
+
/**
|
|
3
|
+
* Runtime policy context for opt-in policy enforcement.
|
|
4
|
+
*
|
|
5
|
+
* Provides a context object that can be used to check if a user/tenant
|
|
6
|
+
* has specific roles, permissions, and attributes at runtime.
|
|
7
|
+
*
|
|
8
|
+
* @module policy/context
|
|
9
|
+
*
|
|
10
|
+
* @example
|
|
11
|
+
* ```typescript
|
|
12
|
+
* import { createPolicyContext } from '@contractspec/lib.contracts';
|
|
13
|
+
*
|
|
14
|
+
* // Create context from user's roles and permissions
|
|
15
|
+
* const ctx = createPolicyContext({
|
|
16
|
+
* id: 'user-123',
|
|
17
|
+
* tenantId: 'tenant-456',
|
|
18
|
+
* roles: ['admin', 'editor'],
|
|
19
|
+
* permissions: ['read:articles', 'write:articles'],
|
|
20
|
+
* attributes: { department: 'engineering' },
|
|
21
|
+
* });
|
|
22
|
+
*
|
|
23
|
+
* // Check roles and permissions
|
|
24
|
+
* if (ctx.hasRole('admin')) {
|
|
25
|
+
* // User has admin role
|
|
26
|
+
* }
|
|
27
|
+
*
|
|
28
|
+
* // Audit access attempts
|
|
29
|
+
* ctx.auditAccess('article.update', 'allowed');
|
|
30
|
+
* ```
|
|
31
|
+
*/
|
|
32
|
+
/**
|
|
33
|
+
* Error thrown when a policy violation occurs.
|
|
34
|
+
*/
|
|
35
|
+
var PolicyViolationError = class extends Error {
|
|
36
|
+
violationType;
|
|
37
|
+
details;
|
|
38
|
+
constructor(type, details) {
|
|
39
|
+
const message = formatPolicyViolationMessage(type, details);
|
|
40
|
+
super(message);
|
|
41
|
+
this.name = "PolicyViolationError";
|
|
42
|
+
this.violationType = type;
|
|
43
|
+
this.details = details;
|
|
44
|
+
}
|
|
45
|
+
};
|
|
46
|
+
function formatPolicyViolationMessage(type, details) {
|
|
47
|
+
const prefix = details.operation ? `Policy violation for "${details.operation}": ` : "Policy violation: ";
|
|
48
|
+
switch (type) {
|
|
49
|
+
case "missing_role": return `${prefix}Missing required role "${details.requiredRole}"`;
|
|
50
|
+
case "missing_permission": return `${prefix}Missing required permission "${details.requiredPermission}"`;
|
|
51
|
+
case "missing_attribute": return `${prefix}Missing or invalid attribute "${details.requiredAttribute?.key}"`;
|
|
52
|
+
case "rate_limit_exceeded": return `${prefix}Rate limit exceeded for key "${details.rateLimitKey}"`;
|
|
53
|
+
case "consent_required": return `${prefix}Consent required: ${details.consentIds?.join(", ")}`;
|
|
54
|
+
case "access_denied": return `${prefix}${details.reason ?? "Access denied"}`;
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
var PolicyContextImpl = class {
|
|
58
|
+
user;
|
|
59
|
+
roles;
|
|
60
|
+
permissions;
|
|
61
|
+
rateLimitStates = /* @__PURE__ */ new Map();
|
|
62
|
+
rateLimitConfigs;
|
|
63
|
+
auditHandler;
|
|
64
|
+
constructor(user, options = {}) {
|
|
65
|
+
this.user = user;
|
|
66
|
+
this.roles = new Set(user.roles);
|
|
67
|
+
this.permissions = new Set(user.permissions);
|
|
68
|
+
this.rateLimitConfigs = options.rateLimits ?? {};
|
|
69
|
+
this.auditHandler = options.auditHandler;
|
|
70
|
+
}
|
|
71
|
+
hasRole(role) {
|
|
72
|
+
return this.roles.has(role);
|
|
73
|
+
}
|
|
74
|
+
hasAnyRole(roles) {
|
|
75
|
+
return roles.some((r) => this.roles.has(r));
|
|
76
|
+
}
|
|
77
|
+
hasAllRoles(roles) {
|
|
78
|
+
return roles.every((r) => this.roles.has(r));
|
|
79
|
+
}
|
|
80
|
+
requireRole(role) {
|
|
81
|
+
if (!this.hasRole(role)) throw new PolicyViolationError("missing_role", { requiredRole: role });
|
|
82
|
+
}
|
|
83
|
+
hasPermission(permission) {
|
|
84
|
+
return this.permissions.has(permission);
|
|
85
|
+
}
|
|
86
|
+
hasAnyPermission(permissions) {
|
|
87
|
+
return permissions.some((p) => this.permissions.has(p));
|
|
88
|
+
}
|
|
89
|
+
hasAllPermissions(permissions) {
|
|
90
|
+
return permissions.every((p) => this.permissions.has(p));
|
|
91
|
+
}
|
|
92
|
+
requirePermission(permission) {
|
|
93
|
+
if (!this.hasPermission(permission)) throw new PolicyViolationError("missing_permission", { requiredPermission: permission });
|
|
94
|
+
}
|
|
95
|
+
getAttribute(key) {
|
|
96
|
+
return this.user.attributes[key];
|
|
97
|
+
}
|
|
98
|
+
checkAttribute(key, expected) {
|
|
99
|
+
return this.user.attributes[key] === expected;
|
|
100
|
+
}
|
|
101
|
+
checkAttributeOneOf(key, allowedValues) {
|
|
102
|
+
return allowedValues.includes(this.user.attributes[key]);
|
|
103
|
+
}
|
|
104
|
+
checkRateLimit(key) {
|
|
105
|
+
const config = this.rateLimitConfigs[key];
|
|
106
|
+
if (!config) return {
|
|
107
|
+
allowed: true,
|
|
108
|
+
remaining: Infinity
|
|
109
|
+
};
|
|
110
|
+
const now = Date.now();
|
|
111
|
+
const state = this.rateLimitStates.get(key);
|
|
112
|
+
if (!state || now - state.windowStart >= config.windowMs) return {
|
|
113
|
+
allowed: true,
|
|
114
|
+
remaining: config.limit
|
|
115
|
+
};
|
|
116
|
+
const remaining = Math.max(0, config.limit - state.count);
|
|
117
|
+
if (remaining <= 0) return {
|
|
118
|
+
allowed: false,
|
|
119
|
+
remaining: 0,
|
|
120
|
+
resetAt: new Date(state.windowStart + config.windowMs),
|
|
121
|
+
retryAfterMs: state.windowStart + config.windowMs - now
|
|
122
|
+
};
|
|
123
|
+
return {
|
|
124
|
+
allowed: true,
|
|
125
|
+
remaining
|
|
126
|
+
};
|
|
127
|
+
}
|
|
128
|
+
consumeRateLimit(key, cost = 1) {
|
|
129
|
+
const config = this.rateLimitConfigs[key];
|
|
130
|
+
if (!config) return {
|
|
131
|
+
allowed: true,
|
|
132
|
+
remaining: Infinity
|
|
133
|
+
};
|
|
134
|
+
const now = Date.now();
|
|
135
|
+
let state = this.rateLimitStates.get(key);
|
|
136
|
+
if (!state || now - state.windowStart >= config.windowMs) state = {
|
|
137
|
+
key,
|
|
138
|
+
count: 0,
|
|
139
|
+
windowStart: now,
|
|
140
|
+
windowMs: config.windowMs,
|
|
141
|
+
limit: config.limit
|
|
142
|
+
};
|
|
143
|
+
if (state.count + cost > config.limit) {
|
|
144
|
+
const resetAt = new Date(state.windowStart + config.windowMs);
|
|
145
|
+
const retryAfterMs = state.windowStart + config.windowMs - now;
|
|
146
|
+
this.rateLimitStates.set(key, state);
|
|
147
|
+
return {
|
|
148
|
+
allowed: false,
|
|
149
|
+
remaining: Math.max(0, config.limit - state.count),
|
|
150
|
+
resetAt,
|
|
151
|
+
retryAfterMs
|
|
152
|
+
};
|
|
153
|
+
}
|
|
154
|
+
state.count += cost;
|
|
155
|
+
this.rateLimitStates.set(key, state);
|
|
156
|
+
return {
|
|
157
|
+
allowed: true,
|
|
158
|
+
remaining: Math.max(0, config.limit - state.count)
|
|
159
|
+
};
|
|
160
|
+
}
|
|
161
|
+
auditAccess(operation, result, reason) {
|
|
162
|
+
if (!this.auditHandler) return;
|
|
163
|
+
const entry = {
|
|
164
|
+
timestamp: /* @__PURE__ */ new Date(),
|
|
165
|
+
operation,
|
|
166
|
+
result,
|
|
167
|
+
reason,
|
|
168
|
+
userId: this.user.id,
|
|
169
|
+
tenantId: this.user.tenantId,
|
|
170
|
+
attributes: this.user.attributes
|
|
171
|
+
};
|
|
172
|
+
Promise.resolve(this.auditHandler(entry)).catch(() => {});
|
|
173
|
+
}
|
|
174
|
+
};
|
|
175
|
+
/**
|
|
176
|
+
* Creates a policy context from user information.
|
|
177
|
+
*
|
|
178
|
+
* @param user - User information for policy evaluation
|
|
179
|
+
* @param options - Additional context options
|
|
180
|
+
* @returns PolicyContext for checking/requiring policy compliance
|
|
181
|
+
*
|
|
182
|
+
* @example
|
|
183
|
+
* ```typescript
|
|
184
|
+
* const ctx = createPolicyContext({
|
|
185
|
+
* id: 'user-123',
|
|
186
|
+
* roles: ['editor'],
|
|
187
|
+
* permissions: ['read:articles'],
|
|
188
|
+
* attributes: { department: 'marketing' },
|
|
189
|
+
* });
|
|
190
|
+
*
|
|
191
|
+
* ctx.requireRole('editor');
|
|
192
|
+
* ctx.requirePermission('read:articles');
|
|
193
|
+
* ```
|
|
194
|
+
*/
|
|
195
|
+
function createPolicyContext(user, options) {
|
|
196
|
+
return new PolicyContextImpl(user, options);
|
|
197
|
+
}
|
|
198
|
+
/**
|
|
199
|
+
* Creates an empty policy context (no roles/permissions).
|
|
200
|
+
* Useful for anonymous users or testing.
|
|
201
|
+
*/
|
|
202
|
+
function createAnonymousPolicyContext(options) {
|
|
203
|
+
return new PolicyContextImpl({
|
|
204
|
+
id: "anonymous",
|
|
205
|
+
roles: [],
|
|
206
|
+
permissions: [],
|
|
207
|
+
attributes: {}
|
|
208
|
+
}, options);
|
|
209
|
+
}
|
|
210
|
+
/**
|
|
211
|
+
* Creates a bypass policy context with all roles/permissions.
|
|
212
|
+
* Useful for admin users or internal services.
|
|
213
|
+
*
|
|
214
|
+
* @param allRoles - All roles to grant
|
|
215
|
+
* @param allPermissions - All permissions to grant
|
|
216
|
+
*/
|
|
217
|
+
function createBypassPolicyContext(allRoles, allPermissions, options) {
|
|
218
|
+
return new PolicyContextImpl({
|
|
219
|
+
id: "system",
|
|
220
|
+
roles: allRoles,
|
|
221
|
+
permissions: allPermissions,
|
|
222
|
+
attributes: { bypass: true }
|
|
223
|
+
}, options);
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
//#endregion
|
|
227
|
+
export { PolicyViolationError, createAnonymousPolicyContext, createBypassPolicyContext, createPolicyContext };
|
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
import { AnyOperationSpec } from "../operations/operation.js";
|
|
2
|
+
import { PolicyContext } from "./context.js";
|
|
3
|
+
|
|
4
|
+
//#region src/policy/guards.d.ts
|
|
5
|
+
|
|
6
|
+
/** Result of a policy guard check. */
|
|
7
|
+
interface PolicyGuardResult {
|
|
8
|
+
/** Whether the guard passed. */
|
|
9
|
+
allowed: boolean;
|
|
10
|
+
/** Reason for denial if guard failed. */
|
|
11
|
+
reason?: string;
|
|
12
|
+
/** Missing requirements if guard failed. */
|
|
13
|
+
missing?: {
|
|
14
|
+
roles?: string[];
|
|
15
|
+
permissions?: string[];
|
|
16
|
+
flags?: string[];
|
|
17
|
+
};
|
|
18
|
+
}
|
|
19
|
+
/**
|
|
20
|
+
* Check if an operation's policy constraints are satisfied.
|
|
21
|
+
*
|
|
22
|
+
* @param ctx - Policy context to check against
|
|
23
|
+
* @param operation - Operation spec to check
|
|
24
|
+
* @param flags - Available feature flags
|
|
25
|
+
* @returns Guard result indicating if operation is allowed
|
|
26
|
+
*
|
|
27
|
+
* @example
|
|
28
|
+
* ```typescript
|
|
29
|
+
* const result = checkPolicyForOperation(ctx, myOperation, ['feature-x']);
|
|
30
|
+
* if (!result.allowed) {
|
|
31
|
+
* console.log('Denied:', result.reason);
|
|
32
|
+
* }
|
|
33
|
+
* ```
|
|
34
|
+
*/
|
|
35
|
+
declare function checkPolicyForOperation(ctx: PolicyContext, operation: AnyOperationSpec, flags?: string[]): PolicyGuardResult;
|
|
36
|
+
/**
|
|
37
|
+
* Assert that an operation's policy constraints are satisfied.
|
|
38
|
+
*
|
|
39
|
+
* @param ctx - Policy context to check against
|
|
40
|
+
* @param operation - Operation spec to check
|
|
41
|
+
* @param flags - Available feature flags
|
|
42
|
+
* @throws {PolicyViolationError} If policy is not satisfied
|
|
43
|
+
*
|
|
44
|
+
* @example
|
|
45
|
+
* ```typescript
|
|
46
|
+
* // Throws if policy not satisfied
|
|
47
|
+
* assertPolicyForOperation(ctx, myOperation);
|
|
48
|
+
*
|
|
49
|
+
* // Safe to proceed with operation
|
|
50
|
+
* await handler(input);
|
|
51
|
+
* ```
|
|
52
|
+
*/
|
|
53
|
+
declare function assertPolicyForOperation(ctx: PolicyContext, operation: AnyOperationSpec, flags?: string[]): void;
|
|
54
|
+
/**
|
|
55
|
+
* Filter operations to only those with satisfied policy constraints.
|
|
56
|
+
*
|
|
57
|
+
* @param ctx - Policy context to check against
|
|
58
|
+
* @param operations - Operations to filter
|
|
59
|
+
* @param flags - Available feature flags
|
|
60
|
+
* @returns Operations that have their policies satisfied
|
|
61
|
+
*/
|
|
62
|
+
declare function filterOperationsByPolicy(ctx: PolicyContext, operations: AnyOperationSpec[], flags?: string[]): AnyOperationSpec[];
|
|
63
|
+
/**
|
|
64
|
+
* Check if user has the required role for an operation.
|
|
65
|
+
*
|
|
66
|
+
* @param ctx - Policy context to check against
|
|
67
|
+
* @param requiredRole - Role required for the operation
|
|
68
|
+
* @param operation - Optional operation name for error messages
|
|
69
|
+
* @returns Guard result
|
|
70
|
+
*/
|
|
71
|
+
declare function checkRole(ctx: PolicyContext, requiredRole: string, operation?: string): PolicyGuardResult;
|
|
72
|
+
/**
|
|
73
|
+
* Assert user has the required role.
|
|
74
|
+
*
|
|
75
|
+
* @param ctx - Policy context to check against
|
|
76
|
+
* @param requiredRole - Role required
|
|
77
|
+
* @param operation - Optional operation name for error messages
|
|
78
|
+
* @throws {PolicyViolationError} If role is missing
|
|
79
|
+
*/
|
|
80
|
+
declare function assertRole(ctx: PolicyContext, requiredRole: string, operation?: string): void;
|
|
81
|
+
/**
|
|
82
|
+
* Check if user has any of the required roles.
|
|
83
|
+
*
|
|
84
|
+
* @param ctx - Policy context to check against
|
|
85
|
+
* @param requiredRoles - Any of these roles is sufficient
|
|
86
|
+
* @param operation - Optional operation name for error messages
|
|
87
|
+
* @returns Guard result
|
|
88
|
+
*/
|
|
89
|
+
declare function checkAnyRole(ctx: PolicyContext, requiredRoles: string[], operation?: string): PolicyGuardResult;
|
|
90
|
+
/**
|
|
91
|
+
* Check if user has the required permission.
|
|
92
|
+
*
|
|
93
|
+
* @param ctx - Policy context to check against
|
|
94
|
+
* @param requiredPermission - Permission required
|
|
95
|
+
* @param operation - Optional operation name for error messages
|
|
96
|
+
* @returns Guard result
|
|
97
|
+
*/
|
|
98
|
+
declare function checkPermission(ctx: PolicyContext, requiredPermission: string, operation?: string): PolicyGuardResult;
|
|
99
|
+
/**
|
|
100
|
+
* Assert user has the required permission.
|
|
101
|
+
*
|
|
102
|
+
* @param ctx - Policy context to check against
|
|
103
|
+
* @param requiredPermission - Permission required
|
|
104
|
+
* @param operation - Optional operation name for error messages
|
|
105
|
+
* @throws {PolicyViolationError} If permission is missing
|
|
106
|
+
*/
|
|
107
|
+
declare function assertPermission(ctx: PolicyContext, requiredPermission: string, operation?: string): void;
|
|
108
|
+
/**
|
|
109
|
+
* Check if user has all of the required permissions.
|
|
110
|
+
*
|
|
111
|
+
* @param ctx - Policy context to check against
|
|
112
|
+
* @param requiredPermissions - All of these permissions are required
|
|
113
|
+
* @param operation - Optional operation name for error messages
|
|
114
|
+
* @returns Guard result
|
|
115
|
+
*/
|
|
116
|
+
declare function checkAllPermissions(ctx: PolicyContext, requiredPermissions: string[], operation?: string): PolicyGuardResult;
|
|
117
|
+
interface CombinedPolicyRequirements {
|
|
118
|
+
roles?: string[];
|
|
119
|
+
anyRole?: string[];
|
|
120
|
+
permissions?: string[];
|
|
121
|
+
anyPermission?: string[];
|
|
122
|
+
flags?: string[];
|
|
123
|
+
}
|
|
124
|
+
/**
|
|
125
|
+
* Check multiple policy requirements at once.
|
|
126
|
+
*
|
|
127
|
+
* @param ctx - Policy context to check against
|
|
128
|
+
* @param requirements - Combined requirements to check
|
|
129
|
+
* @param flags - Available feature flags
|
|
130
|
+
* @param operation - Optional operation name for error messages
|
|
131
|
+
* @returns Guard result
|
|
132
|
+
*/
|
|
133
|
+
declare function checkCombinedPolicy(ctx: PolicyContext, requirements: CombinedPolicyRequirements, flags?: string[], operation?: string): PolicyGuardResult;
|
|
134
|
+
/**
|
|
135
|
+
* Assert multiple policy requirements at once.
|
|
136
|
+
*
|
|
137
|
+
* @param ctx - Policy context to check against
|
|
138
|
+
* @param requirements - Combined requirements to check
|
|
139
|
+
* @param flags - Available feature flags
|
|
140
|
+
* @param operation - Optional operation name for error messages
|
|
141
|
+
* @throws {PolicyViolationError} If any requirement is not met
|
|
142
|
+
*/
|
|
143
|
+
declare function assertCombinedPolicy(ctx: PolicyContext, requirements: CombinedPolicyRequirements, flags?: string[], operation?: string): void;
|
|
144
|
+
//#endregion
|
|
145
|
+
export { CombinedPolicyRequirements, PolicyGuardResult, assertCombinedPolicy, assertPermission, assertPolicyForOperation, assertRole, checkAllPermissions, checkAnyRole, checkCombinedPolicy, checkPermission, checkPolicyForOperation, checkRole, filterOperationsByPolicy };
|
|
@@ -0,0 +1,254 @@
|
|
|
1
|
+
import { PolicyViolationError } from "./context.js";
|
|
2
|
+
|
|
3
|
+
//#region src/policy/guards.ts
|
|
4
|
+
function checkAuthLevel(ctx, required) {
|
|
5
|
+
if (required === "anonymous") return true;
|
|
6
|
+
if (required === "user") return ctx.user.id !== "anonymous";
|
|
7
|
+
if (required === "admin") return ctx.hasRole("admin");
|
|
8
|
+
return false;
|
|
9
|
+
}
|
|
10
|
+
/**
|
|
11
|
+
* Check if an operation's policy constraints are satisfied.
|
|
12
|
+
*
|
|
13
|
+
* @param ctx - Policy context to check against
|
|
14
|
+
* @param operation - Operation spec to check
|
|
15
|
+
* @param flags - Available feature flags
|
|
16
|
+
* @returns Guard result indicating if operation is allowed
|
|
17
|
+
*
|
|
18
|
+
* @example
|
|
19
|
+
* ```typescript
|
|
20
|
+
* const result = checkPolicyForOperation(ctx, myOperation, ['feature-x']);
|
|
21
|
+
* if (!result.allowed) {
|
|
22
|
+
* console.log('Denied:', result.reason);
|
|
23
|
+
* }
|
|
24
|
+
* ```
|
|
25
|
+
*/
|
|
26
|
+
function checkPolicyForOperation(ctx, operation, flags = []) {
|
|
27
|
+
const { policy } = operation;
|
|
28
|
+
const operationName = `${operation.meta.key}.v${operation.meta.version}`;
|
|
29
|
+
const missing = {};
|
|
30
|
+
if (!checkAuthLevel(ctx, policy.auth)) return {
|
|
31
|
+
allowed: false,
|
|
32
|
+
reason: `Operation "${operationName}" requires "${policy.auth}" auth level`,
|
|
33
|
+
missing: { roles: policy.auth === "admin" ? ["admin"] : void 0 }
|
|
34
|
+
};
|
|
35
|
+
if (policy.flags?.length) {
|
|
36
|
+
const availableFlags = new Set(flags);
|
|
37
|
+
const missingFlags = policy.flags.filter((f) => !availableFlags.has(f));
|
|
38
|
+
if (missingFlags.length > 0) {
|
|
39
|
+
missing.flags = missingFlags;
|
|
40
|
+
return {
|
|
41
|
+
allowed: false,
|
|
42
|
+
reason: `Operation "${operationName}" requires feature flags: ${missingFlags.join(", ")}`,
|
|
43
|
+
missing
|
|
44
|
+
};
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
return { allowed: true };
|
|
48
|
+
}
|
|
49
|
+
/**
|
|
50
|
+
* Assert that an operation's policy constraints are satisfied.
|
|
51
|
+
*
|
|
52
|
+
* @param ctx - Policy context to check against
|
|
53
|
+
* @param operation - Operation spec to check
|
|
54
|
+
* @param flags - Available feature flags
|
|
55
|
+
* @throws {PolicyViolationError} If policy is not satisfied
|
|
56
|
+
*
|
|
57
|
+
* @example
|
|
58
|
+
* ```typescript
|
|
59
|
+
* // Throws if policy not satisfied
|
|
60
|
+
* assertPolicyForOperation(ctx, myOperation);
|
|
61
|
+
*
|
|
62
|
+
* // Safe to proceed with operation
|
|
63
|
+
* await handler(input);
|
|
64
|
+
* ```
|
|
65
|
+
*/
|
|
66
|
+
function assertPolicyForOperation(ctx, operation, flags = []) {
|
|
67
|
+
const result = checkPolicyForOperation(ctx, operation, flags);
|
|
68
|
+
if (!result.allowed) {
|
|
69
|
+
const operationName$1 = `${operation.meta.key}.v${operation.meta.version}`;
|
|
70
|
+
ctx.auditAccess(operationName$1, "denied", result.reason);
|
|
71
|
+
throw new PolicyViolationError("access_denied", {
|
|
72
|
+
operation: operationName$1,
|
|
73
|
+
reason: result.reason
|
|
74
|
+
});
|
|
75
|
+
}
|
|
76
|
+
const operationName = `${operation.meta.key}.v${operation.meta.version}`;
|
|
77
|
+
ctx.auditAccess(operationName, "allowed");
|
|
78
|
+
}
|
|
79
|
+
/**
|
|
80
|
+
* Filter operations to only those with satisfied policy constraints.
|
|
81
|
+
*
|
|
82
|
+
* @param ctx - Policy context to check against
|
|
83
|
+
* @param operations - Operations to filter
|
|
84
|
+
* @param flags - Available feature flags
|
|
85
|
+
* @returns Operations that have their policies satisfied
|
|
86
|
+
*/
|
|
87
|
+
function filterOperationsByPolicy(ctx, operations, flags = []) {
|
|
88
|
+
return operations.filter((op) => checkPolicyForOperation(ctx, op, flags).allowed);
|
|
89
|
+
}
|
|
90
|
+
/**
|
|
91
|
+
* Check if user has the required role for an operation.
|
|
92
|
+
*
|
|
93
|
+
* @param ctx - Policy context to check against
|
|
94
|
+
* @param requiredRole - Role required for the operation
|
|
95
|
+
* @param operation - Optional operation name for error messages
|
|
96
|
+
* @returns Guard result
|
|
97
|
+
*/
|
|
98
|
+
function checkRole(ctx, requiredRole, operation) {
|
|
99
|
+
if (ctx.hasRole(requiredRole)) return { allowed: true };
|
|
100
|
+
return {
|
|
101
|
+
allowed: false,
|
|
102
|
+
reason: operation ? `Operation "${operation}" requires role "${requiredRole}"` : `Missing required role "${requiredRole}"`,
|
|
103
|
+
missing: { roles: [requiredRole] }
|
|
104
|
+
};
|
|
105
|
+
}
|
|
106
|
+
/**
|
|
107
|
+
* Assert user has the required role.
|
|
108
|
+
*
|
|
109
|
+
* @param ctx - Policy context to check against
|
|
110
|
+
* @param requiredRole - Role required
|
|
111
|
+
* @param operation - Optional operation name for error messages
|
|
112
|
+
* @throws {PolicyViolationError} If role is missing
|
|
113
|
+
*/
|
|
114
|
+
function assertRole(ctx, requiredRole, operation) {
|
|
115
|
+
if (!checkRole(ctx, requiredRole, operation).allowed) throw new PolicyViolationError("missing_role", {
|
|
116
|
+
operation,
|
|
117
|
+
requiredRole
|
|
118
|
+
});
|
|
119
|
+
}
|
|
120
|
+
/**
|
|
121
|
+
* Check if user has any of the required roles.
|
|
122
|
+
*
|
|
123
|
+
* @param ctx - Policy context to check against
|
|
124
|
+
* @param requiredRoles - Any of these roles is sufficient
|
|
125
|
+
* @param operation - Optional operation name for error messages
|
|
126
|
+
* @returns Guard result
|
|
127
|
+
*/
|
|
128
|
+
function checkAnyRole(ctx, requiredRoles, operation) {
|
|
129
|
+
if (ctx.hasAnyRole(requiredRoles)) return { allowed: true };
|
|
130
|
+
return {
|
|
131
|
+
allowed: false,
|
|
132
|
+
reason: operation ? `Operation "${operation}" requires one of roles: ${requiredRoles.join(", ")}` : `Missing required role (need one of: ${requiredRoles.join(", ")})`,
|
|
133
|
+
missing: { roles: requiredRoles }
|
|
134
|
+
};
|
|
135
|
+
}
|
|
136
|
+
/**
|
|
137
|
+
* Check if user has the required permission.
|
|
138
|
+
*
|
|
139
|
+
* @param ctx - Policy context to check against
|
|
140
|
+
* @param requiredPermission - Permission required
|
|
141
|
+
* @param operation - Optional operation name for error messages
|
|
142
|
+
* @returns Guard result
|
|
143
|
+
*/
|
|
144
|
+
function checkPermission(ctx, requiredPermission, operation) {
|
|
145
|
+
if (ctx.hasPermission(requiredPermission)) return { allowed: true };
|
|
146
|
+
return {
|
|
147
|
+
allowed: false,
|
|
148
|
+
reason: operation ? `Operation "${operation}" requires permission "${requiredPermission}"` : `Missing required permission "${requiredPermission}"`,
|
|
149
|
+
missing: { permissions: [requiredPermission] }
|
|
150
|
+
};
|
|
151
|
+
}
|
|
152
|
+
/**
|
|
153
|
+
* Assert user has the required permission.
|
|
154
|
+
*
|
|
155
|
+
* @param ctx - Policy context to check against
|
|
156
|
+
* @param requiredPermission - Permission required
|
|
157
|
+
* @param operation - Optional operation name for error messages
|
|
158
|
+
* @throws {PolicyViolationError} If permission is missing
|
|
159
|
+
*/
|
|
160
|
+
function assertPermission(ctx, requiredPermission, operation) {
|
|
161
|
+
if (!checkPermission(ctx, requiredPermission, operation).allowed) throw new PolicyViolationError("missing_permission", {
|
|
162
|
+
operation,
|
|
163
|
+
requiredPermission
|
|
164
|
+
});
|
|
165
|
+
}
|
|
166
|
+
/**
|
|
167
|
+
* Check if user has all of the required permissions.
|
|
168
|
+
*
|
|
169
|
+
* @param ctx - Policy context to check against
|
|
170
|
+
* @param requiredPermissions - All of these permissions are required
|
|
171
|
+
* @param operation - Optional operation name for error messages
|
|
172
|
+
* @returns Guard result
|
|
173
|
+
*/
|
|
174
|
+
function checkAllPermissions(ctx, requiredPermissions, operation) {
|
|
175
|
+
const missing = requiredPermissions.filter((p) => !ctx.hasPermission(p));
|
|
176
|
+
if (missing.length === 0) return { allowed: true };
|
|
177
|
+
return {
|
|
178
|
+
allowed: false,
|
|
179
|
+
reason: operation ? `Operation "${operation}" requires permissions: ${missing.join(", ")}` : `Missing required permissions: ${missing.join(", ")}`,
|
|
180
|
+
missing: { permissions: missing }
|
|
181
|
+
};
|
|
182
|
+
}
|
|
183
|
+
/**
|
|
184
|
+
* Check multiple policy requirements at once.
|
|
185
|
+
*
|
|
186
|
+
* @param ctx - Policy context to check against
|
|
187
|
+
* @param requirements - Combined requirements to check
|
|
188
|
+
* @param flags - Available feature flags
|
|
189
|
+
* @param operation - Optional operation name for error messages
|
|
190
|
+
* @returns Guard result
|
|
191
|
+
*/
|
|
192
|
+
function checkCombinedPolicy(ctx, requirements, flags = [], operation) {
|
|
193
|
+
const missing = {};
|
|
194
|
+
const reasons = [];
|
|
195
|
+
if (requirements.roles?.length) {
|
|
196
|
+
const missingRoles = requirements.roles.filter((r) => !ctx.hasRole(r));
|
|
197
|
+
if (missingRoles.length > 0) {
|
|
198
|
+
missing.roles = missingRoles;
|
|
199
|
+
reasons.push(`Missing roles: ${missingRoles.join(", ")}`);
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
if (requirements.anyRole?.length) {
|
|
203
|
+
if (!ctx.hasAnyRole(requirements.anyRole)) {
|
|
204
|
+
missing.roles = [...missing.roles ?? [], ...requirements.anyRole];
|
|
205
|
+
reasons.push(`Need one of roles: ${requirements.anyRole.join(", ")}`);
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
if (requirements.permissions?.length) {
|
|
209
|
+
const missingPerms = requirements.permissions.filter((p) => !ctx.hasPermission(p));
|
|
210
|
+
if (missingPerms.length > 0) {
|
|
211
|
+
missing.permissions = missingPerms;
|
|
212
|
+
reasons.push(`Missing permissions: ${missingPerms.join(", ")}`);
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
if (requirements.anyPermission?.length) {
|
|
216
|
+
if (!ctx.hasAnyPermission(requirements.anyPermission)) {
|
|
217
|
+
missing.permissions = [...missing.permissions ?? [], ...requirements.anyPermission];
|
|
218
|
+
reasons.push(`Need one of permissions: ${requirements.anyPermission.join(", ")}`);
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
if (requirements.flags?.length) {
|
|
222
|
+
const availableFlags = new Set(flags);
|
|
223
|
+
const missingFlags = requirements.flags.filter((f) => !availableFlags.has(f));
|
|
224
|
+
if (missingFlags.length > 0) {
|
|
225
|
+
missing.flags = missingFlags;
|
|
226
|
+
reasons.push(`Missing feature flags: ${missingFlags.join(", ")}`);
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
if (reasons.length > 0) return {
|
|
230
|
+
allowed: false,
|
|
231
|
+
reason: (operation ? `Operation "${operation}": ` : "") + reasons.join("; "),
|
|
232
|
+
missing
|
|
233
|
+
};
|
|
234
|
+
return { allowed: true };
|
|
235
|
+
}
|
|
236
|
+
/**
|
|
237
|
+
* Assert multiple policy requirements at once.
|
|
238
|
+
*
|
|
239
|
+
* @param ctx - Policy context to check against
|
|
240
|
+
* @param requirements - Combined requirements to check
|
|
241
|
+
* @param flags - Available feature flags
|
|
242
|
+
* @param operation - Optional operation name for error messages
|
|
243
|
+
* @throws {PolicyViolationError} If any requirement is not met
|
|
244
|
+
*/
|
|
245
|
+
function assertCombinedPolicy(ctx, requirements, flags = [], operation) {
|
|
246
|
+
const result = checkCombinedPolicy(ctx, requirements, flags, operation);
|
|
247
|
+
if (!result.allowed) throw new PolicyViolationError("access_denied", {
|
|
248
|
+
operation,
|
|
249
|
+
reason: result.reason
|
|
250
|
+
});
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
//#endregion
|
|
254
|
+
export { assertCombinedPolicy, assertPermission, assertPolicyForOperation, assertRole, checkAllPermissions, checkAnyRole, checkCombinedPolicy, checkPermission, checkPolicyForOperation, checkRole, filterOperationsByPolicy };
|
package/dist/policy/index.d.ts
CHANGED
|
@@ -2,4 +2,15 @@ import { AttributeMatcher, ConsentDefinition, FieldPolicyRule, PIIPolicy, Policy
|
|
|
2
2
|
import { PolicyRegistry } from "./registry.js";
|
|
3
3
|
import { DecisionContext, PolicyEngine, ResourceContext, SubjectContext, SubjectRelationship } from "./engine.js";
|
|
4
4
|
import { OPAAdapterOptions, OPAClient, OPAEvaluationResult, OPAPolicyAdapter, buildOPAInput } from "./opa-adapter.js";
|
|
5
|
-
|
|
5
|
+
import { AuditEntry, AuditHandler, PolicyContext, PolicyContextOptions, PolicyUser, PolicyViolationDetails, PolicyViolationError, PolicyViolationType, RateLimitResult, RateLimitState, createAnonymousPolicyContext, createBypassPolicyContext, createPolicyContext } from "./context.js";
|
|
6
|
+
import { CombinedPolicyRequirements, PolicyGuardResult, assertCombinedPolicy, assertPermission, assertPolicyForOperation, assertRole, checkAllPermissions, checkAnyRole, checkCombinedPolicy, checkPermission, checkPolicyForOperation, checkRole, filterOperationsByPolicy } from "./guards.js";
|
|
7
|
+
import { PolicyConsistencyDeps, PolicyValidationError, PolicyValidationIssue, PolicyValidationLevel, PolicyValidationResult, assertPolicyConsistency, assertPolicySpecValid, validatePolicyConsistency, validatePolicySpec } from "./validation.js";
|
|
8
|
+
|
|
9
|
+
//#region src/policy/index.d.ts
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Helper to define a Policy.
|
|
13
|
+
*/
|
|
14
|
+
declare const definePolicy: (spec: PolicySpec) => PolicySpec;
|
|
15
|
+
//#endregion
|
|
16
|
+
export { AttributeMatcher, AuditEntry, AuditHandler, CombinedPolicyRequirements, ConsentDefinition, DecisionContext, FieldPolicyRule, OPAAdapterOptions, OPAClient, OPAEvaluationResult, OPAPolicyAdapter, PIIPolicy, PolicyCondition, PolicyConsistencyDeps, PolicyContext, PolicyContextOptions, PolicyEffect, PolicyEngine, PolicyGuardResult, PolicyMeta, PolicyOPAConfig, PolicyRef, PolicyRegistry, PolicyRule, PolicySpec, PolicyUser, PolicyValidationError, PolicyValidationIssue, PolicyValidationLevel, PolicyValidationResult, PolicyViolationDetails, PolicyViolationError, PolicyViolationType, RateLimitDefinition, RateLimitResult, RateLimitState, RelationshipDefinition, RelationshipMatcher, ResourceContext, ResourceMatcher, SubjectContext, SubjectMatcher, SubjectRelationship, assertCombinedPolicy, assertPermission, assertPolicyConsistency, assertPolicyForOperation, assertPolicySpecValid, assertRole, buildOPAInput, checkAllPermissions, checkAnyRole, checkCombinedPolicy, checkPermission, checkPolicyForOperation, checkRole, createAnonymousPolicyContext, createBypassPolicyContext, createPolicyContext, definePolicy, filterOperationsByPolicy, validatePolicyConsistency, validatePolicySpec };
|
package/dist/policy/index.js
CHANGED
|
@@ -1,5 +1,15 @@
|
|
|
1
1
|
import { PolicyRegistry } from "./registry.js";
|
|
2
2
|
import { PolicyEngine } from "./engine.js";
|
|
3
3
|
import { OPAPolicyAdapter, buildOPAInput } from "./opa-adapter.js";
|
|
4
|
+
import { PolicyViolationError, createAnonymousPolicyContext, createBypassPolicyContext, createPolicyContext } from "./context.js";
|
|
5
|
+
import { assertCombinedPolicy, assertPermission, assertPolicyForOperation, assertRole, checkAllPermissions, checkAnyRole, checkCombinedPolicy, checkPermission, checkPolicyForOperation, checkRole, filterOperationsByPolicy } from "./guards.js";
|
|
6
|
+
import { PolicyValidationError, assertPolicyConsistency, assertPolicySpecValid, validatePolicyConsistency, validatePolicySpec } from "./validation.js";
|
|
4
7
|
|
|
5
|
-
|
|
8
|
+
//#region src/policy/index.ts
|
|
9
|
+
/**
|
|
10
|
+
* Helper to define a Policy.
|
|
11
|
+
*/
|
|
12
|
+
const definePolicy = (spec) => spec;
|
|
13
|
+
|
|
14
|
+
//#endregion
|
|
15
|
+
export { OPAPolicyAdapter, PolicyEngine, PolicyRegistry, PolicyValidationError, PolicyViolationError, assertCombinedPolicy, assertPermission, assertPolicyConsistency, assertPolicyForOperation, assertPolicySpecValid, assertRole, buildOPAInput, checkAllPermissions, checkAnyRole, checkCombinedPolicy, checkPermission, checkPolicyForOperation, checkRole, createAnonymousPolicyContext, createBypassPolicyContext, createPolicyContext, definePolicy, filterOperationsByPolicy, validatePolicyConsistency, validatePolicySpec };
|
package/dist/policy/spec.d.ts
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
|
+
import { VersionedSpecRef } from "../versioning/refs.js";
|
|
1
2
|
import { OwnerShipMeta } from "../ownership.js";
|
|
2
3
|
|
|
3
4
|
//#region src/policy/spec.d.ts
|
|
5
|
+
/** Effect of a policy rule: allow or deny access. */
|
|
4
6
|
type PolicyEffect = 'allow' | 'deny';
|
|
5
7
|
interface RelationshipDefinition {
|
|
6
8
|
subjectType: string;
|
|
@@ -95,9 +97,10 @@ interface PolicySpec {
|
|
|
95
97
|
rateLimits?: RateLimitDefinition[];
|
|
96
98
|
opa?: PolicyOPAConfig;
|
|
97
99
|
}
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
100
|
+
/**
|
|
101
|
+
* Reference to a policy spec.
|
|
102
|
+
* Uses key and version to identify a specific policy.
|
|
103
|
+
*/
|
|
104
|
+
type PolicyRef = VersionedSpecRef;
|
|
102
105
|
//#endregion
|
|
103
106
|
export { AttributeMatcher, ConsentDefinition, FieldPolicyRule, PIIPolicy, PolicyCondition, PolicyEffect, PolicyMeta, PolicyOPAConfig, PolicyRef, PolicyRule, PolicySpec, RateLimitDefinition, RelationshipDefinition, RelationshipMatcher, ResourceMatcher, SubjectMatcher };
|