@siremzam/sentinel 0.3.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.
Files changed (43) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +662 -0
  3. package/dist/engine.d.ts +70 -0
  4. package/dist/engine.d.ts.map +1 -0
  5. package/dist/engine.js +562 -0
  6. package/dist/engine.js.map +1 -0
  7. package/dist/index.d.ts +11 -0
  8. package/dist/index.d.ts.map +1 -0
  9. package/dist/index.js +7 -0
  10. package/dist/index.js.map +1 -0
  11. package/dist/middleware/express.d.ts +35 -0
  12. package/dist/middleware/express.d.ts.map +1 -0
  13. package/dist/middleware/express.js +41 -0
  14. package/dist/middleware/express.js.map +1 -0
  15. package/dist/middleware/fastify.d.ts +29 -0
  16. package/dist/middleware/fastify.d.ts.map +1 -0
  17. package/dist/middleware/fastify.js +41 -0
  18. package/dist/middleware/fastify.js.map +1 -0
  19. package/dist/middleware/nestjs.d.ts +67 -0
  20. package/dist/middleware/nestjs.d.ts.map +1 -0
  21. package/dist/middleware/nestjs.js +82 -0
  22. package/dist/middleware/nestjs.js.map +1 -0
  23. package/dist/policy-builder.d.ts +39 -0
  24. package/dist/policy-builder.d.ts.map +1 -0
  25. package/dist/policy-builder.js +92 -0
  26. package/dist/policy-builder.js.map +1 -0
  27. package/dist/role-hierarchy.d.ts +42 -0
  28. package/dist/role-hierarchy.d.ts.map +1 -0
  29. package/dist/role-hierarchy.js +87 -0
  30. package/dist/role-hierarchy.js.map +1 -0
  31. package/dist/serialization.d.ts +52 -0
  32. package/dist/serialization.d.ts.map +1 -0
  33. package/dist/serialization.js +144 -0
  34. package/dist/serialization.js.map +1 -0
  35. package/dist/server.d.ts +52 -0
  36. package/dist/server.d.ts.map +1 -0
  37. package/dist/server.js +163 -0
  38. package/dist/server.js.map +1 -0
  39. package/dist/types.d.ts +137 -0
  40. package/dist/types.d.ts.map +1 -0
  41. package/dist/types.js +27 -0
  42. package/dist/types.js.map +1 -0
  43. package/package.json +75 -0
