@siremzam/sentinel 0.3.1 → 0.3.2
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 +573 -177
- package/dist/engine-C6IASR5F.d.cts +283 -0
- package/dist/engine-C6IASR5F.d.ts +283 -0
- package/dist/index.cjs +877 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +58 -0
- package/dist/index.d.ts +58 -10
- package/dist/index.js +838 -5
- package/dist/index.js.map +1 -1
- package/dist/middleware/express.cjs +58 -0
- package/dist/middleware/express.cjs.map +1 -0
- package/dist/middleware/express.d.cts +35 -0
- package/dist/middleware/express.d.ts +6 -6
- package/dist/middleware/express.js +31 -39
- package/dist/middleware/express.js.map +1 -1
- package/dist/middleware/fastify.cjs +59 -0
- package/dist/middleware/fastify.cjs.map +1 -0
- package/dist/middleware/fastify.d.cts +29 -0
- package/dist/middleware/fastify.d.ts +6 -6
- package/dist/middleware/fastify.js +32 -39
- package/dist/middleware/fastify.js.map +1 -1
- package/dist/middleware/nestjs.cjs +84 -0
- package/dist/middleware/nestjs.cjs.map +1 -0
- package/dist/middleware/nestjs.d.cts +67 -0
- package/dist/middleware/nestjs.d.ts +9 -9
- package/dist/middleware/nestjs.js +51 -76
- package/dist/middleware/nestjs.js.map +1 -1
- package/dist/server.cjs +184 -0
- package/dist/server.cjs.map +1 -0
- package/dist/server.d.cts +54 -0
- package/dist/server.d.ts +10 -8
- package/dist/server.js +149 -153
- package/dist/server.js.map +1 -1
- package/package.json +22 -9
- package/dist/engine.d.ts +0 -70
- package/dist/engine.d.ts.map +0 -1
- package/dist/engine.js +0 -562
- package/dist/engine.js.map +0 -1
- package/dist/index.d.ts.map +0 -1
- package/dist/middleware/express.d.ts.map +0 -1
- package/dist/middleware/fastify.d.ts.map +0 -1
- package/dist/middleware/nestjs.d.ts.map +0 -1
- package/dist/policy-builder.d.ts +0 -39
- package/dist/policy-builder.d.ts.map +0 -1
- package/dist/policy-builder.js +0 -92
- package/dist/policy-builder.js.map +0 -1
- package/dist/role-hierarchy.d.ts +0 -42
- package/dist/role-hierarchy.d.ts.map +0 -1
- package/dist/role-hierarchy.js +0 -87
- package/dist/role-hierarchy.js.map +0 -1
- package/dist/serialization.d.ts +0 -52
- package/dist/serialization.d.ts.map +0 -1
- package/dist/serialization.js +0 -144
- package/dist/serialization.js.map +0 -1
- package/dist/server.d.ts.map +0 -1
- package/dist/types.d.ts +0 -137
- package/dist/types.d.ts.map +0 -1
- package/dist/types.js +0 -27
- package/dist/types.js.map +0 -1
|
@@ -0,0 +1,84 @@
|
|
|
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/nestjs.ts
|
|
21
|
+
var nestjs_exports = {};
|
|
22
|
+
__export(nestjs_exports, {
|
|
23
|
+
createAuthGuard: () => createAuthGuard,
|
|
24
|
+
createAuthorizeDecorator: () => createAuthorizeDecorator,
|
|
25
|
+
getAuthorizeMetadata: () => getAuthorizeMetadata
|
|
26
|
+
});
|
|
27
|
+
module.exports = __toCommonJS(nestjs_exports);
|
|
28
|
+
var metadataStore = /* @__PURE__ */ new WeakMap();
|
|
29
|
+
function getAuthorizeMetadata(...targets) {
|
|
30
|
+
for (const target of targets) {
|
|
31
|
+
const meta = metadataStore.get(target);
|
|
32
|
+
if (meta) return meta;
|
|
33
|
+
}
|
|
34
|
+
return void 0;
|
|
35
|
+
}
|
|
36
|
+
function createAuthorizeDecorator() {
|
|
37
|
+
return function Authorize(action, resource) {
|
|
38
|
+
return (_target, _propertyKey, descriptor) => {
|
|
39
|
+
if (!descriptor?.value) return descriptor;
|
|
40
|
+
const metadata = {
|
|
41
|
+
action,
|
|
42
|
+
resource
|
|
43
|
+
};
|
|
44
|
+
metadataStore.set(descriptor.value, metadata);
|
|
45
|
+
return descriptor;
|
|
46
|
+
};
|
|
47
|
+
};
|
|
48
|
+
}
|
|
49
|
+
function createAuthGuard(options) {
|
|
50
|
+
const { engine, getSubject, getResourceContext, getTenantId } = options;
|
|
51
|
+
class AccessControlGuard {
|
|
52
|
+
canActivate(context) {
|
|
53
|
+
const handler = context.getHandler();
|
|
54
|
+
const cls = context.getClass();
|
|
55
|
+
const metadata = getAuthorizeMetadata(handler, cls);
|
|
56
|
+
if (!metadata) return true;
|
|
57
|
+
const request = context.switchToHttp().getRequest();
|
|
58
|
+
const subject = getSubject(request);
|
|
59
|
+
if (!subject) return false;
|
|
60
|
+
try {
|
|
61
|
+
const resourceContext = getResourceContext?.(request) ?? {};
|
|
62
|
+
const tenantId = getTenantId?.(request);
|
|
63
|
+
const decision = engine.evaluate(
|
|
64
|
+
subject,
|
|
65
|
+
metadata.action,
|
|
66
|
+
metadata.resource,
|
|
67
|
+
resourceContext,
|
|
68
|
+
tenantId
|
|
69
|
+
);
|
|
70
|
+
return decision.allowed;
|
|
71
|
+
} catch {
|
|
72
|
+
return false;
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
return AccessControlGuard;
|
|
77
|
+
}
|
|
78
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
79
|
+
0 && (module.exports = {
|
|
80
|
+
createAuthGuard,
|
|
81
|
+
createAuthorizeDecorator,
|
|
82
|
+
getAuthorizeMetadata
|
|
83
|
+
});
|
|
84
|
+
//# sourceMappingURL=nestjs.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../src/middleware/nestjs.ts"],"sourcesContent":["import type { AccessEngine } from \"../engine.js\";\nimport type { SchemaDefinition, InferAction, InferResource, Subject, ResourceContext } from \"../types.js\";\n\n/**\n * NestJS-compatible guard and decorator factory.\n *\n * Since we don't want a hard dependency on @nestjs/common or reflect-metadata,\n * this module uses a WeakMap to store metadata and provides factory functions\n * that produce NestJS-shaped guards and decorators.\n */\n\n// ---------------------------------------------------------------------------\n// Minimal NestJS interfaces (avoids @nestjs/common dependency)\n// ---------------------------------------------------------------------------\n\ninterface ExecutionContext {\n switchToHttp(): {\n getRequest(): Record<string, unknown>;\n getResponse(): Record<string, unknown>;\n };\n getHandler(): (...args: unknown[]) => unknown;\n getClass(): new (...args: unknown[]) => unknown;\n}\n\ninterface CanActivate {\n canActivate(context: ExecutionContext): boolean | Promise<boolean>;\n}\n\n// ---------------------------------------------------------------------------\n// Metadata storage (no reflect-metadata needed)\n// ---------------------------------------------------------------------------\n\nexport interface AuthorizeMetadata {\n action: string;\n resource: string;\n}\n\nconst metadataStore = new WeakMap<object, AuthorizeMetadata>();\n\n/**\n * Retrieve stored authorization metadata for a handler or class.\n */\nexport function getAuthorizeMetadata(\n ...targets: object[]\n): AuthorizeMetadata | undefined {\n for (const target of targets) {\n const meta = metadataStore.get(target);\n if (meta) return meta;\n }\n return undefined;\n}\n\n/**\n * Creates a method decorator that stores authorization metadata.\n *\n * Usage in a NestJS controller:\n * ```ts\n * const Authorize = createAuthorizeDecorator<MySchema>();\n *\n * @Controller(\"invoices\")\n * class InvoiceController {\n * @Post(\":id/approve\")\n * @Authorize(\"invoice:approve\", \"invoice\")\n * approve(@Param(\"id\") id: string) { ... }\n * }\n * ```\n */\nexport function createAuthorizeDecorator<S extends SchemaDefinition>() {\n return function Authorize(\n action: InferAction<S>,\n resource: InferResource<S>,\n ): MethodDecorator {\n return (_target, _propertyKey, descriptor: PropertyDescriptor) => {\n if (!descriptor?.value) return descriptor;\n const metadata: AuthorizeMetadata = {\n action: action as string,\n resource: resource as string,\n };\n metadataStore.set(descriptor.value as object, metadata);\n return descriptor;\n };\n };\n}\n\n// ---------------------------------------------------------------------------\n// Guard factory\n// ---------------------------------------------------------------------------\n\nexport interface NestGuardOptions<S extends SchemaDefinition> {\n engine: AccessEngine<S>;\n getSubject: (request: Record<string, unknown>) => Subject<S> | undefined;\n getResourceContext?: (request: Record<string, unknown>) => ResourceContext;\n getTenantId?: (request: Record<string, unknown>) => string | undefined;\n}\n\n/**\n * Creates a NestJS CanActivate guard class.\n *\n * Usage:\n * ```ts\n * const AuthGuard = createAuthGuard({\n * engine,\n * getSubject: (req) => req.user as Subject<MySchema>,\n * });\n *\n * // Use as a global guard:\n * app.useGlobalGuards(new AuthGuard());\n * ```\n */\nexport function createAuthGuard<S extends SchemaDefinition>(\n options: NestGuardOptions<S>,\n): new () => CanActivate {\n const { engine, getSubject, getResourceContext, getTenantId } = options;\n\n class AccessControlGuard implements CanActivate {\n canActivate(context: ExecutionContext): boolean {\n const handler = context.getHandler();\n const cls = context.getClass();\n const metadata = getAuthorizeMetadata(handler, cls);\n\n if (!metadata) return true;\n\n const request = context.switchToHttp().getRequest();\n const subject = getSubject(request);\n if (!subject) return false;\n\n try {\n const resourceContext = getResourceContext?.(request) ?? {};\n const tenantId = getTenantId?.(request);\n\n const decision = engine.evaluate(\n subject,\n metadata.action as Parameters<typeof engine.evaluate>[1],\n metadata.resource as Parameters<typeof engine.evaluate>[2],\n resourceContext,\n tenantId,\n );\n\n return decision.allowed;\n } catch {\n return false;\n }\n }\n }\n\n return AccessControlGuard;\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAqCA,IAAM,gBAAgB,oBAAI,QAAmC;AAKtD,SAAS,wBACX,SAC4B;AAC/B,aAAW,UAAU,SAAS;AAC5B,UAAM,OAAO,cAAc,IAAI,MAAM;AACrC,QAAI,KAAM,QAAO;AAAA,EACnB;AACA,SAAO;AACT;AAiBO,SAAS,2BAAuD;AACrE,SAAO,SAAS,UACd,QACA,UACiB;AACjB,WAAO,CAAC,SAAS,cAAc,eAAmC;AAChE,UAAI,CAAC,YAAY,MAAO,QAAO;AAC/B,YAAM,WAA8B;AAAA,QAClC;AAAA,QACA;AAAA,MACF;AACA,oBAAc,IAAI,WAAW,OAAiB,QAAQ;AACtD,aAAO;AAAA,IACT;AAAA,EACF;AACF;AA2BO,SAAS,gBACd,SACuB;AACvB,QAAM,EAAE,QAAQ,YAAY,oBAAoB,YAAY,IAAI;AAAA,EAEhE,MAAM,mBAA0C;AAAA,IAC9C,YAAY,SAAoC;AAC9C,YAAM,UAAU,QAAQ,WAAW;AACnC,YAAM,MAAM,QAAQ,SAAS;AAC7B,YAAM,WAAW,qBAAqB,SAAS,GAAG;AAElD,UAAI,CAAC,SAAU,QAAO;AAEtB,YAAM,UAAU,QAAQ,aAAa,EAAE,WAAW;AAClD,YAAM,UAAU,WAAW,OAAO;AAClC,UAAI,CAAC,QAAS,QAAO;AAErB,UAAI;AACF,cAAM,kBAAkB,qBAAqB,OAAO,KAAK,CAAC;AAC1D,cAAM,WAAW,cAAc,OAAO;AAEtC,cAAM,WAAW,OAAO;AAAA,UACtB;AAAA,UACA,SAAS;AAAA,UACT,SAAS;AAAA,UACT;AAAA,UACA;AAAA,QACF;AAEA,eAAO,SAAS;AAAA,MAClB,QAAQ;AACN,eAAO;AAAA,MACT;AAAA,IACF;AAAA,EACF;AAEA,SAAO;AACT;","names":[]}
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
import { S as SchemaDefinition, A as AccessEngine, r as Subject, R as ResourceContext, I as InferAction, k as InferResource } from '../engine-C6IASR5F.cjs';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* NestJS-compatible guard and decorator factory.
|
|
5
|
+
*
|
|
6
|
+
* Since we don't want a hard dependency on @nestjs/common or reflect-metadata,
|
|
7
|
+
* this module uses a WeakMap to store metadata and provides factory functions
|
|
8
|
+
* that produce NestJS-shaped guards and decorators.
|
|
9
|
+
*/
|
|
10
|
+
interface ExecutionContext {
|
|
11
|
+
switchToHttp(): {
|
|
12
|
+
getRequest(): Record<string, unknown>;
|
|
13
|
+
getResponse(): Record<string, unknown>;
|
|
14
|
+
};
|
|
15
|
+
getHandler(): (...args: unknown[]) => unknown;
|
|
16
|
+
getClass(): new (...args: unknown[]) => unknown;
|
|
17
|
+
}
|
|
18
|
+
interface CanActivate {
|
|
19
|
+
canActivate(context: ExecutionContext): boolean | Promise<boolean>;
|
|
20
|
+
}
|
|
21
|
+
interface AuthorizeMetadata {
|
|
22
|
+
action: string;
|
|
23
|
+
resource: string;
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* Retrieve stored authorization metadata for a handler or class.
|
|
27
|
+
*/
|
|
28
|
+
declare function getAuthorizeMetadata(...targets: object[]): AuthorizeMetadata | undefined;
|
|
29
|
+
/**
|
|
30
|
+
* Creates a method decorator that stores authorization metadata.
|
|
31
|
+
*
|
|
32
|
+
* Usage in a NestJS controller:
|
|
33
|
+
* ```ts
|
|
34
|
+
* const Authorize = createAuthorizeDecorator<MySchema>();
|
|
35
|
+
*
|
|
36
|
+
* @Controller("invoices")
|
|
37
|
+
* class InvoiceController {
|
|
38
|
+
* @Post(":id/approve")
|
|
39
|
+
* @Authorize("invoice:approve", "invoice")
|
|
40
|
+
* approve(@Param("id") id: string) { ... }
|
|
41
|
+
* }
|
|
42
|
+
* ```
|
|
43
|
+
*/
|
|
44
|
+
declare function createAuthorizeDecorator<S extends SchemaDefinition>(): (action: InferAction<S>, resource: InferResource<S>) => MethodDecorator;
|
|
45
|
+
interface NestGuardOptions<S extends SchemaDefinition> {
|
|
46
|
+
engine: AccessEngine<S>;
|
|
47
|
+
getSubject: (request: Record<string, unknown>) => Subject<S> | undefined;
|
|
48
|
+
getResourceContext?: (request: Record<string, unknown>) => ResourceContext;
|
|
49
|
+
getTenantId?: (request: Record<string, unknown>) => string | undefined;
|
|
50
|
+
}
|
|
51
|
+
/**
|
|
52
|
+
* Creates a NestJS CanActivate guard class.
|
|
53
|
+
*
|
|
54
|
+
* Usage:
|
|
55
|
+
* ```ts
|
|
56
|
+
* const AuthGuard = createAuthGuard({
|
|
57
|
+
* engine,
|
|
58
|
+
* getSubject: (req) => req.user as Subject<MySchema>,
|
|
59
|
+
* });
|
|
60
|
+
*
|
|
61
|
+
* // Use as a global guard:
|
|
62
|
+
* app.useGlobalGuards(new AuthGuard());
|
|
63
|
+
* ```
|
|
64
|
+
*/
|
|
65
|
+
declare function createAuthGuard<S extends SchemaDefinition>(options: NestGuardOptions<S>): new () => CanActivate;
|
|
66
|
+
|
|
67
|
+
export { type AuthorizeMetadata, type NestGuardOptions, createAuthGuard, createAuthorizeDecorator, getAuthorizeMetadata };
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import
|
|
2
|
-
|
|
1
|
+
import { S as SchemaDefinition, A as AccessEngine, r as Subject, R as ResourceContext, I as InferAction, k as InferResource } from '../engine-C6IASR5F.js';
|
|
2
|
+
|
|
3
3
|
/**
|
|
4
4
|
* NestJS-compatible guard and decorator factory.
|
|
5
5
|
*
|
|
@@ -18,14 +18,14 @@ interface ExecutionContext {
|
|
|
18
18
|
interface CanActivate {
|
|
19
19
|
canActivate(context: ExecutionContext): boolean | Promise<boolean>;
|
|
20
20
|
}
|
|
21
|
-
|
|
21
|
+
interface AuthorizeMetadata {
|
|
22
22
|
action: string;
|
|
23
23
|
resource: string;
|
|
24
24
|
}
|
|
25
25
|
/**
|
|
26
26
|
* Retrieve stored authorization metadata for a handler or class.
|
|
27
27
|
*/
|
|
28
|
-
|
|
28
|
+
declare function getAuthorizeMetadata(...targets: object[]): AuthorizeMetadata | undefined;
|
|
29
29
|
/**
|
|
30
30
|
* Creates a method decorator that stores authorization metadata.
|
|
31
31
|
*
|
|
@@ -41,8 +41,8 @@ export declare function getAuthorizeMetadata(...targets: object[]): AuthorizeMet
|
|
|
41
41
|
* }
|
|
42
42
|
* ```
|
|
43
43
|
*/
|
|
44
|
-
|
|
45
|
-
|
|
44
|
+
declare function createAuthorizeDecorator<S extends SchemaDefinition>(): (action: InferAction<S>, resource: InferResource<S>) => MethodDecorator;
|
|
45
|
+
interface NestGuardOptions<S extends SchemaDefinition> {
|
|
46
46
|
engine: AccessEngine<S>;
|
|
47
47
|
getSubject: (request: Record<string, unknown>) => Subject<S> | undefined;
|
|
48
48
|
getResourceContext?: (request: Record<string, unknown>) => ResourceContext;
|
|
@@ -62,6 +62,6 @@ export interface NestGuardOptions<S extends SchemaDefinition> {
|
|
|
62
62
|
* app.useGlobalGuards(new AuthGuard());
|
|
63
63
|
* ```
|
|
64
64
|
*/
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
65
|
+
declare function createAuthGuard<S extends SchemaDefinition>(options: NestGuardOptions<S>): new () => CanActivate;
|
|
66
|
+
|
|
67
|
+
export { type AuthorizeMetadata, type NestGuardOptions, createAuthGuard, createAuthorizeDecorator, getAuthorizeMetadata };
|
|
@@ -1,82 +1,57 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
return meta;
|
|
10
|
-
}
|
|
11
|
-
return undefined;
|
|
1
|
+
// src/middleware/nestjs.ts
|
|
2
|
+
var metadataStore = /* @__PURE__ */ new WeakMap();
|
|
3
|
+
function getAuthorizeMetadata(...targets) {
|
|
4
|
+
for (const target of targets) {
|
|
5
|
+
const meta = metadataStore.get(target);
|
|
6
|
+
if (meta) return meta;
|
|
7
|
+
}
|
|
8
|
+
return void 0;
|
|
12
9
|
}
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
* @Authorize("invoice:approve", "invoice")
|
|
24
|
-
* approve(@Param("id") id: string) { ... }
|
|
25
|
-
* }
|
|
26
|
-
* ```
|
|
27
|
-
*/
|
|
28
|
-
export function createAuthorizeDecorator() {
|
|
29
|
-
return function Authorize(action, resource) {
|
|
30
|
-
return (_target, _propertyKey, descriptor) => {
|
|
31
|
-
if (!descriptor?.value)
|
|
32
|
-
return descriptor;
|
|
33
|
-
const metadata = {
|
|
34
|
-
action: action,
|
|
35
|
-
resource: resource,
|
|
36
|
-
};
|
|
37
|
-
metadataStore.set(descriptor.value, metadata);
|
|
38
|
-
return descriptor;
|
|
39
|
-
};
|
|
10
|
+
function createAuthorizeDecorator() {
|
|
11
|
+
return function Authorize(action, resource) {
|
|
12
|
+
return (_target, _propertyKey, descriptor) => {
|
|
13
|
+
if (!descriptor?.value) return descriptor;
|
|
14
|
+
const metadata = {
|
|
15
|
+
action,
|
|
16
|
+
resource
|
|
17
|
+
};
|
|
18
|
+
metadataStore.set(descriptor.value, metadata);
|
|
19
|
+
return descriptor;
|
|
40
20
|
};
|
|
21
|
+
};
|
|
41
22
|
}
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
if (!subject)
|
|
68
|
-
return false;
|
|
69
|
-
try {
|
|
70
|
-
const resourceContext = getResourceContext?.(request) ?? {};
|
|
71
|
-
const tenantId = getTenantId?.(request);
|
|
72
|
-
const decision = engine.evaluate(subject, metadata.action, metadata.resource, resourceContext, tenantId);
|
|
73
|
-
return decision.allowed;
|
|
74
|
-
}
|
|
75
|
-
catch {
|
|
76
|
-
return false;
|
|
77
|
-
}
|
|
78
|
-
}
|
|
23
|
+
function createAuthGuard(options) {
|
|
24
|
+
const { engine, getSubject, getResourceContext, getTenantId } = options;
|
|
25
|
+
class AccessControlGuard {
|
|
26
|
+
canActivate(context) {
|
|
27
|
+
const handler = context.getHandler();
|
|
28
|
+
const cls = context.getClass();
|
|
29
|
+
const metadata = getAuthorizeMetadata(handler, cls);
|
|
30
|
+
if (!metadata) return true;
|
|
31
|
+
const request = context.switchToHttp().getRequest();
|
|
32
|
+
const subject = getSubject(request);
|
|
33
|
+
if (!subject) return false;
|
|
34
|
+
try {
|
|
35
|
+
const resourceContext = getResourceContext?.(request) ?? {};
|
|
36
|
+
const tenantId = getTenantId?.(request);
|
|
37
|
+
const decision = engine.evaluate(
|
|
38
|
+
subject,
|
|
39
|
+
metadata.action,
|
|
40
|
+
metadata.resource,
|
|
41
|
+
resourceContext,
|
|
42
|
+
tenantId
|
|
43
|
+
);
|
|
44
|
+
return decision.allowed;
|
|
45
|
+
} catch {
|
|
46
|
+
return false;
|
|
47
|
+
}
|
|
79
48
|
}
|
|
80
|
-
|
|
49
|
+
}
|
|
50
|
+
return AccessControlGuard;
|
|
81
51
|
}
|
|
52
|
+
export {
|
|
53
|
+
createAuthGuard,
|
|
54
|
+
createAuthorizeDecorator,
|
|
55
|
+
getAuthorizeMetadata
|
|
56
|
+
};
|
|
82
57
|
//# sourceMappingURL=nestjs.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"
|
|
1
|
+
{"version":3,"sources":["../../src/middleware/nestjs.ts"],"sourcesContent":["import type { AccessEngine } from \"../engine.js\";\nimport type { SchemaDefinition, InferAction, InferResource, Subject, ResourceContext } from \"../types.js\";\n\n/**\n * NestJS-compatible guard and decorator factory.\n *\n * Since we don't want a hard dependency on @nestjs/common or reflect-metadata,\n * this module uses a WeakMap to store metadata and provides factory functions\n * that produce NestJS-shaped guards and decorators.\n */\n\n// ---------------------------------------------------------------------------\n// Minimal NestJS interfaces (avoids @nestjs/common dependency)\n// ---------------------------------------------------------------------------\n\ninterface ExecutionContext {\n switchToHttp(): {\n getRequest(): Record<string, unknown>;\n getResponse(): Record<string, unknown>;\n };\n getHandler(): (...args: unknown[]) => unknown;\n getClass(): new (...args: unknown[]) => unknown;\n}\n\ninterface CanActivate {\n canActivate(context: ExecutionContext): boolean | Promise<boolean>;\n}\n\n// ---------------------------------------------------------------------------\n// Metadata storage (no reflect-metadata needed)\n// ---------------------------------------------------------------------------\n\nexport interface AuthorizeMetadata {\n action: string;\n resource: string;\n}\n\nconst metadataStore = new WeakMap<object, AuthorizeMetadata>();\n\n/**\n * Retrieve stored authorization metadata for a handler or class.\n */\nexport function getAuthorizeMetadata(\n ...targets: object[]\n): AuthorizeMetadata | undefined {\n for (const target of targets) {\n const meta = metadataStore.get(target);\n if (meta) return meta;\n }\n return undefined;\n}\n\n/**\n * Creates a method decorator that stores authorization metadata.\n *\n * Usage in a NestJS controller:\n * ```ts\n * const Authorize = createAuthorizeDecorator<MySchema>();\n *\n * @Controller(\"invoices\")\n * class InvoiceController {\n * @Post(\":id/approve\")\n * @Authorize(\"invoice:approve\", \"invoice\")\n * approve(@Param(\"id\") id: string) { ... }\n * }\n * ```\n */\nexport function createAuthorizeDecorator<S extends SchemaDefinition>() {\n return function Authorize(\n action: InferAction<S>,\n resource: InferResource<S>,\n ): MethodDecorator {\n return (_target, _propertyKey, descriptor: PropertyDescriptor) => {\n if (!descriptor?.value) return descriptor;\n const metadata: AuthorizeMetadata = {\n action: action as string,\n resource: resource as string,\n };\n metadataStore.set(descriptor.value as object, metadata);\n return descriptor;\n };\n };\n}\n\n// ---------------------------------------------------------------------------\n// Guard factory\n// ---------------------------------------------------------------------------\n\nexport interface NestGuardOptions<S extends SchemaDefinition> {\n engine: AccessEngine<S>;\n getSubject: (request: Record<string, unknown>) => Subject<S> | undefined;\n getResourceContext?: (request: Record<string, unknown>) => ResourceContext;\n getTenantId?: (request: Record<string, unknown>) => string | undefined;\n}\n\n/**\n * Creates a NestJS CanActivate guard class.\n *\n * Usage:\n * ```ts\n * const AuthGuard = createAuthGuard({\n * engine,\n * getSubject: (req) => req.user as Subject<MySchema>,\n * });\n *\n * // Use as a global guard:\n * app.useGlobalGuards(new AuthGuard());\n * ```\n */\nexport function createAuthGuard<S extends SchemaDefinition>(\n options: NestGuardOptions<S>,\n): new () => CanActivate {\n const { engine, getSubject, getResourceContext, getTenantId } = options;\n\n class AccessControlGuard implements CanActivate {\n canActivate(context: ExecutionContext): boolean {\n const handler = context.getHandler();\n const cls = context.getClass();\n const metadata = getAuthorizeMetadata(handler, cls);\n\n if (!metadata) return true;\n\n const request = context.switchToHttp().getRequest();\n const subject = getSubject(request);\n if (!subject) return false;\n\n try {\n const resourceContext = getResourceContext?.(request) ?? {};\n const tenantId = getTenantId?.(request);\n\n const decision = engine.evaluate(\n subject,\n metadata.action as Parameters<typeof engine.evaluate>[1],\n metadata.resource as Parameters<typeof engine.evaluate>[2],\n resourceContext,\n tenantId,\n );\n\n return decision.allowed;\n } catch {\n return false;\n }\n }\n }\n\n return AccessControlGuard;\n}\n"],"mappings":";AAqCA,IAAM,gBAAgB,oBAAI,QAAmC;AAKtD,SAAS,wBACX,SAC4B;AAC/B,aAAW,UAAU,SAAS;AAC5B,UAAM,OAAO,cAAc,IAAI,MAAM;AACrC,QAAI,KAAM,QAAO;AAAA,EACnB;AACA,SAAO;AACT;AAiBO,SAAS,2BAAuD;AACrE,SAAO,SAAS,UACd,QACA,UACiB;AACjB,WAAO,CAAC,SAAS,cAAc,eAAmC;AAChE,UAAI,CAAC,YAAY,MAAO,QAAO;AAC/B,YAAM,WAA8B;AAAA,QAClC;AAAA,QACA;AAAA,MACF;AACA,oBAAc,IAAI,WAAW,OAAiB,QAAQ;AACtD,aAAO;AAAA,IACT;AAAA,EACF;AACF;AA2BO,SAAS,gBACd,SACuB;AACvB,QAAM,EAAE,QAAQ,YAAY,oBAAoB,YAAY,IAAI;AAAA,EAEhE,MAAM,mBAA0C;AAAA,IAC9C,YAAY,SAAoC;AAC9C,YAAM,UAAU,QAAQ,WAAW;AACnC,YAAM,MAAM,QAAQ,SAAS;AAC7B,YAAM,WAAW,qBAAqB,SAAS,GAAG;AAElD,UAAI,CAAC,SAAU,QAAO;AAEtB,YAAM,UAAU,QAAQ,aAAa,EAAE,WAAW;AAClD,YAAM,UAAU,WAAW,OAAO;AAClC,UAAI,CAAC,QAAS,QAAO;AAErB,UAAI;AACF,cAAM,kBAAkB,qBAAqB,OAAO,KAAK,CAAC;AAC1D,cAAM,WAAW,cAAc,OAAO;AAEtC,cAAM,WAAW,OAAO;AAAA,UACtB;AAAA,UACA,SAAS;AAAA,UACT,SAAS;AAAA,UACT;AAAA,UACA;AAAA,QACF;AAEA,eAAO,SAAS;AAAA,MAClB,QAAQ;AACN,eAAO;AAAA,MACT;AAAA,IACF;AAAA,EACF;AAEA,SAAO;AACT;","names":[]}
|
package/dist/server.cjs
ADDED
|
@@ -0,0 +1,184 @@
|
|
|
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/server.ts
|
|
21
|
+
var server_exports = {};
|
|
22
|
+
__export(server_exports, {
|
|
23
|
+
createAuthServer: () => createAuthServer
|
|
24
|
+
});
|
|
25
|
+
module.exports = __toCommonJS(server_exports);
|
|
26
|
+
var import_node_http = require("http");
|
|
27
|
+
var DEFAULT_MAX_BODY_BYTES = 1024 * 1024;
|
|
28
|
+
function readBody(req, maxBytes) {
|
|
29
|
+
return new Promise((resolve, reject) => {
|
|
30
|
+
const chunks = [];
|
|
31
|
+
let received = 0;
|
|
32
|
+
let settled = false;
|
|
33
|
+
function settle(fn) {
|
|
34
|
+
if (!settled) {
|
|
35
|
+
settled = true;
|
|
36
|
+
fn();
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
req.on("data", (chunk) => {
|
|
40
|
+
if (settled) return;
|
|
41
|
+
received += chunk.length;
|
|
42
|
+
if (received > maxBytes) {
|
|
43
|
+
req.resume();
|
|
44
|
+
settle(() => reject(new Error(`Request body exceeds ${maxBytes} bytes`)));
|
|
45
|
+
return;
|
|
46
|
+
}
|
|
47
|
+
chunks.push(chunk);
|
|
48
|
+
});
|
|
49
|
+
req.on("end", () => {
|
|
50
|
+
settle(() => resolve(Buffer.concat(chunks).toString("utf-8")));
|
|
51
|
+
});
|
|
52
|
+
req.on("error", (err) => {
|
|
53
|
+
settle(() => reject(err));
|
|
54
|
+
});
|
|
55
|
+
});
|
|
56
|
+
}
|
|
57
|
+
function sendJson(res, status, body) {
|
|
58
|
+
const json = JSON.stringify(body);
|
|
59
|
+
res.writeHead(status, {
|
|
60
|
+
"Content-Type": "application/json",
|
|
61
|
+
"Content-Length": Buffer.byteLength(json)
|
|
62
|
+
});
|
|
63
|
+
res.end(json);
|
|
64
|
+
}
|
|
65
|
+
function createAuthServer(options) {
|
|
66
|
+
const {
|
|
67
|
+
engine,
|
|
68
|
+
port = 3100,
|
|
69
|
+
host = "0.0.0.0",
|
|
70
|
+
maxBodyBytes = DEFAULT_MAX_BODY_BYTES
|
|
71
|
+
} = options;
|
|
72
|
+
const startTime = Date.now();
|
|
73
|
+
const server = (0, import_node_http.createServer)(async (req, res) => {
|
|
74
|
+
const pathname = new URL(req.url ?? "/", "http://localhost").pathname;
|
|
75
|
+
const method = req.method ?? "GET";
|
|
76
|
+
try {
|
|
77
|
+
if (options.authenticate) {
|
|
78
|
+
const authed = await options.authenticate(req);
|
|
79
|
+
if (authed !== true) {
|
|
80
|
+
sendJson(res, 401, { error: "Unauthorized" });
|
|
81
|
+
return;
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
if (method === "GET" && pathname === "/health") {
|
|
85
|
+
const body = {
|
|
86
|
+
status: "ok",
|
|
87
|
+
rulesLoaded: engine.getRules().length,
|
|
88
|
+
uptime: Date.now() - startTime
|
|
89
|
+
};
|
|
90
|
+
sendJson(res, 200, body);
|
|
91
|
+
return;
|
|
92
|
+
}
|
|
93
|
+
if (method === "GET" && pathname === "/rules") {
|
|
94
|
+
const rules = engine.getRules().map((r) => ({
|
|
95
|
+
id: r.id,
|
|
96
|
+
effect: r.effect,
|
|
97
|
+
roles: r.roles,
|
|
98
|
+
actions: r.actions,
|
|
99
|
+
resources: r.resources,
|
|
100
|
+
priority: r.priority,
|
|
101
|
+
description: r.description,
|
|
102
|
+
hasConditions: (r.conditions?.length ?? 0) > 0
|
|
103
|
+
}));
|
|
104
|
+
sendJson(res, 200, { rules });
|
|
105
|
+
return;
|
|
106
|
+
}
|
|
107
|
+
if (method === "POST" && pathname === "/evaluate") {
|
|
108
|
+
let raw;
|
|
109
|
+
try {
|
|
110
|
+
raw = await readBody(req, maxBodyBytes);
|
|
111
|
+
} catch (err) {
|
|
112
|
+
sendJson(res, 413, {
|
|
113
|
+
error: err instanceof Error ? err.message : "Payload too large"
|
|
114
|
+
});
|
|
115
|
+
return;
|
|
116
|
+
}
|
|
117
|
+
let body;
|
|
118
|
+
try {
|
|
119
|
+
body = JSON.parse(raw);
|
|
120
|
+
} catch {
|
|
121
|
+
sendJson(res, 400, { error: "Invalid JSON body" });
|
|
122
|
+
return;
|
|
123
|
+
}
|
|
124
|
+
if (!body.subject || !body.action || !body.resource) {
|
|
125
|
+
sendJson(res, 400, {
|
|
126
|
+
error: "Missing required fields: subject, action, resource"
|
|
127
|
+
});
|
|
128
|
+
return;
|
|
129
|
+
}
|
|
130
|
+
if (typeof body.action !== "string" || typeof body.resource !== "string") {
|
|
131
|
+
sendJson(res, 400, { error: "action and resource must be strings" });
|
|
132
|
+
return;
|
|
133
|
+
}
|
|
134
|
+
if (typeof body.subject !== "object" || typeof body.subject.id !== "string" || !Array.isArray(body.subject.roles)) {
|
|
135
|
+
sendJson(res, 400, {
|
|
136
|
+
error: "subject must have a string id and a roles array"
|
|
137
|
+
});
|
|
138
|
+
return;
|
|
139
|
+
}
|
|
140
|
+
const subject = options.resolveSubject ? await options.resolveSubject(body) : body.subject;
|
|
141
|
+
const decision = engine.evaluate(
|
|
142
|
+
subject,
|
|
143
|
+
body.action,
|
|
144
|
+
body.resource,
|
|
145
|
+
body.resourceContext ?? {},
|
|
146
|
+
body.tenantId
|
|
147
|
+
);
|
|
148
|
+
const response = {
|
|
149
|
+
allowed: decision.allowed,
|
|
150
|
+
effect: decision.effect,
|
|
151
|
+
reason: decision.reason,
|
|
152
|
+
matchedRuleId: decision.matchedRule?.id ?? null,
|
|
153
|
+
durationMs: decision.durationMs
|
|
154
|
+
};
|
|
155
|
+
sendJson(res, 200, response);
|
|
156
|
+
return;
|
|
157
|
+
}
|
|
158
|
+
sendJson(res, 404, { error: "Not found" });
|
|
159
|
+
} catch (err) {
|
|
160
|
+
const message = err instanceof Error ? err.message : "Internal server error";
|
|
161
|
+
sendJson(res, 500, { error: message });
|
|
162
|
+
}
|
|
163
|
+
});
|
|
164
|
+
return {
|
|
165
|
+
start() {
|
|
166
|
+
return new Promise((resolve) => {
|
|
167
|
+
server.listen(port, host, () => {
|
|
168
|
+
resolve();
|
|
169
|
+
});
|
|
170
|
+
});
|
|
171
|
+
},
|
|
172
|
+
stop() {
|
|
173
|
+
return new Promise((resolve, reject) => {
|
|
174
|
+
server.close((err) => err ? reject(err) : resolve());
|
|
175
|
+
});
|
|
176
|
+
},
|
|
177
|
+
httpServer: server
|
|
178
|
+
};
|
|
179
|
+
}
|
|
180
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
181
|
+
0 && (module.exports = {
|
|
182
|
+
createAuthServer
|
|
183
|
+
});
|
|
184
|
+
//# sourceMappingURL=server.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/server.ts"],"sourcesContent":["import { createServer, type IncomingMessage, type ServerResponse } from \"node:http\";\nimport type { AccessEngine } from \"./engine.js\";\nimport type { SchemaDefinition, Subject, ResourceContext } from \"./types.js\";\n\nconst DEFAULT_MAX_BODY_BYTES = 1024 * 1024; // 1 MB\n\n// ---------------------------------------------------------------------------\n// Standalone HTTP authorization server\n// ---------------------------------------------------------------------------\n\nexport interface ServerOptions<S extends SchemaDefinition> {\n engine: AccessEngine<S>;\n port?: number;\n host?: string;\n /**\n * Optional hook to resolve a Subject from the request body.\n * Defaults to using body.subject directly.\n */\n resolveSubject?: (body: EvalRequestBody) => Subject<S> | Promise<Subject<S>>;\n /**\n * Optional authentication hook. Return true to allow the request,\n * false to reject with 401. Called before any endpoint logic.\n * If not provided, all requests are allowed (suitable for internal networks only).\n */\n authenticate?: (req: IncomingMessage) => boolean | Promise<boolean>;\n /** Maximum request body size in bytes. Defaults to 1 MB. */\n maxBodyBytes?: number;\n}\n\nexport interface EvalRequestBody {\n subject: {\n id: string;\n roles: { role: string; tenantId?: string }[];\n attributes?: Record<string, unknown>;\n };\n action: string;\n resource: string;\n resourceContext?: ResourceContext;\n tenantId?: string;\n}\n\ninterface EvalResponseBody {\n allowed: boolean;\n effect: string;\n reason: string;\n matchedRuleId: string | null;\n durationMs: number;\n}\n\ninterface HealthResponse {\n status: \"ok\";\n rulesLoaded: number;\n uptime: number;\n}\n\nfunction readBody(req: IncomingMessage, maxBytes: number): Promise<string> {\n return new Promise((resolve, reject) => {\n const chunks: Buffer[] = [];\n let received = 0;\n let settled = false;\n\n function settle(fn: () => void) {\n if (!settled) {\n settled = true;\n fn();\n }\n }\n\n req.on(\"data\", (chunk: Buffer) => {\n if (settled) return;\n received += chunk.length;\n if (received > maxBytes) {\n req.resume();\n settle(() => reject(new Error(`Request body exceeds ${maxBytes} bytes`)));\n return;\n }\n chunks.push(chunk);\n });\n req.on(\"end\", () => {\n settle(() => resolve(Buffer.concat(chunks).toString(\"utf-8\")));\n });\n req.on(\"error\", (err) => {\n settle(() => reject(err));\n });\n });\n}\n\nfunction sendJson(res: ServerResponse, status: number, body: unknown): void {\n const json = JSON.stringify(body);\n res.writeHead(status, {\n \"Content-Type\": \"application/json\",\n \"Content-Length\": Buffer.byteLength(json),\n });\n res.end(json);\n}\n\n/**\n * Create a standalone HTTP authorization server.\n *\n * Endpoints:\n * POST /evaluate — evaluate an authorization request\n * GET /health — health check + rule count\n * GET /rules — list all loaded rules (without conditions)\n *\n * **Security**: This server has no authentication by default.\n * In production, provide an `authenticate` hook or run behind a VPN/service mesh.\n */\nexport function createAuthServer<S extends SchemaDefinition>(\n options: ServerOptions<S>,\n) {\n const {\n engine,\n port = 3100,\n host = \"0.0.0.0\",\n maxBodyBytes = DEFAULT_MAX_BODY_BYTES,\n } = options;\n const startTime = Date.now();\n\n const server = createServer(async (req, res) => {\n const pathname = new URL(req.url ?? \"/\", \"http://localhost\").pathname;\n const method = req.method ?? \"GET\";\n\n try {\n if (options.authenticate) {\n const authed = await options.authenticate(req);\n if (authed !== true) {\n sendJson(res, 401, { error: \"Unauthorized\" });\n return;\n }\n }\n\n if (method === \"GET\" && pathname === \"/health\") {\n const body: HealthResponse = {\n status: \"ok\",\n rulesLoaded: engine.getRules().length,\n uptime: Date.now() - startTime,\n };\n sendJson(res, 200, body);\n return;\n }\n\n if (method === \"GET\" && pathname === \"/rules\") {\n const rules = engine.getRules().map((r) => ({\n id: r.id,\n effect: r.effect,\n roles: r.roles,\n actions: r.actions,\n resources: r.resources,\n priority: r.priority,\n description: r.description,\n hasConditions: (r.conditions?.length ?? 0) > 0,\n }));\n sendJson(res, 200, { rules });\n return;\n }\n\n if (method === \"POST\" && pathname === \"/evaluate\") {\n let raw: string;\n try {\n raw = await readBody(req, maxBodyBytes);\n } catch (err) {\n sendJson(res, 413, {\n error: err instanceof Error ? err.message : \"Payload too large\",\n });\n return;\n }\n\n let body: EvalRequestBody;\n try {\n body = JSON.parse(raw);\n } catch {\n sendJson(res, 400, { error: \"Invalid JSON body\" });\n return;\n }\n\n if (!body.subject || !body.action || !body.resource) {\n sendJson(res, 400, {\n error: \"Missing required fields: subject, action, resource\",\n });\n return;\n }\n\n if (typeof body.action !== \"string\" || typeof body.resource !== \"string\") {\n sendJson(res, 400, { error: \"action and resource must be strings\" });\n return;\n }\n\n if (\n typeof body.subject !== \"object\" ||\n typeof body.subject.id !== \"string\" ||\n !Array.isArray(body.subject.roles)\n ) {\n sendJson(res, 400, {\n error: \"subject must have a string id and a roles array\",\n });\n return;\n }\n\n const subject: Subject<S> = options.resolveSubject\n ? await options.resolveSubject(body)\n : (body.subject as unknown as Subject<S>);\n\n const decision = engine.evaluate(\n subject,\n body.action as Parameters<typeof engine.evaluate>[1],\n body.resource as Parameters<typeof engine.evaluate>[2],\n body.resourceContext ?? {},\n body.tenantId,\n );\n\n const response: EvalResponseBody = {\n allowed: decision.allowed,\n effect: decision.effect,\n reason: decision.reason,\n matchedRuleId: decision.matchedRule?.id ?? null,\n durationMs: decision.durationMs,\n };\n sendJson(res, 200, response);\n return;\n }\n\n sendJson(res, 404, { error: \"Not found\" });\n } catch (err) {\n const message = err instanceof Error ? err.message : \"Internal server error\";\n sendJson(res, 500, { error: message });\n }\n });\n\n return {\n start(): Promise<void> {\n return new Promise((resolve) => {\n server.listen(port, host, () => {\n resolve();\n });\n });\n },\n\n stop(): Promise<void> {\n return new Promise((resolve, reject) => {\n server.close((err) => (err ? reject(err) : resolve()));\n });\n },\n\n httpServer: server,\n };\n}\n"],"mappings":"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","names":[]}
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import * as http from 'http';
|
|
2
|
+
import { IncomingMessage, ServerResponse } from 'node:http';
|
|
3
|
+
import { R as ResourceContext, S as SchemaDefinition, A as AccessEngine, r as Subject } from './engine-C6IASR5F.cjs';
|
|
4
|
+
|
|
5
|
+
interface ServerOptions<S extends SchemaDefinition> {
|
|
6
|
+
engine: AccessEngine<S>;
|
|
7
|
+
port?: number;
|
|
8
|
+
host?: string;
|
|
9
|
+
/**
|
|
10
|
+
* Optional hook to resolve a Subject from the request body.
|
|
11
|
+
* Defaults to using body.subject directly.
|
|
12
|
+
*/
|
|
13
|
+
resolveSubject?: (body: EvalRequestBody) => Subject<S> | Promise<Subject<S>>;
|
|
14
|
+
/**
|
|
15
|
+
* Optional authentication hook. Return true to allow the request,
|
|
16
|
+
* false to reject with 401. Called before any endpoint logic.
|
|
17
|
+
* If not provided, all requests are allowed (suitable for internal networks only).
|
|
18
|
+
*/
|
|
19
|
+
authenticate?: (req: IncomingMessage) => boolean | Promise<boolean>;
|
|
20
|
+
/** Maximum request body size in bytes. Defaults to 1 MB. */
|
|
21
|
+
maxBodyBytes?: number;
|
|
22
|
+
}
|
|
23
|
+
interface EvalRequestBody {
|
|
24
|
+
subject: {
|
|
25
|
+
id: string;
|
|
26
|
+
roles: {
|
|
27
|
+
role: string;
|
|
28
|
+
tenantId?: string;
|
|
29
|
+
}[];
|
|
30
|
+
attributes?: Record<string, unknown>;
|
|
31
|
+
};
|
|
32
|
+
action: string;
|
|
33
|
+
resource: string;
|
|
34
|
+
resourceContext?: ResourceContext;
|
|
35
|
+
tenantId?: string;
|
|
36
|
+
}
|
|
37
|
+
/**
|
|
38
|
+
* Create a standalone HTTP authorization server.
|
|
39
|
+
*
|
|
40
|
+
* Endpoints:
|
|
41
|
+
* POST /evaluate — evaluate an authorization request
|
|
42
|
+
* GET /health — health check + rule count
|
|
43
|
+
* GET /rules — list all loaded rules (without conditions)
|
|
44
|
+
*
|
|
45
|
+
* **Security**: This server has no authentication by default.
|
|
46
|
+
* In production, provide an `authenticate` hook or run behind a VPN/service mesh.
|
|
47
|
+
*/
|
|
48
|
+
declare function createAuthServer<S extends SchemaDefinition>(options: ServerOptions<S>): {
|
|
49
|
+
start(): Promise<void>;
|
|
50
|
+
stop(): Promise<void>;
|
|
51
|
+
httpServer: http.Server<typeof IncomingMessage, typeof ServerResponse>;
|
|
52
|
+
};
|
|
53
|
+
|
|
54
|
+
export { type EvalRequestBody, type ServerOptions, createAuthServer };
|