@siremzam/sentinel 0.3.2 → 0.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/README.md +23 -8
- package/dist/{engine-C6IASR5F.d.cts → engine-Ccws3LFj.d.cts} +8 -3
- package/dist/{engine-C6IASR5F.d.ts → engine-Ccws3LFj.d.ts} +8 -3
- package/dist/index.cjs +19 -1
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +2 -2
- package/dist/index.d.ts +2 -2
- package/dist/index.js +19 -1
- package/dist/index.js.map +1 -1
- package/dist/middleware/express.d.cts +1 -1
- package/dist/middleware/express.d.ts +1 -1
- package/dist/middleware/fastify.d.cts +1 -1
- package/dist/middleware/fastify.d.ts +1 -1
- package/dist/middleware/hono.cjs +57 -0
- package/dist/middleware/hono.cjs.map +1 -0
- package/dist/middleware/hono.d.cts +45 -0
- package/dist/middleware/hono.d.ts +45 -0
- package/dist/middleware/hono.js +32 -0
- package/dist/middleware/hono.js.map +1 -0
- package/dist/middleware/nestjs.d.cts +1 -1
- package/dist/middleware/nestjs.d.ts +1 -1
- package/dist/server.d.cts +1 -1
- package/dist/server.d.ts +1 -1
- package/package.json +7 -1
package/README.md
CHANGED
|
@@ -28,11 +28,11 @@ This library was built from a different starting point:
|
|
|
28
28
|
|
|
29
29
|
---
|
|
30
30
|
|
|
31
|
-
### What's New in 0.3.
|
|
31
|
+
### What's New in 0.3.3
|
|
32
32
|
|
|
33
|
+
- Hono middleware — `honoGuard()` via `@siremzam/sentinel/middleware/hono`
|
|
33
34
|
- README rewrite: evaluation walkthrough, concepts glossary, patterns & recipes, migration guide, benchmark data
|
|
34
35
|
- Standalone example (`examples/standalone/`) — no HTTP server needed
|
|
35
|
-
- "When NOT to Use This" and "Testing Your Policies" sections
|
|
36
36
|
|
|
37
37
|
See the full [CHANGELOG](./CHANGELOG.md).
|
|
38
38
|
|
|
@@ -61,7 +61,7 @@ See the full [CHANGELOG](./CHANGELOG.md).
|
|
|
61
61
|
- [toAuditEntry()](#toauditentry)
|
|
62
62
|
- [permitted() — UI Rendering](#permitted--ui-rendering)
|
|
63
63
|
- [Integration](#integration)
|
|
64
|
-
- [Middleware (Express, Fastify, NestJS)](#middleware)
|
|
64
|
+
- [Middleware (Express, Fastify, Hono, NestJS)](#middleware)
|
|
65
65
|
- [Server Mode](#server-mode)
|
|
66
66
|
- [JSON Policy Serialization](#json-policy-serialization)
|
|
67
67
|
- [Performance](#performance)
|
|
@@ -93,7 +93,7 @@ See the full [CHANGELOG](./CHANGELOG.md).
|
|
|
93
93
|
| UI permission set | `permitted()` returns `Set` | No | `permission.filter()` | `ability.can()` per action |
|
|
94
94
|
| JSON policy storage | `exportRules` / `importRules` + `ConditionRegistry` | CSV / JSON adapters | No | Via `@casl/ability/extra` |
|
|
95
95
|
| Server mode (HTTP microservice) | Built-in (`createAuthServer`) | No | No | No |
|
|
96
|
-
| Middleware | Express, Fastify, NestJS | Express (community) | Express (community) | Express, NestJS |
|
|
96
|
+
| Middleware | Express, Fastify, Hono, NestJS | Express (community) | Express (community) | Express, NestJS |
|
|
97
97
|
| Dependencies | **0** | 2+ | 2 | 1+ |
|
|
98
98
|
| DSL required | **No** (pure TypeScript) | Yes (Casbin model) | No | No |
|
|
99
99
|
|
|
@@ -325,12 +325,11 @@ Conditions receive the full `EvaluationContext` — subject, action, resource, r
|
|
|
325
325
|
|
|
326
326
|
### Async Conditions
|
|
327
327
|
|
|
328
|
-
For conditions that need database lookups or API calls:
|
|
328
|
+
For conditions that need database lookups or API calls, use async functions and the `*Async` evaluation methods:
|
|
329
329
|
|
|
330
330
|
```typescript
|
|
331
331
|
const engine = new AccessEngine<MySchema>({
|
|
332
332
|
schema: {} as MySchema,
|
|
333
|
-
asyncConditions: true,
|
|
334
333
|
});
|
|
335
334
|
|
|
336
335
|
engine.addRule(
|
|
@@ -348,7 +347,7 @@ engine.addRule(
|
|
|
348
347
|
const decision = await engine.evaluateAsync(user, "report:export", "report");
|
|
349
348
|
```
|
|
350
349
|
|
|
351
|
-
|
|
350
|
+
Use `evaluateAsync()`, `permittedAsync()`, and `explainAsync()` when you have async conditions. If you accidentally call the synchronous `evaluate()` or `explain()` with async conditions, the engine throws a clear error guiding you to the async API.
|
|
352
351
|
|
|
353
352
|
### Wildcard Action Patterns
|
|
354
353
|
|
|
@@ -573,6 +572,22 @@ fastify.post("/invoices/:id/approve", {
|
|
|
573
572
|
}, handler);
|
|
574
573
|
```
|
|
575
574
|
|
|
575
|
+
**Hono:**
|
|
576
|
+
|
|
577
|
+
```typescript
|
|
578
|
+
import { honoGuard } from "@siremzam/sentinel/middleware/hono";
|
|
579
|
+
|
|
580
|
+
app.post(
|
|
581
|
+
"/invoices/:id/approve",
|
|
582
|
+
honoGuard(engine, "invoice:approve", "invoice", {
|
|
583
|
+
getSubject: (c) => c.get("user"),
|
|
584
|
+
getResourceContext: (c) => ({ id: c.req.param("id") }),
|
|
585
|
+
getTenantId: (c) => c.req.header("x-tenant-id"),
|
|
586
|
+
}),
|
|
587
|
+
handler,
|
|
588
|
+
);
|
|
589
|
+
```
|
|
590
|
+
|
|
576
591
|
**NestJS:**
|
|
577
592
|
|
|
578
593
|
```typescript
|
|
@@ -970,7 +985,7 @@ See [SECURITY.md](./SECURITY.md) for responsible disclosure instructions.
|
|
|
970
985
|
| `defaultEffect` | `"deny"` (default) or `"allow"` |
|
|
971
986
|
| `onDecision` | Listener called on every evaluation |
|
|
972
987
|
| `onConditionError` | Called when a condition throws (fail-closed) |
|
|
973
|
-
| `asyncConditions` |
|
|
988
|
+
| `asyncConditions` | *(deprecated)* When true, sync methods throw immediately. Will be removed in v2. Async conditions are now detected automatically. |
|
|
974
989
|
| `strictTenancy` | Throw if tenantId is omitted for tenant-scoped subjects |
|
|
975
990
|
| `roleHierarchy` | A `RoleHierarchy` instance |
|
|
976
991
|
| `cacheSize` | LRU cache capacity (0 = disabled) |
|
|
@@ -122,9 +122,14 @@ interface EngineOptions<S extends SchemaDefinition> {
|
|
|
122
122
|
onDecision?: DecisionListener<S>;
|
|
123
123
|
onConditionError?: ConditionErrorHandler;
|
|
124
124
|
/**
|
|
125
|
-
* When true,
|
|
126
|
-
*
|
|
127
|
-
*
|
|
125
|
+
* When true, sync methods (evaluate, explain, permitted) throw immediately
|
|
126
|
+
* to force use of evaluateAsync, explainAsync, permittedAsync.
|
|
127
|
+
* When false (default), async conditions are detected at runtime and throw
|
|
128
|
+
* with a clear error pointing to the async API.
|
|
129
|
+
*
|
|
130
|
+
* @deprecated This option is deprecated and will be removed in v2. Async
|
|
131
|
+
* conditions are now detected automatically. Use evaluateAsync(),
|
|
132
|
+
* explainAsync(), or permittedAsync() when you have async conditions.
|
|
128
133
|
*/
|
|
129
134
|
asyncConditions?: boolean;
|
|
130
135
|
/**
|
|
@@ -122,9 +122,14 @@ interface EngineOptions<S extends SchemaDefinition> {
|
|
|
122
122
|
onDecision?: DecisionListener<S>;
|
|
123
123
|
onConditionError?: ConditionErrorHandler;
|
|
124
124
|
/**
|
|
125
|
-
* When true,
|
|
126
|
-
*
|
|
127
|
-
*
|
|
125
|
+
* When true, sync methods (evaluate, explain, permitted) throw immediately
|
|
126
|
+
* to force use of evaluateAsync, explainAsync, permittedAsync.
|
|
127
|
+
* When false (default), async conditions are detected at runtime and throw
|
|
128
|
+
* with a clear error pointing to the async API.
|
|
129
|
+
*
|
|
130
|
+
* @deprecated This option is deprecated and will be removed in v2. Async
|
|
131
|
+
* conditions are now detected automatically. Use evaluateAsync(),
|
|
132
|
+
* explainAsync(), or permittedAsync() when you have async conditions.
|
|
128
133
|
*/
|
|
129
134
|
asyncConditions?: boolean;
|
|
130
135
|
/**
|
package/dist/index.cjs
CHANGED
|
@@ -123,6 +123,11 @@ function createPolicyFactory() {
|
|
|
123
123
|
function escapeRegexMeta(s) {
|
|
124
124
|
return s.replace(/[.+?^${}()|[\]\\]/g, "\\$&");
|
|
125
125
|
}
|
|
126
|
+
function isThenable(value) {
|
|
127
|
+
return value != null && typeof value === "object" && typeof value.then === "function";
|
|
128
|
+
}
|
|
129
|
+
var ASYNC_CONDITION_EVALUATE_MSG = "Async condition encountered. Use evaluateAsync() instead.";
|
|
130
|
+
var ASYNC_CONDITION_EXPLAIN_MSG = "Async condition encountered. Use explainAsync() instead.";
|
|
126
131
|
function compileActionPatterns(actions) {
|
|
127
132
|
if (actions === "*") return null;
|
|
128
133
|
const patterns = [];
|
|
@@ -365,6 +370,9 @@ var AccessEngine = class {
|
|
|
365
370
|
for (let i = 0; i < rule.conditions.length; i++) {
|
|
366
371
|
try {
|
|
367
372
|
const result = rule.conditions[i](ctx);
|
|
373
|
+
if (isThenable(result)) {
|
|
374
|
+
throw new Error(ASYNC_CONDITION_EXPLAIN_MSG);
|
|
375
|
+
}
|
|
368
376
|
if (result !== true) {
|
|
369
377
|
conditionResults.push({ index: i, passed: false });
|
|
370
378
|
allConditionsPassed = false;
|
|
@@ -372,6 +380,9 @@ var AccessEngine = class {
|
|
|
372
380
|
conditionResults.push({ index: i, passed: true });
|
|
373
381
|
}
|
|
374
382
|
} catch (err) {
|
|
383
|
+
if (err instanceof Error && err.message === ASYNC_CONDITION_EXPLAIN_MSG) {
|
|
384
|
+
throw err;
|
|
385
|
+
}
|
|
375
386
|
conditionResults.push({
|
|
376
387
|
index: i,
|
|
377
388
|
passed: false,
|
|
@@ -503,8 +514,15 @@ var AccessEngine = class {
|
|
|
503
514
|
if (!rule.conditions) return true;
|
|
504
515
|
for (let i = 0; i < rule.conditions.length; i++) {
|
|
505
516
|
try {
|
|
506
|
-
|
|
517
|
+
const result = rule.conditions[i](ctx);
|
|
518
|
+
if (isThenable(result)) {
|
|
519
|
+
throw new Error(ASYNC_CONDITION_EVALUATE_MSG);
|
|
520
|
+
}
|
|
521
|
+
if (result !== true) return false;
|
|
507
522
|
} catch (err) {
|
|
523
|
+
if (err instanceof Error && err.message === ASYNC_CONDITION_EVALUATE_MSG) {
|
|
524
|
+
throw err;
|
|
525
|
+
}
|
|
508
526
|
this.emitConditionError(rule.id, i, err);
|
|
509
527
|
return false;
|
|
510
528
|
}
|
package/dist/index.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/index.ts","../src/policy-builder.ts","../src/engine.ts","../src/role-hierarchy.ts","../src/serialization.ts","../src/types.ts"],"sourcesContent":["export { AccessEngine } from \"./engine.js\";\nexport type { AccessEngineOptions } from \"./engine.js\";\nexport { RuleBuilder, allow, deny, createPolicyFactory } from \"./policy-builder.js\";\nexport { RoleHierarchy } from \"./role-hierarchy.js\";\nexport {\n ConditionRegistry,\n exportRules,\n exportRulesToJson,\n importRules,\n importRulesFromJson,\n} from \"./serialization.js\";\n\nexport { toAuditEntry } from \"./types.js\";\n\nexport type {\n SchemaDefinition,\n ActionString,\n InferRole,\n InferResource,\n InferAction,\n InferTenantId,\n RoleAssignment,\n Subject,\n ResourceContext,\n EvaluationContext,\n Condition,\n PolicyEffect,\n PolicyRule,\n Decision,\n AuditEntry,\n DecisionListener,\n ConditionError,\n ConditionErrorHandler,\n EngineOptions,\n ExplainResult,\n RuleEvaluation,\n ConditionResult,\n} from \"./types.js\";\n\nexport type {\n JsonPolicyRule,\n JsonPolicyDocument,\n} from \"./serialization.js\";\n\nexport type { ServerOptions, EvalRequestBody } from \"./server.js\";\n","import type {\n SchemaDefinition,\n InferRole,\n InferAction,\n InferResource,\n PolicyRule,\n PolicyEffect,\n Condition,\n} from \"./types.js\";\n\nlet ruleCounter = 0;\n\nfunction nextRuleId(prefix: string): string {\n return `${prefix}-${++ruleCounter}`;\n}\n\nexport class RuleBuilder<S extends SchemaDefinition> {\n private _effect: PolicyEffect;\n private _roles: InferRole<S>[] | \"*\" = \"*\";\n private _actions: InferAction<S>[] | \"*\" = \"*\";\n private _resources: InferResource<S>[] | \"*\" = \"*\";\n private _conditions: Condition<S>[] = [];\n private _priority = 0;\n private _description?: string;\n private _id: string;\n\n constructor(effect: PolicyEffect) {\n this._effect = effect;\n this._id = nextRuleId(effect);\n }\n\n id(id: string): this {\n this._id = id;\n return this;\n }\n\n roles(...roles: InferRole<S>[]): this {\n this._roles = roles;\n return this;\n }\n\n anyRole(): this {\n this._roles = \"*\";\n return this;\n }\n\n actions(...actions: InferAction<S>[]): this {\n this._actions = actions;\n return this;\n }\n\n anyAction(): this {\n this._actions = \"*\";\n return this;\n }\n\n on(...resources: InferResource<S>[]): this {\n this._resources = resources;\n return this;\n }\n\n anyResource(): this {\n this._resources = \"*\";\n return this;\n }\n\n when(condition: Condition<S>): this {\n this._conditions.push(condition);\n return this;\n }\n\n priority(p: number): this {\n this._priority = p;\n return this;\n }\n\n describe(desc: string): this {\n this._description = desc;\n return this;\n }\n\n build(): PolicyRule<S> {\n return {\n id: this._id,\n effect: this._effect,\n roles: this._roles,\n actions: this._actions,\n resources: this._resources,\n conditions: this._conditions.length > 0 ? this._conditions : undefined,\n priority: this._priority,\n description: this._description,\n };\n }\n}\n\nexport function allow<S extends SchemaDefinition>(): RuleBuilder<S> {\n return new RuleBuilder<S>(\"allow\");\n}\n\nexport function deny<S extends SchemaDefinition>(): RuleBuilder<S> {\n return new RuleBuilder<S>(\"deny\");\n}\n\n/**\n * Creates schema-bound allow/deny factories so you don't need to pass\n * the generic parameter on every call.\n *\n * ```ts\n * const { allow, deny } = createPolicyFactory<MySchema>();\n * allow().roles(\"admin\").anyAction().anyResource().build();\n * ```\n */\nexport function createPolicyFactory<S extends SchemaDefinition>(): {\n allow: () => RuleBuilder<S>;\n deny: () => RuleBuilder<S>;\n} {\n return {\n allow: () => new RuleBuilder<S>(\"allow\"),\n deny: () => new RuleBuilder<S>(\"deny\"),\n };\n}\n","import type {\n SchemaDefinition,\n InferAction,\n InferResource,\n InferRole,\n PolicyRule,\n Decision,\n Subject,\n ResourceContext,\n EvaluationContext,\n DecisionListener,\n EngineOptions,\n ConditionErrorHandler,\n ExplainResult,\n RuleEvaluation,\n ConditionResult,\n} from \"./types.js\";\nimport type { RuleBuilder } from \"./policy-builder.js\";\nimport { allow as _allow, deny as _deny } from \"./policy-builder.js\";\nimport type { RoleHierarchy } from \"./role-hierarchy.js\";\n\n// ---------------------------------------------------------------------------\n// Compiled rule — internal representation with pre-compiled regex\n// ---------------------------------------------------------------------------\n\ninterface CompiledRule<S extends SchemaDefinition> {\n rule: PolicyRule<S>;\n actionPatterns: RegExp[] | null;\n}\n\nfunction escapeRegexMeta(s: string): string {\n return s.replace(/[.+?^${}()|[\\]\\\\]/g, \"\\\\$&\");\n}\n\nfunction compileActionPatterns(actions: string[] | \"*\"): RegExp[] | null {\n if (actions === \"*\") return null;\n const patterns: RegExp[] = [];\n for (const action of actions) {\n if (action.includes(\"*\")) {\n const escaped = escapeRegexMeta(action).replace(/\\*/g, \"[^:]*\");\n patterns.push(new RegExp(\"^\" + escaped + \"$\"));\n }\n }\n return patterns.length > 0 ? patterns : null;\n}\n\n// ---------------------------------------------------------------------------\n// Engine\n// ---------------------------------------------------------------------------\n\nexport interface AccessEngineOptions<S extends SchemaDefinition> extends EngineOptions<S> {\n roleHierarchy?: RoleHierarchy<S>;\n /**\n * Enable LRU cache for evaluation results.\n * Only caches evaluations of rules WITHOUT conditions (context-independent).\n * Rules with conditions are never cached since their result depends on resourceContext.\n */\n cacheSize?: number;\n}\n\nexport class AccessEngine<S extends SchemaDefinition> {\n private compiled: CompiledRule<S>[] = [];\n private listeners: DecisionListener<S>[] = [];\n private asyncConditions: boolean;\n private _defaultDeny: boolean;\n private _strictTenancy: boolean;\n private hierarchy?: RoleHierarchy<S>;\n private cache?: LRUCache<Decision<S>>;\n private conditionErrorHandler?: ConditionErrorHandler;\n\n constructor(options: AccessEngineOptions<S>) {\n this.asyncConditions = options.asyncConditions ?? false;\n this._defaultDeny = (options.defaultEffect ?? \"deny\") === \"deny\";\n this._strictTenancy = options.strictTenancy ?? false;\n this.hierarchy = options.roleHierarchy;\n this.conditionErrorHandler = options.onConditionError;\n if (options.cacheSize && options.cacheSize > 0) {\n this.cache = new LRUCache(options.cacheSize);\n }\n if (options.onDecision) {\n this.listeners.push(options.onDecision);\n }\n }\n\n // -----------------------------------------------------------------------\n // Rule management\n // -----------------------------------------------------------------------\n\n addRule(rule: PolicyRule<S>): this {\n const frozen = Object.freeze({ ...rule });\n this.compiled.push({\n rule: frozen,\n actionPatterns: compileActionPatterns(frozen.actions as string[] | \"*\"),\n });\n this.cache?.clear();\n return this;\n }\n\n addRules(...rules: PolicyRule<S>[]): this {\n for (const rule of rules) {\n const frozen = Object.freeze({ ...rule });\n this.compiled.push({\n rule: frozen,\n actionPatterns: compileActionPatterns(frozen.actions as string[] | \"*\"),\n });\n }\n this.cache?.clear();\n return this;\n }\n\n removeRule(ruleId: string): boolean {\n const idx = this.compiled.findIndex((c) => c.rule.id === ruleId);\n if (idx === -1) return false;\n this.compiled.splice(idx, 1);\n this.cache?.clear();\n return true;\n }\n\n getRules(): ReadonlyArray<PolicyRule<S>> {\n return this.compiled.map((c) => c.rule);\n }\n\n clearRules(): void {\n this.compiled = [];\n this.cache?.clear();\n }\n\n // -----------------------------------------------------------------------\n // Cache control\n // -----------------------------------------------------------------------\n\n clearCache(): void {\n this.cache?.clear();\n }\n\n get cacheStats(): { size: number; maxSize: number } | null {\n if (!this.cache) return null;\n return { size: this.cache.size, maxSize: this.cache.maxSize };\n }\n\n // -----------------------------------------------------------------------\n // Fluent rule builders bound to this engine's schema\n // -----------------------------------------------------------------------\n\n allow(): RuleBuilder<S> {\n return _allow<S>();\n }\n\n deny(): RuleBuilder<S> {\n return _deny<S>();\n }\n\n // -----------------------------------------------------------------------\n // Observability\n // -----------------------------------------------------------------------\n\n onDecision(listener: DecisionListener<S>): () => void {\n this.listeners.push(listener);\n return () => {\n const idx = this.listeners.indexOf(listener);\n if (idx !== -1) this.listeners.splice(idx, 1);\n };\n }\n\n private emit(decision: Decision<S>): void {\n for (const listener of this.listeners) {\n try {\n const result = listener(decision);\n if (result instanceof Promise) {\n result.catch(() => {});\n }\n } catch {\n // listeners must not break evaluation\n }\n }\n }\n\n // -----------------------------------------------------------------------\n // Evaluation\n // -----------------------------------------------------------------------\n\n evaluate(\n subject: Subject<S>,\n action: InferAction<S>,\n resource: InferResource<S>,\n resourceContext: ResourceContext = {},\n tenantId?: string,\n ): Decision<S> {\n if (this.asyncConditions) {\n throw new Error(\n \"Engine has asyncConditions enabled. Use evaluateAsync() instead.\",\n );\n }\n this.validateInput(subject, action, resource);\n this.enforceTenancy(subject, tenantId);\n\n const cacheKey = this.cache\n ? buildCacheKey(subject.id, action as string, resource as string, tenantId)\n : undefined;\n if (cacheKey) {\n const cached = this.cache!.get(cacheKey);\n if (cached) {\n this.emit(cached);\n return cached;\n }\n }\n\n const start = performance.now();\n const ctx = this.buildContext(subject, action, resource, resourceContext, tenantId);\n const candidates = this.matchRules(subject, action, resource, tenantId);\n\n let matched: CompiledRule<S> | null = null;\n let matchedHasConditions = false;\n\n for (const compiled of candidates) {\n const { rule } = compiled;\n if (!rule.conditions || rule.conditions.length === 0) {\n matched = compiled;\n matchedHasConditions = false;\n break;\n }\n const allMet = this.evaluateConditionsSync(rule, ctx);\n if (allMet) {\n matched = compiled;\n matchedHasConditions = true;\n break;\n }\n }\n\n const decision = this.buildDecision(matched?.rule ?? null, ctx, start);\n\n if (cacheKey && !matchedHasConditions) {\n this.cache!.set(cacheKey, decision);\n }\n\n this.emit(decision);\n return decision;\n }\n\n async evaluateAsync(\n subject: Subject<S>,\n action: InferAction<S>,\n resource: InferResource<S>,\n resourceContext: ResourceContext = {},\n tenantId?: string,\n ): Promise<Decision<S>> {\n this.validateInput(subject, action, resource);\n this.enforceTenancy(subject, tenantId);\n const start = performance.now();\n const ctx = this.buildContext(subject, action, resource, resourceContext, tenantId);\n const candidates = this.matchRules(subject, action, resource, tenantId);\n\n let matched: PolicyRule<S> | null = null;\n\n for (const compiled of candidates) {\n const { rule } = compiled;\n if (!rule.conditions || rule.conditions.length === 0) {\n matched = rule;\n break;\n }\n const results = await Promise.all(\n rule.conditions.map((c, i) =>\n Promise.resolve()\n .then(() => c(ctx))\n .catch((err) => {\n this.emitConditionError(rule.id, i, err);\n return false;\n }),\n ),\n );\n if (results.every(Boolean)) {\n matched = rule;\n break;\n }\n }\n\n const decision = this.buildDecision(matched, ctx, start);\n this.emit(decision);\n return decision;\n }\n\n // -----------------------------------------------------------------------\n // permitted() — list allowed actions on a resource for UI rendering\n // -----------------------------------------------------------------------\n\n permitted(\n subject: Subject<S>,\n resource: InferResource<S>,\n actions: InferAction<S>[],\n resourceContext: ResourceContext = {},\n tenantId?: string,\n ): Set<InferAction<S>> {\n const allowed = new Set<InferAction<S>>();\n for (const action of actions) {\n const decision = this.asyncConditions\n ? (() => { throw new Error(\"Use permittedAsync() with asyncConditions enabled.\"); })()\n : this.evaluate(subject, action, resource, resourceContext, tenantId);\n if (decision.allowed) {\n allowed.add(action);\n }\n }\n return allowed;\n }\n\n async permittedAsync(\n subject: Subject<S>,\n resource: InferResource<S>,\n actions: InferAction<S>[],\n resourceContext: ResourceContext = {},\n tenantId?: string,\n ): Promise<Set<InferAction<S>>> {\n const allowed = new Set<InferAction<S>>();\n const results = await Promise.all(\n actions.map((action) =>\n this.evaluateAsync(subject, action, resource, resourceContext, tenantId),\n ),\n );\n for (let i = 0; i < actions.length; i++) {\n if (results[i]!.allowed) {\n allowed.add(actions[i]!);\n }\n }\n return allowed;\n }\n\n // -----------------------------------------------------------------------\n // explain() — full evaluation trace for debugging\n // -----------------------------------------------------------------------\n\n explain(\n subject: Subject<S>,\n action: InferAction<S>,\n resource: InferResource<S>,\n resourceContext: ResourceContext = {},\n tenantId?: string,\n ): ExplainResult<S> {\n if (this.asyncConditions) {\n throw new Error(\n \"Engine has asyncConditions enabled. Use explainAsync() instead.\",\n );\n }\n this.enforceTenancy(subject, tenantId);\n const start = performance.now();\n const ctx = this.buildContext(subject, action, resource, resourceContext, tenantId);\n const subjectRoles = this.resolveRoles(subject, tenantId);\n\n const evaluatedRules: RuleEvaluation<S>[] = [];\n let firstMatch: PolicyRule<S> | null = null;\n\n const sorted = this.sortCandidates([...this.compiled]);\n\n for (const compiled of sorted) {\n const { rule } = compiled;\n const roleMatched = rule.roles === \"*\" || rule.roles.some((r) => subjectRoles.has(r));\n const actionMatched = rule.actions === \"*\" || this.matchesAction(compiled, action);\n const resourceMatched = rule.resources === \"*\" || rule.resources.includes(resource);\n\n const conditionResults: ConditionResult[] = [];\n let allConditionsPassed = true;\n\n if (roleMatched && actionMatched && resourceMatched && rule.conditions) {\n for (let i = 0; i < rule.conditions.length; i++) {\n try {\n const result = rule.conditions[i]!(ctx);\n if (result !== true) {\n conditionResults.push({ index: i, passed: false });\n allConditionsPassed = false;\n } else {\n conditionResults.push({ index: i, passed: true });\n }\n } catch (err) {\n conditionResults.push({\n index: i,\n passed: false,\n error: err instanceof Error ? err.message : String(err),\n });\n allConditionsPassed = false;\n }\n }\n }\n\n const matched =\n roleMatched && actionMatched && resourceMatched &&\n (!rule.conditions || rule.conditions.length === 0 || allConditionsPassed);\n\n evaluatedRules.push({\n rule,\n roleMatched,\n actionMatched,\n resourceMatched,\n conditionResults,\n matched,\n });\n\n if (matched && !firstMatch) {\n firstMatch = rule;\n }\n }\n\n const allowed = firstMatch != null ? firstMatch.effect === \"allow\" : !this._defaultDeny;\n const effect = firstMatch?.effect ?? \"default-deny\";\n const reason = firstMatch\n ? `Matched rule \"${firstMatch.id}\"${firstMatch.description ? `: ${firstMatch.description}` : \"\"}`\n : \"No matching rule — default deny\";\n\n return {\n allowed,\n effect,\n reason,\n evaluatedRules,\n durationMs: performance.now() - start,\n };\n }\n\n async explainAsync(\n subject: Subject<S>,\n action: InferAction<S>,\n resource: InferResource<S>,\n resourceContext: ResourceContext = {},\n tenantId?: string,\n ): Promise<ExplainResult<S>> {\n this.enforceTenancy(subject, tenantId);\n const start = performance.now();\n const ctx = this.buildContext(subject, action, resource, resourceContext, tenantId);\n const subjectRoles = this.resolveRoles(subject, tenantId);\n\n const evaluatedRules: RuleEvaluation<S>[] = [];\n let firstMatch: PolicyRule<S> | null = null;\n\n const sorted = this.sortCandidates([...this.compiled]);\n\n for (const compiled of sorted) {\n const { rule } = compiled;\n const roleMatched = rule.roles === \"*\" || rule.roles.some((r) => subjectRoles.has(r));\n const actionMatched = rule.actions === \"*\" || this.matchesAction(compiled, action);\n const resourceMatched = rule.resources === \"*\" || rule.resources.includes(resource);\n\n const conditionResults: ConditionResult[] = [];\n let allConditionsPassed = true;\n\n if (roleMatched && actionMatched && resourceMatched && rule.conditions) {\n for (let i = 0; i < rule.conditions.length; i++) {\n try {\n const result = await rule.conditions[i]!(ctx);\n if (result !== true) {\n conditionResults.push({ index: i, passed: false });\n allConditionsPassed = false;\n } else {\n conditionResults.push({ index: i, passed: true });\n }\n } catch (err) {\n conditionResults.push({\n index: i,\n passed: false,\n error: err instanceof Error ? err.message : String(err),\n });\n allConditionsPassed = false;\n }\n }\n }\n\n const matched =\n roleMatched && actionMatched && resourceMatched &&\n (!rule.conditions || rule.conditions.length === 0 || allConditionsPassed);\n\n evaluatedRules.push({\n rule,\n roleMatched,\n actionMatched,\n resourceMatched,\n conditionResults,\n matched,\n });\n\n if (matched && !firstMatch) {\n firstMatch = rule;\n }\n }\n\n const allowed = firstMatch != null ? firstMatch.effect === \"allow\" : !this._defaultDeny;\n const effect = firstMatch?.effect ?? \"default-deny\";\n const reason = firstMatch\n ? `Matched rule \"${firstMatch.id}\"${firstMatch.description ? `: ${firstMatch.description}` : \"\"}`\n : \"No matching rule — default deny\";\n\n return {\n allowed,\n effect,\n reason,\n evaluatedRules,\n durationMs: performance.now() - start,\n };\n }\n\n // -----------------------------------------------------------------------\n // Fluent check API: can(subject).perform(action).on(resource)\n // -----------------------------------------------------------------------\n\n can(subject: Subject<S>): PerformStep<S> {\n return new PerformStep(this, subject);\n }\n\n // -----------------------------------------------------------------------\n // Internal helpers\n // -----------------------------------------------------------------------\n\n private validateInput(\n subject: Subject<S>,\n action: InferAction<S>,\n resource: InferResource<S>,\n ): void {\n if (!subject || typeof subject.id !== \"string\") {\n throw new Error(\"subject must be an object with a string id\");\n }\n if (!Array.isArray(subject.roles)) {\n throw new Error(\"subject.roles must be an array\");\n }\n if (!action || typeof action !== \"string\") {\n throw new Error(\"action must be a non-empty string\");\n }\n if (!resource || typeof resource !== \"string\") {\n throw new Error(\"resource must be a non-empty string\");\n }\n }\n\n private enforceTenancy(subject: Subject<S>, tenantId?: string): void {\n if (!this._strictTenancy || tenantId != null) return;\n const hasTenantScoped = subject.roles.some((r) => r.tenantId != null);\n if (hasTenantScoped) {\n throw new Error(\n \"strictTenancy is enabled and subject has tenant-scoped roles, \" +\n \"but no tenantId was provided to evaluate(). This could cause \" +\n \"cross-tenant privilege escalation. Pass an explicit tenantId.\",\n );\n }\n }\n\n private buildContext(\n subject: Subject<S>,\n action: InferAction<S>,\n resource: InferResource<S>,\n resourceContext: ResourceContext,\n tenantId?: string,\n ): EvaluationContext<S> {\n return { subject, action, resource, resourceContext, tenantId };\n }\n\n private evaluateConditionsSync(\n rule: PolicyRule<S>,\n ctx: EvaluationContext<S>,\n ): boolean {\n if (!rule.conditions) return true;\n for (let i = 0; i < rule.conditions.length; i++) {\n try {\n if (rule.conditions[i]!(ctx) !== true) return false;\n } catch (err) {\n this.emitConditionError(rule.id, i, err);\n return false;\n }\n }\n return true;\n }\n\n private emitConditionError(ruleId: string, conditionIndex: number, error: unknown): void {\n if (this.conditionErrorHandler) {\n try {\n this.conditionErrorHandler({ ruleId, conditionIndex, error });\n } catch {\n // error handler must not break evaluation\n }\n }\n }\n\n private matchRules(\n subject: Subject<S>,\n action: InferAction<S>,\n resource: InferResource<S>,\n tenantId?: string,\n ): CompiledRule<S>[] {\n const subjectRoles = this.resolveRoles(subject, tenantId);\n\n const matched = this.compiled.filter((compiled) => {\n const { rule } = compiled;\n if (rule.roles !== \"*\" && !rule.roles.some((r) => subjectRoles.has(r))) return false;\n if (rule.actions !== \"*\" && !this.matchesAction(compiled, action)) return false;\n if (rule.resources !== \"*\" && !rule.resources.includes(resource)) return false;\n return true;\n });\n\n return this.sortCandidates(matched);\n }\n\n private sortCandidates(candidates: CompiledRule<S>[]): CompiledRule<S>[] {\n return candidates.sort((a, b) => {\n const pa = a.rule.priority ?? 0;\n const pb = b.rule.priority ?? 0;\n if (pb !== pa) return pb - pa;\n if (a.rule.effect === \"deny\" && b.rule.effect === \"allow\") return -1;\n if (a.rule.effect === \"allow\" && b.rule.effect === \"deny\") return 1;\n return 0;\n });\n }\n\n private matchesAction(\n compiled: CompiledRule<S>,\n action: InferAction<S>,\n ): boolean {\n const { rule, actionPatterns } = compiled;\n if (rule.actions === \"*\") return true;\n const actionStr = action as string;\n if ((rule.actions as string[]).includes(actionStr)) return true;\n if (actionPatterns) {\n for (const pattern of actionPatterns) {\n if (pattern.test(actionStr)) return true;\n }\n }\n return false;\n }\n\n private resolveRoles(\n subject: Subject<S>,\n tenantId?: string,\n ): Set<string> {\n const directRoles = new Set<string>();\n for (const assignment of subject.roles) {\n if (tenantId == null || assignment.tenantId == null || assignment.tenantId === tenantId) {\n directRoles.add(assignment.role);\n }\n }\n\n if (!this.hierarchy) return directRoles;\n\n const expanded = new Set<string>();\n for (const role of directRoles) {\n for (const r of this.hierarchy.resolve(role as InferRole<S>)) {\n expanded.add(r);\n }\n }\n return expanded;\n }\n\n private buildDecision(\n matched: PolicyRule<S> | null,\n ctx: EvaluationContext<S>,\n start: number,\n ): Decision<S> {\n const allowed =\n matched != null ? matched.effect === \"allow\" : !this._defaultDeny;\n const effect = matched?.effect ?? \"default-deny\";\n const reason = matched\n ? `Matched rule \"${matched.id}\"${matched.description ? `: ${matched.description}` : \"\"}`\n : \"No matching rule — default deny\";\n\n return {\n allowed,\n effect,\n matchedRule: matched,\n subject: ctx.subject,\n action: ctx.action,\n resource: ctx.resource,\n resourceContext: ctx.resourceContext,\n tenantId: ctx.tenantId,\n timestamp: Date.now(),\n durationMs: performance.now() - start,\n reason,\n };\n }\n}\n\n// ---------------------------------------------------------------------------\n// Fluent check chain: can(user).perform(action).on(resource)\n// ---------------------------------------------------------------------------\n\nclass PerformStep<S extends SchemaDefinition> {\n constructor(\n private engine: AccessEngine<S>,\n private subject: Subject<S>,\n ) {}\n\n perform(action: InferAction<S>): OnStep<S> {\n return new OnStep(this.engine, this.subject, action);\n }\n}\n\nclass OnStep<S extends SchemaDefinition> {\n constructor(\n private engine: AccessEngine<S>,\n private subject: Subject<S>,\n private action: InferAction<S>,\n ) {}\n\n on(\n resource: InferResource<S>,\n resourceContext: ResourceContext = {},\n tenantId?: string,\n ): Decision<S> {\n return this.engine.evaluate(\n this.subject,\n this.action,\n resource,\n resourceContext,\n tenantId,\n );\n }\n\n async onAsync(\n resource: InferResource<S>,\n resourceContext: ResourceContext = {},\n tenantId?: string,\n ): Promise<Decision<S>> {\n return this.engine.evaluateAsync(\n this.subject,\n this.action,\n resource,\n resourceContext,\n tenantId,\n );\n }\n}\n\n// ---------------------------------------------------------------------------\n// Simple LRU cache\n// ---------------------------------------------------------------------------\n\nfunction buildCacheKey(\n subjectId: string,\n action: string,\n resource: string,\n tenantId?: string,\n): string {\n return `${subjectId.length}:${subjectId}\\0${action}\\0${resource}\\0${tenantId ?? \"\"}`;\n}\n\nclass LRUCache<V> {\n private map = new Map<string, V>();\n readonly maxSize: number;\n\n constructor(maxSize: number) {\n this.maxSize = maxSize;\n }\n\n get size(): number {\n return this.map.size;\n }\n\n get(key: string): V | undefined {\n const value = this.map.get(key);\n if (value !== undefined) {\n this.map.delete(key);\n this.map.set(key, value);\n }\n return value;\n }\n\n set(key: string, value: V): void {\n if (this.map.has(key)) {\n this.map.delete(key);\n } else if (this.map.size >= this.maxSize) {\n const oldest = this.map.keys().next().value;\n if (oldest !== undefined) this.map.delete(oldest);\n }\n this.map.set(key, value);\n }\n\n clear(): void {\n this.map.clear();\n }\n}\n","import type { SchemaDefinition, InferRole } from \"./types.js\";\n\n/**\n * Defines a role inheritance hierarchy.\n *\n * When a role inherits from another, it gains all permissions of its parent roles.\n * Cycles are detected and rejected at definition time.\n *\n * ```ts\n * const hierarchy = new RoleHierarchy<MySchema>()\n * .define(\"admin\", [\"manager\", \"viewer\"])\n * .define(\"manager\", [\"member\"])\n * .define(\"member\", [\"viewer\"]);\n *\n * hierarchy.resolve(\"admin\");\n * // Set { \"admin\", \"manager\", \"member\", \"viewer\" }\n * ```\n */\nexport class RoleHierarchy<S extends SchemaDefinition> {\n private parents = new Map<string, string[]>();\n private cache = new Map<string, Set<string>>();\n\n /**\n * Define that `role` inherits permissions from `inheritsFrom` roles.\n * Clears the resolution cache.\n */\n define(role: InferRole<S>, inheritsFrom: InferRole<S>[]): this {\n this.parents.set(role, inheritsFrom as string[]);\n this.cache.clear();\n this.detectCycle(role as string, new Set());\n return this;\n }\n\n /**\n * Resolve the full set of roles a given role expands to,\n * including all inherited roles (transitive).\n */\n resolve(role: InferRole<S>): Set<string> {\n const roleStr = role as string;\n const cached = this.cache.get(roleStr);\n if (cached) return cached;\n\n const result = new Set<string>();\n this.walk(roleStr, result);\n this.cache.set(roleStr, result);\n return result;\n }\n\n /**\n * Resolve multiple roles at once, returning the merged set.\n */\n resolveAll(roles: Iterable<InferRole<S>>): Set<string> {\n const result = new Set<string>();\n for (const role of roles) {\n for (const r of this.resolve(role)) {\n result.add(r);\n }\n }\n return result;\n }\n\n /**\n * Get all defined roles that have inheritance rules.\n */\n definedRoles(): string[] {\n return [...this.parents.keys()];\n }\n\n private walk(role: string, visited: Set<string>): void {\n if (visited.has(role)) return;\n visited.add(role);\n const parents = this.parents.get(role);\n if (parents) {\n for (const parent of parents) {\n this.walk(parent, visited);\n }\n }\n }\n\n private detectCycle(role: string, visiting: Set<string>): void {\n if (visiting.has(role)) {\n throw new Error(\n `Cycle detected in role hierarchy: ${[...visiting, role].join(\" → \")}`,\n );\n }\n visiting.add(role);\n const parents = this.parents.get(role);\n if (parents) {\n for (const parent of parents) {\n this.detectCycle(parent, visiting);\n }\n }\n visiting.delete(role);\n }\n}\n","import type {\n SchemaDefinition,\n PolicyRule,\n PolicyEffect,\n Condition,\n} from \"./types.js\";\n\n// ---------------------------------------------------------------------------\n// JSON-safe policy representation (conditions become named references)\n// ---------------------------------------------------------------------------\n\nexport interface JsonPolicyRule {\n id: string;\n effect: PolicyEffect;\n roles: string[] | \"*\";\n actions: string[] | \"*\";\n resources: string[] | \"*\";\n conditions?: string[];\n priority?: number;\n description?: string;\n}\n\nexport interface JsonPolicyDocument {\n version: 1;\n rules: JsonPolicyRule[];\n}\n\n/**\n * A registry that maps condition names to condition functions.\n * This allows JSON policies to reference conditions by name\n * while keeping the actual logic in code.\n *\n * ```ts\n * const conditions = new ConditionRegistry<MySchema>();\n * conditions.register(\"isOwner\", ctx => ctx.subject.id === ctx.resourceContext.ownerId);\n * conditions.register(\"isActive\", ctx => ctx.resourceContext.status === \"active\");\n * ```\n */\nexport class ConditionRegistry<S extends SchemaDefinition> {\n private conditions = new Map<string, Condition<S>>();\n\n register(name: string, condition: Condition<S>): this {\n if (!name || typeof name !== \"string\") {\n throw new Error(\"Condition name must be a non-empty string\");\n }\n if (typeof condition !== \"function\") {\n throw new Error(`Condition \"${name}\" must be a function`);\n }\n this.conditions.set(name, condition);\n return this;\n }\n\n get(name: string): Condition<S> | undefined {\n return this.conditions.get(name);\n }\n\n has(name: string): boolean {\n return this.conditions.has(name);\n }\n\n names(): string[] {\n return [...this.conditions.keys()];\n }\n}\n\n// ---------------------------------------------------------------------------\n// Export: PolicyRule[] → JSON\n// ---------------------------------------------------------------------------\n\n/**\n * Serialize rules to a JSON-safe document.\n * Conditions are stripped unless a reverse lookup map is provided.\n */\nexport function exportRules<S extends SchemaDefinition>(\n rules: ReadonlyArray<PolicyRule<S>>,\n conditionNames?: Map<Condition<S>, string>,\n): JsonPolicyDocument {\n const jsonRules: JsonPolicyRule[] = rules.map((rule) => {\n const jr: JsonPolicyRule = {\n id: rule.id,\n effect: rule.effect,\n roles: rule.roles,\n actions: rule.actions as string[] | \"*\",\n resources: rule.resources as string[] | \"*\",\n priority: rule.priority,\n description: rule.description,\n };\n\n if (rule.conditions && conditionNames) {\n const names: string[] = [];\n for (const cond of rule.conditions) {\n const name = conditionNames.get(cond);\n if (name) names.push(name);\n }\n if (names.length > 0) jr.conditions = names;\n }\n\n return jr;\n });\n\n return { version: 1, rules: jsonRules };\n}\n\n/**\n * Serialize rules to a JSON string.\n */\nexport function exportRulesToJson<S extends SchemaDefinition>(\n rules: ReadonlyArray<PolicyRule<S>>,\n conditionNames?: Map<Condition<S>, string>,\n): string {\n return JSON.stringify(exportRules(rules, conditionNames), null, 2);\n}\n\n// ---------------------------------------------------------------------------\n// Import: JSON → PolicyRule[]\n// ---------------------------------------------------------------------------\n\n/**\n * Deserialize a JSON policy document into PolicyRule objects.\n * Condition names are resolved via the provided registry.\n */\nexport function importRules<S extends SchemaDefinition>(\n doc: JsonPolicyDocument,\n registry?: ConditionRegistry<S>,\n): PolicyRule<S>[] {\n if (!doc || typeof doc !== \"object\") {\n throw new Error(\"Policy document must be a non-null object\");\n }\n if (doc.version !== 1) {\n throw new Error(`Unsupported policy document version: ${doc.version}`);\n }\n if (!Array.isArray(doc.rules)) {\n throw new Error(\"Policy document must have a 'rules' array\");\n }\n\n return doc.rules.map((jr, index) => {\n if (!jr || typeof jr !== \"object\") {\n throw new Error(`Rule at index ${index} must be a non-null object`);\n }\n\n if (!jr.id || typeof jr.id !== \"string\") {\n throw new Error(`Rule at index ${index} is missing a valid \"id\" field.`);\n }\n\n if (jr.effect !== \"allow\" && jr.effect !== \"deny\") {\n throw new Error(\n `Invalid effect \"${jr.effect}\" in rule \"${jr.id}\". Must be \"allow\" or \"deny\".`,\n );\n }\n\n if (jr.roles !== \"*\" && !Array.isArray(jr.roles)) {\n throw new Error(`Rule \"${jr.id}\": roles must be \"*\" or an array of strings`);\n }\n if (jr.actions !== \"*\" && !Array.isArray(jr.actions)) {\n throw new Error(`Rule \"${jr.id}\": actions must be \"*\" or an array of strings`);\n }\n if (jr.resources !== \"*\" && !Array.isArray(jr.resources)) {\n throw new Error(`Rule \"${jr.id}\": resources must be \"*\" or an array of strings`);\n }\n\n const conditions: Condition<S>[] = [];\n if (jr.conditions && registry) {\n for (const name of jr.conditions) {\n const cond = registry.get(name);\n if (!cond) {\n throw new Error(\n `Unknown condition \"${name}\" in rule \"${jr.id}\". ` +\n `Registered conditions: ${registry.names().join(\", \") || \"(none)\"}`,\n );\n }\n conditions.push(cond);\n }\n }\n\n return {\n id: jr.id,\n effect: jr.effect,\n roles: jr.roles,\n actions: jr.actions,\n resources: jr.resources,\n conditions: conditions.length > 0 ? conditions : undefined,\n priority: jr.priority,\n description: jr.description,\n } as PolicyRule<S>;\n });\n}\n\n/**\n * Parse a JSON string into PolicyRule objects.\n */\nexport function importRulesFromJson<S extends SchemaDefinition>(\n json: string,\n registry?: ConditionRegistry<S>,\n): PolicyRule<S>[] {\n let doc: JsonPolicyDocument;\n try {\n doc = JSON.parse(json) as JsonPolicyDocument;\n } catch (err) {\n throw new Error(\n `Failed to parse policy JSON: ${err instanceof Error ? err.message : String(err)}`,\n );\n }\n return importRules(doc, registry);\n}\n","/**\n * Core type definitions for the authorization engine.\n *\n * The type system is designed so that defining a schema once\n * propagates full autocomplete through every API surface:\n * policies, checks, audits, and middleware.\n */\n\n// ---------------------------------------------------------------------------\n// Schema definition types — what users provide to configure the engine\n// ---------------------------------------------------------------------------\n\nexport type ActionString = `${string}:${string}`;\n\nexport interface SchemaDefinition {\n roles: string;\n resources: string;\n actions: ActionString;\n tenantId?: string;\n}\n\n/**\n * Infer concrete union types from a schema definition.\n * Used internally to thread type narrowing everywhere.\n */\nexport type InferRole<S extends SchemaDefinition> = S[\"roles\"];\nexport type InferResource<S extends SchemaDefinition> = S[\"resources\"];\nexport type InferAction<S extends SchemaDefinition> = S[\"actions\"];\nexport type InferTenantId<S extends SchemaDefinition> = S[\"tenantId\"] extends string\n ? S[\"tenantId\"]\n : string;\n\n// ---------------------------------------------------------------------------\n// User / Subject\n// ---------------------------------------------------------------------------\n\nexport interface RoleAssignment<S extends SchemaDefinition> {\n role: InferRole<S>;\n tenantId?: InferTenantId<S>;\n}\n\nexport interface Subject<S extends SchemaDefinition> {\n id: string;\n roles: RoleAssignment<S>[];\n attributes?: Record<string, unknown>;\n}\n\n// ---------------------------------------------------------------------------\n// Resource context passed during evaluation\n// ---------------------------------------------------------------------------\n\nexport interface ResourceContext {\n id?: string;\n tenantId?: string;\n [key: string]: unknown;\n}\n\n// ---------------------------------------------------------------------------\n// Evaluation context — what conditions receive\n// ---------------------------------------------------------------------------\n\nexport interface EvaluationContext<S extends SchemaDefinition> {\n subject: Subject<S>;\n action: InferAction<S>;\n resource: InferResource<S>;\n resourceContext: ResourceContext;\n tenantId?: string;\n environment?: Record<string, unknown>;\n}\n\n// ---------------------------------------------------------------------------\n// Condition — a predicate attached to a policy rule\n// ---------------------------------------------------------------------------\n\nexport type Condition<S extends SchemaDefinition> = (\n ctx: EvaluationContext<S>,\n) => boolean | Promise<boolean>;\n\n// ---------------------------------------------------------------------------\n// Policy rule — the atomic unit of authorization\n// ---------------------------------------------------------------------------\n\nexport type PolicyEffect = \"allow\" | \"deny\";\n\nexport interface PolicyRule<S extends SchemaDefinition> {\n readonly id: string;\n readonly effect: PolicyEffect;\n readonly roles: InferRole<S>[] | \"*\";\n readonly actions: InferAction<S>[] | \"*\";\n readonly resources: InferResource<S>[] | \"*\";\n readonly conditions?: Condition<S>[];\n /**\n * Higher priority wins. Deny at equal priority wins over allow.\n * Default: 0\n */\n readonly priority?: number;\n readonly description?: string;\n}\n\n// ---------------------------------------------------------------------------\n// Decision — the result of evaluating a request\n// ---------------------------------------------------------------------------\n\nexport interface Decision<S extends SchemaDefinition> {\n allowed: boolean;\n effect: PolicyEffect | \"default-deny\";\n matchedRule: PolicyRule<S> | null;\n subject: Subject<S>;\n action: InferAction<S>;\n resource: InferResource<S>;\n resourceContext: ResourceContext;\n tenantId?: string;\n timestamp: number;\n durationMs: number;\n reason: string;\n}\n\n// ---------------------------------------------------------------------------\n// Audit entry — serialization-safe version of Decision\n// ---------------------------------------------------------------------------\n\nexport interface AuditEntry {\n allowed: boolean;\n effect: string;\n matchedRuleId: string | null;\n matchedRuleDescription: string | null;\n subjectId: string;\n action: string;\n resource: string;\n tenantId?: string;\n timestamp: number;\n durationMs: number;\n reason: string;\n}\n\n/**\n * Convert a Decision to a serialization-safe AuditEntry\n * (strips functions, large objects, and condition references).\n */\nexport function toAuditEntry<S extends SchemaDefinition>(decision: Decision<S>): AuditEntry {\n return {\n allowed: decision.allowed,\n effect: decision.effect,\n matchedRuleId: decision.matchedRule?.id ?? null,\n matchedRuleDescription: decision.matchedRule?.description ?? null,\n subjectId: decision.subject.id,\n action: decision.action as string,\n resource: decision.resource as string,\n tenantId: decision.tenantId,\n timestamp: decision.timestamp,\n durationMs: decision.durationMs,\n reason: decision.reason,\n };\n}\n\n// ---------------------------------------------------------------------------\n// Explain result — detailed evaluation trace for debugging\n// ---------------------------------------------------------------------------\n\nexport interface ConditionResult {\n index: number;\n passed: boolean;\n error?: string;\n}\n\nexport interface RuleEvaluation<S extends SchemaDefinition> {\n rule: PolicyRule<S>;\n roleMatched: boolean;\n actionMatched: boolean;\n resourceMatched: boolean;\n conditionResults: ConditionResult[];\n matched: boolean;\n}\n\nexport interface ExplainResult<S extends SchemaDefinition> {\n allowed: boolean;\n effect: PolicyEffect | \"default-deny\";\n reason: string;\n evaluatedRules: RuleEvaluation<S>[];\n durationMs: number;\n}\n\n// ---------------------------------------------------------------------------\n// Audit / Observability\n// ---------------------------------------------------------------------------\n\nexport type DecisionListener<S extends SchemaDefinition> = (\n decision: Decision<S>,\n) => void | Promise<void>;\n\nexport interface ConditionError {\n ruleId: string;\n conditionIndex: number;\n error: unknown;\n}\n\nexport type ConditionErrorHandler = (err: ConditionError) => void;\n\n// ---------------------------------------------------------------------------\n// Engine options\n// ---------------------------------------------------------------------------\n\nexport interface EngineOptions<S extends SchemaDefinition> {\n schema: S;\n defaultEffect?: PolicyEffect;\n onDecision?: DecisionListener<S>;\n onConditionError?: ConditionErrorHandler;\n /**\n * When true, async conditions are awaited.\n * When false (default), only synchronous conditions are supported\n * and evaluate is guaranteed synchronous.\n */\n asyncConditions?: boolean;\n /**\n * When true, evaluate() throws if tenantId is omitted and the subject\n * has any tenant-scoped role assignments. Prevents accidental\n * cross-tenant privilege escalation.\n */\n strictTenancy?: boolean;\n}\n"],"mappings":"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","names":[]}
|
|
1
|
+
{"version":3,"sources":["../src/index.ts","../src/policy-builder.ts","../src/engine.ts","../src/role-hierarchy.ts","../src/serialization.ts","../src/types.ts"],"sourcesContent":["export { AccessEngine } from \"./engine.js\";\nexport type { AccessEngineOptions } from \"./engine.js\";\nexport { RuleBuilder, allow, deny, createPolicyFactory } from \"./policy-builder.js\";\nexport { RoleHierarchy } from \"./role-hierarchy.js\";\nexport {\n ConditionRegistry,\n exportRules,\n exportRulesToJson,\n importRules,\n importRulesFromJson,\n} from \"./serialization.js\";\n\nexport { toAuditEntry } from \"./types.js\";\n\nexport type {\n SchemaDefinition,\n ActionString,\n InferRole,\n InferResource,\n InferAction,\n InferTenantId,\n RoleAssignment,\n Subject,\n ResourceContext,\n EvaluationContext,\n Condition,\n PolicyEffect,\n PolicyRule,\n Decision,\n AuditEntry,\n DecisionListener,\n ConditionError,\n ConditionErrorHandler,\n EngineOptions,\n ExplainResult,\n RuleEvaluation,\n ConditionResult,\n} from \"./types.js\";\n\nexport type {\n JsonPolicyRule,\n JsonPolicyDocument,\n} from \"./serialization.js\";\n\nexport type { ServerOptions, EvalRequestBody } from \"./server.js\";\n","import type {\n SchemaDefinition,\n InferRole,\n InferAction,\n InferResource,\n PolicyRule,\n PolicyEffect,\n Condition,\n} from \"./types.js\";\n\nlet ruleCounter = 0;\n\nfunction nextRuleId(prefix: string): string {\n return `${prefix}-${++ruleCounter}`;\n}\n\nexport class RuleBuilder<S extends SchemaDefinition> {\n private _effect: PolicyEffect;\n private _roles: InferRole<S>[] | \"*\" = \"*\";\n private _actions: InferAction<S>[] | \"*\" = \"*\";\n private _resources: InferResource<S>[] | \"*\" = \"*\";\n private _conditions: Condition<S>[] = [];\n private _priority = 0;\n private _description?: string;\n private _id: string;\n\n constructor(effect: PolicyEffect) {\n this._effect = effect;\n this._id = nextRuleId(effect);\n }\n\n id(id: string): this {\n this._id = id;\n return this;\n }\n\n roles(...roles: InferRole<S>[]): this {\n this._roles = roles;\n return this;\n }\n\n anyRole(): this {\n this._roles = \"*\";\n return this;\n }\n\n actions(...actions: InferAction<S>[]): this {\n this._actions = actions;\n return this;\n }\n\n anyAction(): this {\n this._actions = \"*\";\n return this;\n }\n\n on(...resources: InferResource<S>[]): this {\n this._resources = resources;\n return this;\n }\n\n anyResource(): this {\n this._resources = \"*\";\n return this;\n }\n\n when(condition: Condition<S>): this {\n this._conditions.push(condition);\n return this;\n }\n\n priority(p: number): this {\n this._priority = p;\n return this;\n }\n\n describe(desc: string): this {\n this._description = desc;\n return this;\n }\n\n build(): PolicyRule<S> {\n return {\n id: this._id,\n effect: this._effect,\n roles: this._roles,\n actions: this._actions,\n resources: this._resources,\n conditions: this._conditions.length > 0 ? this._conditions : undefined,\n priority: this._priority,\n description: this._description,\n };\n }\n}\n\nexport function allow<S extends SchemaDefinition>(): RuleBuilder<S> {\n return new RuleBuilder<S>(\"allow\");\n}\n\nexport function deny<S extends SchemaDefinition>(): RuleBuilder<S> {\n return new RuleBuilder<S>(\"deny\");\n}\n\n/**\n * Creates schema-bound allow/deny factories so you don't need to pass\n * the generic parameter on every call.\n *\n * ```ts\n * const { allow, deny } = createPolicyFactory<MySchema>();\n * allow().roles(\"admin\").anyAction().anyResource().build();\n * ```\n */\nexport function createPolicyFactory<S extends SchemaDefinition>(): {\n allow: () => RuleBuilder<S>;\n deny: () => RuleBuilder<S>;\n} {\n return {\n allow: () => new RuleBuilder<S>(\"allow\"),\n deny: () => new RuleBuilder<S>(\"deny\"),\n };\n}\n","import type {\n SchemaDefinition,\n InferAction,\n InferResource,\n InferRole,\n PolicyRule,\n Decision,\n Subject,\n ResourceContext,\n EvaluationContext,\n DecisionListener,\n EngineOptions,\n ConditionErrorHandler,\n ExplainResult,\n RuleEvaluation,\n ConditionResult,\n} from \"./types.js\";\nimport type { RuleBuilder } from \"./policy-builder.js\";\nimport { allow as _allow, deny as _deny } from \"./policy-builder.js\";\nimport type { RoleHierarchy } from \"./role-hierarchy.js\";\n\n// ---------------------------------------------------------------------------\n// Compiled rule — internal representation with pre-compiled regex\n// ---------------------------------------------------------------------------\n\ninterface CompiledRule<S extends SchemaDefinition> {\n rule: PolicyRule<S>;\n actionPatterns: RegExp[] | null;\n}\n\nfunction escapeRegexMeta(s: string): string {\n return s.replace(/[.+?^${}()|[\\]\\\\]/g, \"\\\\$&\");\n}\n\nfunction isThenable(value: unknown): value is PromiseLike<unknown> {\n return (\n value != null &&\n typeof value === \"object\" &&\n typeof (value as PromiseLike<unknown>).then === \"function\"\n );\n}\n\nconst ASYNC_CONDITION_EVALUATE_MSG =\n \"Async condition encountered. Use evaluateAsync() instead.\";\nconst ASYNC_CONDITION_EXPLAIN_MSG =\n \"Async condition encountered. Use explainAsync() instead.\";\n\nfunction compileActionPatterns(actions: string[] | \"*\"): RegExp[] | null {\n if (actions === \"*\") return null;\n const patterns: RegExp[] = [];\n for (const action of actions) {\n if (action.includes(\"*\")) {\n const escaped = escapeRegexMeta(action).replace(/\\*/g, \"[^:]*\");\n patterns.push(new RegExp(\"^\" + escaped + \"$\"));\n }\n }\n return patterns.length > 0 ? patterns : null;\n}\n\n// ---------------------------------------------------------------------------\n// Engine\n// ---------------------------------------------------------------------------\n\nexport interface AccessEngineOptions<S extends SchemaDefinition> extends EngineOptions<S> {\n roleHierarchy?: RoleHierarchy<S>;\n /**\n * Enable LRU cache for evaluation results.\n * Only caches evaluations of rules WITHOUT conditions (context-independent).\n * Rules with conditions are never cached since their result depends on resourceContext.\n */\n cacheSize?: number;\n}\n\nexport class AccessEngine<S extends SchemaDefinition> {\n private compiled: CompiledRule<S>[] = [];\n private listeners: DecisionListener<S>[] = [];\n private asyncConditions: boolean;\n private _defaultDeny: boolean;\n private _strictTenancy: boolean;\n private hierarchy?: RoleHierarchy<S>;\n private cache?: LRUCache<Decision<S>>;\n private conditionErrorHandler?: ConditionErrorHandler;\n\n constructor(options: AccessEngineOptions<S>) {\n this.asyncConditions = options.asyncConditions ?? false;\n this._defaultDeny = (options.defaultEffect ?? \"deny\") === \"deny\";\n this._strictTenancy = options.strictTenancy ?? false;\n this.hierarchy = options.roleHierarchy;\n this.conditionErrorHandler = options.onConditionError;\n if (options.cacheSize && options.cacheSize > 0) {\n this.cache = new LRUCache(options.cacheSize);\n }\n if (options.onDecision) {\n this.listeners.push(options.onDecision);\n }\n }\n\n // -----------------------------------------------------------------------\n // Rule management\n // -----------------------------------------------------------------------\n\n addRule(rule: PolicyRule<S>): this {\n const frozen = Object.freeze({ ...rule });\n this.compiled.push({\n rule: frozen,\n actionPatterns: compileActionPatterns(frozen.actions as string[] | \"*\"),\n });\n this.cache?.clear();\n return this;\n }\n\n addRules(...rules: PolicyRule<S>[]): this {\n for (const rule of rules) {\n const frozen = Object.freeze({ ...rule });\n this.compiled.push({\n rule: frozen,\n actionPatterns: compileActionPatterns(frozen.actions as string[] | \"*\"),\n });\n }\n this.cache?.clear();\n return this;\n }\n\n removeRule(ruleId: string): boolean {\n const idx = this.compiled.findIndex((c) => c.rule.id === ruleId);\n if (idx === -1) return false;\n this.compiled.splice(idx, 1);\n this.cache?.clear();\n return true;\n }\n\n getRules(): ReadonlyArray<PolicyRule<S>> {\n return this.compiled.map((c) => c.rule);\n }\n\n clearRules(): void {\n this.compiled = [];\n this.cache?.clear();\n }\n\n // -----------------------------------------------------------------------\n // Cache control\n // -----------------------------------------------------------------------\n\n clearCache(): void {\n this.cache?.clear();\n }\n\n get cacheStats(): { size: number; maxSize: number } | null {\n if (!this.cache) return null;\n return { size: this.cache.size, maxSize: this.cache.maxSize };\n }\n\n // -----------------------------------------------------------------------\n // Fluent rule builders bound to this engine's schema\n // -----------------------------------------------------------------------\n\n allow(): RuleBuilder<S> {\n return _allow<S>();\n }\n\n deny(): RuleBuilder<S> {\n return _deny<S>();\n }\n\n // -----------------------------------------------------------------------\n // Observability\n // -----------------------------------------------------------------------\n\n onDecision(listener: DecisionListener<S>): () => void {\n this.listeners.push(listener);\n return () => {\n const idx = this.listeners.indexOf(listener);\n if (idx !== -1) this.listeners.splice(idx, 1);\n };\n }\n\n private emit(decision: Decision<S>): void {\n for (const listener of this.listeners) {\n try {\n const result = listener(decision);\n if (result instanceof Promise) {\n result.catch(() => {});\n }\n } catch {\n // listeners must not break evaluation\n }\n }\n }\n\n // -----------------------------------------------------------------------\n // Evaluation\n // -----------------------------------------------------------------------\n\n evaluate(\n subject: Subject<S>,\n action: InferAction<S>,\n resource: InferResource<S>,\n resourceContext: ResourceContext = {},\n tenantId?: string,\n ): Decision<S> {\n if (this.asyncConditions) {\n throw new Error(\n \"Engine has asyncConditions enabled. Use evaluateAsync() instead.\",\n );\n }\n this.validateInput(subject, action, resource);\n this.enforceTenancy(subject, tenantId);\n\n const cacheKey = this.cache\n ? buildCacheKey(subject.id, action as string, resource as string, tenantId)\n : undefined;\n if (cacheKey) {\n const cached = this.cache!.get(cacheKey);\n if (cached) {\n this.emit(cached);\n return cached;\n }\n }\n\n const start = performance.now();\n const ctx = this.buildContext(subject, action, resource, resourceContext, tenantId);\n const candidates = this.matchRules(subject, action, resource, tenantId);\n\n let matched: CompiledRule<S> | null = null;\n let matchedHasConditions = false;\n\n for (const compiled of candidates) {\n const { rule } = compiled;\n if (!rule.conditions || rule.conditions.length === 0) {\n matched = compiled;\n matchedHasConditions = false;\n break;\n }\n const allMet = this.evaluateConditionsSync(rule, ctx);\n if (allMet) {\n matched = compiled;\n matchedHasConditions = true;\n break;\n }\n }\n\n const decision = this.buildDecision(matched?.rule ?? null, ctx, start);\n\n if (cacheKey && !matchedHasConditions) {\n this.cache!.set(cacheKey, decision);\n }\n\n this.emit(decision);\n return decision;\n }\n\n async evaluateAsync(\n subject: Subject<S>,\n action: InferAction<S>,\n resource: InferResource<S>,\n resourceContext: ResourceContext = {},\n tenantId?: string,\n ): Promise<Decision<S>> {\n this.validateInput(subject, action, resource);\n this.enforceTenancy(subject, tenantId);\n const start = performance.now();\n const ctx = this.buildContext(subject, action, resource, resourceContext, tenantId);\n const candidates = this.matchRules(subject, action, resource, tenantId);\n\n let matched: PolicyRule<S> | null = null;\n\n for (const compiled of candidates) {\n const { rule } = compiled;\n if (!rule.conditions || rule.conditions.length === 0) {\n matched = rule;\n break;\n }\n const results = await Promise.all(\n rule.conditions.map((c, i) =>\n Promise.resolve()\n .then(() => c(ctx))\n .catch((err) => {\n this.emitConditionError(rule.id, i, err);\n return false;\n }),\n ),\n );\n if (results.every(Boolean)) {\n matched = rule;\n break;\n }\n }\n\n const decision = this.buildDecision(matched, ctx, start);\n this.emit(decision);\n return decision;\n }\n\n // -----------------------------------------------------------------------\n // permitted() — list allowed actions on a resource for UI rendering\n // -----------------------------------------------------------------------\n\n permitted(\n subject: Subject<S>,\n resource: InferResource<S>,\n actions: InferAction<S>[],\n resourceContext: ResourceContext = {},\n tenantId?: string,\n ): Set<InferAction<S>> {\n const allowed = new Set<InferAction<S>>();\n for (const action of actions) {\n const decision = this.asyncConditions\n ? (() => { throw new Error(\"Use permittedAsync() with asyncConditions enabled.\"); })()\n : this.evaluate(subject, action, resource, resourceContext, tenantId);\n if (decision.allowed) {\n allowed.add(action);\n }\n }\n return allowed;\n }\n\n async permittedAsync(\n subject: Subject<S>,\n resource: InferResource<S>,\n actions: InferAction<S>[],\n resourceContext: ResourceContext = {},\n tenantId?: string,\n ): Promise<Set<InferAction<S>>> {\n const allowed = new Set<InferAction<S>>();\n const results = await Promise.all(\n actions.map((action) =>\n this.evaluateAsync(subject, action, resource, resourceContext, tenantId),\n ),\n );\n for (let i = 0; i < actions.length; i++) {\n if (results[i]!.allowed) {\n allowed.add(actions[i]!);\n }\n }\n return allowed;\n }\n\n // -----------------------------------------------------------------------\n // explain() — full evaluation trace for debugging\n // -----------------------------------------------------------------------\n\n explain(\n subject: Subject<S>,\n action: InferAction<S>,\n resource: InferResource<S>,\n resourceContext: ResourceContext = {},\n tenantId?: string,\n ): ExplainResult<S> {\n if (this.asyncConditions) {\n throw new Error(\n \"Engine has asyncConditions enabled. Use explainAsync() instead.\",\n );\n }\n this.enforceTenancy(subject, tenantId);\n const start = performance.now();\n const ctx = this.buildContext(subject, action, resource, resourceContext, tenantId);\n const subjectRoles = this.resolveRoles(subject, tenantId);\n\n const evaluatedRules: RuleEvaluation<S>[] = [];\n let firstMatch: PolicyRule<S> | null = null;\n\n const sorted = this.sortCandidates([...this.compiled]);\n\n for (const compiled of sorted) {\n const { rule } = compiled;\n const roleMatched = rule.roles === \"*\" || rule.roles.some((r) => subjectRoles.has(r));\n const actionMatched = rule.actions === \"*\" || this.matchesAction(compiled, action);\n const resourceMatched = rule.resources === \"*\" || rule.resources.includes(resource);\n\n const conditionResults: ConditionResult[] = [];\n let allConditionsPassed = true;\n\n if (roleMatched && actionMatched && resourceMatched && rule.conditions) {\n for (let i = 0; i < rule.conditions.length; i++) {\n try {\n const result = rule.conditions[i]!(ctx);\n if (isThenable(result)) {\n throw new Error(ASYNC_CONDITION_EXPLAIN_MSG);\n }\n if (result !== true) {\n conditionResults.push({ index: i, passed: false });\n allConditionsPassed = false;\n } else {\n conditionResults.push({ index: i, passed: true });\n }\n } catch (err) {\n if (\n err instanceof Error &&\n err.message === ASYNC_CONDITION_EXPLAIN_MSG\n ) {\n throw err;\n }\n conditionResults.push({\n index: i,\n passed: false,\n error: err instanceof Error ? err.message : String(err),\n });\n allConditionsPassed = false;\n }\n }\n }\n\n const matched =\n roleMatched && actionMatched && resourceMatched &&\n (!rule.conditions || rule.conditions.length === 0 || allConditionsPassed);\n\n evaluatedRules.push({\n rule,\n roleMatched,\n actionMatched,\n resourceMatched,\n conditionResults,\n matched,\n });\n\n if (matched && !firstMatch) {\n firstMatch = rule;\n }\n }\n\n const allowed = firstMatch != null ? firstMatch.effect === \"allow\" : !this._defaultDeny;\n const effect = firstMatch?.effect ?? \"default-deny\";\n const reason = firstMatch\n ? `Matched rule \"${firstMatch.id}\"${firstMatch.description ? `: ${firstMatch.description}` : \"\"}`\n : \"No matching rule — default deny\";\n\n return {\n allowed,\n effect,\n reason,\n evaluatedRules,\n durationMs: performance.now() - start,\n };\n }\n\n async explainAsync(\n subject: Subject<S>,\n action: InferAction<S>,\n resource: InferResource<S>,\n resourceContext: ResourceContext = {},\n tenantId?: string,\n ): Promise<ExplainResult<S>> {\n this.enforceTenancy(subject, tenantId);\n const start = performance.now();\n const ctx = this.buildContext(subject, action, resource, resourceContext, tenantId);\n const subjectRoles = this.resolveRoles(subject, tenantId);\n\n const evaluatedRules: RuleEvaluation<S>[] = [];\n let firstMatch: PolicyRule<S> | null = null;\n\n const sorted = this.sortCandidates([...this.compiled]);\n\n for (const compiled of sorted) {\n const { rule } = compiled;\n const roleMatched = rule.roles === \"*\" || rule.roles.some((r) => subjectRoles.has(r));\n const actionMatched = rule.actions === \"*\" || this.matchesAction(compiled, action);\n const resourceMatched = rule.resources === \"*\" || rule.resources.includes(resource);\n\n const conditionResults: ConditionResult[] = [];\n let allConditionsPassed = true;\n\n if (roleMatched && actionMatched && resourceMatched && rule.conditions) {\n for (let i = 0; i < rule.conditions.length; i++) {\n try {\n const result = await rule.conditions[i]!(ctx);\n if (result !== true) {\n conditionResults.push({ index: i, passed: false });\n allConditionsPassed = false;\n } else {\n conditionResults.push({ index: i, passed: true });\n }\n } catch (err) {\n conditionResults.push({\n index: i,\n passed: false,\n error: err instanceof Error ? err.message : String(err),\n });\n allConditionsPassed = false;\n }\n }\n }\n\n const matched =\n roleMatched && actionMatched && resourceMatched &&\n (!rule.conditions || rule.conditions.length === 0 || allConditionsPassed);\n\n evaluatedRules.push({\n rule,\n roleMatched,\n actionMatched,\n resourceMatched,\n conditionResults,\n matched,\n });\n\n if (matched && !firstMatch) {\n firstMatch = rule;\n }\n }\n\n const allowed = firstMatch != null ? firstMatch.effect === \"allow\" : !this._defaultDeny;\n const effect = firstMatch?.effect ?? \"default-deny\";\n const reason = firstMatch\n ? `Matched rule \"${firstMatch.id}\"${firstMatch.description ? `: ${firstMatch.description}` : \"\"}`\n : \"No matching rule — default deny\";\n\n return {\n allowed,\n effect,\n reason,\n evaluatedRules,\n durationMs: performance.now() - start,\n };\n }\n\n // -----------------------------------------------------------------------\n // Fluent check API: can(subject).perform(action).on(resource)\n // -----------------------------------------------------------------------\n\n can(subject: Subject<S>): PerformStep<S> {\n return new PerformStep(this, subject);\n }\n\n // -----------------------------------------------------------------------\n // Internal helpers\n // -----------------------------------------------------------------------\n\n private validateInput(\n subject: Subject<S>,\n action: InferAction<S>,\n resource: InferResource<S>,\n ): void {\n if (!subject || typeof subject.id !== \"string\") {\n throw new Error(\"subject must be an object with a string id\");\n }\n if (!Array.isArray(subject.roles)) {\n throw new Error(\"subject.roles must be an array\");\n }\n if (!action || typeof action !== \"string\") {\n throw new Error(\"action must be a non-empty string\");\n }\n if (!resource || typeof resource !== \"string\") {\n throw new Error(\"resource must be a non-empty string\");\n }\n }\n\n private enforceTenancy(subject: Subject<S>, tenantId?: string): void {\n if (!this._strictTenancy || tenantId != null) return;\n const hasTenantScoped = subject.roles.some((r) => r.tenantId != null);\n if (hasTenantScoped) {\n throw new Error(\n \"strictTenancy is enabled and subject has tenant-scoped roles, \" +\n \"but no tenantId was provided to evaluate(). This could cause \" +\n \"cross-tenant privilege escalation. Pass an explicit tenantId.\",\n );\n }\n }\n\n private buildContext(\n subject: Subject<S>,\n action: InferAction<S>,\n resource: InferResource<S>,\n resourceContext: ResourceContext,\n tenantId?: string,\n ): EvaluationContext<S> {\n return { subject, action, resource, resourceContext, tenantId };\n }\n\n private evaluateConditionsSync(\n rule: PolicyRule<S>,\n ctx: EvaluationContext<S>,\n ): boolean {\n if (!rule.conditions) return true;\n for (let i = 0; i < rule.conditions.length; i++) {\n try {\n const result = rule.conditions[i]!(ctx);\n if (isThenable(result)) {\n throw new Error(ASYNC_CONDITION_EVALUATE_MSG);\n }\n if (result !== true) return false;\n } catch (err) {\n if (\n err instanceof Error &&\n err.message === ASYNC_CONDITION_EVALUATE_MSG\n ) {\n throw err;\n }\n this.emitConditionError(rule.id, i, err);\n return false;\n }\n }\n return true;\n }\n\n private emitConditionError(ruleId: string, conditionIndex: number, error: unknown): void {\n if (this.conditionErrorHandler) {\n try {\n this.conditionErrorHandler({ ruleId, conditionIndex, error });\n } catch {\n // error handler must not break evaluation\n }\n }\n }\n\n private matchRules(\n subject: Subject<S>,\n action: InferAction<S>,\n resource: InferResource<S>,\n tenantId?: string,\n ): CompiledRule<S>[] {\n const subjectRoles = this.resolveRoles(subject, tenantId);\n\n const matched = this.compiled.filter((compiled) => {\n const { rule } = compiled;\n if (rule.roles !== \"*\" && !rule.roles.some((r) => subjectRoles.has(r))) return false;\n if (rule.actions !== \"*\" && !this.matchesAction(compiled, action)) return false;\n if (rule.resources !== \"*\" && !rule.resources.includes(resource)) return false;\n return true;\n });\n\n return this.sortCandidates(matched);\n }\n\n private sortCandidates(candidates: CompiledRule<S>[]): CompiledRule<S>[] {\n return candidates.sort((a, b) => {\n const pa = a.rule.priority ?? 0;\n const pb = b.rule.priority ?? 0;\n if (pb !== pa) return pb - pa;\n if (a.rule.effect === \"deny\" && b.rule.effect === \"allow\") return -1;\n if (a.rule.effect === \"allow\" && b.rule.effect === \"deny\") return 1;\n return 0;\n });\n }\n\n private matchesAction(\n compiled: CompiledRule<S>,\n action: InferAction<S>,\n ): boolean {\n const { rule, actionPatterns } = compiled;\n if (rule.actions === \"*\") return true;\n const actionStr = action as string;\n if ((rule.actions as string[]).includes(actionStr)) return true;\n if (actionPatterns) {\n for (const pattern of actionPatterns) {\n if (pattern.test(actionStr)) return true;\n }\n }\n return false;\n }\n\n private resolveRoles(\n subject: Subject<S>,\n tenantId?: string,\n ): Set<string> {\n const directRoles = new Set<string>();\n for (const assignment of subject.roles) {\n if (tenantId == null || assignment.tenantId == null || assignment.tenantId === tenantId) {\n directRoles.add(assignment.role);\n }\n }\n\n if (!this.hierarchy) return directRoles;\n\n const expanded = new Set<string>();\n for (const role of directRoles) {\n for (const r of this.hierarchy.resolve(role as InferRole<S>)) {\n expanded.add(r);\n }\n }\n return expanded;\n }\n\n private buildDecision(\n matched: PolicyRule<S> | null,\n ctx: EvaluationContext<S>,\n start: number,\n ): Decision<S> {\n const allowed =\n matched != null ? matched.effect === \"allow\" : !this._defaultDeny;\n const effect = matched?.effect ?? \"default-deny\";\n const reason = matched\n ? `Matched rule \"${matched.id}\"${matched.description ? `: ${matched.description}` : \"\"}`\n : \"No matching rule — default deny\";\n\n return {\n allowed,\n effect,\n matchedRule: matched,\n subject: ctx.subject,\n action: ctx.action,\n resource: ctx.resource,\n resourceContext: ctx.resourceContext,\n tenantId: ctx.tenantId,\n timestamp: Date.now(),\n durationMs: performance.now() - start,\n reason,\n };\n }\n}\n\n// ---------------------------------------------------------------------------\n// Fluent check chain: can(user).perform(action).on(resource)\n// ---------------------------------------------------------------------------\n\nclass PerformStep<S extends SchemaDefinition> {\n constructor(\n private engine: AccessEngine<S>,\n private subject: Subject<S>,\n ) {}\n\n perform(action: InferAction<S>): OnStep<S> {\n return new OnStep(this.engine, this.subject, action);\n }\n}\n\nclass OnStep<S extends SchemaDefinition> {\n constructor(\n private engine: AccessEngine<S>,\n private subject: Subject<S>,\n private action: InferAction<S>,\n ) {}\n\n on(\n resource: InferResource<S>,\n resourceContext: ResourceContext = {},\n tenantId?: string,\n ): Decision<S> {\n return this.engine.evaluate(\n this.subject,\n this.action,\n resource,\n resourceContext,\n tenantId,\n );\n }\n\n async onAsync(\n resource: InferResource<S>,\n resourceContext: ResourceContext = {},\n tenantId?: string,\n ): Promise<Decision<S>> {\n return this.engine.evaluateAsync(\n this.subject,\n this.action,\n resource,\n resourceContext,\n tenantId,\n );\n }\n}\n\n// ---------------------------------------------------------------------------\n// Simple LRU cache\n// ---------------------------------------------------------------------------\n\nfunction buildCacheKey(\n subjectId: string,\n action: string,\n resource: string,\n tenantId?: string,\n): string {\n return `${subjectId.length}:${subjectId}\\0${action}\\0${resource}\\0${tenantId ?? \"\"}`;\n}\n\nclass LRUCache<V> {\n private map = new Map<string, V>();\n readonly maxSize: number;\n\n constructor(maxSize: number) {\n this.maxSize = maxSize;\n }\n\n get size(): number {\n return this.map.size;\n }\n\n get(key: string): V | undefined {\n const value = this.map.get(key);\n if (value !== undefined) {\n this.map.delete(key);\n this.map.set(key, value);\n }\n return value;\n }\n\n set(key: string, value: V): void {\n if (this.map.has(key)) {\n this.map.delete(key);\n } else if (this.map.size >= this.maxSize) {\n const oldest = this.map.keys().next().value;\n if (oldest !== undefined) this.map.delete(oldest);\n }\n this.map.set(key, value);\n }\n\n clear(): void {\n this.map.clear();\n }\n}\n","import type { SchemaDefinition, InferRole } from \"./types.js\";\n\n/**\n * Defines a role inheritance hierarchy.\n *\n * When a role inherits from another, it gains all permissions of its parent roles.\n * Cycles are detected and rejected at definition time.\n *\n * ```ts\n * const hierarchy = new RoleHierarchy<MySchema>()\n * .define(\"admin\", [\"manager\", \"viewer\"])\n * .define(\"manager\", [\"member\"])\n * .define(\"member\", [\"viewer\"]);\n *\n * hierarchy.resolve(\"admin\");\n * // Set { \"admin\", \"manager\", \"member\", \"viewer\" }\n * ```\n */\nexport class RoleHierarchy<S extends SchemaDefinition> {\n private parents = new Map<string, string[]>();\n private cache = new Map<string, Set<string>>();\n\n /**\n * Define that `role` inherits permissions from `inheritsFrom` roles.\n * Clears the resolution cache.\n */\n define(role: InferRole<S>, inheritsFrom: InferRole<S>[]): this {\n this.parents.set(role, inheritsFrom as string[]);\n this.cache.clear();\n this.detectCycle(role as string, new Set());\n return this;\n }\n\n /**\n * Resolve the full set of roles a given role expands to,\n * including all inherited roles (transitive).\n */\n resolve(role: InferRole<S>): Set<string> {\n const roleStr = role as string;\n const cached = this.cache.get(roleStr);\n if (cached) return cached;\n\n const result = new Set<string>();\n this.walk(roleStr, result);\n this.cache.set(roleStr, result);\n return result;\n }\n\n /**\n * Resolve multiple roles at once, returning the merged set.\n */\n resolveAll(roles: Iterable<InferRole<S>>): Set<string> {\n const result = new Set<string>();\n for (const role of roles) {\n for (const r of this.resolve(role)) {\n result.add(r);\n }\n }\n return result;\n }\n\n /**\n * Get all defined roles that have inheritance rules.\n */\n definedRoles(): string[] {\n return [...this.parents.keys()];\n }\n\n private walk(role: string, visited: Set<string>): void {\n if (visited.has(role)) return;\n visited.add(role);\n const parents = this.parents.get(role);\n if (parents) {\n for (const parent of parents) {\n this.walk(parent, visited);\n }\n }\n }\n\n private detectCycle(role: string, visiting: Set<string>): void {\n if (visiting.has(role)) {\n throw new Error(\n `Cycle detected in role hierarchy: ${[...visiting, role].join(\" → \")}`,\n );\n }\n visiting.add(role);\n const parents = this.parents.get(role);\n if (parents) {\n for (const parent of parents) {\n this.detectCycle(parent, visiting);\n }\n }\n visiting.delete(role);\n }\n}\n","import type {\n SchemaDefinition,\n PolicyRule,\n PolicyEffect,\n Condition,\n} from \"./types.js\";\n\n// ---------------------------------------------------------------------------\n// JSON-safe policy representation (conditions become named references)\n// ---------------------------------------------------------------------------\n\nexport interface JsonPolicyRule {\n id: string;\n effect: PolicyEffect;\n roles: string[] | \"*\";\n actions: string[] | \"*\";\n resources: string[] | \"*\";\n conditions?: string[];\n priority?: number;\n description?: string;\n}\n\nexport interface JsonPolicyDocument {\n version: 1;\n rules: JsonPolicyRule[];\n}\n\n/**\n * A registry that maps condition names to condition functions.\n * This allows JSON policies to reference conditions by name\n * while keeping the actual logic in code.\n *\n * ```ts\n * const conditions = new ConditionRegistry<MySchema>();\n * conditions.register(\"isOwner\", ctx => ctx.subject.id === ctx.resourceContext.ownerId);\n * conditions.register(\"isActive\", ctx => ctx.resourceContext.status === \"active\");\n * ```\n */\nexport class ConditionRegistry<S extends SchemaDefinition> {\n private conditions = new Map<string, Condition<S>>();\n\n register(name: string, condition: Condition<S>): this {\n if (!name || typeof name !== \"string\") {\n throw new Error(\"Condition name must be a non-empty string\");\n }\n if (typeof condition !== \"function\") {\n throw new Error(`Condition \"${name}\" must be a function`);\n }\n this.conditions.set(name, condition);\n return this;\n }\n\n get(name: string): Condition<S> | undefined {\n return this.conditions.get(name);\n }\n\n has(name: string): boolean {\n return this.conditions.has(name);\n }\n\n names(): string[] {\n return [...this.conditions.keys()];\n }\n}\n\n// ---------------------------------------------------------------------------\n// Export: PolicyRule[] → JSON\n// ---------------------------------------------------------------------------\n\n/**\n * Serialize rules to a JSON-safe document.\n * Conditions are stripped unless a reverse lookup map is provided.\n */\nexport function exportRules<S extends SchemaDefinition>(\n rules: ReadonlyArray<PolicyRule<S>>,\n conditionNames?: Map<Condition<S>, string>,\n): JsonPolicyDocument {\n const jsonRules: JsonPolicyRule[] = rules.map((rule) => {\n const jr: JsonPolicyRule = {\n id: rule.id,\n effect: rule.effect,\n roles: rule.roles,\n actions: rule.actions as string[] | \"*\",\n resources: rule.resources as string[] | \"*\",\n priority: rule.priority,\n description: rule.description,\n };\n\n if (rule.conditions && conditionNames) {\n const names: string[] = [];\n for (const cond of rule.conditions) {\n const name = conditionNames.get(cond);\n if (name) names.push(name);\n }\n if (names.length > 0) jr.conditions = names;\n }\n\n return jr;\n });\n\n return { version: 1, rules: jsonRules };\n}\n\n/**\n * Serialize rules to a JSON string.\n */\nexport function exportRulesToJson<S extends SchemaDefinition>(\n rules: ReadonlyArray<PolicyRule<S>>,\n conditionNames?: Map<Condition<S>, string>,\n): string {\n return JSON.stringify(exportRules(rules, conditionNames), null, 2);\n}\n\n// ---------------------------------------------------------------------------\n// Import: JSON → PolicyRule[]\n// ---------------------------------------------------------------------------\n\n/**\n * Deserialize a JSON policy document into PolicyRule objects.\n * Condition names are resolved via the provided registry.\n */\nexport function importRules<S extends SchemaDefinition>(\n doc: JsonPolicyDocument,\n registry?: ConditionRegistry<S>,\n): PolicyRule<S>[] {\n if (!doc || typeof doc !== \"object\") {\n throw new Error(\"Policy document must be a non-null object\");\n }\n if (doc.version !== 1) {\n throw new Error(`Unsupported policy document version: ${doc.version}`);\n }\n if (!Array.isArray(doc.rules)) {\n throw new Error(\"Policy document must have a 'rules' array\");\n }\n\n return doc.rules.map((jr, index) => {\n if (!jr || typeof jr !== \"object\") {\n throw new Error(`Rule at index ${index} must be a non-null object`);\n }\n\n if (!jr.id || typeof jr.id !== \"string\") {\n throw new Error(`Rule at index ${index} is missing a valid \"id\" field.`);\n }\n\n if (jr.effect !== \"allow\" && jr.effect !== \"deny\") {\n throw new Error(\n `Invalid effect \"${jr.effect}\" in rule \"${jr.id}\". Must be \"allow\" or \"deny\".`,\n );\n }\n\n if (jr.roles !== \"*\" && !Array.isArray(jr.roles)) {\n throw new Error(`Rule \"${jr.id}\": roles must be \"*\" or an array of strings`);\n }\n if (jr.actions !== \"*\" && !Array.isArray(jr.actions)) {\n throw new Error(`Rule \"${jr.id}\": actions must be \"*\" or an array of strings`);\n }\n if (jr.resources !== \"*\" && !Array.isArray(jr.resources)) {\n throw new Error(`Rule \"${jr.id}\": resources must be \"*\" or an array of strings`);\n }\n\n const conditions: Condition<S>[] = [];\n if (jr.conditions && registry) {\n for (const name of jr.conditions) {\n const cond = registry.get(name);\n if (!cond) {\n throw new Error(\n `Unknown condition \"${name}\" in rule \"${jr.id}\". ` +\n `Registered conditions: ${registry.names().join(\", \") || \"(none)\"}`,\n );\n }\n conditions.push(cond);\n }\n }\n\n return {\n id: jr.id,\n effect: jr.effect,\n roles: jr.roles,\n actions: jr.actions,\n resources: jr.resources,\n conditions: conditions.length > 0 ? conditions : undefined,\n priority: jr.priority,\n description: jr.description,\n } as PolicyRule<S>;\n });\n}\n\n/**\n * Parse a JSON string into PolicyRule objects.\n */\nexport function importRulesFromJson<S extends SchemaDefinition>(\n json: string,\n registry?: ConditionRegistry<S>,\n): PolicyRule<S>[] {\n let doc: JsonPolicyDocument;\n try {\n doc = JSON.parse(json) as JsonPolicyDocument;\n } catch (err) {\n throw new Error(\n `Failed to parse policy JSON: ${err instanceof Error ? err.message : String(err)}`,\n );\n }\n return importRules(doc, registry);\n}\n","/**\n * Core type definitions for the authorization engine.\n *\n * The type system is designed so that defining a schema once\n * propagates full autocomplete through every API surface:\n * policies, checks, audits, and middleware.\n */\n\n// ---------------------------------------------------------------------------\n// Schema definition types — what users provide to configure the engine\n// ---------------------------------------------------------------------------\n\nexport type ActionString = `${string}:${string}`;\n\nexport interface SchemaDefinition {\n roles: string;\n resources: string;\n actions: ActionString;\n tenantId?: string;\n}\n\n/**\n * Infer concrete union types from a schema definition.\n * Used internally to thread type narrowing everywhere.\n */\nexport type InferRole<S extends SchemaDefinition> = S[\"roles\"];\nexport type InferResource<S extends SchemaDefinition> = S[\"resources\"];\nexport type InferAction<S extends SchemaDefinition> = S[\"actions\"];\nexport type InferTenantId<S extends SchemaDefinition> = S[\"tenantId\"] extends string\n ? S[\"tenantId\"]\n : string;\n\n// ---------------------------------------------------------------------------\n// User / Subject\n// ---------------------------------------------------------------------------\n\nexport interface RoleAssignment<S extends SchemaDefinition> {\n role: InferRole<S>;\n tenantId?: InferTenantId<S>;\n}\n\nexport interface Subject<S extends SchemaDefinition> {\n id: string;\n roles: RoleAssignment<S>[];\n attributes?: Record<string, unknown>;\n}\n\n// ---------------------------------------------------------------------------\n// Resource context passed during evaluation\n// ---------------------------------------------------------------------------\n\nexport interface ResourceContext {\n id?: string;\n tenantId?: string;\n [key: string]: unknown;\n}\n\n// ---------------------------------------------------------------------------\n// Evaluation context — what conditions receive\n// ---------------------------------------------------------------------------\n\nexport interface EvaluationContext<S extends SchemaDefinition> {\n subject: Subject<S>;\n action: InferAction<S>;\n resource: InferResource<S>;\n resourceContext: ResourceContext;\n tenantId?: string;\n environment?: Record<string, unknown>;\n}\n\n// ---------------------------------------------------------------------------\n// Condition — a predicate attached to a policy rule\n// ---------------------------------------------------------------------------\n\nexport type Condition<S extends SchemaDefinition> = (\n ctx: EvaluationContext<S>,\n) => boolean | Promise<boolean>;\n\n// ---------------------------------------------------------------------------\n// Policy rule — the atomic unit of authorization\n// ---------------------------------------------------------------------------\n\nexport type PolicyEffect = \"allow\" | \"deny\";\n\nexport interface PolicyRule<S extends SchemaDefinition> {\n readonly id: string;\n readonly effect: PolicyEffect;\n readonly roles: InferRole<S>[] | \"*\";\n readonly actions: InferAction<S>[] | \"*\";\n readonly resources: InferResource<S>[] | \"*\";\n readonly conditions?: Condition<S>[];\n /**\n * Higher priority wins. Deny at equal priority wins over allow.\n * Default: 0\n */\n readonly priority?: number;\n readonly description?: string;\n}\n\n// ---------------------------------------------------------------------------\n// Decision — the result of evaluating a request\n// ---------------------------------------------------------------------------\n\nexport interface Decision<S extends SchemaDefinition> {\n allowed: boolean;\n effect: PolicyEffect | \"default-deny\";\n matchedRule: PolicyRule<S> | null;\n subject: Subject<S>;\n action: InferAction<S>;\n resource: InferResource<S>;\n resourceContext: ResourceContext;\n tenantId?: string;\n timestamp: number;\n durationMs: number;\n reason: string;\n}\n\n// ---------------------------------------------------------------------------\n// Audit entry — serialization-safe version of Decision\n// ---------------------------------------------------------------------------\n\nexport interface AuditEntry {\n allowed: boolean;\n effect: string;\n matchedRuleId: string | null;\n matchedRuleDescription: string | null;\n subjectId: string;\n action: string;\n resource: string;\n tenantId?: string;\n timestamp: number;\n durationMs: number;\n reason: string;\n}\n\n/**\n * Convert a Decision to a serialization-safe AuditEntry\n * (strips functions, large objects, and condition references).\n */\nexport function toAuditEntry<S extends SchemaDefinition>(decision: Decision<S>): AuditEntry {\n return {\n allowed: decision.allowed,\n effect: decision.effect,\n matchedRuleId: decision.matchedRule?.id ?? null,\n matchedRuleDescription: decision.matchedRule?.description ?? null,\n subjectId: decision.subject.id,\n action: decision.action as string,\n resource: decision.resource as string,\n tenantId: decision.tenantId,\n timestamp: decision.timestamp,\n durationMs: decision.durationMs,\n reason: decision.reason,\n };\n}\n\n// ---------------------------------------------------------------------------\n// Explain result — detailed evaluation trace for debugging\n// ---------------------------------------------------------------------------\n\nexport interface ConditionResult {\n index: number;\n passed: boolean;\n error?: string;\n}\n\nexport interface RuleEvaluation<S extends SchemaDefinition> {\n rule: PolicyRule<S>;\n roleMatched: boolean;\n actionMatched: boolean;\n resourceMatched: boolean;\n conditionResults: ConditionResult[];\n matched: boolean;\n}\n\nexport interface ExplainResult<S extends SchemaDefinition> {\n allowed: boolean;\n effect: PolicyEffect | \"default-deny\";\n reason: string;\n evaluatedRules: RuleEvaluation<S>[];\n durationMs: number;\n}\n\n// ---------------------------------------------------------------------------\n// Audit / Observability\n// ---------------------------------------------------------------------------\n\nexport type DecisionListener<S extends SchemaDefinition> = (\n decision: Decision<S>,\n) => void | Promise<void>;\n\nexport interface ConditionError {\n ruleId: string;\n conditionIndex: number;\n error: unknown;\n}\n\nexport type ConditionErrorHandler = (err: ConditionError) => void;\n\n// ---------------------------------------------------------------------------\n// Engine options\n// ---------------------------------------------------------------------------\n\nexport interface EngineOptions<S extends SchemaDefinition> {\n schema: S;\n defaultEffect?: PolicyEffect;\n onDecision?: DecisionListener<S>;\n onConditionError?: ConditionErrorHandler;\n /**\n * When true, sync methods (evaluate, explain, permitted) throw immediately\n * to force use of evaluateAsync, explainAsync, permittedAsync.\n * When false (default), async conditions are detected at runtime and throw\n * with a clear error pointing to the async API.\n *\n * @deprecated This option is deprecated and will be removed in v2. Async\n * conditions are now detected automatically. Use evaluateAsync(),\n * explainAsync(), or permittedAsync() when you have async conditions.\n */\n asyncConditions?: boolean;\n /**\n * When true, evaluate() throws if tenantId is omitted and the subject\n * has any tenant-scoped role assignments. Prevents accidental\n * cross-tenant privilege escalation.\n */\n strictTenancy?: boolean;\n}\n"],"mappings":"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","names":[]}
|
package/dist/index.d.cts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { S as SchemaDefinition, C as Condition, P as PolicyEffect, a as PolicyRule } from './engine-
|
|
2
|
-
export { A as AccessEngine, b as AccessEngineOptions, c as ActionString, d as AuditEntry, e as ConditionError, f as ConditionErrorHandler, g as ConditionResult, D as Decision, h as DecisionListener, E as EngineOptions, i as EvaluationContext, j as ExplainResult, I as InferAction, k as InferResource, l as InferRole, m as InferTenantId, R as ResourceContext, n as RoleAssignment, o as RoleHierarchy, p as RuleBuilder, q as RuleEvaluation, r as Subject, s as allow, t as createPolicyFactory, u as deny, v as toAuditEntry } from './engine-
|
|
1
|
+
import { S as SchemaDefinition, C as Condition, P as PolicyEffect, a as PolicyRule } from './engine-Ccws3LFj.cjs';
|
|
2
|
+
export { A as AccessEngine, b as AccessEngineOptions, c as ActionString, d as AuditEntry, e as ConditionError, f as ConditionErrorHandler, g as ConditionResult, D as Decision, h as DecisionListener, E as EngineOptions, i as EvaluationContext, j as ExplainResult, I as InferAction, k as InferResource, l as InferRole, m as InferTenantId, R as ResourceContext, n as RoleAssignment, o as RoleHierarchy, p as RuleBuilder, q as RuleEvaluation, r as Subject, s as allow, t as createPolicyFactory, u as deny, v as toAuditEntry } from './engine-Ccws3LFj.cjs';
|
|
3
3
|
export { EvalRequestBody, ServerOptions } from './server.cjs';
|
|
4
4
|
import 'http';
|
|
5
5
|
import 'node:http';
|
package/dist/index.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { S as SchemaDefinition, C as Condition, P as PolicyEffect, a as PolicyRule } from './engine-
|
|
2
|
-
export { A as AccessEngine, b as AccessEngineOptions, c as ActionString, d as AuditEntry, e as ConditionError, f as ConditionErrorHandler, g as ConditionResult, D as Decision, h as DecisionListener, E as EngineOptions, i as EvaluationContext, j as ExplainResult, I as InferAction, k as InferResource, l as InferRole, m as InferTenantId, R as ResourceContext, n as RoleAssignment, o as RoleHierarchy, p as RuleBuilder, q as RuleEvaluation, r as Subject, s as allow, t as createPolicyFactory, u as deny, v as toAuditEntry } from './engine-
|
|
1
|
+
import { S as SchemaDefinition, C as Condition, P as PolicyEffect, a as PolicyRule } from './engine-Ccws3LFj.js';
|
|
2
|
+
export { A as AccessEngine, b as AccessEngineOptions, c as ActionString, d as AuditEntry, e as ConditionError, f as ConditionErrorHandler, g as ConditionResult, D as Decision, h as DecisionListener, E as EngineOptions, i as EvaluationContext, j as ExplainResult, I as InferAction, k as InferResource, l as InferRole, m as InferTenantId, R as ResourceContext, n as RoleAssignment, o as RoleHierarchy, p as RuleBuilder, q as RuleEvaluation, r as Subject, s as allow, t as createPolicyFactory, u as deny, v as toAuditEntry } from './engine-Ccws3LFj.js';
|
|
3
3
|
export { EvalRequestBody, ServerOptions } from './server.js';
|
|
4
4
|
import 'http';
|
|
5
5
|
import 'node:http';
|
package/dist/index.js
CHANGED
|
@@ -86,6 +86,11 @@ function createPolicyFactory() {
|
|
|
86
86
|
function escapeRegexMeta(s) {
|
|
87
87
|
return s.replace(/[.+?^${}()|[\]\\]/g, "\\$&");
|
|
88
88
|
}
|
|
89
|
+
function isThenable(value) {
|
|
90
|
+
return value != null && typeof value === "object" && typeof value.then === "function";
|
|
91
|
+
}
|
|
92
|
+
var ASYNC_CONDITION_EVALUATE_MSG = "Async condition encountered. Use evaluateAsync() instead.";
|
|
93
|
+
var ASYNC_CONDITION_EXPLAIN_MSG = "Async condition encountered. Use explainAsync() instead.";
|
|
89
94
|
function compileActionPatterns(actions) {
|
|
90
95
|
if (actions === "*") return null;
|
|
91
96
|
const patterns = [];
|
|
@@ -328,6 +333,9 @@ var AccessEngine = class {
|
|
|
328
333
|
for (let i = 0; i < rule.conditions.length; i++) {
|
|
329
334
|
try {
|
|
330
335
|
const result = rule.conditions[i](ctx);
|
|
336
|
+
if (isThenable(result)) {
|
|
337
|
+
throw new Error(ASYNC_CONDITION_EXPLAIN_MSG);
|
|
338
|
+
}
|
|
331
339
|
if (result !== true) {
|
|
332
340
|
conditionResults.push({ index: i, passed: false });
|
|
333
341
|
allConditionsPassed = false;
|
|
@@ -335,6 +343,9 @@ var AccessEngine = class {
|
|
|
335
343
|
conditionResults.push({ index: i, passed: true });
|
|
336
344
|
}
|
|
337
345
|
} catch (err) {
|
|
346
|
+
if (err instanceof Error && err.message === ASYNC_CONDITION_EXPLAIN_MSG) {
|
|
347
|
+
throw err;
|
|
348
|
+
}
|
|
338
349
|
conditionResults.push({
|
|
339
350
|
index: i,
|
|
340
351
|
passed: false,
|
|
@@ -466,8 +477,15 @@ var AccessEngine = class {
|
|
|
466
477
|
if (!rule.conditions) return true;
|
|
467
478
|
for (let i = 0; i < rule.conditions.length; i++) {
|
|
468
479
|
try {
|
|
469
|
-
|
|
480
|
+
const result = rule.conditions[i](ctx);
|
|
481
|
+
if (isThenable(result)) {
|
|
482
|
+
throw new Error(ASYNC_CONDITION_EVALUATE_MSG);
|
|
483
|
+
}
|
|
484
|
+
if (result !== true) return false;
|
|
470
485
|
} catch (err) {
|
|
486
|
+
if (err instanceof Error && err.message === ASYNC_CONDITION_EVALUATE_MSG) {
|
|
487
|
+
throw err;
|
|
488
|
+
}
|
|
471
489
|
this.emitConditionError(rule.id, i, err);
|
|
472
490
|
return false;
|
|
473
491
|
}
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/policy-builder.ts","../src/engine.ts","../src/role-hierarchy.ts","../src/serialization.ts","../src/types.ts"],"sourcesContent":["import type {\n SchemaDefinition,\n InferRole,\n InferAction,\n InferResource,\n PolicyRule,\n PolicyEffect,\n Condition,\n} from \"./types.js\";\n\nlet ruleCounter = 0;\n\nfunction nextRuleId(prefix: string): string {\n return `${prefix}-${++ruleCounter}`;\n}\n\nexport class RuleBuilder<S extends SchemaDefinition> {\n private _effect: PolicyEffect;\n private _roles: InferRole<S>[] | \"*\" = \"*\";\n private _actions: InferAction<S>[] | \"*\" = \"*\";\n private _resources: InferResource<S>[] | \"*\" = \"*\";\n private _conditions: Condition<S>[] = [];\n private _priority = 0;\n private _description?: string;\n private _id: string;\n\n constructor(effect: PolicyEffect) {\n this._effect = effect;\n this._id = nextRuleId(effect);\n }\n\n id(id: string): this {\n this._id = id;\n return this;\n }\n\n roles(...roles: InferRole<S>[]): this {\n this._roles = roles;\n return this;\n }\n\n anyRole(): this {\n this._roles = \"*\";\n return this;\n }\n\n actions(...actions: InferAction<S>[]): this {\n this._actions = actions;\n return this;\n }\n\n anyAction(): this {\n this._actions = \"*\";\n return this;\n }\n\n on(...resources: InferResource<S>[]): this {\n this._resources = resources;\n return this;\n }\n\n anyResource(): this {\n this._resources = \"*\";\n return this;\n }\n\n when(condition: Condition<S>): this {\n this._conditions.push(condition);\n return this;\n }\n\n priority(p: number): this {\n this._priority = p;\n return this;\n }\n\n describe(desc: string): this {\n this._description = desc;\n return this;\n }\n\n build(): PolicyRule<S> {\n return {\n id: this._id,\n effect: this._effect,\n roles: this._roles,\n actions: this._actions,\n resources: this._resources,\n conditions: this._conditions.length > 0 ? this._conditions : undefined,\n priority: this._priority,\n description: this._description,\n };\n }\n}\n\nexport function allow<S extends SchemaDefinition>(): RuleBuilder<S> {\n return new RuleBuilder<S>(\"allow\");\n}\n\nexport function deny<S extends SchemaDefinition>(): RuleBuilder<S> {\n return new RuleBuilder<S>(\"deny\");\n}\n\n/**\n * Creates schema-bound allow/deny factories so you don't need to pass\n * the generic parameter on every call.\n *\n * ```ts\n * const { allow, deny } = createPolicyFactory<MySchema>();\n * allow().roles(\"admin\").anyAction().anyResource().build();\n * ```\n */\nexport function createPolicyFactory<S extends SchemaDefinition>(): {\n allow: () => RuleBuilder<S>;\n deny: () => RuleBuilder<S>;\n} {\n return {\n allow: () => new RuleBuilder<S>(\"allow\"),\n deny: () => new RuleBuilder<S>(\"deny\"),\n };\n}\n","import type {\n SchemaDefinition,\n InferAction,\n InferResource,\n InferRole,\n PolicyRule,\n Decision,\n Subject,\n ResourceContext,\n EvaluationContext,\n DecisionListener,\n EngineOptions,\n ConditionErrorHandler,\n ExplainResult,\n RuleEvaluation,\n ConditionResult,\n} from \"./types.js\";\nimport type { RuleBuilder } from \"./policy-builder.js\";\nimport { allow as _allow, deny as _deny } from \"./policy-builder.js\";\nimport type { RoleHierarchy } from \"./role-hierarchy.js\";\n\n// ---------------------------------------------------------------------------\n// Compiled rule — internal representation with pre-compiled regex\n// ---------------------------------------------------------------------------\n\ninterface CompiledRule<S extends SchemaDefinition> {\n rule: PolicyRule<S>;\n actionPatterns: RegExp[] | null;\n}\n\nfunction escapeRegexMeta(s: string): string {\n return s.replace(/[.+?^${}()|[\\]\\\\]/g, \"\\\\$&\");\n}\n\nfunction compileActionPatterns(actions: string[] | \"*\"): RegExp[] | null {\n if (actions === \"*\") return null;\n const patterns: RegExp[] = [];\n for (const action of actions) {\n if (action.includes(\"*\")) {\n const escaped = escapeRegexMeta(action).replace(/\\*/g, \"[^:]*\");\n patterns.push(new RegExp(\"^\" + escaped + \"$\"));\n }\n }\n return patterns.length > 0 ? patterns : null;\n}\n\n// ---------------------------------------------------------------------------\n// Engine\n// ---------------------------------------------------------------------------\n\nexport interface AccessEngineOptions<S extends SchemaDefinition> extends EngineOptions<S> {\n roleHierarchy?: RoleHierarchy<S>;\n /**\n * Enable LRU cache for evaluation results.\n * Only caches evaluations of rules WITHOUT conditions (context-independent).\n * Rules with conditions are never cached since their result depends on resourceContext.\n */\n cacheSize?: number;\n}\n\nexport class AccessEngine<S extends SchemaDefinition> {\n private compiled: CompiledRule<S>[] = [];\n private listeners: DecisionListener<S>[] = [];\n private asyncConditions: boolean;\n private _defaultDeny: boolean;\n private _strictTenancy: boolean;\n private hierarchy?: RoleHierarchy<S>;\n private cache?: LRUCache<Decision<S>>;\n private conditionErrorHandler?: ConditionErrorHandler;\n\n constructor(options: AccessEngineOptions<S>) {\n this.asyncConditions = options.asyncConditions ?? false;\n this._defaultDeny = (options.defaultEffect ?? \"deny\") === \"deny\";\n this._strictTenancy = options.strictTenancy ?? false;\n this.hierarchy = options.roleHierarchy;\n this.conditionErrorHandler = options.onConditionError;\n if (options.cacheSize && options.cacheSize > 0) {\n this.cache = new LRUCache(options.cacheSize);\n }\n if (options.onDecision) {\n this.listeners.push(options.onDecision);\n }\n }\n\n // -----------------------------------------------------------------------\n // Rule management\n // -----------------------------------------------------------------------\n\n addRule(rule: PolicyRule<S>): this {\n const frozen = Object.freeze({ ...rule });\n this.compiled.push({\n rule: frozen,\n actionPatterns: compileActionPatterns(frozen.actions as string[] | \"*\"),\n });\n this.cache?.clear();\n return this;\n }\n\n addRules(...rules: PolicyRule<S>[]): this {\n for (const rule of rules) {\n const frozen = Object.freeze({ ...rule });\n this.compiled.push({\n rule: frozen,\n actionPatterns: compileActionPatterns(frozen.actions as string[] | \"*\"),\n });\n }\n this.cache?.clear();\n return this;\n }\n\n removeRule(ruleId: string): boolean {\n const idx = this.compiled.findIndex((c) => c.rule.id === ruleId);\n if (idx === -1) return false;\n this.compiled.splice(idx, 1);\n this.cache?.clear();\n return true;\n }\n\n getRules(): ReadonlyArray<PolicyRule<S>> {\n return this.compiled.map((c) => c.rule);\n }\n\n clearRules(): void {\n this.compiled = [];\n this.cache?.clear();\n }\n\n // -----------------------------------------------------------------------\n // Cache control\n // -----------------------------------------------------------------------\n\n clearCache(): void {\n this.cache?.clear();\n }\n\n get cacheStats(): { size: number; maxSize: number } | null {\n if (!this.cache) return null;\n return { size: this.cache.size, maxSize: this.cache.maxSize };\n }\n\n // -----------------------------------------------------------------------\n // Fluent rule builders bound to this engine's schema\n // -----------------------------------------------------------------------\n\n allow(): RuleBuilder<S> {\n return _allow<S>();\n }\n\n deny(): RuleBuilder<S> {\n return _deny<S>();\n }\n\n // -----------------------------------------------------------------------\n // Observability\n // -----------------------------------------------------------------------\n\n onDecision(listener: DecisionListener<S>): () => void {\n this.listeners.push(listener);\n return () => {\n const idx = this.listeners.indexOf(listener);\n if (idx !== -1) this.listeners.splice(idx, 1);\n };\n }\n\n private emit(decision: Decision<S>): void {\n for (const listener of this.listeners) {\n try {\n const result = listener(decision);\n if (result instanceof Promise) {\n result.catch(() => {});\n }\n } catch {\n // listeners must not break evaluation\n }\n }\n }\n\n // -----------------------------------------------------------------------\n // Evaluation\n // -----------------------------------------------------------------------\n\n evaluate(\n subject: Subject<S>,\n action: InferAction<S>,\n resource: InferResource<S>,\n resourceContext: ResourceContext = {},\n tenantId?: string,\n ): Decision<S> {\n if (this.asyncConditions) {\n throw new Error(\n \"Engine has asyncConditions enabled. Use evaluateAsync() instead.\",\n );\n }\n this.validateInput(subject, action, resource);\n this.enforceTenancy(subject, tenantId);\n\n const cacheKey = this.cache\n ? buildCacheKey(subject.id, action as string, resource as string, tenantId)\n : undefined;\n if (cacheKey) {\n const cached = this.cache!.get(cacheKey);\n if (cached) {\n this.emit(cached);\n return cached;\n }\n }\n\n const start = performance.now();\n const ctx = this.buildContext(subject, action, resource, resourceContext, tenantId);\n const candidates = this.matchRules(subject, action, resource, tenantId);\n\n let matched: CompiledRule<S> | null = null;\n let matchedHasConditions = false;\n\n for (const compiled of candidates) {\n const { rule } = compiled;\n if (!rule.conditions || rule.conditions.length === 0) {\n matched = compiled;\n matchedHasConditions = false;\n break;\n }\n const allMet = this.evaluateConditionsSync(rule, ctx);\n if (allMet) {\n matched = compiled;\n matchedHasConditions = true;\n break;\n }\n }\n\n const decision = this.buildDecision(matched?.rule ?? null, ctx, start);\n\n if (cacheKey && !matchedHasConditions) {\n this.cache!.set(cacheKey, decision);\n }\n\n this.emit(decision);\n return decision;\n }\n\n async evaluateAsync(\n subject: Subject<S>,\n action: InferAction<S>,\n resource: InferResource<S>,\n resourceContext: ResourceContext = {},\n tenantId?: string,\n ): Promise<Decision<S>> {\n this.validateInput(subject, action, resource);\n this.enforceTenancy(subject, tenantId);\n const start = performance.now();\n const ctx = this.buildContext(subject, action, resource, resourceContext, tenantId);\n const candidates = this.matchRules(subject, action, resource, tenantId);\n\n let matched: PolicyRule<S> | null = null;\n\n for (const compiled of candidates) {\n const { rule } = compiled;\n if (!rule.conditions || rule.conditions.length === 0) {\n matched = rule;\n break;\n }\n const results = await Promise.all(\n rule.conditions.map((c, i) =>\n Promise.resolve()\n .then(() => c(ctx))\n .catch((err) => {\n this.emitConditionError(rule.id, i, err);\n return false;\n }),\n ),\n );\n if (results.every(Boolean)) {\n matched = rule;\n break;\n }\n }\n\n const decision = this.buildDecision(matched, ctx, start);\n this.emit(decision);\n return decision;\n }\n\n // -----------------------------------------------------------------------\n // permitted() — list allowed actions on a resource for UI rendering\n // -----------------------------------------------------------------------\n\n permitted(\n subject: Subject<S>,\n resource: InferResource<S>,\n actions: InferAction<S>[],\n resourceContext: ResourceContext = {},\n tenantId?: string,\n ): Set<InferAction<S>> {\n const allowed = new Set<InferAction<S>>();\n for (const action of actions) {\n const decision = this.asyncConditions\n ? (() => { throw new Error(\"Use permittedAsync() with asyncConditions enabled.\"); })()\n : this.evaluate(subject, action, resource, resourceContext, tenantId);\n if (decision.allowed) {\n allowed.add(action);\n }\n }\n return allowed;\n }\n\n async permittedAsync(\n subject: Subject<S>,\n resource: InferResource<S>,\n actions: InferAction<S>[],\n resourceContext: ResourceContext = {},\n tenantId?: string,\n ): Promise<Set<InferAction<S>>> {\n const allowed = new Set<InferAction<S>>();\n const results = await Promise.all(\n actions.map((action) =>\n this.evaluateAsync(subject, action, resource, resourceContext, tenantId),\n ),\n );\n for (let i = 0; i < actions.length; i++) {\n if (results[i]!.allowed) {\n allowed.add(actions[i]!);\n }\n }\n return allowed;\n }\n\n // -----------------------------------------------------------------------\n // explain() — full evaluation trace for debugging\n // -----------------------------------------------------------------------\n\n explain(\n subject: Subject<S>,\n action: InferAction<S>,\n resource: InferResource<S>,\n resourceContext: ResourceContext = {},\n tenantId?: string,\n ): ExplainResult<S> {\n if (this.asyncConditions) {\n throw new Error(\n \"Engine has asyncConditions enabled. Use explainAsync() instead.\",\n );\n }\n this.enforceTenancy(subject, tenantId);\n const start = performance.now();\n const ctx = this.buildContext(subject, action, resource, resourceContext, tenantId);\n const subjectRoles = this.resolveRoles(subject, tenantId);\n\n const evaluatedRules: RuleEvaluation<S>[] = [];\n let firstMatch: PolicyRule<S> | null = null;\n\n const sorted = this.sortCandidates([...this.compiled]);\n\n for (const compiled of sorted) {\n const { rule } = compiled;\n const roleMatched = rule.roles === \"*\" || rule.roles.some((r) => subjectRoles.has(r));\n const actionMatched = rule.actions === \"*\" || this.matchesAction(compiled, action);\n const resourceMatched = rule.resources === \"*\" || rule.resources.includes(resource);\n\n const conditionResults: ConditionResult[] = [];\n let allConditionsPassed = true;\n\n if (roleMatched && actionMatched && resourceMatched && rule.conditions) {\n for (let i = 0; i < rule.conditions.length; i++) {\n try {\n const result = rule.conditions[i]!(ctx);\n if (result !== true) {\n conditionResults.push({ index: i, passed: false });\n allConditionsPassed = false;\n } else {\n conditionResults.push({ index: i, passed: true });\n }\n } catch (err) {\n conditionResults.push({\n index: i,\n passed: false,\n error: err instanceof Error ? err.message : String(err),\n });\n allConditionsPassed = false;\n }\n }\n }\n\n const matched =\n roleMatched && actionMatched && resourceMatched &&\n (!rule.conditions || rule.conditions.length === 0 || allConditionsPassed);\n\n evaluatedRules.push({\n rule,\n roleMatched,\n actionMatched,\n resourceMatched,\n conditionResults,\n matched,\n });\n\n if (matched && !firstMatch) {\n firstMatch = rule;\n }\n }\n\n const allowed = firstMatch != null ? firstMatch.effect === \"allow\" : !this._defaultDeny;\n const effect = firstMatch?.effect ?? \"default-deny\";\n const reason = firstMatch\n ? `Matched rule \"${firstMatch.id}\"${firstMatch.description ? `: ${firstMatch.description}` : \"\"}`\n : \"No matching rule — default deny\";\n\n return {\n allowed,\n effect,\n reason,\n evaluatedRules,\n durationMs: performance.now() - start,\n };\n }\n\n async explainAsync(\n subject: Subject<S>,\n action: InferAction<S>,\n resource: InferResource<S>,\n resourceContext: ResourceContext = {},\n tenantId?: string,\n ): Promise<ExplainResult<S>> {\n this.enforceTenancy(subject, tenantId);\n const start = performance.now();\n const ctx = this.buildContext(subject, action, resource, resourceContext, tenantId);\n const subjectRoles = this.resolveRoles(subject, tenantId);\n\n const evaluatedRules: RuleEvaluation<S>[] = [];\n let firstMatch: PolicyRule<S> | null = null;\n\n const sorted = this.sortCandidates([...this.compiled]);\n\n for (const compiled of sorted) {\n const { rule } = compiled;\n const roleMatched = rule.roles === \"*\" || rule.roles.some((r) => subjectRoles.has(r));\n const actionMatched = rule.actions === \"*\" || this.matchesAction(compiled, action);\n const resourceMatched = rule.resources === \"*\" || rule.resources.includes(resource);\n\n const conditionResults: ConditionResult[] = [];\n let allConditionsPassed = true;\n\n if (roleMatched && actionMatched && resourceMatched && rule.conditions) {\n for (let i = 0; i < rule.conditions.length; i++) {\n try {\n const result = await rule.conditions[i]!(ctx);\n if (result !== true) {\n conditionResults.push({ index: i, passed: false });\n allConditionsPassed = false;\n } else {\n conditionResults.push({ index: i, passed: true });\n }\n } catch (err) {\n conditionResults.push({\n index: i,\n passed: false,\n error: err instanceof Error ? err.message : String(err),\n });\n allConditionsPassed = false;\n }\n }\n }\n\n const matched =\n roleMatched && actionMatched && resourceMatched &&\n (!rule.conditions || rule.conditions.length === 0 || allConditionsPassed);\n\n evaluatedRules.push({\n rule,\n roleMatched,\n actionMatched,\n resourceMatched,\n conditionResults,\n matched,\n });\n\n if (matched && !firstMatch) {\n firstMatch = rule;\n }\n }\n\n const allowed = firstMatch != null ? firstMatch.effect === \"allow\" : !this._defaultDeny;\n const effect = firstMatch?.effect ?? \"default-deny\";\n const reason = firstMatch\n ? `Matched rule \"${firstMatch.id}\"${firstMatch.description ? `: ${firstMatch.description}` : \"\"}`\n : \"No matching rule — default deny\";\n\n return {\n allowed,\n effect,\n reason,\n evaluatedRules,\n durationMs: performance.now() - start,\n };\n }\n\n // -----------------------------------------------------------------------\n // Fluent check API: can(subject).perform(action).on(resource)\n // -----------------------------------------------------------------------\n\n can(subject: Subject<S>): PerformStep<S> {\n return new PerformStep(this, subject);\n }\n\n // -----------------------------------------------------------------------\n // Internal helpers\n // -----------------------------------------------------------------------\n\n private validateInput(\n subject: Subject<S>,\n action: InferAction<S>,\n resource: InferResource<S>,\n ): void {\n if (!subject || typeof subject.id !== \"string\") {\n throw new Error(\"subject must be an object with a string id\");\n }\n if (!Array.isArray(subject.roles)) {\n throw new Error(\"subject.roles must be an array\");\n }\n if (!action || typeof action !== \"string\") {\n throw new Error(\"action must be a non-empty string\");\n }\n if (!resource || typeof resource !== \"string\") {\n throw new Error(\"resource must be a non-empty string\");\n }\n }\n\n private enforceTenancy(subject: Subject<S>, tenantId?: string): void {\n if (!this._strictTenancy || tenantId != null) return;\n const hasTenantScoped = subject.roles.some((r) => r.tenantId != null);\n if (hasTenantScoped) {\n throw new Error(\n \"strictTenancy is enabled and subject has tenant-scoped roles, \" +\n \"but no tenantId was provided to evaluate(). This could cause \" +\n \"cross-tenant privilege escalation. Pass an explicit tenantId.\",\n );\n }\n }\n\n private buildContext(\n subject: Subject<S>,\n action: InferAction<S>,\n resource: InferResource<S>,\n resourceContext: ResourceContext,\n tenantId?: string,\n ): EvaluationContext<S> {\n return { subject, action, resource, resourceContext, tenantId };\n }\n\n private evaluateConditionsSync(\n rule: PolicyRule<S>,\n ctx: EvaluationContext<S>,\n ): boolean {\n if (!rule.conditions) return true;\n for (let i = 0; i < rule.conditions.length; i++) {\n try {\n if (rule.conditions[i]!(ctx) !== true) return false;\n } catch (err) {\n this.emitConditionError(rule.id, i, err);\n return false;\n }\n }\n return true;\n }\n\n private emitConditionError(ruleId: string, conditionIndex: number, error: unknown): void {\n if (this.conditionErrorHandler) {\n try {\n this.conditionErrorHandler({ ruleId, conditionIndex, error });\n } catch {\n // error handler must not break evaluation\n }\n }\n }\n\n private matchRules(\n subject: Subject<S>,\n action: InferAction<S>,\n resource: InferResource<S>,\n tenantId?: string,\n ): CompiledRule<S>[] {\n const subjectRoles = this.resolveRoles(subject, tenantId);\n\n const matched = this.compiled.filter((compiled) => {\n const { rule } = compiled;\n if (rule.roles !== \"*\" && !rule.roles.some((r) => subjectRoles.has(r))) return false;\n if (rule.actions !== \"*\" && !this.matchesAction(compiled, action)) return false;\n if (rule.resources !== \"*\" && !rule.resources.includes(resource)) return false;\n return true;\n });\n\n return this.sortCandidates(matched);\n }\n\n private sortCandidates(candidates: CompiledRule<S>[]): CompiledRule<S>[] {\n return candidates.sort((a, b) => {\n const pa = a.rule.priority ?? 0;\n const pb = b.rule.priority ?? 0;\n if (pb !== pa) return pb - pa;\n if (a.rule.effect === \"deny\" && b.rule.effect === \"allow\") return -1;\n if (a.rule.effect === \"allow\" && b.rule.effect === \"deny\") return 1;\n return 0;\n });\n }\n\n private matchesAction(\n compiled: CompiledRule<S>,\n action: InferAction<S>,\n ): boolean {\n const { rule, actionPatterns } = compiled;\n if (rule.actions === \"*\") return true;\n const actionStr = action as string;\n if ((rule.actions as string[]).includes(actionStr)) return true;\n if (actionPatterns) {\n for (const pattern of actionPatterns) {\n if (pattern.test(actionStr)) return true;\n }\n }\n return false;\n }\n\n private resolveRoles(\n subject: Subject<S>,\n tenantId?: string,\n ): Set<string> {\n const directRoles = new Set<string>();\n for (const assignment of subject.roles) {\n if (tenantId == null || assignment.tenantId == null || assignment.tenantId === tenantId) {\n directRoles.add(assignment.role);\n }\n }\n\n if (!this.hierarchy) return directRoles;\n\n const expanded = new Set<string>();\n for (const role of directRoles) {\n for (const r of this.hierarchy.resolve(role as InferRole<S>)) {\n expanded.add(r);\n }\n }\n return expanded;\n }\n\n private buildDecision(\n matched: PolicyRule<S> | null,\n ctx: EvaluationContext<S>,\n start: number,\n ): Decision<S> {\n const allowed =\n matched != null ? matched.effect === \"allow\" : !this._defaultDeny;\n const effect = matched?.effect ?? \"default-deny\";\n const reason = matched\n ? `Matched rule \"${matched.id}\"${matched.description ? `: ${matched.description}` : \"\"}`\n : \"No matching rule — default deny\";\n\n return {\n allowed,\n effect,\n matchedRule: matched,\n subject: ctx.subject,\n action: ctx.action,\n resource: ctx.resource,\n resourceContext: ctx.resourceContext,\n tenantId: ctx.tenantId,\n timestamp: Date.now(),\n durationMs: performance.now() - start,\n reason,\n };\n }\n}\n\n// ---------------------------------------------------------------------------\n// Fluent check chain: can(user).perform(action).on(resource)\n// ---------------------------------------------------------------------------\n\nclass PerformStep<S extends SchemaDefinition> {\n constructor(\n private engine: AccessEngine<S>,\n private subject: Subject<S>,\n ) {}\n\n perform(action: InferAction<S>): OnStep<S> {\n return new OnStep(this.engine, this.subject, action);\n }\n}\n\nclass OnStep<S extends SchemaDefinition> {\n constructor(\n private engine: AccessEngine<S>,\n private subject: Subject<S>,\n private action: InferAction<S>,\n ) {}\n\n on(\n resource: InferResource<S>,\n resourceContext: ResourceContext = {},\n tenantId?: string,\n ): Decision<S> {\n return this.engine.evaluate(\n this.subject,\n this.action,\n resource,\n resourceContext,\n tenantId,\n );\n }\n\n async onAsync(\n resource: InferResource<S>,\n resourceContext: ResourceContext = {},\n tenantId?: string,\n ): Promise<Decision<S>> {\n return this.engine.evaluateAsync(\n this.subject,\n this.action,\n resource,\n resourceContext,\n tenantId,\n );\n }\n}\n\n// ---------------------------------------------------------------------------\n// Simple LRU cache\n// ---------------------------------------------------------------------------\n\nfunction buildCacheKey(\n subjectId: string,\n action: string,\n resource: string,\n tenantId?: string,\n): string {\n return `${subjectId.length}:${subjectId}\\0${action}\\0${resource}\\0${tenantId ?? \"\"}`;\n}\n\nclass LRUCache<V> {\n private map = new Map<string, V>();\n readonly maxSize: number;\n\n constructor(maxSize: number) {\n this.maxSize = maxSize;\n }\n\n get size(): number {\n return this.map.size;\n }\n\n get(key: string): V | undefined {\n const value = this.map.get(key);\n if (value !== undefined) {\n this.map.delete(key);\n this.map.set(key, value);\n }\n return value;\n }\n\n set(key: string, value: V): void {\n if (this.map.has(key)) {\n this.map.delete(key);\n } else if (this.map.size >= this.maxSize) {\n const oldest = this.map.keys().next().value;\n if (oldest !== undefined) this.map.delete(oldest);\n }\n this.map.set(key, value);\n }\n\n clear(): void {\n this.map.clear();\n }\n}\n","import type { SchemaDefinition, InferRole } from \"./types.js\";\n\n/**\n * Defines a role inheritance hierarchy.\n *\n * When a role inherits from another, it gains all permissions of its parent roles.\n * Cycles are detected and rejected at definition time.\n *\n * ```ts\n * const hierarchy = new RoleHierarchy<MySchema>()\n * .define(\"admin\", [\"manager\", \"viewer\"])\n * .define(\"manager\", [\"member\"])\n * .define(\"member\", [\"viewer\"]);\n *\n * hierarchy.resolve(\"admin\");\n * // Set { \"admin\", \"manager\", \"member\", \"viewer\" }\n * ```\n */\nexport class RoleHierarchy<S extends SchemaDefinition> {\n private parents = new Map<string, string[]>();\n private cache = new Map<string, Set<string>>();\n\n /**\n * Define that `role` inherits permissions from `inheritsFrom` roles.\n * Clears the resolution cache.\n */\n define(role: InferRole<S>, inheritsFrom: InferRole<S>[]): this {\n this.parents.set(role, inheritsFrom as string[]);\n this.cache.clear();\n this.detectCycle(role as string, new Set());\n return this;\n }\n\n /**\n * Resolve the full set of roles a given role expands to,\n * including all inherited roles (transitive).\n */\n resolve(role: InferRole<S>): Set<string> {\n const roleStr = role as string;\n const cached = this.cache.get(roleStr);\n if (cached) return cached;\n\n const result = new Set<string>();\n this.walk(roleStr, result);\n this.cache.set(roleStr, result);\n return result;\n }\n\n /**\n * Resolve multiple roles at once, returning the merged set.\n */\n resolveAll(roles: Iterable<InferRole<S>>): Set<string> {\n const result = new Set<string>();\n for (const role of roles) {\n for (const r of this.resolve(role)) {\n result.add(r);\n }\n }\n return result;\n }\n\n /**\n * Get all defined roles that have inheritance rules.\n */\n definedRoles(): string[] {\n return [...this.parents.keys()];\n }\n\n private walk(role: string, visited: Set<string>): void {\n if (visited.has(role)) return;\n visited.add(role);\n const parents = this.parents.get(role);\n if (parents) {\n for (const parent of parents) {\n this.walk(parent, visited);\n }\n }\n }\n\n private detectCycle(role: string, visiting: Set<string>): void {\n if (visiting.has(role)) {\n throw new Error(\n `Cycle detected in role hierarchy: ${[...visiting, role].join(\" → \")}`,\n );\n }\n visiting.add(role);\n const parents = this.parents.get(role);\n if (parents) {\n for (const parent of parents) {\n this.detectCycle(parent, visiting);\n }\n }\n visiting.delete(role);\n }\n}\n","import type {\n SchemaDefinition,\n PolicyRule,\n PolicyEffect,\n Condition,\n} from \"./types.js\";\n\n// ---------------------------------------------------------------------------\n// JSON-safe policy representation (conditions become named references)\n// ---------------------------------------------------------------------------\n\nexport interface JsonPolicyRule {\n id: string;\n effect: PolicyEffect;\n roles: string[] | \"*\";\n actions: string[] | \"*\";\n resources: string[] | \"*\";\n conditions?: string[];\n priority?: number;\n description?: string;\n}\n\nexport interface JsonPolicyDocument {\n version: 1;\n rules: JsonPolicyRule[];\n}\n\n/**\n * A registry that maps condition names to condition functions.\n * This allows JSON policies to reference conditions by name\n * while keeping the actual logic in code.\n *\n * ```ts\n * const conditions = new ConditionRegistry<MySchema>();\n * conditions.register(\"isOwner\", ctx => ctx.subject.id === ctx.resourceContext.ownerId);\n * conditions.register(\"isActive\", ctx => ctx.resourceContext.status === \"active\");\n * ```\n */\nexport class ConditionRegistry<S extends SchemaDefinition> {\n private conditions = new Map<string, Condition<S>>();\n\n register(name: string, condition: Condition<S>): this {\n if (!name || typeof name !== \"string\") {\n throw new Error(\"Condition name must be a non-empty string\");\n }\n if (typeof condition !== \"function\") {\n throw new Error(`Condition \"${name}\" must be a function`);\n }\n this.conditions.set(name, condition);\n return this;\n }\n\n get(name: string): Condition<S> | undefined {\n return this.conditions.get(name);\n }\n\n has(name: string): boolean {\n return this.conditions.has(name);\n }\n\n names(): string[] {\n return [...this.conditions.keys()];\n }\n}\n\n// ---------------------------------------------------------------------------\n// Export: PolicyRule[] → JSON\n// ---------------------------------------------------------------------------\n\n/**\n * Serialize rules to a JSON-safe document.\n * Conditions are stripped unless a reverse lookup map is provided.\n */\nexport function exportRules<S extends SchemaDefinition>(\n rules: ReadonlyArray<PolicyRule<S>>,\n conditionNames?: Map<Condition<S>, string>,\n): JsonPolicyDocument {\n const jsonRules: JsonPolicyRule[] = rules.map((rule) => {\n const jr: JsonPolicyRule = {\n id: rule.id,\n effect: rule.effect,\n roles: rule.roles,\n actions: rule.actions as string[] | \"*\",\n resources: rule.resources as string[] | \"*\",\n priority: rule.priority,\n description: rule.description,\n };\n\n if (rule.conditions && conditionNames) {\n const names: string[] = [];\n for (const cond of rule.conditions) {\n const name = conditionNames.get(cond);\n if (name) names.push(name);\n }\n if (names.length > 0) jr.conditions = names;\n }\n\n return jr;\n });\n\n return { version: 1, rules: jsonRules };\n}\n\n/**\n * Serialize rules to a JSON string.\n */\nexport function exportRulesToJson<S extends SchemaDefinition>(\n rules: ReadonlyArray<PolicyRule<S>>,\n conditionNames?: Map<Condition<S>, string>,\n): string {\n return JSON.stringify(exportRules(rules, conditionNames), null, 2);\n}\n\n// ---------------------------------------------------------------------------\n// Import: JSON → PolicyRule[]\n// ---------------------------------------------------------------------------\n\n/**\n * Deserialize a JSON policy document into PolicyRule objects.\n * Condition names are resolved via the provided registry.\n */\nexport function importRules<S extends SchemaDefinition>(\n doc: JsonPolicyDocument,\n registry?: ConditionRegistry<S>,\n): PolicyRule<S>[] {\n if (!doc || typeof doc !== \"object\") {\n throw new Error(\"Policy document must be a non-null object\");\n }\n if (doc.version !== 1) {\n throw new Error(`Unsupported policy document version: ${doc.version}`);\n }\n if (!Array.isArray(doc.rules)) {\n throw new Error(\"Policy document must have a 'rules' array\");\n }\n\n return doc.rules.map((jr, index) => {\n if (!jr || typeof jr !== \"object\") {\n throw new Error(`Rule at index ${index} must be a non-null object`);\n }\n\n if (!jr.id || typeof jr.id !== \"string\") {\n throw new Error(`Rule at index ${index} is missing a valid \"id\" field.`);\n }\n\n if (jr.effect !== \"allow\" && jr.effect !== \"deny\") {\n throw new Error(\n `Invalid effect \"${jr.effect}\" in rule \"${jr.id}\". Must be \"allow\" or \"deny\".`,\n );\n }\n\n if (jr.roles !== \"*\" && !Array.isArray(jr.roles)) {\n throw new Error(`Rule \"${jr.id}\": roles must be \"*\" or an array of strings`);\n }\n if (jr.actions !== \"*\" && !Array.isArray(jr.actions)) {\n throw new Error(`Rule \"${jr.id}\": actions must be \"*\" or an array of strings`);\n }\n if (jr.resources !== \"*\" && !Array.isArray(jr.resources)) {\n throw new Error(`Rule \"${jr.id}\": resources must be \"*\" or an array of strings`);\n }\n\n const conditions: Condition<S>[] = [];\n if (jr.conditions && registry) {\n for (const name of jr.conditions) {\n const cond = registry.get(name);\n if (!cond) {\n throw new Error(\n `Unknown condition \"${name}\" in rule \"${jr.id}\". ` +\n `Registered conditions: ${registry.names().join(\", \") || \"(none)\"}`,\n );\n }\n conditions.push(cond);\n }\n }\n\n return {\n id: jr.id,\n effect: jr.effect,\n roles: jr.roles,\n actions: jr.actions,\n resources: jr.resources,\n conditions: conditions.length > 0 ? conditions : undefined,\n priority: jr.priority,\n description: jr.description,\n } as PolicyRule<S>;\n });\n}\n\n/**\n * Parse a JSON string into PolicyRule objects.\n */\nexport function importRulesFromJson<S extends SchemaDefinition>(\n json: string,\n registry?: ConditionRegistry<S>,\n): PolicyRule<S>[] {\n let doc: JsonPolicyDocument;\n try {\n doc = JSON.parse(json) as JsonPolicyDocument;\n } catch (err) {\n throw new Error(\n `Failed to parse policy JSON: ${err instanceof Error ? err.message : String(err)}`,\n );\n }\n return importRules(doc, registry);\n}\n","/**\n * Core type definitions for the authorization engine.\n *\n * The type system is designed so that defining a schema once\n * propagates full autocomplete through every API surface:\n * policies, checks, audits, and middleware.\n */\n\n// ---------------------------------------------------------------------------\n// Schema definition types — what users provide to configure the engine\n// ---------------------------------------------------------------------------\n\nexport type ActionString = `${string}:${string}`;\n\nexport interface SchemaDefinition {\n roles: string;\n resources: string;\n actions: ActionString;\n tenantId?: string;\n}\n\n/**\n * Infer concrete union types from a schema definition.\n * Used internally to thread type narrowing everywhere.\n */\nexport type InferRole<S extends SchemaDefinition> = S[\"roles\"];\nexport type InferResource<S extends SchemaDefinition> = S[\"resources\"];\nexport type InferAction<S extends SchemaDefinition> = S[\"actions\"];\nexport type InferTenantId<S extends SchemaDefinition> = S[\"tenantId\"] extends string\n ? S[\"tenantId\"]\n : string;\n\n// ---------------------------------------------------------------------------\n// User / Subject\n// ---------------------------------------------------------------------------\n\nexport interface RoleAssignment<S extends SchemaDefinition> {\n role: InferRole<S>;\n tenantId?: InferTenantId<S>;\n}\n\nexport interface Subject<S extends SchemaDefinition> {\n id: string;\n roles: RoleAssignment<S>[];\n attributes?: Record<string, unknown>;\n}\n\n// ---------------------------------------------------------------------------\n// Resource context passed during evaluation\n// ---------------------------------------------------------------------------\n\nexport interface ResourceContext {\n id?: string;\n tenantId?: string;\n [key: string]: unknown;\n}\n\n// ---------------------------------------------------------------------------\n// Evaluation context — what conditions receive\n// ---------------------------------------------------------------------------\n\nexport interface EvaluationContext<S extends SchemaDefinition> {\n subject: Subject<S>;\n action: InferAction<S>;\n resource: InferResource<S>;\n resourceContext: ResourceContext;\n tenantId?: string;\n environment?: Record<string, unknown>;\n}\n\n// ---------------------------------------------------------------------------\n// Condition — a predicate attached to a policy rule\n// ---------------------------------------------------------------------------\n\nexport type Condition<S extends SchemaDefinition> = (\n ctx: EvaluationContext<S>,\n) => boolean | Promise<boolean>;\n\n// ---------------------------------------------------------------------------\n// Policy rule — the atomic unit of authorization\n// ---------------------------------------------------------------------------\n\nexport type PolicyEffect = \"allow\" | \"deny\";\n\nexport interface PolicyRule<S extends SchemaDefinition> {\n readonly id: string;\n readonly effect: PolicyEffect;\n readonly roles: InferRole<S>[] | \"*\";\n readonly actions: InferAction<S>[] | \"*\";\n readonly resources: InferResource<S>[] | \"*\";\n readonly conditions?: Condition<S>[];\n /**\n * Higher priority wins. Deny at equal priority wins over allow.\n * Default: 0\n */\n readonly priority?: number;\n readonly description?: string;\n}\n\n// ---------------------------------------------------------------------------\n// Decision — the result of evaluating a request\n// ---------------------------------------------------------------------------\n\nexport interface Decision<S extends SchemaDefinition> {\n allowed: boolean;\n effect: PolicyEffect | \"default-deny\";\n matchedRule: PolicyRule<S> | null;\n subject: Subject<S>;\n action: InferAction<S>;\n resource: InferResource<S>;\n resourceContext: ResourceContext;\n tenantId?: string;\n timestamp: number;\n durationMs: number;\n reason: string;\n}\n\n// ---------------------------------------------------------------------------\n// Audit entry — serialization-safe version of Decision\n// ---------------------------------------------------------------------------\n\nexport interface AuditEntry {\n allowed: boolean;\n effect: string;\n matchedRuleId: string | null;\n matchedRuleDescription: string | null;\n subjectId: string;\n action: string;\n resource: string;\n tenantId?: string;\n timestamp: number;\n durationMs: number;\n reason: string;\n}\n\n/**\n * Convert a Decision to a serialization-safe AuditEntry\n * (strips functions, large objects, and condition references).\n */\nexport function toAuditEntry<S extends SchemaDefinition>(decision: Decision<S>): AuditEntry {\n return {\n allowed: decision.allowed,\n effect: decision.effect,\n matchedRuleId: decision.matchedRule?.id ?? null,\n matchedRuleDescription: decision.matchedRule?.description ?? null,\n subjectId: decision.subject.id,\n action: decision.action as string,\n resource: decision.resource as string,\n tenantId: decision.tenantId,\n timestamp: decision.timestamp,\n durationMs: decision.durationMs,\n reason: decision.reason,\n };\n}\n\n// ---------------------------------------------------------------------------\n// Explain result — detailed evaluation trace for debugging\n// ---------------------------------------------------------------------------\n\nexport interface ConditionResult {\n index: number;\n passed: boolean;\n error?: string;\n}\n\nexport interface RuleEvaluation<S extends SchemaDefinition> {\n rule: PolicyRule<S>;\n roleMatched: boolean;\n actionMatched: boolean;\n resourceMatched: boolean;\n conditionResults: ConditionResult[];\n matched: boolean;\n}\n\nexport interface ExplainResult<S extends SchemaDefinition> {\n allowed: boolean;\n effect: PolicyEffect | \"default-deny\";\n reason: string;\n evaluatedRules: RuleEvaluation<S>[];\n durationMs: number;\n}\n\n// ---------------------------------------------------------------------------\n// Audit / Observability\n// ---------------------------------------------------------------------------\n\nexport type DecisionListener<S extends SchemaDefinition> = (\n decision: Decision<S>,\n) => void | Promise<void>;\n\nexport interface ConditionError {\n ruleId: string;\n conditionIndex: number;\n error: unknown;\n}\n\nexport type ConditionErrorHandler = (err: ConditionError) => void;\n\n// ---------------------------------------------------------------------------\n// Engine options\n// ---------------------------------------------------------------------------\n\nexport interface EngineOptions<S extends SchemaDefinition> {\n schema: S;\n defaultEffect?: PolicyEffect;\n onDecision?: DecisionListener<S>;\n onConditionError?: ConditionErrorHandler;\n /**\n * When true, async conditions are awaited.\n * When false (default), only synchronous conditions are supported\n * and evaluate is guaranteed synchronous.\n */\n asyncConditions?: boolean;\n /**\n * When true, evaluate() throws if tenantId is omitted and the subject\n * has any tenant-scoped role assignments. Prevents accidental\n * cross-tenant privilege escalation.\n */\n strictTenancy?: boolean;\n}\n"],"mappings":"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","names":[]}
|
|
1
|
+
{"version":3,"sources":["../src/policy-builder.ts","../src/engine.ts","../src/role-hierarchy.ts","../src/serialization.ts","../src/types.ts"],"sourcesContent":["import type {\n SchemaDefinition,\n InferRole,\n InferAction,\n InferResource,\n PolicyRule,\n PolicyEffect,\n Condition,\n} from \"./types.js\";\n\nlet ruleCounter = 0;\n\nfunction nextRuleId(prefix: string): string {\n return `${prefix}-${++ruleCounter}`;\n}\n\nexport class RuleBuilder<S extends SchemaDefinition> {\n private _effect: PolicyEffect;\n private _roles: InferRole<S>[] | \"*\" = \"*\";\n private _actions: InferAction<S>[] | \"*\" = \"*\";\n private _resources: InferResource<S>[] | \"*\" = \"*\";\n private _conditions: Condition<S>[] = [];\n private _priority = 0;\n private _description?: string;\n private _id: string;\n\n constructor(effect: PolicyEffect) {\n this._effect = effect;\n this._id = nextRuleId(effect);\n }\n\n id(id: string): this {\n this._id = id;\n return this;\n }\n\n roles(...roles: InferRole<S>[]): this {\n this._roles = roles;\n return this;\n }\n\n anyRole(): this {\n this._roles = \"*\";\n return this;\n }\n\n actions(...actions: InferAction<S>[]): this {\n this._actions = actions;\n return this;\n }\n\n anyAction(): this {\n this._actions = \"*\";\n return this;\n }\n\n on(...resources: InferResource<S>[]): this {\n this._resources = resources;\n return this;\n }\n\n anyResource(): this {\n this._resources = \"*\";\n return this;\n }\n\n when(condition: Condition<S>): this {\n this._conditions.push(condition);\n return this;\n }\n\n priority(p: number): this {\n this._priority = p;\n return this;\n }\n\n describe(desc: string): this {\n this._description = desc;\n return this;\n }\n\n build(): PolicyRule<S> {\n return {\n id: this._id,\n effect: this._effect,\n roles: this._roles,\n actions: this._actions,\n resources: this._resources,\n conditions: this._conditions.length > 0 ? this._conditions : undefined,\n priority: this._priority,\n description: this._description,\n };\n }\n}\n\nexport function allow<S extends SchemaDefinition>(): RuleBuilder<S> {\n return new RuleBuilder<S>(\"allow\");\n}\n\nexport function deny<S extends SchemaDefinition>(): RuleBuilder<S> {\n return new RuleBuilder<S>(\"deny\");\n}\n\n/**\n * Creates schema-bound allow/deny factories so you don't need to pass\n * the generic parameter on every call.\n *\n * ```ts\n * const { allow, deny } = createPolicyFactory<MySchema>();\n * allow().roles(\"admin\").anyAction().anyResource().build();\n * ```\n */\nexport function createPolicyFactory<S extends SchemaDefinition>(): {\n allow: () => RuleBuilder<S>;\n deny: () => RuleBuilder<S>;\n} {\n return {\n allow: () => new RuleBuilder<S>(\"allow\"),\n deny: () => new RuleBuilder<S>(\"deny\"),\n };\n}\n","import type {\n SchemaDefinition,\n InferAction,\n InferResource,\n InferRole,\n PolicyRule,\n Decision,\n Subject,\n ResourceContext,\n EvaluationContext,\n DecisionListener,\n EngineOptions,\n ConditionErrorHandler,\n ExplainResult,\n RuleEvaluation,\n ConditionResult,\n} from \"./types.js\";\nimport type { RuleBuilder } from \"./policy-builder.js\";\nimport { allow as _allow, deny as _deny } from \"./policy-builder.js\";\nimport type { RoleHierarchy } from \"./role-hierarchy.js\";\n\n// ---------------------------------------------------------------------------\n// Compiled rule — internal representation with pre-compiled regex\n// ---------------------------------------------------------------------------\n\ninterface CompiledRule<S extends SchemaDefinition> {\n rule: PolicyRule<S>;\n actionPatterns: RegExp[] | null;\n}\n\nfunction escapeRegexMeta(s: string): string {\n return s.replace(/[.+?^${}()|[\\]\\\\]/g, \"\\\\$&\");\n}\n\nfunction isThenable(value: unknown): value is PromiseLike<unknown> {\n return (\n value != null &&\n typeof value === \"object\" &&\n typeof (value as PromiseLike<unknown>).then === \"function\"\n );\n}\n\nconst ASYNC_CONDITION_EVALUATE_MSG =\n \"Async condition encountered. Use evaluateAsync() instead.\";\nconst ASYNC_CONDITION_EXPLAIN_MSG =\n \"Async condition encountered. Use explainAsync() instead.\";\n\nfunction compileActionPatterns(actions: string[] | \"*\"): RegExp[] | null {\n if (actions === \"*\") return null;\n const patterns: RegExp[] = [];\n for (const action of actions) {\n if (action.includes(\"*\")) {\n const escaped = escapeRegexMeta(action).replace(/\\*/g, \"[^:]*\");\n patterns.push(new RegExp(\"^\" + escaped + \"$\"));\n }\n }\n return patterns.length > 0 ? patterns : null;\n}\n\n// ---------------------------------------------------------------------------\n// Engine\n// ---------------------------------------------------------------------------\n\nexport interface AccessEngineOptions<S extends SchemaDefinition> extends EngineOptions<S> {\n roleHierarchy?: RoleHierarchy<S>;\n /**\n * Enable LRU cache for evaluation results.\n * Only caches evaluations of rules WITHOUT conditions (context-independent).\n * Rules with conditions are never cached since their result depends on resourceContext.\n */\n cacheSize?: number;\n}\n\nexport class AccessEngine<S extends SchemaDefinition> {\n private compiled: CompiledRule<S>[] = [];\n private listeners: DecisionListener<S>[] = [];\n private asyncConditions: boolean;\n private _defaultDeny: boolean;\n private _strictTenancy: boolean;\n private hierarchy?: RoleHierarchy<S>;\n private cache?: LRUCache<Decision<S>>;\n private conditionErrorHandler?: ConditionErrorHandler;\n\n constructor(options: AccessEngineOptions<S>) {\n this.asyncConditions = options.asyncConditions ?? false;\n this._defaultDeny = (options.defaultEffect ?? \"deny\") === \"deny\";\n this._strictTenancy = options.strictTenancy ?? false;\n this.hierarchy = options.roleHierarchy;\n this.conditionErrorHandler = options.onConditionError;\n if (options.cacheSize && options.cacheSize > 0) {\n this.cache = new LRUCache(options.cacheSize);\n }\n if (options.onDecision) {\n this.listeners.push(options.onDecision);\n }\n }\n\n // -----------------------------------------------------------------------\n // Rule management\n // -----------------------------------------------------------------------\n\n addRule(rule: PolicyRule<S>): this {\n const frozen = Object.freeze({ ...rule });\n this.compiled.push({\n rule: frozen,\n actionPatterns: compileActionPatterns(frozen.actions as string[] | \"*\"),\n });\n this.cache?.clear();\n return this;\n }\n\n addRules(...rules: PolicyRule<S>[]): this {\n for (const rule of rules) {\n const frozen = Object.freeze({ ...rule });\n this.compiled.push({\n rule: frozen,\n actionPatterns: compileActionPatterns(frozen.actions as string[] | \"*\"),\n });\n }\n this.cache?.clear();\n return this;\n }\n\n removeRule(ruleId: string): boolean {\n const idx = this.compiled.findIndex((c) => c.rule.id === ruleId);\n if (idx === -1) return false;\n this.compiled.splice(idx, 1);\n this.cache?.clear();\n return true;\n }\n\n getRules(): ReadonlyArray<PolicyRule<S>> {\n return this.compiled.map((c) => c.rule);\n }\n\n clearRules(): void {\n this.compiled = [];\n this.cache?.clear();\n }\n\n // -----------------------------------------------------------------------\n // Cache control\n // -----------------------------------------------------------------------\n\n clearCache(): void {\n this.cache?.clear();\n }\n\n get cacheStats(): { size: number; maxSize: number } | null {\n if (!this.cache) return null;\n return { size: this.cache.size, maxSize: this.cache.maxSize };\n }\n\n // -----------------------------------------------------------------------\n // Fluent rule builders bound to this engine's schema\n // -----------------------------------------------------------------------\n\n allow(): RuleBuilder<S> {\n return _allow<S>();\n }\n\n deny(): RuleBuilder<S> {\n return _deny<S>();\n }\n\n // -----------------------------------------------------------------------\n // Observability\n // -----------------------------------------------------------------------\n\n onDecision(listener: DecisionListener<S>): () => void {\n this.listeners.push(listener);\n return () => {\n const idx = this.listeners.indexOf(listener);\n if (idx !== -1) this.listeners.splice(idx, 1);\n };\n }\n\n private emit(decision: Decision<S>): void {\n for (const listener of this.listeners) {\n try {\n const result = listener(decision);\n if (result instanceof Promise) {\n result.catch(() => {});\n }\n } catch {\n // listeners must not break evaluation\n }\n }\n }\n\n // -----------------------------------------------------------------------\n // Evaluation\n // -----------------------------------------------------------------------\n\n evaluate(\n subject: Subject<S>,\n action: InferAction<S>,\n resource: InferResource<S>,\n resourceContext: ResourceContext = {},\n tenantId?: string,\n ): Decision<S> {\n if (this.asyncConditions) {\n throw new Error(\n \"Engine has asyncConditions enabled. Use evaluateAsync() instead.\",\n );\n }\n this.validateInput(subject, action, resource);\n this.enforceTenancy(subject, tenantId);\n\n const cacheKey = this.cache\n ? buildCacheKey(subject.id, action as string, resource as string, tenantId)\n : undefined;\n if (cacheKey) {\n const cached = this.cache!.get(cacheKey);\n if (cached) {\n this.emit(cached);\n return cached;\n }\n }\n\n const start = performance.now();\n const ctx = this.buildContext(subject, action, resource, resourceContext, tenantId);\n const candidates = this.matchRules(subject, action, resource, tenantId);\n\n let matched: CompiledRule<S> | null = null;\n let matchedHasConditions = false;\n\n for (const compiled of candidates) {\n const { rule } = compiled;\n if (!rule.conditions || rule.conditions.length === 0) {\n matched = compiled;\n matchedHasConditions = false;\n break;\n }\n const allMet = this.evaluateConditionsSync(rule, ctx);\n if (allMet) {\n matched = compiled;\n matchedHasConditions = true;\n break;\n }\n }\n\n const decision = this.buildDecision(matched?.rule ?? null, ctx, start);\n\n if (cacheKey && !matchedHasConditions) {\n this.cache!.set(cacheKey, decision);\n }\n\n this.emit(decision);\n return decision;\n }\n\n async evaluateAsync(\n subject: Subject<S>,\n action: InferAction<S>,\n resource: InferResource<S>,\n resourceContext: ResourceContext = {},\n tenantId?: string,\n ): Promise<Decision<S>> {\n this.validateInput(subject, action, resource);\n this.enforceTenancy(subject, tenantId);\n const start = performance.now();\n const ctx = this.buildContext(subject, action, resource, resourceContext, tenantId);\n const candidates = this.matchRules(subject, action, resource, tenantId);\n\n let matched: PolicyRule<S> | null = null;\n\n for (const compiled of candidates) {\n const { rule } = compiled;\n if (!rule.conditions || rule.conditions.length === 0) {\n matched = rule;\n break;\n }\n const results = await Promise.all(\n rule.conditions.map((c, i) =>\n Promise.resolve()\n .then(() => c(ctx))\n .catch((err) => {\n this.emitConditionError(rule.id, i, err);\n return false;\n }),\n ),\n );\n if (results.every(Boolean)) {\n matched = rule;\n break;\n }\n }\n\n const decision = this.buildDecision(matched, ctx, start);\n this.emit(decision);\n return decision;\n }\n\n // -----------------------------------------------------------------------\n // permitted() — list allowed actions on a resource for UI rendering\n // -----------------------------------------------------------------------\n\n permitted(\n subject: Subject<S>,\n resource: InferResource<S>,\n actions: InferAction<S>[],\n resourceContext: ResourceContext = {},\n tenantId?: string,\n ): Set<InferAction<S>> {\n const allowed = new Set<InferAction<S>>();\n for (const action of actions) {\n const decision = this.asyncConditions\n ? (() => { throw new Error(\"Use permittedAsync() with asyncConditions enabled.\"); })()\n : this.evaluate(subject, action, resource, resourceContext, tenantId);\n if (decision.allowed) {\n allowed.add(action);\n }\n }\n return allowed;\n }\n\n async permittedAsync(\n subject: Subject<S>,\n resource: InferResource<S>,\n actions: InferAction<S>[],\n resourceContext: ResourceContext = {},\n tenantId?: string,\n ): Promise<Set<InferAction<S>>> {\n const allowed = new Set<InferAction<S>>();\n const results = await Promise.all(\n actions.map((action) =>\n this.evaluateAsync(subject, action, resource, resourceContext, tenantId),\n ),\n );\n for (let i = 0; i < actions.length; i++) {\n if (results[i]!.allowed) {\n allowed.add(actions[i]!);\n }\n }\n return allowed;\n }\n\n // -----------------------------------------------------------------------\n // explain() — full evaluation trace for debugging\n // -----------------------------------------------------------------------\n\n explain(\n subject: Subject<S>,\n action: InferAction<S>,\n resource: InferResource<S>,\n resourceContext: ResourceContext = {},\n tenantId?: string,\n ): ExplainResult<S> {\n if (this.asyncConditions) {\n throw new Error(\n \"Engine has asyncConditions enabled. Use explainAsync() instead.\",\n );\n }\n this.enforceTenancy(subject, tenantId);\n const start = performance.now();\n const ctx = this.buildContext(subject, action, resource, resourceContext, tenantId);\n const subjectRoles = this.resolveRoles(subject, tenantId);\n\n const evaluatedRules: RuleEvaluation<S>[] = [];\n let firstMatch: PolicyRule<S> | null = null;\n\n const sorted = this.sortCandidates([...this.compiled]);\n\n for (const compiled of sorted) {\n const { rule } = compiled;\n const roleMatched = rule.roles === \"*\" || rule.roles.some((r) => subjectRoles.has(r));\n const actionMatched = rule.actions === \"*\" || this.matchesAction(compiled, action);\n const resourceMatched = rule.resources === \"*\" || rule.resources.includes(resource);\n\n const conditionResults: ConditionResult[] = [];\n let allConditionsPassed = true;\n\n if (roleMatched && actionMatched && resourceMatched && rule.conditions) {\n for (let i = 0; i < rule.conditions.length; i++) {\n try {\n const result = rule.conditions[i]!(ctx);\n if (isThenable(result)) {\n throw new Error(ASYNC_CONDITION_EXPLAIN_MSG);\n }\n if (result !== true) {\n conditionResults.push({ index: i, passed: false });\n allConditionsPassed = false;\n } else {\n conditionResults.push({ index: i, passed: true });\n }\n } catch (err) {\n if (\n err instanceof Error &&\n err.message === ASYNC_CONDITION_EXPLAIN_MSG\n ) {\n throw err;\n }\n conditionResults.push({\n index: i,\n passed: false,\n error: err instanceof Error ? err.message : String(err),\n });\n allConditionsPassed = false;\n }\n }\n }\n\n const matched =\n roleMatched && actionMatched && resourceMatched &&\n (!rule.conditions || rule.conditions.length === 0 || allConditionsPassed);\n\n evaluatedRules.push({\n rule,\n roleMatched,\n actionMatched,\n resourceMatched,\n conditionResults,\n matched,\n });\n\n if (matched && !firstMatch) {\n firstMatch = rule;\n }\n }\n\n const allowed = firstMatch != null ? firstMatch.effect === \"allow\" : !this._defaultDeny;\n const effect = firstMatch?.effect ?? \"default-deny\";\n const reason = firstMatch\n ? `Matched rule \"${firstMatch.id}\"${firstMatch.description ? `: ${firstMatch.description}` : \"\"}`\n : \"No matching rule — default deny\";\n\n return {\n allowed,\n effect,\n reason,\n evaluatedRules,\n durationMs: performance.now() - start,\n };\n }\n\n async explainAsync(\n subject: Subject<S>,\n action: InferAction<S>,\n resource: InferResource<S>,\n resourceContext: ResourceContext = {},\n tenantId?: string,\n ): Promise<ExplainResult<S>> {\n this.enforceTenancy(subject, tenantId);\n const start = performance.now();\n const ctx = this.buildContext(subject, action, resource, resourceContext, tenantId);\n const subjectRoles = this.resolveRoles(subject, tenantId);\n\n const evaluatedRules: RuleEvaluation<S>[] = [];\n let firstMatch: PolicyRule<S> | null = null;\n\n const sorted = this.sortCandidates([...this.compiled]);\n\n for (const compiled of sorted) {\n const { rule } = compiled;\n const roleMatched = rule.roles === \"*\" || rule.roles.some((r) => subjectRoles.has(r));\n const actionMatched = rule.actions === \"*\" || this.matchesAction(compiled, action);\n const resourceMatched = rule.resources === \"*\" || rule.resources.includes(resource);\n\n const conditionResults: ConditionResult[] = [];\n let allConditionsPassed = true;\n\n if (roleMatched && actionMatched && resourceMatched && rule.conditions) {\n for (let i = 0; i < rule.conditions.length; i++) {\n try {\n const result = await rule.conditions[i]!(ctx);\n if (result !== true) {\n conditionResults.push({ index: i, passed: false });\n allConditionsPassed = false;\n } else {\n conditionResults.push({ index: i, passed: true });\n }\n } catch (err) {\n conditionResults.push({\n index: i,\n passed: false,\n error: err instanceof Error ? err.message : String(err),\n });\n allConditionsPassed = false;\n }\n }\n }\n\n const matched =\n roleMatched && actionMatched && resourceMatched &&\n (!rule.conditions || rule.conditions.length === 0 || allConditionsPassed);\n\n evaluatedRules.push({\n rule,\n roleMatched,\n actionMatched,\n resourceMatched,\n conditionResults,\n matched,\n });\n\n if (matched && !firstMatch) {\n firstMatch = rule;\n }\n }\n\n const allowed = firstMatch != null ? firstMatch.effect === \"allow\" : !this._defaultDeny;\n const effect = firstMatch?.effect ?? \"default-deny\";\n const reason = firstMatch\n ? `Matched rule \"${firstMatch.id}\"${firstMatch.description ? `: ${firstMatch.description}` : \"\"}`\n : \"No matching rule — default deny\";\n\n return {\n allowed,\n effect,\n reason,\n evaluatedRules,\n durationMs: performance.now() - start,\n };\n }\n\n // -----------------------------------------------------------------------\n // Fluent check API: can(subject).perform(action).on(resource)\n // -----------------------------------------------------------------------\n\n can(subject: Subject<S>): PerformStep<S> {\n return new PerformStep(this, subject);\n }\n\n // -----------------------------------------------------------------------\n // Internal helpers\n // -----------------------------------------------------------------------\n\n private validateInput(\n subject: Subject<S>,\n action: InferAction<S>,\n resource: InferResource<S>,\n ): void {\n if (!subject || typeof subject.id !== \"string\") {\n throw new Error(\"subject must be an object with a string id\");\n }\n if (!Array.isArray(subject.roles)) {\n throw new Error(\"subject.roles must be an array\");\n }\n if (!action || typeof action !== \"string\") {\n throw new Error(\"action must be a non-empty string\");\n }\n if (!resource || typeof resource !== \"string\") {\n throw new Error(\"resource must be a non-empty string\");\n }\n }\n\n private enforceTenancy(subject: Subject<S>, tenantId?: string): void {\n if (!this._strictTenancy || tenantId != null) return;\n const hasTenantScoped = subject.roles.some((r) => r.tenantId != null);\n if (hasTenantScoped) {\n throw new Error(\n \"strictTenancy is enabled and subject has tenant-scoped roles, \" +\n \"but no tenantId was provided to evaluate(). This could cause \" +\n \"cross-tenant privilege escalation. Pass an explicit tenantId.\",\n );\n }\n }\n\n private buildContext(\n subject: Subject<S>,\n action: InferAction<S>,\n resource: InferResource<S>,\n resourceContext: ResourceContext,\n tenantId?: string,\n ): EvaluationContext<S> {\n return { subject, action, resource, resourceContext, tenantId };\n }\n\n private evaluateConditionsSync(\n rule: PolicyRule<S>,\n ctx: EvaluationContext<S>,\n ): boolean {\n if (!rule.conditions) return true;\n for (let i = 0; i < rule.conditions.length; i++) {\n try {\n const result = rule.conditions[i]!(ctx);\n if (isThenable(result)) {\n throw new Error(ASYNC_CONDITION_EVALUATE_MSG);\n }\n if (result !== true) return false;\n } catch (err) {\n if (\n err instanceof Error &&\n err.message === ASYNC_CONDITION_EVALUATE_MSG\n ) {\n throw err;\n }\n this.emitConditionError(rule.id, i, err);\n return false;\n }\n }\n return true;\n }\n\n private emitConditionError(ruleId: string, conditionIndex: number, error: unknown): void {\n if (this.conditionErrorHandler) {\n try {\n this.conditionErrorHandler({ ruleId, conditionIndex, error });\n } catch {\n // error handler must not break evaluation\n }\n }\n }\n\n private matchRules(\n subject: Subject<S>,\n action: InferAction<S>,\n resource: InferResource<S>,\n tenantId?: string,\n ): CompiledRule<S>[] {\n const subjectRoles = this.resolveRoles(subject, tenantId);\n\n const matched = this.compiled.filter((compiled) => {\n const { rule } = compiled;\n if (rule.roles !== \"*\" && !rule.roles.some((r) => subjectRoles.has(r))) return false;\n if (rule.actions !== \"*\" && !this.matchesAction(compiled, action)) return false;\n if (rule.resources !== \"*\" && !rule.resources.includes(resource)) return false;\n return true;\n });\n\n return this.sortCandidates(matched);\n }\n\n private sortCandidates(candidates: CompiledRule<S>[]): CompiledRule<S>[] {\n return candidates.sort((a, b) => {\n const pa = a.rule.priority ?? 0;\n const pb = b.rule.priority ?? 0;\n if (pb !== pa) return pb - pa;\n if (a.rule.effect === \"deny\" && b.rule.effect === \"allow\") return -1;\n if (a.rule.effect === \"allow\" && b.rule.effect === \"deny\") return 1;\n return 0;\n });\n }\n\n private matchesAction(\n compiled: CompiledRule<S>,\n action: InferAction<S>,\n ): boolean {\n const { rule, actionPatterns } = compiled;\n if (rule.actions === \"*\") return true;\n const actionStr = action as string;\n if ((rule.actions as string[]).includes(actionStr)) return true;\n if (actionPatterns) {\n for (const pattern of actionPatterns) {\n if (pattern.test(actionStr)) return true;\n }\n }\n return false;\n }\n\n private resolveRoles(\n subject: Subject<S>,\n tenantId?: string,\n ): Set<string> {\n const directRoles = new Set<string>();\n for (const assignment of subject.roles) {\n if (tenantId == null || assignment.tenantId == null || assignment.tenantId === tenantId) {\n directRoles.add(assignment.role);\n }\n }\n\n if (!this.hierarchy) return directRoles;\n\n const expanded = new Set<string>();\n for (const role of directRoles) {\n for (const r of this.hierarchy.resolve(role as InferRole<S>)) {\n expanded.add(r);\n }\n }\n return expanded;\n }\n\n private buildDecision(\n matched: PolicyRule<S> | null,\n ctx: EvaluationContext<S>,\n start: number,\n ): Decision<S> {\n const allowed =\n matched != null ? matched.effect === \"allow\" : !this._defaultDeny;\n const effect = matched?.effect ?? \"default-deny\";\n const reason = matched\n ? `Matched rule \"${matched.id}\"${matched.description ? `: ${matched.description}` : \"\"}`\n : \"No matching rule — default deny\";\n\n return {\n allowed,\n effect,\n matchedRule: matched,\n subject: ctx.subject,\n action: ctx.action,\n resource: ctx.resource,\n resourceContext: ctx.resourceContext,\n tenantId: ctx.tenantId,\n timestamp: Date.now(),\n durationMs: performance.now() - start,\n reason,\n };\n }\n}\n\n// ---------------------------------------------------------------------------\n// Fluent check chain: can(user).perform(action).on(resource)\n// ---------------------------------------------------------------------------\n\nclass PerformStep<S extends SchemaDefinition> {\n constructor(\n private engine: AccessEngine<S>,\n private subject: Subject<S>,\n ) {}\n\n perform(action: InferAction<S>): OnStep<S> {\n return new OnStep(this.engine, this.subject, action);\n }\n}\n\nclass OnStep<S extends SchemaDefinition> {\n constructor(\n private engine: AccessEngine<S>,\n private subject: Subject<S>,\n private action: InferAction<S>,\n ) {}\n\n on(\n resource: InferResource<S>,\n resourceContext: ResourceContext = {},\n tenantId?: string,\n ): Decision<S> {\n return this.engine.evaluate(\n this.subject,\n this.action,\n resource,\n resourceContext,\n tenantId,\n );\n }\n\n async onAsync(\n resource: InferResource<S>,\n resourceContext: ResourceContext = {},\n tenantId?: string,\n ): Promise<Decision<S>> {\n return this.engine.evaluateAsync(\n this.subject,\n this.action,\n resource,\n resourceContext,\n tenantId,\n );\n }\n}\n\n// ---------------------------------------------------------------------------\n// Simple LRU cache\n// ---------------------------------------------------------------------------\n\nfunction buildCacheKey(\n subjectId: string,\n action: string,\n resource: string,\n tenantId?: string,\n): string {\n return `${subjectId.length}:${subjectId}\\0${action}\\0${resource}\\0${tenantId ?? \"\"}`;\n}\n\nclass LRUCache<V> {\n private map = new Map<string, V>();\n readonly maxSize: number;\n\n constructor(maxSize: number) {\n this.maxSize = maxSize;\n }\n\n get size(): number {\n return this.map.size;\n }\n\n get(key: string): V | undefined {\n const value = this.map.get(key);\n if (value !== undefined) {\n this.map.delete(key);\n this.map.set(key, value);\n }\n return value;\n }\n\n set(key: string, value: V): void {\n if (this.map.has(key)) {\n this.map.delete(key);\n } else if (this.map.size >= this.maxSize) {\n const oldest = this.map.keys().next().value;\n if (oldest !== undefined) this.map.delete(oldest);\n }\n this.map.set(key, value);\n }\n\n clear(): void {\n this.map.clear();\n }\n}\n","import type { SchemaDefinition, InferRole } from \"./types.js\";\n\n/**\n * Defines a role inheritance hierarchy.\n *\n * When a role inherits from another, it gains all permissions of its parent roles.\n * Cycles are detected and rejected at definition time.\n *\n * ```ts\n * const hierarchy = new RoleHierarchy<MySchema>()\n * .define(\"admin\", [\"manager\", \"viewer\"])\n * .define(\"manager\", [\"member\"])\n * .define(\"member\", [\"viewer\"]);\n *\n * hierarchy.resolve(\"admin\");\n * // Set { \"admin\", \"manager\", \"member\", \"viewer\" }\n * ```\n */\nexport class RoleHierarchy<S extends SchemaDefinition> {\n private parents = new Map<string, string[]>();\n private cache = new Map<string, Set<string>>();\n\n /**\n * Define that `role` inherits permissions from `inheritsFrom` roles.\n * Clears the resolution cache.\n */\n define(role: InferRole<S>, inheritsFrom: InferRole<S>[]): this {\n this.parents.set(role, inheritsFrom as string[]);\n this.cache.clear();\n this.detectCycle(role as string, new Set());\n return this;\n }\n\n /**\n * Resolve the full set of roles a given role expands to,\n * including all inherited roles (transitive).\n */\n resolve(role: InferRole<S>): Set<string> {\n const roleStr = role as string;\n const cached = this.cache.get(roleStr);\n if (cached) return cached;\n\n const result = new Set<string>();\n this.walk(roleStr, result);\n this.cache.set(roleStr, result);\n return result;\n }\n\n /**\n * Resolve multiple roles at once, returning the merged set.\n */\n resolveAll(roles: Iterable<InferRole<S>>): Set<string> {\n const result = new Set<string>();\n for (const role of roles) {\n for (const r of this.resolve(role)) {\n result.add(r);\n }\n }\n return result;\n }\n\n /**\n * Get all defined roles that have inheritance rules.\n */\n definedRoles(): string[] {\n return [...this.parents.keys()];\n }\n\n private walk(role: string, visited: Set<string>): void {\n if (visited.has(role)) return;\n visited.add(role);\n const parents = this.parents.get(role);\n if (parents) {\n for (const parent of parents) {\n this.walk(parent, visited);\n }\n }\n }\n\n private detectCycle(role: string, visiting: Set<string>): void {\n if (visiting.has(role)) {\n throw new Error(\n `Cycle detected in role hierarchy: ${[...visiting, role].join(\" → \")}`,\n );\n }\n visiting.add(role);\n const parents = this.parents.get(role);\n if (parents) {\n for (const parent of parents) {\n this.detectCycle(parent, visiting);\n }\n }\n visiting.delete(role);\n }\n}\n","import type {\n SchemaDefinition,\n PolicyRule,\n PolicyEffect,\n Condition,\n} from \"./types.js\";\n\n// ---------------------------------------------------------------------------\n// JSON-safe policy representation (conditions become named references)\n// ---------------------------------------------------------------------------\n\nexport interface JsonPolicyRule {\n id: string;\n effect: PolicyEffect;\n roles: string[] | \"*\";\n actions: string[] | \"*\";\n resources: string[] | \"*\";\n conditions?: string[];\n priority?: number;\n description?: string;\n}\n\nexport interface JsonPolicyDocument {\n version: 1;\n rules: JsonPolicyRule[];\n}\n\n/**\n * A registry that maps condition names to condition functions.\n * This allows JSON policies to reference conditions by name\n * while keeping the actual logic in code.\n *\n * ```ts\n * const conditions = new ConditionRegistry<MySchema>();\n * conditions.register(\"isOwner\", ctx => ctx.subject.id === ctx.resourceContext.ownerId);\n * conditions.register(\"isActive\", ctx => ctx.resourceContext.status === \"active\");\n * ```\n */\nexport class ConditionRegistry<S extends SchemaDefinition> {\n private conditions = new Map<string, Condition<S>>();\n\n register(name: string, condition: Condition<S>): this {\n if (!name || typeof name !== \"string\") {\n throw new Error(\"Condition name must be a non-empty string\");\n }\n if (typeof condition !== \"function\") {\n throw new Error(`Condition \"${name}\" must be a function`);\n }\n this.conditions.set(name, condition);\n return this;\n }\n\n get(name: string): Condition<S> | undefined {\n return this.conditions.get(name);\n }\n\n has(name: string): boolean {\n return this.conditions.has(name);\n }\n\n names(): string[] {\n return [...this.conditions.keys()];\n }\n}\n\n// ---------------------------------------------------------------------------\n// Export: PolicyRule[] → JSON\n// ---------------------------------------------------------------------------\n\n/**\n * Serialize rules to a JSON-safe document.\n * Conditions are stripped unless a reverse lookup map is provided.\n */\nexport function exportRules<S extends SchemaDefinition>(\n rules: ReadonlyArray<PolicyRule<S>>,\n conditionNames?: Map<Condition<S>, string>,\n): JsonPolicyDocument {\n const jsonRules: JsonPolicyRule[] = rules.map((rule) => {\n const jr: JsonPolicyRule = {\n id: rule.id,\n effect: rule.effect,\n roles: rule.roles,\n actions: rule.actions as string[] | \"*\",\n resources: rule.resources as string[] | \"*\",\n priority: rule.priority,\n description: rule.description,\n };\n\n if (rule.conditions && conditionNames) {\n const names: string[] = [];\n for (const cond of rule.conditions) {\n const name = conditionNames.get(cond);\n if (name) names.push(name);\n }\n if (names.length > 0) jr.conditions = names;\n }\n\n return jr;\n });\n\n return { version: 1, rules: jsonRules };\n}\n\n/**\n * Serialize rules to a JSON string.\n */\nexport function exportRulesToJson<S extends SchemaDefinition>(\n rules: ReadonlyArray<PolicyRule<S>>,\n conditionNames?: Map<Condition<S>, string>,\n): string {\n return JSON.stringify(exportRules(rules, conditionNames), null, 2);\n}\n\n// ---------------------------------------------------------------------------\n// Import: JSON → PolicyRule[]\n// ---------------------------------------------------------------------------\n\n/**\n * Deserialize a JSON policy document into PolicyRule objects.\n * Condition names are resolved via the provided registry.\n */\nexport function importRules<S extends SchemaDefinition>(\n doc: JsonPolicyDocument,\n registry?: ConditionRegistry<S>,\n): PolicyRule<S>[] {\n if (!doc || typeof doc !== \"object\") {\n throw new Error(\"Policy document must be a non-null object\");\n }\n if (doc.version !== 1) {\n throw new Error(`Unsupported policy document version: ${doc.version}`);\n }\n if (!Array.isArray(doc.rules)) {\n throw new Error(\"Policy document must have a 'rules' array\");\n }\n\n return doc.rules.map((jr, index) => {\n if (!jr || typeof jr !== \"object\") {\n throw new Error(`Rule at index ${index} must be a non-null object`);\n }\n\n if (!jr.id || typeof jr.id !== \"string\") {\n throw new Error(`Rule at index ${index} is missing a valid \"id\" field.`);\n }\n\n if (jr.effect !== \"allow\" && jr.effect !== \"deny\") {\n throw new Error(\n `Invalid effect \"${jr.effect}\" in rule \"${jr.id}\". Must be \"allow\" or \"deny\".`,\n );\n }\n\n if (jr.roles !== \"*\" && !Array.isArray(jr.roles)) {\n throw new Error(`Rule \"${jr.id}\": roles must be \"*\" or an array of strings`);\n }\n if (jr.actions !== \"*\" && !Array.isArray(jr.actions)) {\n throw new Error(`Rule \"${jr.id}\": actions must be \"*\" or an array of strings`);\n }\n if (jr.resources !== \"*\" && !Array.isArray(jr.resources)) {\n throw new Error(`Rule \"${jr.id}\": resources must be \"*\" or an array of strings`);\n }\n\n const conditions: Condition<S>[] = [];\n if (jr.conditions && registry) {\n for (const name of jr.conditions) {\n const cond = registry.get(name);\n if (!cond) {\n throw new Error(\n `Unknown condition \"${name}\" in rule \"${jr.id}\". ` +\n `Registered conditions: ${registry.names().join(\", \") || \"(none)\"}`,\n );\n }\n conditions.push(cond);\n }\n }\n\n return {\n id: jr.id,\n effect: jr.effect,\n roles: jr.roles,\n actions: jr.actions,\n resources: jr.resources,\n conditions: conditions.length > 0 ? conditions : undefined,\n priority: jr.priority,\n description: jr.description,\n } as PolicyRule<S>;\n });\n}\n\n/**\n * Parse a JSON string into PolicyRule objects.\n */\nexport function importRulesFromJson<S extends SchemaDefinition>(\n json: string,\n registry?: ConditionRegistry<S>,\n): PolicyRule<S>[] {\n let doc: JsonPolicyDocument;\n try {\n doc = JSON.parse(json) as JsonPolicyDocument;\n } catch (err) {\n throw new Error(\n `Failed to parse policy JSON: ${err instanceof Error ? err.message : String(err)}`,\n );\n }\n return importRules(doc, registry);\n}\n","/**\n * Core type definitions for the authorization engine.\n *\n * The type system is designed so that defining a schema once\n * propagates full autocomplete through every API surface:\n * policies, checks, audits, and middleware.\n */\n\n// ---------------------------------------------------------------------------\n// Schema definition types — what users provide to configure the engine\n// ---------------------------------------------------------------------------\n\nexport type ActionString = `${string}:${string}`;\n\nexport interface SchemaDefinition {\n roles: string;\n resources: string;\n actions: ActionString;\n tenantId?: string;\n}\n\n/**\n * Infer concrete union types from a schema definition.\n * Used internally to thread type narrowing everywhere.\n */\nexport type InferRole<S extends SchemaDefinition> = S[\"roles\"];\nexport type InferResource<S extends SchemaDefinition> = S[\"resources\"];\nexport type InferAction<S extends SchemaDefinition> = S[\"actions\"];\nexport type InferTenantId<S extends SchemaDefinition> = S[\"tenantId\"] extends string\n ? S[\"tenantId\"]\n : string;\n\n// ---------------------------------------------------------------------------\n// User / Subject\n// ---------------------------------------------------------------------------\n\nexport interface RoleAssignment<S extends SchemaDefinition> {\n role: InferRole<S>;\n tenantId?: InferTenantId<S>;\n}\n\nexport interface Subject<S extends SchemaDefinition> {\n id: string;\n roles: RoleAssignment<S>[];\n attributes?: Record<string, unknown>;\n}\n\n// ---------------------------------------------------------------------------\n// Resource context passed during evaluation\n// ---------------------------------------------------------------------------\n\nexport interface ResourceContext {\n id?: string;\n tenantId?: string;\n [key: string]: unknown;\n}\n\n// ---------------------------------------------------------------------------\n// Evaluation context — what conditions receive\n// ---------------------------------------------------------------------------\n\nexport interface EvaluationContext<S extends SchemaDefinition> {\n subject: Subject<S>;\n action: InferAction<S>;\n resource: InferResource<S>;\n resourceContext: ResourceContext;\n tenantId?: string;\n environment?: Record<string, unknown>;\n}\n\n// ---------------------------------------------------------------------------\n// Condition — a predicate attached to a policy rule\n// ---------------------------------------------------------------------------\n\nexport type Condition<S extends SchemaDefinition> = (\n ctx: EvaluationContext<S>,\n) => boolean | Promise<boolean>;\n\n// ---------------------------------------------------------------------------\n// Policy rule — the atomic unit of authorization\n// ---------------------------------------------------------------------------\n\nexport type PolicyEffect = \"allow\" | \"deny\";\n\nexport interface PolicyRule<S extends SchemaDefinition> {\n readonly id: string;\n readonly effect: PolicyEffect;\n readonly roles: InferRole<S>[] | \"*\";\n readonly actions: InferAction<S>[] | \"*\";\n readonly resources: InferResource<S>[] | \"*\";\n readonly conditions?: Condition<S>[];\n /**\n * Higher priority wins. Deny at equal priority wins over allow.\n * Default: 0\n */\n readonly priority?: number;\n readonly description?: string;\n}\n\n// ---------------------------------------------------------------------------\n// Decision — the result of evaluating a request\n// ---------------------------------------------------------------------------\n\nexport interface Decision<S extends SchemaDefinition> {\n allowed: boolean;\n effect: PolicyEffect | \"default-deny\";\n matchedRule: PolicyRule<S> | null;\n subject: Subject<S>;\n action: InferAction<S>;\n resource: InferResource<S>;\n resourceContext: ResourceContext;\n tenantId?: string;\n timestamp: number;\n durationMs: number;\n reason: string;\n}\n\n// ---------------------------------------------------------------------------\n// Audit entry — serialization-safe version of Decision\n// ---------------------------------------------------------------------------\n\nexport interface AuditEntry {\n allowed: boolean;\n effect: string;\n matchedRuleId: string | null;\n matchedRuleDescription: string | null;\n subjectId: string;\n action: string;\n resource: string;\n tenantId?: string;\n timestamp: number;\n durationMs: number;\n reason: string;\n}\n\n/**\n * Convert a Decision to a serialization-safe AuditEntry\n * (strips functions, large objects, and condition references).\n */\nexport function toAuditEntry<S extends SchemaDefinition>(decision: Decision<S>): AuditEntry {\n return {\n allowed: decision.allowed,\n effect: decision.effect,\n matchedRuleId: decision.matchedRule?.id ?? null,\n matchedRuleDescription: decision.matchedRule?.description ?? null,\n subjectId: decision.subject.id,\n action: decision.action as string,\n resource: decision.resource as string,\n tenantId: decision.tenantId,\n timestamp: decision.timestamp,\n durationMs: decision.durationMs,\n reason: decision.reason,\n };\n}\n\n// ---------------------------------------------------------------------------\n// Explain result — detailed evaluation trace for debugging\n// ---------------------------------------------------------------------------\n\nexport interface ConditionResult {\n index: number;\n passed: boolean;\n error?: string;\n}\n\nexport interface RuleEvaluation<S extends SchemaDefinition> {\n rule: PolicyRule<S>;\n roleMatched: boolean;\n actionMatched: boolean;\n resourceMatched: boolean;\n conditionResults: ConditionResult[];\n matched: boolean;\n}\n\nexport interface ExplainResult<S extends SchemaDefinition> {\n allowed: boolean;\n effect: PolicyEffect | \"default-deny\";\n reason: string;\n evaluatedRules: RuleEvaluation<S>[];\n durationMs: number;\n}\n\n// ---------------------------------------------------------------------------\n// Audit / Observability\n// ---------------------------------------------------------------------------\n\nexport type DecisionListener<S extends SchemaDefinition> = (\n decision: Decision<S>,\n) => void | Promise<void>;\n\nexport interface ConditionError {\n ruleId: string;\n conditionIndex: number;\n error: unknown;\n}\n\nexport type ConditionErrorHandler = (err: ConditionError) => void;\n\n// ---------------------------------------------------------------------------\n// Engine options\n// ---------------------------------------------------------------------------\n\nexport interface EngineOptions<S extends SchemaDefinition> {\n schema: S;\n defaultEffect?: PolicyEffect;\n onDecision?: DecisionListener<S>;\n onConditionError?: ConditionErrorHandler;\n /**\n * When true, sync methods (evaluate, explain, permitted) throw immediately\n * to force use of evaluateAsync, explainAsync, permittedAsync.\n * When false (default), async conditions are detected at runtime and throw\n * with a clear error pointing to the async API.\n *\n * @deprecated This option is deprecated and will be removed in v2. Async\n * conditions are now detected automatically. Use evaluateAsync(),\n * explainAsync(), or permittedAsync() when you have async conditions.\n */\n asyncConditions?: boolean;\n /**\n * When true, evaluate() throws if tenantId is omitted and the subject\n * has any tenant-scoped role assignments. Prevents accidental\n * cross-tenant privilege escalation.\n */\n strictTenancy?: boolean;\n}\n"],"mappings":"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","names":[]}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { S as SchemaDefinition, r as Subject, R as ResourceContext, A as AccessEngine, I as InferAction, k as InferResource } from '../engine-
|
|
1
|
+
import { S as SchemaDefinition, r as Subject, R as ResourceContext, A as AccessEngine, I as InferAction, k as InferResource } from '../engine-Ccws3LFj.cjs';
|
|
2
2
|
|
|
3
3
|
/**
|
|
4
4
|
* Minimal Express-compatible types so we don't depend on @types/express at runtime.
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { S as SchemaDefinition, r as Subject, R as ResourceContext, A as AccessEngine, I as InferAction, k as InferResource } from '../engine-
|
|
1
|
+
import { S as SchemaDefinition, r as Subject, R as ResourceContext, A as AccessEngine, I as InferAction, k as InferResource } from '../engine-Ccws3LFj.js';
|
|
2
2
|
|
|
3
3
|
/**
|
|
4
4
|
* Minimal Express-compatible types so we don't depend on @types/express at runtime.
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { S as SchemaDefinition, r as Subject, R as ResourceContext, A as AccessEngine, I as InferAction, k as InferResource } from '../engine-
|
|
1
|
+
import { S as SchemaDefinition, r as Subject, R as ResourceContext, A as AccessEngine, I as InferAction, k as InferResource } from '../engine-Ccws3LFj.cjs';
|
|
2
2
|
|
|
3
3
|
/**
|
|
4
4
|
* Minimal Fastify-compatible types so we don't depend on fastify at runtime.
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { S as SchemaDefinition, r as Subject, R as ResourceContext, A as AccessEngine, I as InferAction, k as InferResource } from '../engine-
|
|
1
|
+
import { S as SchemaDefinition, r as Subject, R as ResourceContext, A as AccessEngine, I as InferAction, k as InferResource } from '../engine-Ccws3LFj.js';
|
|
2
2
|
|
|
3
3
|
/**
|
|
4
4
|
* Minimal Fastify-compatible types so we don't depend on fastify at runtime.
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __defProp = Object.defineProperty;
|
|
3
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
4
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
5
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
6
|
+
var __export = (target, all) => {
|
|
7
|
+
for (var name in all)
|
|
8
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
9
|
+
};
|
|
10
|
+
var __copyProps = (to, from, except, desc) => {
|
|
11
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
12
|
+
for (let key of __getOwnPropNames(from))
|
|
13
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
14
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
15
|
+
}
|
|
16
|
+
return to;
|
|
17
|
+
};
|
|
18
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
19
|
+
|
|
20
|
+
// src/middleware/hono.ts
|
|
21
|
+
var hono_exports = {};
|
|
22
|
+
__export(hono_exports, {
|
|
23
|
+
honoGuard: () => honoGuard
|
|
24
|
+
});
|
|
25
|
+
module.exports = __toCommonJS(hono_exports);
|
|
26
|
+
function honoGuard(engine, action, resource, options) {
|
|
27
|
+
return async (c, next) => {
|
|
28
|
+
const subject = options.getSubject(c);
|
|
29
|
+
if (!subject) {
|
|
30
|
+
return c.json({ error: "Unauthorized \u2014 no subject" }, 401);
|
|
31
|
+
}
|
|
32
|
+
let decision;
|
|
33
|
+
try {
|
|
34
|
+
const resourceContext = options.getResourceContext?.(c) ?? {};
|
|
35
|
+
const tenantId = options.getTenantId?.(c);
|
|
36
|
+
decision = engine.evaluate(subject, action, resource, resourceContext, tenantId);
|
|
37
|
+
} catch {
|
|
38
|
+
return c.json({ error: "Internal authorization error" }, 500);
|
|
39
|
+
}
|
|
40
|
+
if (decision.allowed) {
|
|
41
|
+
await next();
|
|
42
|
+
return;
|
|
43
|
+
}
|
|
44
|
+
if (options.onDenied) {
|
|
45
|
+
return options.onDenied(c);
|
|
46
|
+
}
|
|
47
|
+
return c.json({
|
|
48
|
+
error: "Forbidden",
|
|
49
|
+
reason: decision.reason
|
|
50
|
+
}, 403);
|
|
51
|
+
};
|
|
52
|
+
}
|
|
53
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
54
|
+
0 && (module.exports = {
|
|
55
|
+
honoGuard
|
|
56
|
+
});
|
|
57
|
+
//# sourceMappingURL=hono.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../src/middleware/hono.ts"],"sourcesContent":["import type { AccessEngine } from \"../engine.js\";\nimport type { SchemaDefinition, InferAction, InferResource, Subject, ResourceContext } from \"../types.js\";\n\n/**\n * Minimal Hono-compatible types so we don't depend on hono at runtime.\n */\ninterface HonoContext {\n req: {\n raw: Request;\n header(name: string): string | undefined;\n param(name: string): string | undefined;\n [key: string]: unknown;\n };\n json(data: unknown, status?: number): Response;\n get<T = unknown>(key: string): T;\n set(key: string, value: unknown): void;\n [key: string]: unknown;\n}\n\ntype HonoNext = () => Promise<void>;\ntype HonoMiddleware = (c: HonoContext, next: HonoNext) => Promise<Response | void>;\n\nexport interface HonoGuardOptions<S extends SchemaDefinition> {\n /** Extract the subject from the Hono context. */\n getSubject: (c: HonoContext) => Subject<S> | undefined;\n /** Extract the resource context from the Hono context (optional). */\n getResourceContext?: (c: HonoContext) => ResourceContext;\n /** Extract the tenant ID from the Hono context (optional). */\n getTenantId?: (c: HonoContext) => string | undefined;\n /** Custom denial handler. Return a Response to override the default 403. */\n onDenied?: (c: HonoContext) => Response;\n}\n\n/**\n * Hono middleware factory.\n *\n * Usage:\n * app.post(\n * \"/invoices/:id/approve\",\n * honoGuard(engine, \"invoice:approve\", \"invoice\", {\n * getSubject: (c) => c.get(\"user\"),\n * getTenantId: (c) => c.req.header(\"x-tenant-id\"),\n * }),\n * handler,\n * );\n */\nexport function honoGuard<S extends SchemaDefinition>(\n engine: AccessEngine<S>,\n action: InferAction<S>,\n resource: InferResource<S>,\n options: HonoGuardOptions<S>,\n): HonoMiddleware {\n return async (c: HonoContext, next: HonoNext) => {\n const subject = options.getSubject(c);\n if (!subject) {\n return c.json({ error: \"Unauthorized — no subject\" }, 401);\n }\n\n let decision;\n try {\n const resourceContext = options.getResourceContext?.(c) ?? {};\n const tenantId = options.getTenantId?.(c);\n decision = engine.evaluate(subject, action, resource, resourceContext, tenantId);\n } catch {\n return c.json({ error: \"Internal authorization error\" }, 500);\n }\n\n if (decision.allowed) {\n await next();\n return;\n }\n\n if (options.onDenied) {\n return options.onDenied(c);\n }\n\n return c.json({\n error: \"Forbidden\",\n reason: decision.reason,\n }, 403);\n };\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AA8CO,SAAS,UACd,QACA,QACA,UACA,SACgB;AAChB,SAAO,OAAO,GAAgB,SAAmB;AAC/C,UAAM,UAAU,QAAQ,WAAW,CAAC;AACpC,QAAI,CAAC,SAAS;AACZ,aAAO,EAAE,KAAK,EAAE,OAAO,iCAA4B,GAAG,GAAG;AAAA,IAC3D;AAEA,QAAI;AACJ,QAAI;AACF,YAAM,kBAAkB,QAAQ,qBAAqB,CAAC,KAAK,CAAC;AAC5D,YAAM,WAAW,QAAQ,cAAc,CAAC;AACxC,iBAAW,OAAO,SAAS,SAAS,QAAQ,UAAU,iBAAiB,QAAQ;AAAA,IACjF,QAAQ;AACN,aAAO,EAAE,KAAK,EAAE,OAAO,+BAA+B,GAAG,GAAG;AAAA,IAC9D;AAEA,QAAI,SAAS,SAAS;AACpB,YAAM,KAAK;AACX;AAAA,IACF;AAEA,QAAI,QAAQ,UAAU;AACpB,aAAO,QAAQ,SAAS,CAAC;AAAA,IAC3B;AAEA,WAAO,EAAE,KAAK;AAAA,MACZ,OAAO;AAAA,MACP,QAAQ,SAAS;AAAA,IACnB,GAAG,GAAG;AAAA,EACR;AACF;","names":[]}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import { S as SchemaDefinition, r as Subject, R as ResourceContext, A as AccessEngine, I as InferAction, k as InferResource } from '../engine-Ccws3LFj.cjs';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Minimal Hono-compatible types so we don't depend on hono at runtime.
|
|
5
|
+
*/
|
|
6
|
+
interface HonoContext {
|
|
7
|
+
req: {
|
|
8
|
+
raw: Request;
|
|
9
|
+
header(name: string): string | undefined;
|
|
10
|
+
param(name: string): string | undefined;
|
|
11
|
+
[key: string]: unknown;
|
|
12
|
+
};
|
|
13
|
+
json(data: unknown, status?: number): Response;
|
|
14
|
+
get<T = unknown>(key: string): T;
|
|
15
|
+
set(key: string, value: unknown): void;
|
|
16
|
+
[key: string]: unknown;
|
|
17
|
+
}
|
|
18
|
+
type HonoNext = () => Promise<void>;
|
|
19
|
+
type HonoMiddleware = (c: HonoContext, next: HonoNext) => Promise<Response | void>;
|
|
20
|
+
interface HonoGuardOptions<S extends SchemaDefinition> {
|
|
21
|
+
/** Extract the subject from the Hono context. */
|
|
22
|
+
getSubject: (c: HonoContext) => Subject<S> | undefined;
|
|
23
|
+
/** Extract the resource context from the Hono context (optional). */
|
|
24
|
+
getResourceContext?: (c: HonoContext) => ResourceContext;
|
|
25
|
+
/** Extract the tenant ID from the Hono context (optional). */
|
|
26
|
+
getTenantId?: (c: HonoContext) => string | undefined;
|
|
27
|
+
/** Custom denial handler. Return a Response to override the default 403. */
|
|
28
|
+
onDenied?: (c: HonoContext) => Response;
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* Hono middleware factory.
|
|
32
|
+
*
|
|
33
|
+
* Usage:
|
|
34
|
+
* app.post(
|
|
35
|
+
* "/invoices/:id/approve",
|
|
36
|
+
* honoGuard(engine, "invoice:approve", "invoice", {
|
|
37
|
+
* getSubject: (c) => c.get("user"),
|
|
38
|
+
* getTenantId: (c) => c.req.header("x-tenant-id"),
|
|
39
|
+
* }),
|
|
40
|
+
* handler,
|
|
41
|
+
* );
|
|
42
|
+
*/
|
|
43
|
+
declare function honoGuard<S extends SchemaDefinition>(engine: AccessEngine<S>, action: InferAction<S>, resource: InferResource<S>, options: HonoGuardOptions<S>): HonoMiddleware;
|
|
44
|
+
|
|
45
|
+
export { type HonoGuardOptions, honoGuard };
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import { S as SchemaDefinition, r as Subject, R as ResourceContext, A as AccessEngine, I as InferAction, k as InferResource } from '../engine-Ccws3LFj.js';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Minimal Hono-compatible types so we don't depend on hono at runtime.
|
|
5
|
+
*/
|
|
6
|
+
interface HonoContext {
|
|
7
|
+
req: {
|
|
8
|
+
raw: Request;
|
|
9
|
+
header(name: string): string | undefined;
|
|
10
|
+
param(name: string): string | undefined;
|
|
11
|
+
[key: string]: unknown;
|
|
12
|
+
};
|
|
13
|
+
json(data: unknown, status?: number): Response;
|
|
14
|
+
get<T = unknown>(key: string): T;
|
|
15
|
+
set(key: string, value: unknown): void;
|
|
16
|
+
[key: string]: unknown;
|
|
17
|
+
}
|
|
18
|
+
type HonoNext = () => Promise<void>;
|
|
19
|
+
type HonoMiddleware = (c: HonoContext, next: HonoNext) => Promise<Response | void>;
|
|
20
|
+
interface HonoGuardOptions<S extends SchemaDefinition> {
|
|
21
|
+
/** Extract the subject from the Hono context. */
|
|
22
|
+
getSubject: (c: HonoContext) => Subject<S> | undefined;
|
|
23
|
+
/** Extract the resource context from the Hono context (optional). */
|
|
24
|
+
getResourceContext?: (c: HonoContext) => ResourceContext;
|
|
25
|
+
/** Extract the tenant ID from the Hono context (optional). */
|
|
26
|
+
getTenantId?: (c: HonoContext) => string | undefined;
|
|
27
|
+
/** Custom denial handler. Return a Response to override the default 403. */
|
|
28
|
+
onDenied?: (c: HonoContext) => Response;
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* Hono middleware factory.
|
|
32
|
+
*
|
|
33
|
+
* Usage:
|
|
34
|
+
* app.post(
|
|
35
|
+
* "/invoices/:id/approve",
|
|
36
|
+
* honoGuard(engine, "invoice:approve", "invoice", {
|
|
37
|
+
* getSubject: (c) => c.get("user"),
|
|
38
|
+
* getTenantId: (c) => c.req.header("x-tenant-id"),
|
|
39
|
+
* }),
|
|
40
|
+
* handler,
|
|
41
|
+
* );
|
|
42
|
+
*/
|
|
43
|
+
declare function honoGuard<S extends SchemaDefinition>(engine: AccessEngine<S>, action: InferAction<S>, resource: InferResource<S>, options: HonoGuardOptions<S>): HonoMiddleware;
|
|
44
|
+
|
|
45
|
+
export { type HonoGuardOptions, honoGuard };
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
// src/middleware/hono.ts
|
|
2
|
+
function honoGuard(engine, action, resource, options) {
|
|
3
|
+
return async (c, next) => {
|
|
4
|
+
const subject = options.getSubject(c);
|
|
5
|
+
if (!subject) {
|
|
6
|
+
return c.json({ error: "Unauthorized \u2014 no subject" }, 401);
|
|
7
|
+
}
|
|
8
|
+
let decision;
|
|
9
|
+
try {
|
|
10
|
+
const resourceContext = options.getResourceContext?.(c) ?? {};
|
|
11
|
+
const tenantId = options.getTenantId?.(c);
|
|
12
|
+
decision = engine.evaluate(subject, action, resource, resourceContext, tenantId);
|
|
13
|
+
} catch {
|
|
14
|
+
return c.json({ error: "Internal authorization error" }, 500);
|
|
15
|
+
}
|
|
16
|
+
if (decision.allowed) {
|
|
17
|
+
await next();
|
|
18
|
+
return;
|
|
19
|
+
}
|
|
20
|
+
if (options.onDenied) {
|
|
21
|
+
return options.onDenied(c);
|
|
22
|
+
}
|
|
23
|
+
return c.json({
|
|
24
|
+
error: "Forbidden",
|
|
25
|
+
reason: decision.reason
|
|
26
|
+
}, 403);
|
|
27
|
+
};
|
|
28
|
+
}
|
|
29
|
+
export {
|
|
30
|
+
honoGuard
|
|
31
|
+
};
|
|
32
|
+
//# sourceMappingURL=hono.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../src/middleware/hono.ts"],"sourcesContent":["import type { AccessEngine } from \"../engine.js\";\nimport type { SchemaDefinition, InferAction, InferResource, Subject, ResourceContext } from \"../types.js\";\n\n/**\n * Minimal Hono-compatible types so we don't depend on hono at runtime.\n */\ninterface HonoContext {\n req: {\n raw: Request;\n header(name: string): string | undefined;\n param(name: string): string | undefined;\n [key: string]: unknown;\n };\n json(data: unknown, status?: number): Response;\n get<T = unknown>(key: string): T;\n set(key: string, value: unknown): void;\n [key: string]: unknown;\n}\n\ntype HonoNext = () => Promise<void>;\ntype HonoMiddleware = (c: HonoContext, next: HonoNext) => Promise<Response | void>;\n\nexport interface HonoGuardOptions<S extends SchemaDefinition> {\n /** Extract the subject from the Hono context. */\n getSubject: (c: HonoContext) => Subject<S> | undefined;\n /** Extract the resource context from the Hono context (optional). */\n getResourceContext?: (c: HonoContext) => ResourceContext;\n /** Extract the tenant ID from the Hono context (optional). */\n getTenantId?: (c: HonoContext) => string | undefined;\n /** Custom denial handler. Return a Response to override the default 403. */\n onDenied?: (c: HonoContext) => Response;\n}\n\n/**\n * Hono middleware factory.\n *\n * Usage:\n * app.post(\n * \"/invoices/:id/approve\",\n * honoGuard(engine, \"invoice:approve\", \"invoice\", {\n * getSubject: (c) => c.get(\"user\"),\n * getTenantId: (c) => c.req.header(\"x-tenant-id\"),\n * }),\n * handler,\n * );\n */\nexport function honoGuard<S extends SchemaDefinition>(\n engine: AccessEngine<S>,\n action: InferAction<S>,\n resource: InferResource<S>,\n options: HonoGuardOptions<S>,\n): HonoMiddleware {\n return async (c: HonoContext, next: HonoNext) => {\n const subject = options.getSubject(c);\n if (!subject) {\n return c.json({ error: \"Unauthorized — no subject\" }, 401);\n }\n\n let decision;\n try {\n const resourceContext = options.getResourceContext?.(c) ?? {};\n const tenantId = options.getTenantId?.(c);\n decision = engine.evaluate(subject, action, resource, resourceContext, tenantId);\n } catch {\n return c.json({ error: \"Internal authorization error\" }, 500);\n }\n\n if (decision.allowed) {\n await next();\n return;\n }\n\n if (options.onDenied) {\n return options.onDenied(c);\n }\n\n return c.json({\n error: \"Forbidden\",\n reason: decision.reason,\n }, 403);\n };\n}\n"],"mappings":";AA8CO,SAAS,UACd,QACA,QACA,UACA,SACgB;AAChB,SAAO,OAAO,GAAgB,SAAmB;AAC/C,UAAM,UAAU,QAAQ,WAAW,CAAC;AACpC,QAAI,CAAC,SAAS;AACZ,aAAO,EAAE,KAAK,EAAE,OAAO,iCAA4B,GAAG,GAAG;AAAA,IAC3D;AAEA,QAAI;AACJ,QAAI;AACF,YAAM,kBAAkB,QAAQ,qBAAqB,CAAC,KAAK,CAAC;AAC5D,YAAM,WAAW,QAAQ,cAAc,CAAC;AACxC,iBAAW,OAAO,SAAS,SAAS,QAAQ,UAAU,iBAAiB,QAAQ;AAAA,IACjF,QAAQ;AACN,aAAO,EAAE,KAAK,EAAE,OAAO,+BAA+B,GAAG,GAAG;AAAA,IAC9D;AAEA,QAAI,SAAS,SAAS;AACpB,YAAM,KAAK;AACX;AAAA,IACF;AAEA,QAAI,QAAQ,UAAU;AACpB,aAAO,QAAQ,SAAS,CAAC;AAAA,IAC3B;AAEA,WAAO,EAAE,KAAK;AAAA,MACZ,OAAO;AAAA,MACP,QAAQ,SAAS;AAAA,IACnB,GAAG,GAAG;AAAA,EACR;AACF;","names":[]}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { S as SchemaDefinition, A as AccessEngine, r as Subject, R as ResourceContext, I as InferAction, k as InferResource } from '../engine-
|
|
1
|
+
import { S as SchemaDefinition, A as AccessEngine, r as Subject, R as ResourceContext, I as InferAction, k as InferResource } from '../engine-Ccws3LFj.cjs';
|
|
2
2
|
|
|
3
3
|
/**
|
|
4
4
|
* NestJS-compatible guard and decorator factory.
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { S as SchemaDefinition, A as AccessEngine, r as Subject, R as ResourceContext, I as InferAction, k as InferResource } from '../engine-
|
|
1
|
+
import { S as SchemaDefinition, A as AccessEngine, r as Subject, R as ResourceContext, I as InferAction, k as InferResource } from '../engine-Ccws3LFj.js';
|
|
2
2
|
|
|
3
3
|
/**
|
|
4
4
|
* NestJS-compatible guard and decorator factory.
|
package/dist/server.d.cts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import * as http from 'http';
|
|
2
2
|
import { IncomingMessage, ServerResponse } from 'node:http';
|
|
3
|
-
import { R as ResourceContext, S as SchemaDefinition, A as AccessEngine, r as Subject } from './engine-
|
|
3
|
+
import { R as ResourceContext, S as SchemaDefinition, A as AccessEngine, r as Subject } from './engine-Ccws3LFj.cjs';
|
|
4
4
|
|
|
5
5
|
interface ServerOptions<S extends SchemaDefinition> {
|
|
6
6
|
engine: AccessEngine<S>;
|
package/dist/server.d.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import * as http from 'http';
|
|
2
2
|
import { IncomingMessage, ServerResponse } from 'node:http';
|
|
3
|
-
import { R as ResourceContext, S as SchemaDefinition, A as AccessEngine, r as Subject } from './engine-
|
|
3
|
+
import { R as ResourceContext, S as SchemaDefinition, A as AccessEngine, r as Subject } from './engine-Ccws3LFj.js';
|
|
4
4
|
|
|
5
5
|
interface ServerOptions<S extends SchemaDefinition> {
|
|
6
6
|
engine: AccessEngine<S>;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@siremzam/sentinel",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.4.0",
|
|
4
4
|
"description": "TypeScript-first, domain-driven authorization engine for modern SaaS apps",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.js",
|
|
@@ -30,6 +30,12 @@
|
|
|
30
30
|
"require": "./dist/middleware/nestjs.cjs",
|
|
31
31
|
"default": "./dist/middleware/nestjs.js"
|
|
32
32
|
},
|
|
33
|
+
"./middleware/hono": {
|
|
34
|
+
"types": "./dist/middleware/hono.d.ts",
|
|
35
|
+
"import": "./dist/middleware/hono.js",
|
|
36
|
+
"require": "./dist/middleware/hono.cjs",
|
|
37
|
+
"default": "./dist/middleware/hono.js"
|
|
38
|
+
},
|
|
33
39
|
"./server": {
|
|
34
40
|
"types": "./dist/server.d.ts",
|
|
35
41
|
"import": "./dist/server.js",
|