@@ -0,0 +1,87 @@
1
+ /**
2
+ * Defines a role inheritance hierarchy.
3
+ *
4
+ * When a role inherits from another, it gains all permissions of its parent roles.
5
+ * Cycles are detected and rejected at definition time.
6
+ *
7
+ * ```ts
8
+ * const hierarchy = new RoleHierarchy<MySchema>()
9
+ * .define("admin", ["manager", "viewer"])
10
+ * .define("manager", ["member"])
11
+ * .define("member", ["viewer"]);
12
+ *
13
+ * hierarchy.resolve("admin");
14
+ * // Set { "admin", "manager", "member", "viewer" }
15
+ * ```
16
+ */
17
+ export class RoleHierarchy {
18
+ parents = new Map();
19
+ cache = new Map();
20
+ /**
21
+ * Define that `role` inherits permissions from `inheritsFrom` roles.
22
+ * Clears the resolution cache.
23
+ */
24
+ define(role, inheritsFrom) {
25
+ this.parents.set(role, inheritsFrom);
26
+ this.cache.clear();
27
+ this.detectCycle(role, new Set());
28
+ return this;
29
+ }
30
+ /**
31
+ * Resolve the full set of roles a given role expands to,
32
+ * including all inherited roles (transitive).
33
+ */
34
+ resolve(role) {
35
+ const roleStr = role;
36
+ const cached = this.cache.get(roleStr);
37
+ if (cached)
38
+ return cached;
39
+ const result = new Set();
40
+ this.walk(roleStr, result);
41
+ this.cache.set(roleStr, result);
42
+ return result;
43
+ }
44
+ /**
45
+ * Resolve multiple roles at once, returning the merged set.
46
+ */
47
+ resolveAll(roles) {
48
+ const result = new Set();
49
+ for (const role of roles) {
50
+ for (const r of this.resolve(role)) {
51
+ result.add(r);
52
+ }
53
+ }
54
+ return result;
55
+ }
56
+ /**
57
+ * Get all defined roles that have inheritance rules.
58
+ */
59
+ definedRoles() {
60
+ return [...this.parents.keys()];
61
+ }
62
+ walk(role, visited) {
63
+ if (visited.has(role))
64
+ return;
65
+ visited.add(role);
66
+ const parents = this.parents.get(role);
67
+ if (parents) {
68
+ for (const parent of parents) {
69
+ this.walk(parent, visited);
70
+ }
71
+ }
72
+ }
73
+ detectCycle(role, visiting) {
74
+ if (visiting.has(role)) {
75
+ throw new Error(`Cycle detected in role hierarchy: ${[...visiting, role].join(" → ")}`);
76
+ }
77
+ visiting.add(role);
78
+ const parents = this.parents.get(role);
79
+ if (parents) {
80
+ for (const parent of parents) {
81
+ this.detectCycle(parent, visiting);
82
+ }
83
+ }
84
+ visiting.delete(role);
85
+ }
86
+ }
87
+ //# sourceMappingURL=role-hierarchy.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"role-hierarchy.js","sourceRoot":"","sources":["../src/role-hierarchy.ts"],"names":[],"mappings":"AAEA;;;;;;;;;;;;;;;GAeG;AACH,MAAM,OAAO,aAAa;IAChB,OAAO,GAAG,IAAI,GAAG,EAAoB,CAAC;IACtC,KAAK,GAAG,IAAI,GAAG,EAAuB,CAAC;IAE/C;;;OAGG;IACH,MAAM,CAAC,IAAkB,EAAE,YAA4B;QACrD,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,IAAI,EAAE,YAAwB,CAAC,CAAC;QACjD,IAAI,CAAC,KAAK,CAAC,KAAK,EAAE,CAAC;QACnB,IAAI,CAAC,WAAW,CAAC,IAAc,EAAE,IAAI,GAAG,EAAE,CAAC,CAAC;QAC5C,OAAO,IAAI,CAAC;IACd,CAAC;IAED;;;OAGG;IACH,OAAO,CAAC,IAAkB;QACxB,MAAM,OAAO,GAAG,IAAc,CAAC;QAC/B,MAAM,MAAM,GAAG,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC;QACvC,IAAI,MAAM;YAAE,OAAO,MAAM,CAAC;QAE1B,MAAM,MAAM,GAAG,IAAI,GAAG,EAAU,CAAC;QACjC,IAAI,CAAC,IAAI,CAAC,OAAO,EAAE,MAAM,CAAC,CAAC;QAC3B,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,OAAO,EAAE,MAAM,CAAC,CAAC;QAChC,OAAO,MAAM,CAAC;IAChB,CAAC;IAED;;OAEG;IACH,UAAU,CAAC,KAA6B;QACtC,MAAM,MAAM,GAAG,IAAI,GAAG,EAAU,CAAC;QACjC,KAAK,MAAM,IAAI,IAAI,KAAK,EAAE,CAAC;YACzB,KAAK,MAAM,CAAC,IAAI,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC,EAAE,CAAC;gBACnC,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC;YAChB,CAAC;QACH,CAAC;QACD,OAAO,MAAM,CAAC;IAChB,CAAC;IAED;;OAEG;IACH,YAAY;QACV,OAAO,CAAC,GAAG,IAAI,CAAC,OAAO,CAAC,IAAI,EAAE,CAAC,CAAC;IAClC,CAAC;IAEO,IAAI,CAAC,IAAY,EAAE,OAAoB;QAC7C,IAAI,OAAO,CAAC,GAAG,CAAC,IAAI,CAAC;YAAE,OAAO;QAC9B,OAAO,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC;QAClB,MAAM,OAAO,GAAG,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC;QACvC,IAAI,OAAO,EAAE,CAAC;YACZ,KAAK,MAAM,MAAM,IAAI,OAAO,EAAE,CAAC;gBAC7B,IAAI,CAAC,IAAI,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;YAC7B,CAAC;QACH,CAAC;IACH,CAAC;IAEO,WAAW,CAAC,IAAY,EAAE,QAAqB;QACrD,IAAI,QAAQ,CAAC,GAAG,CAAC,IAAI,CAAC,EAAE,CAAC;YACvB,MAAM,IAAI,KAAK,CACb,qCAAqC,CAAC,GAAG,QAAQ,EAAE,IAAI,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,EAAE,CACvE,CAAC;QACJ,CAAC;QACD,QAAQ,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC;QACnB,MAAM,OAAO,GAAG,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC;QACvC,IAAI,OAAO,EAAE,CAAC;YACZ,KAAK,MAAM,MAAM,IAAI,OAAO,EAAE,CAAC;gBAC7B,IAAI,CAAC,WAAW,CAAC,MAAM,EAAE,QAAQ,CAAC,CAAC;YACrC,CAAC;QACH,CAAC;QACD,QAAQ,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC;IACxB,CAAC;CACF"}
@@ -0,0 +1,52 @@
1
+ import type { SchemaDefinition, PolicyRule, PolicyEffect, Condition } from "./types.js";
2
+ export interface JsonPolicyRule {
3
+ id: string;
4
+ effect: PolicyEffect;
5
+ roles: string[] | "*";
6
+ actions: string[] | "*";
7
+ resources: string[] | "*";
8
+ conditions?: string[];
9
+ priority?: number;
10
+ description?: string;
11
+ }
12
+ export interface JsonPolicyDocument {
13
+ version: 1;
14
+ rules: JsonPolicyRule[];
15
+ }
16
+ /**
17
+ * A registry that maps condition names to condition functions.
18
+ * This allows JSON policies to reference conditions by name
19
+ * while keeping the actual logic in code.
20
+ *
21
+ * ```ts
22
+ * const conditions = new ConditionRegistry<MySchema>();
23
+ * conditions.register("isOwner", ctx => ctx.subject.id === ctx.resourceContext.ownerId);
24
+ * conditions.register("isActive", ctx => ctx.resourceContext.status === "active");
25
+ * ```
26
+ */
27
+ export declare class ConditionRegistry<S extends SchemaDefinition> {
28
+ private conditions;
29
+ register(name: string, condition: Condition<S>): this;
30
+ get(name: string): Condition<S> | undefined;
31
+ has(name: string): boolean;
32
+ names(): string[];
33
+ }
34
+ /**
35
+ * Serialize rules to a JSON-safe document.
36
+ * Conditions are stripped unless a reverse lookup map is provided.
37
+ */
38
+ export declare function exportRules<S extends SchemaDefinition>(rules: ReadonlyArray<PolicyRule<S>>, conditionNames?: Map<Condition<S>, string>): JsonPolicyDocument;
39
+ /**
40
+ * Serialize rules to a JSON string.
41
+ */
42
+ export declare function exportRulesToJson<S extends SchemaDefinition>(rules: ReadonlyArray<PolicyRule<S>>, conditionNames?: Map<Condition<S>, string>): string;
43
+ /**
44
+ * Deserialize a JSON policy document into PolicyRule objects.
45
+ * Condition names are resolved via the provided registry.
46
+ */
47
+ export declare function importRules<S extends SchemaDefinition>(doc: JsonPolicyDocument, registry?: ConditionRegistry<S>): PolicyRule<S>[];
48
+ /**
49
+ * Parse a JSON string into PolicyRule objects.
50
+ */
51
+ export declare function importRulesFromJson<S extends SchemaDefinition>(json: string, registry?: ConditionRegistry<S>): PolicyRule<S>[];
52
+ //# sourceMappingURL=serialization.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"serialization.d.ts","sourceRoot":"","sources":["../src/serialization.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,gBAAgB,EAChB,UAAU,EACV,YAAY,EACZ,SAAS,EACV,MAAM,YAAY,CAAC;AAMpB,MAAM,WAAW,cAAc;IAC7B,EAAE,EAAE,MAAM,CAAC;IACX,MAAM,EAAE,YAAY,CAAC;IACrB,KAAK,EAAE,MAAM,EAAE,GAAG,GAAG,CAAC;IACtB,OAAO,EAAE,MAAM,EAAE,GAAG,GAAG,CAAC;IACxB,SAAS,EAAE,MAAM,EAAE,GAAG,GAAG,CAAC;IAC1B,UAAU,CAAC,EAAE,MAAM,EAAE,CAAC;IACtB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,WAAW,CAAC,EAAE,MAAM,CAAC;CACtB;AAED,MAAM,WAAW,kBAAkB;IACjC,OAAO,EAAE,CAAC,CAAC;IACX,KAAK,EAAE,cAAc,EAAE,CAAC;CACzB;AAED;;;;;;;;;;GAUG;AACH,qBAAa,iBAAiB,CAAC,CAAC,SAAS,gBAAgB;IACvD,OAAO,CAAC,UAAU,CAAmC;IAErD,QAAQ,CAAC,IAAI,EAAE,MAAM,EAAE,SAAS,EAAE,SAAS,CAAC,CAAC,CAAC,GAAG,IAAI;IAWrD,GAAG,CAAC,IAAI,EAAE,MAAM,GAAG,SAAS,CAAC,CAAC,CAAC,GAAG,SAAS;IAI3C,GAAG,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO;IAI1B,KAAK,IAAI,MAAM,EAAE;CAGlB;AAMD;;;GAGG;AACH,wBAAgB,WAAW,CAAC,CAAC,SAAS,gBAAgB,EACpD,KAAK,EAAE,aAAa,CAAC,UAAU,CAAC,CAAC,CAAC,CAAC,EACnC,cAAc,CAAC,EAAE,GAAG,CAAC,SAAS,CAAC,CAAC,CAAC,EAAE,MAAM,CAAC,GACzC,kBAAkB,CAyBpB;AAED;;GAEG;AACH,wBAAgB,iBAAiB,CAAC,CAAC,SAAS,gBAAgB,EAC1D,KAAK,EAAE,aAAa,CAAC,UAAU,CAAC,CAAC,CAAC,CAAC,EACnC,cAAc,CAAC,EAAE,GAAG,CAAC,SAAS,CAAC,CAAC,CAAC,EAAE,MAAM,CAAC,GACzC,MAAM,CAER;AAMD;;;GAGG;AACH,wBAAgB,WAAW,CAAC,CAAC,SAAS,gBAAgB,EACpD,GAAG,EAAE,kBAAkB,EACvB,QAAQ,CAAC,EAAE,iBAAiB,CAAC,CAAC,CAAC,GAC9B,UAAU,CAAC,CAAC,CAAC,EAAE,CA6DjB;AAED;;GAEG;AACH,wBAAgB,mBAAmB,CAAC,CAAC,SAAS,gBAAgB,EAC5D,IAAI,EAAE,MAAM,EACZ,QAAQ,CAAC,EAAE,iBAAiB,CAAC,CAAC,CAAC,GAC9B,UAAU,CAAC,CAAC,CAAC,EAAE,CAUjB"}
@@ -0,0 +1,144 @@
1
+ /**
2
+ * A registry that maps condition names to condition functions.
3
+ * This allows JSON policies to reference conditions by name
4
+ * while keeping the actual logic in code.
5
+ *
6
+ * ```ts
7
+ * const conditions = new ConditionRegistry<MySchema>();
8
+ * conditions.register("isOwner", ctx => ctx.subject.id === ctx.resourceContext.ownerId);
9
+ * conditions.register("isActive", ctx => ctx.resourceContext.status === "active");
10
+ * ```
11
+ */
12
+ export class ConditionRegistry {
13
+ conditions = new Map();
14
+ register(name, condition) {
15
+ if (!name || typeof name !== "string") {
16
+ throw new Error("Condition name must be a non-empty string");
17
+ }
18
+ if (typeof condition !== "function") {
19
+ throw new Error(`Condition "${name}" must be a function`);
20
+ }
21
+ this.conditions.set(name, condition);
22
+ return this;
23
+ }
24
+ get(name) {
25
+ return this.conditions.get(name);
26
+ }
27
+ has(name) {
28
+ return this.conditions.has(name);
29
+ }
30
+ names() {
31
+ return [...this.conditions.keys()];
32
+ }
33
+ }
34
+ // ---------------------------------------------------------------------------
35
+ // Export: PolicyRule[] → JSON
36
+ // ---------------------------------------------------------------------------
37
+ /**
38
+ * Serialize rules to a JSON-safe document.
39
+ * Conditions are stripped unless a reverse lookup map is provided.
40
+ */
41
+ export function exportRules(rules, conditionNames) {
42
+ const jsonRules = rules.map((rule) => {
43
+ const jr = {
44
+ id: rule.id,
45
+ effect: rule.effect,
46
+ roles: rule.roles,
47
+ actions: rule.actions,
48
+ resources: rule.resources,
49
+ priority: rule.priority,
50
+ description: rule.description,
51
+ };
52
+ if (rule.conditions && conditionNames) {
53
+ const names = [];
54
+ for (const cond of rule.conditions) {
55
+ const name = conditionNames.get(cond);
56
+ if (name)
57
+ names.push(name);
58
+ }
59
+ if (names.length > 0)
60
+ jr.conditions = names;
61
+ }
62
+ return jr;
63
+ });
64
+ return { version: 1, rules: jsonRules };
65
+ }
66
+ /**
67
+ * Serialize rules to a JSON string.
68
+ */
69
+ export function exportRulesToJson(rules, conditionNames) {
70
+ return JSON.stringify(exportRules(rules, conditionNames), null, 2);
71
+ }
72
+ // ---------------------------------------------------------------------------
73
+ // Import: JSON → PolicyRule[]
74
+ // ---------------------------------------------------------------------------
75
+ /**
76
+ * Deserialize a JSON policy document into PolicyRule objects.
77
+ * Condition names are resolved via the provided registry.
78
+ */
79
+ export function importRules(doc, registry) {
80
+ if (!doc || typeof doc !== "object") {
81
+ throw new Error("Policy document must be a non-null object");
82
+ }
83
+ if (doc.version !== 1) {
84
+ throw new Error(`Unsupported policy document version: ${doc.version}`);
85
+ }
86
+ if (!Array.isArray(doc.rules)) {
87
+ throw new Error("Policy document must have a 'rules' array");
88
+ }
89
+ return doc.rules.map((jr, index) => {
90
+ if (!jr || typeof jr !== "object") {
91
+ throw new Error(`Rule at index ${index} must be a non-null object`);
92
+ }
93
+ if (!jr.id || typeof jr.id !== "string") {
94
+ throw new Error(`Rule at index ${index} is missing a valid "id" field.`);
95
+ }
96
+ if (jr.effect !== "allow" && jr.effect !== "deny") {
97
+ throw new Error(`Invalid effect "${jr.effect}" in rule "${jr.id}". Must be "allow" or "deny".`);
98
+ }
99
+ if (jr.roles !== "*" && !Array.isArray(jr.roles)) {
100
+ throw new Error(`Rule "${jr.id}": roles must be "*" or an array of strings`);
101
+ }
102
+ if (jr.actions !== "*" && !Array.isArray(jr.actions)) {
103
+ throw new Error(`Rule "${jr.id}": actions must be "*" or an array of strings`);
104
+ }
105
+ if (jr.resources !== "*" && !Array.isArray(jr.resources)) {
106
+ throw new Error(`Rule "${jr.id}": resources must be "*" or an array of strings`);
107
+ }
108
+ const conditions = [];
109
+ if (jr.conditions && registry) {
110
+ for (const name of jr.conditions) {
111
+ const cond = registry.get(name);
112
+ if (!cond) {
113
+ throw new Error(`Unknown condition "${name}" in rule "${jr.id}". ` +
114
+ `Registered conditions: ${registry.names().join(", ") || "(none)"}`);
115
+ }
116
+ conditions.push(cond);
117
+ }
118
+ }
119
+ return {
120
+ id: jr.id,
121
+ effect: jr.effect,
122
+ roles: jr.roles,
123
+ actions: jr.actions,
124
+ resources: jr.resources,
125
+ conditions: conditions.length > 0 ? conditions : undefined,
126
+ priority: jr.priority,
127
+ description: jr.description,
128
+ };
129
+ });
130
+ }
131
+ /**
132
+ * Parse a JSON string into PolicyRule objects.
133
+ */
134
+ export function importRulesFromJson(json, registry) {
135
+ let doc;
136
+ try {
137
+ doc = JSON.parse(json);
138
+ }
139
+ catch (err) {
140
+ throw new Error(`Failed to parse policy JSON: ${err instanceof Error ? err.message : String(err)}`);
141
+ }
142
+ return importRules(doc, registry);
143
+ }
144
+ //# sourceMappingURL=serialization.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"serialization.js","sourceRoot":"","sources":["../src/serialization.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,52 @@
1
+ import { type IncomingMessage, type ServerResponse } from "node:http";
2
+ import type { AccessEngine } from "./engine.js";
3
+ import type { SchemaDefinition, Subject, ResourceContext } from "./types.js";
4
+ export interface ServerOptions<S extends SchemaDefinition> {
5
+ engine: AccessEngine<S>;
6
+ port?: number;
7
+ host?: string;
8
+ /**
9
+ * Optional hook to resolve a Subject from the request body.
10
+ * Defaults to using body.subject directly.
11
+ */
12
+ resolveSubject?: (body: EvalRequestBody) => Subject<S> | Promise<Subject<S>>;
13
+ /**
14
+ * Optional authentication hook. Return true to allow the request,
15
+ * false to reject with 401. Called before any endpoint logic.
16
+ * If not provided, all requests are allowed (suitable for internal networks only).
17
+ */
18
+ authenticate?: (req: IncomingMessage) => boolean | Promise<boolean>;
19
+ /** Maximum request body size in bytes. Defaults to 1 MB. */
20
+ maxBodyBytes?: number;
21
+ }
22
+ export interface EvalRequestBody {
23
+ subject: {
24
+ id: string;
25
+ roles: {
26
+ role: string;
27
+ tenantId?: string;
28
+ }[];
29
+ attributes?: Record<string, unknown>;
30
+ };
31
+ action: string;
32
+ resource: string;
33
+ resourceContext?: ResourceContext;
34
+ tenantId?: string;
35
+ }
36
+ /**
37
+ * Create a standalone HTTP authorization server.
38
+ *
39
+ * Endpoints:
40
+ * POST /evaluate — evaluate an authorization request
41
+ * GET /health — health check + rule count
42
+ * GET /rules — list all loaded rules (without conditions)
43
+ *
44
+ * **Security**: This server has no authentication by default.
45
+ * In production, provide an `authenticate` hook or run behind a VPN/service mesh.
46
+ */
47
+ export declare function createAuthServer<S extends SchemaDefinition>(options: ServerOptions<S>): {
48
+ start(): Promise<void>;
49
+ stop(): Promise<void>;
50
+ httpServer: import("http").Server<typeof IncomingMessage, typeof ServerResponse>;
51
+ };
52
+ //# sourceMappingURL=server.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"server.d.ts","sourceRoot":"","sources":["../src/server.ts"],"names":[],"mappings":"AAAA,OAAO,EAAgB,KAAK,eAAe,EAAE,KAAK,cAAc,EAAE,MAAM,WAAW,CAAC;AACpF,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAChD,OAAO,KAAK,EAAE,gBAAgB,EAAE,OAAO,EAAE,eAAe,EAAE,MAAM,YAAY,CAAC;AAQ7E,MAAM,WAAW,aAAa,CAAC,CAAC,SAAS,gBAAgB;IACvD,MAAM,EAAE,YAAY,CAAC,CAAC,CAAC,CAAC;IACxB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,IAAI,CAAC,EAAE,MAAM,CAAC;IACd;;;OAGG;IACH,cAAc,CAAC,EAAE,CAAC,IAAI,EAAE,eAAe,KAAK,OAAO,CAAC,CAAC,CAAC,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,CAAC;IAC7E;;;;OAIG;IACH,YAAY,CAAC,EAAE,CAAC,GAAG,EAAE,eAAe,KAAK,OAAO,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC;IACpE,4DAA4D;IAC5D,YAAY,CAAC,EAAE,MAAM,CAAC;CACvB;AAED,MAAM,WAAW,eAAe;IAC9B,OAAO,EAAE;QACP,EAAE,EAAE,MAAM,CAAC;QACX,KAAK,EAAE;YAAE,IAAI,EAAE,MAAM,CAAC;YAAC,QAAQ,CAAC,EAAE,MAAM,CAAA;SAAE,EAAE,CAAC;QAC7C,UAAU,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;KACtC,CAAC;IACF,MAAM,EAAE,MAAM,CAAC;IACf,QAAQ,EAAE,MAAM,CAAC;IACjB,eAAe,CAAC,EAAE,eAAe,CAAC;IAClC,QAAQ,CAAC,EAAE,MAAM,CAAC;CACnB;AAyDD;;;;;;;;;;GAUG;AACH,wBAAgB,gBAAgB,CAAC,CAAC,SAAS,gBAAgB,EACzD,OAAO,EAAE,aAAa,CAAC,CAAC,CAAC;aAyHd,OAAO,CAAC,IAAI,CAAC;YAQd,OAAO,CAAC,IAAI,CAAC;;EAQxB"}
package/dist/server.js ADDED
@@ -0,0 +1,163 @@
1
+ import { createServer } from "node:http";
2
+ const DEFAULT_MAX_BODY_BYTES = 1024 * 1024; // 1 MB
3
+ function readBody(req, maxBytes) {
4
+ return new Promise((resolve, reject) => {
5
+ const chunks = [];
6
+ let received = 0;
7
+ let settled = false;
8
+ function settle(fn) {
9
+ if (!settled) {
10
+ settled = true;
11
+ fn();
12
+ }
13
+ }
14
+ req.on("data", (chunk) => {
15
+ if (settled)
16
+ return;
17
+ received += chunk.length;
18
+ if (received > maxBytes) {
19
+ req.resume();
20
+ settle(() => reject(new Error(`Request body exceeds ${maxBytes} bytes`)));
21
+ return;
22
+ }
23
+ chunks.push(chunk);
24
+ });
25
+ req.on("end", () => {
26
+ settle(() => resolve(Buffer.concat(chunks).toString("utf-8")));
27
+ });
28
+ req.on("error", (err) => {
29
+ settle(() => reject(err));
30
+ });
31
+ });
32
+ }
33
+ function sendJson(res, status, body) {
34
+ const json = JSON.stringify(body);
35
+ res.writeHead(status, {
36
+ "Content-Type": "application/json",
37
+ "Content-Length": Buffer.byteLength(json),
38
+ });
39
+ res.end(json);
40
+ }
41
+ /**
42
+ * Create a standalone HTTP authorization server.
43
+ *
44
+ * Endpoints:
45
+ * POST /evaluate — evaluate an authorization request
46
+ * GET /health — health check + rule count
47
+ * GET /rules — list all loaded rules (without conditions)
48
+ *
49
+ * **Security**: This server has no authentication by default.
50
+ * In production, provide an `authenticate` hook or run behind a VPN/service mesh.
51
+ */
52
+ export function createAuthServer(options) {
53
+ const { engine, port = 3100, host = "0.0.0.0", maxBodyBytes = DEFAULT_MAX_BODY_BYTES, } = options;
54
+ const startTime = Date.now();
55
+ const server = createServer(async (req, res) => {
56
+ const pathname = new URL(req.url ?? "/", "http://localhost").pathname;
57
+ const method = req.method ?? "GET";
58
+ try {
59
+ if (options.authenticate) {
60
+ const authed = await options.authenticate(req);
61
+ if (authed !== true) {
62
+ sendJson(res, 401, { error: "Unauthorized" });
63
+ return;
64
+ }
65
+ }
66
+ if (method === "GET" && pathname === "/health") {
67
+ const body = {
68
+ status: "ok",
69
+ rulesLoaded: engine.getRules().length,
70
+ uptime: Date.now() - startTime,
71
+ };
72
+ sendJson(res, 200, body);
73
+ return;
74
+ }
75
+ if (method === "GET" && pathname === "/rules") {
76
+ const rules = engine.getRules().map((r) => ({
77
+ id: r.id,
78
+ effect: r.effect,
79
+ roles: r.roles,
80
+ actions: r.actions,
81
+ resources: r.resources,
82
+ priority: r.priority,
83
+ description: r.description,
84
+ hasConditions: (r.conditions?.length ?? 0) > 0,
85
+ }));
86
+ sendJson(res, 200, { rules });
87
+ return;
88
+ }
89
+ if (method === "POST" && pathname === "/evaluate") {
90
+ let raw;
91
+ try {
92
+ raw = await readBody(req, maxBodyBytes);
93
+ }
94
+ catch (err) {
95
+ sendJson(res, 413, {
96
+ error: err instanceof Error ? err.message : "Payload too large",
97
+ });
98
+ return;
99
+ }
100
+ let body;
101
+ try {
102
+ body = JSON.parse(raw);
103
+ }
104
+ catch {
105
+ sendJson(res, 400, { error: "Invalid JSON body" });
106
+ return;
107
+ }
108
+ if (!body.subject || !body.action || !body.resource) {
109
+ sendJson(res, 400, {
110
+ error: "Missing required fields: subject, action, resource",
111
+ });
112
+ return;
113
+ }
114
+ if (typeof body.action !== "string" || typeof body.resource !== "string") {
115
+ sendJson(res, 400, { error: "action and resource must be strings" });
116
+ return;
117
+ }
118
+ if (typeof body.subject !== "object" ||
119
+ typeof body.subject.id !== "string" ||
120
+ !Array.isArray(body.subject.roles)) {
121
+ sendJson(res, 400, {
122
+ error: "subject must have a string id and a roles array",
123
+ });
124
+ return;
125
+ }
126
+ const subject = options.resolveSubject
127
+ ? await options.resolveSubject(body)
128
+ : body.subject;
129
+ const decision = engine.evaluate(subject, body.action, body.resource, body.resourceContext ?? {}, body.tenantId);
130
+ const response = {
131
+ allowed: decision.allowed,
132
+ effect: decision.effect,
133
+ reason: decision.reason,
134
+ matchedRuleId: decision.matchedRule?.id ?? null,
135
+ durationMs: decision.durationMs,
136
+ };
137
+ sendJson(res, 200, response);
138
+ return;
139
+ }
140
+ sendJson(res, 404, { error: "Not found" });
141
+ }
142
+ catch (err) {
143
+ const message = err instanceof Error ? err.message : "Internal server error";
144
+ sendJson(res, 500, { error: message });
145
+ }
146
+ });
147
+ return {
148
+ start() {
149
+ return new Promise((resolve) => {
150
+ server.listen(port, host, () => {
151
+ resolve();
152
+ });
153
+ });
154
+ },
155
+ stop() {
156
+ return new Promise((resolve, reject) => {
157
+ server.close((err) => (err ? reject(err) : resolve()));
158
+ });
159
+ },
160
+ httpServer: server,
161
+ };
162
+ }
163
+ //# sourceMappingURL=server.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"server.js","sourceRoot":"","sources":["../src/server.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,137 @@
1
+ /**
2
+ * Core type definitions for the authorization engine.
3
+ *
4
+ * The type system is designed so that defining a schema once
5
+ * propagates full autocomplete through every API surface:
6
+ * policies, checks, audits, and middleware.
7
+ */
8
+ export type ActionString = `${string}:${string}`;
9
+ export interface SchemaDefinition {
10
+ roles: string;
11
+ resources: string;
12
+ actions: ActionString;
13
+ tenantId?: string;
14
+ }
15
+ /**
16
+ * Infer concrete union types from a schema definition.
17
+ * Used internally to thread type narrowing everywhere.
18
+ */
19
+ export type InferRole<S extends SchemaDefinition> = S["roles"];
20
+ export type InferResource<S extends SchemaDefinition> = S["resources"];
21
+ export type InferAction<S extends SchemaDefinition> = S["actions"];
22
+ export type InferTenantId<S extends SchemaDefinition> = S["tenantId"] extends string ? S["tenantId"] : string;
23
+ export interface RoleAssignment<S extends SchemaDefinition> {
24
+ role: InferRole<S>;
25
+ tenantId?: InferTenantId<S>;
26
+ }
27
+ export interface Subject<S extends SchemaDefinition> {
28
+ id: string;
29
+ roles: RoleAssignment<S>[];
30
+ attributes?: Record<string, unknown>;
31
+ }
32
+ export interface ResourceContext {
33
+ id?: string;
34
+ tenantId?: string;
35
+ [key: string]: unknown;
36
+ }
37
+ export interface EvaluationContext<S extends SchemaDefinition> {
38
+ subject: Subject<S>;
39
+ action: InferAction<S>;
40
+ resource: InferResource<S>;
41
+ resourceContext: ResourceContext;
42
+ tenantId?: string;
43
+ environment?: Record<string, unknown>;
44
+ }
45
+ export type Condition<S extends SchemaDefinition> = (ctx: EvaluationContext<S>) => boolean | Promise<boolean>;
46
+ export type PolicyEffect = "allow" | "deny";
47
+ export interface PolicyRule<S extends SchemaDefinition> {
48
+ readonly id: string;
49
+ readonly effect: PolicyEffect;
50
+ readonly roles: InferRole<S>[] | "*";
51
+ readonly actions: InferAction<S>[] | "*";
52
+ readonly resources: InferResource<S>[] | "*";
53
+ readonly conditions?: Condition<S>[];
54
+ /**
55
+ * Higher priority wins. Deny at equal priority wins over allow.
56
+ * Default: 0
57
+ */
58
+ readonly priority?: number;
59
+ readonly description?: string;
60
+ }
61
+ export interface Decision<S extends SchemaDefinition> {
62
+ allowed: boolean;
63
+ effect: PolicyEffect | "default-deny";
64
+ matchedRule: PolicyRule<S> | null;
65
+ subject: Subject<S>;
66
+ action: InferAction<S>;
67
+ resource: InferResource<S>;
68
+ resourceContext: ResourceContext;
69
+ tenantId?: string;
70
+ timestamp: number;
71
+ durationMs: number;
72
+ reason: string;
73
+ }
74
+ export interface AuditEntry {
75
+ allowed: boolean;
76
+ effect: string;
77
+ matchedRuleId: string | null;
78
+ matchedRuleDescription: string | null;
79
+ subjectId: string;
80
+ action: string;
81
+ resource: string;
82
+ tenantId?: string;
83
+ timestamp: number;
84
+ durationMs: number;
85
+ reason: string;
86
+ }
87
+ /**
88
+ * Convert a Decision to a serialization-safe AuditEntry
89
+ * (strips functions, large objects, and condition references).
90
+ */
91
+ export declare function toAuditEntry<S extends SchemaDefinition>(decision: Decision<S>): AuditEntry;
92
+ export interface ConditionResult {
93
+ index: number;
94
+ passed: boolean;
95
+ error?: string;
96
+ }
97
+ export interface RuleEvaluation<S extends SchemaDefinition> {
98
+ rule: PolicyRule<S>;
99
+ roleMatched: boolean;
100
+ actionMatched: boolean;
101
+ resourceMatched: boolean;
102
+ conditionResults: ConditionResult[];
103
+ matched: boolean;
104
+ }
105
+ export interface ExplainResult<S extends SchemaDefinition> {
106
+ allowed: boolean;
107
+ effect: PolicyEffect | "default-deny";
108
+ reason: string;
109
+ evaluatedRules: RuleEvaluation<S>[];
110
+ durationMs: number;
111
+ }
112
+ export type DecisionListener<S extends SchemaDefinition> = (decision: Decision<S>) => void | Promise<void>;
113
+ export interface ConditionError {
114
+ ruleId: string;
115
+ conditionIndex: number;
116
+ error: unknown;
117
+ }
118
+ export type ConditionErrorHandler = (err: ConditionError) => void;
119
+ export interface EngineOptions<S extends SchemaDefinition> {
120
+ schema: S;
121
+ defaultEffect?: PolicyEffect;
122
+ onDecision?: DecisionListener<S>;
123
+ onConditionError?: ConditionErrorHandler;
124
+ /**
125
+ * When true, async conditions are awaited.
126
+ * When false (default), only synchronous conditions are supported
127
+ * and evaluate is guaranteed synchronous.
128
+ */
129
+ asyncConditions?: boolean;
130
+ /**
131
+ * When true, evaluate() throws if tenantId is omitted and the subject
132
+ * has any tenant-scoped role assignments. Prevents accidental
133
+ * cross-tenant privilege escalation.
134
+ */
135
+ strictTenancy?: boolean;
136
+ }
137
+ //# sourceMappingURL=types.d.ts.map