@entifix/service-shell 0.4.2 → 0.6.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 +15 -3
- package/dist/auth/index.d.ts +1 -1
- package/dist/auth/index.d.ts.map +1 -1
- package/dist/auth/index.js +1 -1
- package/dist/auth/index.js.map +1 -1
- package/dist/auth/require-organization.d.ts +2 -4
- package/dist/auth/require-organization.d.ts.map +1 -1
- package/dist/auth/require-organization.js +6 -8
- package/dist/auth/require-organization.js.map +1 -1
- package/dist/auth/require-principal.d.ts +1 -1
- package/dist/auth/require-principal.js.map +1 -1
- package/dist/auth/require-service-crossing.d.ts +7 -9
- package/dist/auth/require-service-crossing.d.ts.map +1 -1
- package/dist/auth/require-service-crossing.js +7 -9
- package/dist/auth/require-service-crossing.js.map +1 -1
- package/dist/auth/shared-token.d.ts +34 -0
- package/dist/auth/shared-token.d.ts.map +1 -0
- package/dist/auth/shared-token.js +39 -0
- package/dist/auth/shared-token.js.map +1 -0
- package/dist/entity-metadata-route.d.ts +1 -2
- package/dist/entity-metadata-route.d.ts.map +1 -1
- package/dist/entity-metadata-route.js +1 -2
- package/dist/entity-metadata-route.js.map +1 -1
- package/dist/index.d.ts +0 -5
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +0 -5
- package/dist/index.js.map +1 -1
- package/dist/load-remote-configuration.d.ts +22 -10
- package/dist/load-remote-configuration.d.ts.map +1 -1
- package/dist/load-remote-configuration.js +15 -20
- package/dist/load-remote-configuration.js.map +1 -1
- package/dist/make-service.d.ts +13 -4
- package/dist/make-service.d.ts.map +1 -1
- package/dist/make-service.js +3 -3
- package/dist/make-service.js.map +1 -1
- package/dist/runtime.d.ts +12 -0
- package/dist/runtime.d.ts.map +1 -0
- package/dist/runtime.js +12 -0
- package/dist/runtime.js.map +1 -0
- package/dist/service-description-route.d.ts +7 -6
- package/dist/service-description-route.d.ts.map +1 -1
- package/dist/service-description-route.js +5 -5
- package/dist/service-description-route.js.map +1 -1
- package/dist/sse.d.ts +2 -2
- package/dist/sse.js +1 -1
- package/dist/sse.js.map +1 -1
- package/dist/testing.d.ts +7 -0
- package/dist/testing.d.ts.map +1 -0
- package/dist/testing.js +7 -0
- package/dist/testing.js.map +1 -0
- package/package.json +58 -10
- package/dist/auth/service-token.d.ts +0 -37
- package/dist/auth/service-token.d.ts.map +0 -1
- package/dist/auth/service-token.js +0 -62
- package/dist/auth/service-token.js.map +0 -1
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@entifix/service-shell",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.6.0",
|
|
4
4
|
"description": "Effect HTTP service skeleton: server layer, health and metadata routes, permission guards, remote configuration and OpenTelemetry.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"repository": {
|
|
@@ -26,6 +26,21 @@
|
|
|
26
26
|
"types": "./dist/index.d.ts",
|
|
27
27
|
"import": "./dist/index.js",
|
|
28
28
|
"default": "./dist/index.js"
|
|
29
|
+
},
|
|
30
|
+
"./runtime": {
|
|
31
|
+
"types": "./dist/runtime.d.ts",
|
|
32
|
+
"import": "./dist/runtime.js",
|
|
33
|
+
"default": "./dist/runtime.js"
|
|
34
|
+
},
|
|
35
|
+
"./observability": {
|
|
36
|
+
"types": "./dist/observability.d.ts",
|
|
37
|
+
"import": "./dist/observability.js",
|
|
38
|
+
"default": "./dist/observability.js"
|
|
39
|
+
},
|
|
40
|
+
"./testing": {
|
|
41
|
+
"types": "./dist/testing.d.ts",
|
|
42
|
+
"import": "./dist/testing.js",
|
|
43
|
+
"default": "./dist/testing.js"
|
|
29
44
|
}
|
|
30
45
|
},
|
|
31
46
|
"nx": {
|
|
@@ -50,26 +65,59 @@
|
|
|
50
65
|
}
|
|
51
66
|
},
|
|
52
67
|
"dependencies": {
|
|
68
|
+
"@swc/helpers": "0.5.23",
|
|
69
|
+
"@entifix/business": "0.6.0",
|
|
70
|
+
"@entifix/core": "0.6.0",
|
|
71
|
+
"@entifix/tooling": "0.6.0",
|
|
72
|
+
"@entifix/authz": "0.6.0"
|
|
73
|
+
},
|
|
74
|
+
"devDependencies": {
|
|
53
75
|
"@effect/opentelemetry": "4.0.5",
|
|
54
76
|
"@effect/platform-node": "4.0.5",
|
|
77
|
+
"@opentelemetry/api": "1.9.1",
|
|
55
78
|
"@opentelemetry/context-async-hooks": "2.12.0",
|
|
56
79
|
"@opentelemetry/exporter-metrics-otlp-http": "0.223.0",
|
|
57
80
|
"@opentelemetry/exporter-trace-otlp-http": "0.223.0",
|
|
58
81
|
"@opentelemetry/sdk-metrics": "2.12.0",
|
|
59
82
|
"@opentelemetry/sdk-trace-base": "2.12.0",
|
|
60
|
-
"@swc/helpers": "0.5.23",
|
|
61
|
-
"@entifix/authz": "0.4.2",
|
|
62
|
-
"@entifix/business": "0.4.2",
|
|
63
|
-
"@entifix/core": "0.4.2",
|
|
64
|
-
"@entifix/tooling": "0.4.2"
|
|
65
|
-
},
|
|
66
|
-
"devDependencies": {
|
|
67
|
-
"@opentelemetry/api": "1.9.1",
|
|
68
83
|
"effect": "4.0.5",
|
|
69
|
-
"@entifix/testing-unit": "0.
|
|
84
|
+
"@entifix/testing-unit": "0.6.0"
|
|
70
85
|
},
|
|
71
86
|
"peerDependencies": {
|
|
87
|
+
"@effect/opentelemetry": "^4.0.5",
|
|
88
|
+
"@effect/platform-node": "^4.0.5",
|
|
72
89
|
"@opentelemetry/api": ">=1.9 <2",
|
|
90
|
+
"@opentelemetry/context-async-hooks": ">=2.12 <3",
|
|
91
|
+
"@opentelemetry/exporter-metrics-otlp-http": ">=0.223 <0.224",
|
|
92
|
+
"@opentelemetry/exporter-trace-otlp-http": ">=0.223 <0.224",
|
|
93
|
+
"@opentelemetry/sdk-metrics": ">=2.12 <3",
|
|
94
|
+
"@opentelemetry/sdk-trace-base": ">=2.12 <3",
|
|
73
95
|
"effect": "^4.0.5"
|
|
96
|
+
},
|
|
97
|
+
"peerDependenciesMeta": {
|
|
98
|
+
"@effect/opentelemetry": {
|
|
99
|
+
"optional": true
|
|
100
|
+
},
|
|
101
|
+
"@effect/platform-node": {
|
|
102
|
+
"optional": true
|
|
103
|
+
},
|
|
104
|
+
"@opentelemetry/api": {
|
|
105
|
+
"optional": true
|
|
106
|
+
},
|
|
107
|
+
"@opentelemetry/context-async-hooks": {
|
|
108
|
+
"optional": true
|
|
109
|
+
},
|
|
110
|
+
"@opentelemetry/exporter-metrics-otlp-http": {
|
|
111
|
+
"optional": true
|
|
112
|
+
},
|
|
113
|
+
"@opentelemetry/exporter-trace-otlp-http": {
|
|
114
|
+
"optional": true
|
|
115
|
+
},
|
|
116
|
+
"@opentelemetry/sdk-metrics": {
|
|
117
|
+
"optional": true
|
|
118
|
+
},
|
|
119
|
+
"@opentelemetry/sdk-trace-base": {
|
|
120
|
+
"optional": true
|
|
121
|
+
}
|
|
74
122
|
}
|
|
75
123
|
}
|
|
@@ -1,37 +0,0 @@
|
|
|
1
|
-
import { Effect } from 'effect';
|
|
2
|
-
import { HttpServerRequest, HttpServerResponse } from 'effect/http';
|
|
3
|
-
/** The header a caller of config-service's fleet lookup presents. */
|
|
4
|
-
export declare const SERVICE_TOKEN_HEADER = "x-service-token";
|
|
5
|
-
/**
|
|
6
|
-
* Local-dev default, so `pnpm run back-office:dev` stays zero-config. Same
|
|
7
|
-
* convention as the seeded `jwt.secret`: obviously a placeholder, replaced per
|
|
8
|
-
* environment through `CONFIG_SERVICE_TOKEN`.
|
|
9
|
-
*/
|
|
10
|
-
export declare const DEV_SERVICE_TOKEN = "dev-config-service-token-change-me";
|
|
11
|
-
/**
|
|
12
|
-
* The expected token, read from the environment on each call rather than captured
|
|
13
|
-
* at module load — so a test can set it without re-importing the module.
|
|
14
|
-
*/
|
|
15
|
-
export declare const serviceToken: () => string;
|
|
16
|
-
/**
|
|
17
|
-
* Guards config-service's fleet configuration lookup.
|
|
18
|
-
*
|
|
19
|
-
* `GET /api/config/:service` hands out `jwt.secret`, `mongo.uri`, `redis.uri` and
|
|
20
|
-
* `rabbitmq.uri` in plaintext, because that is exactly what a booting service
|
|
21
|
-
* needs — the values cannot be redacted here without breaking the boot the route
|
|
22
|
-
* exists to serve. So the route is gated on a shared token instead, which closes
|
|
23
|
-
* the drive-by read that being merely "cluster-internal" did not.
|
|
24
|
-
*
|
|
25
|
-
* This is deliberately **not** service identity: every caller presents the same
|
|
26
|
-
* token, so it proves fleet membership and nothing more. Per-service credentials,
|
|
27
|
-
* with the response scoped to the service that asked, are separate work; this is
|
|
28
|
-
* the cheap step that stops the port from being self-serve.
|
|
29
|
-
*
|
|
30
|
-
* It lives here beside {@link loadRemoteConfiguration}, the client half of the
|
|
31
|
-
* same protocol, so the header and the token cannot drift apart.
|
|
32
|
-
*
|
|
33
|
-
* Health endpoints are **not** guarded — a probe must answer without credentials,
|
|
34
|
-
* or a readiness check becomes an authentication problem.
|
|
35
|
-
*/
|
|
36
|
-
export declare const requireServiceToken: <A, E, R>(route: Effect.Effect<A, E, R>) => Effect.Effect<HttpServerResponse.HttpServerResponse | A, import("effect/http/HttpBody").HttpBodyError | E, HttpServerRequest.HttpServerRequest | R>;
|
|
37
|
-
//# sourceMappingURL=service-token.d.ts.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"service-token.d.ts","sourceRoot":"","sources":["../../src/auth/service-token.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,MAAM,EAAE,MAAM,QAAQ,CAAC;AAChC,OAAO,EAAE,iBAAiB,EAAE,kBAAkB,EAAE,MAAM,aAAa,CAAC;AAEpE,qEAAqE;AACrE,eAAO,MAAM,oBAAoB,oBAAoB,CAAC;AAEtD;;;;GAIG;AACH,eAAO,MAAM,iBAAiB,uCAAuC,CAAC;AAEtE;;;GAGG;AACH,eAAO,MAAM,YAAY,QAAO,MACuB,CAAC;AAYxD;;;;;;;;;;;;;;;;;;;GAmBG;AACH,eAAO,MAAM,mBAAmB,GAAI,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,OAAO,MAAM,CAAC,MAAM,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC,CAAC,wJAiBtE,CAAC"}
|
|
@@ -1,62 +0,0 @@
|
|
|
1
|
-
import { timingSafeEqual } from 'node:crypto';
|
|
2
|
-
import { Effect } from 'effect';
|
|
3
|
-
import { HttpServerRequest, HttpServerResponse } from 'effect/http';
|
|
4
|
-
/** The header a caller of config-service's fleet lookup presents. */ export const SERVICE_TOKEN_HEADER = 'x-service-token';
|
|
5
|
-
/**
|
|
6
|
-
* Local-dev default, so `pnpm run back-office:dev` stays zero-config. Same
|
|
7
|
-
* convention as the seeded `jwt.secret`: obviously a placeholder, replaced per
|
|
8
|
-
* environment through `CONFIG_SERVICE_TOKEN`.
|
|
9
|
-
*/ export const DEV_SERVICE_TOKEN = 'dev-config-service-token-change-me';
|
|
10
|
-
/**
|
|
11
|
-
* The expected token, read from the environment on each call rather than captured
|
|
12
|
-
* at module load — so a test can set it without re-importing the module.
|
|
13
|
-
*/ export const serviceToken = ()=>{
|
|
14
|
-
var _process_env_CONFIG_SERVICE_TOKEN;
|
|
15
|
-
return (_process_env_CONFIG_SERVICE_TOKEN = process.env.CONFIG_SERVICE_TOKEN) != null ? _process_env_CONFIG_SERVICE_TOKEN : DEV_SERVICE_TOKEN;
|
|
16
|
-
};
|
|
17
|
-
/** Constant-time comparison, so the token cannot be recovered byte by byte. */ const matches = (provided)=>{
|
|
18
|
-
if (provided === undefined) return false;
|
|
19
|
-
const left = Buffer.from(provided);
|
|
20
|
-
const right = Buffer.from(serviceToken());
|
|
21
|
-
// `timingSafeEqual` throws on a length mismatch, so the lengths are compared
|
|
22
|
-
// first — the length of a shared secret is not what protects it.
|
|
23
|
-
return left.length === right.length && timingSafeEqual(left, right);
|
|
24
|
-
};
|
|
25
|
-
/**
|
|
26
|
-
* Guards config-service's fleet configuration lookup.
|
|
27
|
-
*
|
|
28
|
-
* `GET /api/config/:service` hands out `jwt.secret`, `mongo.uri`, `redis.uri` and
|
|
29
|
-
* `rabbitmq.uri` in plaintext, because that is exactly what a booting service
|
|
30
|
-
* needs — the values cannot be redacted here without breaking the boot the route
|
|
31
|
-
* exists to serve. So the route is gated on a shared token instead, which closes
|
|
32
|
-
* the drive-by read that being merely "cluster-internal" did not.
|
|
33
|
-
*
|
|
34
|
-
* This is deliberately **not** service identity: every caller presents the same
|
|
35
|
-
* token, so it proves fleet membership and nothing more. Per-service credentials,
|
|
36
|
-
* with the response scoped to the service that asked, are separate work; this is
|
|
37
|
-
* the cheap step that stops the port from being self-serve.
|
|
38
|
-
*
|
|
39
|
-
* It lives here beside {@link loadRemoteConfiguration}, the client half of the
|
|
40
|
-
* same protocol, so the header and the token cannot drift apart.
|
|
41
|
-
*
|
|
42
|
-
* Health endpoints are **not** guarded — a probe must answer without credentials,
|
|
43
|
-
* or a readiness check becomes an authentication problem.
|
|
44
|
-
*/ export const requireServiceToken = (route)=>// The return type is inferred rather than written out: `HttpServerResponse.json`
|
|
45
|
-
// contributes an `HttpBodyError` to the failure channel, and spelling the type
|
|
46
|
-
// out while omitting it is what makes `tsc` refuse the declaration emit — which
|
|
47
|
-
// `@nx/js:swc` hides, leaving a green build with no `.d.ts` and a TS6305
|
|
48
|
-
// cascade in every consumer. `requirePrincipal` infers for the same reason.
|
|
49
|
-
Effect.gen(function*() {
|
|
50
|
-
const request = yield* HttpServerRequest.HttpServerRequest;
|
|
51
|
-
if (!matches(request.headers[SERVICE_TOKEN_HEADER])) {
|
|
52
|
-
return yield* HttpServerResponse.json({
|
|
53
|
-
error: 'unauthenticated',
|
|
54
|
-
code: 'unauthenticated'
|
|
55
|
-
}, {
|
|
56
|
-
status: 401
|
|
57
|
-
});
|
|
58
|
-
}
|
|
59
|
-
return yield* route;
|
|
60
|
-
});
|
|
61
|
-
|
|
62
|
-
//# sourceMappingURL=service-token.js.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/auth/service-token.ts"],"sourcesContent":["import { timingSafeEqual } from 'node:crypto';\n\nimport { Effect } from 'effect';\nimport { HttpServerRequest, HttpServerResponse } from 'effect/http';\n\n/** The header a caller of config-service's fleet lookup presents. */\nexport const SERVICE_TOKEN_HEADER = 'x-service-token';\n\n/**\n * Local-dev default, so `pnpm run back-office:dev` stays zero-config. Same\n * convention as the seeded `jwt.secret`: obviously a placeholder, replaced per\n * environment through `CONFIG_SERVICE_TOKEN`.\n */\nexport const DEV_SERVICE_TOKEN = 'dev-config-service-token-change-me';\n\n/**\n * The expected token, read from the environment on each call rather than captured\n * at module load — so a test can set it without re-importing the module.\n */\nexport const serviceToken = (): string =>\n process.env.CONFIG_SERVICE_TOKEN ?? DEV_SERVICE_TOKEN;\n\n/** Constant-time comparison, so the token cannot be recovered byte by byte. */\nconst matches = (provided: string | undefined): boolean => {\n if (provided === undefined) return false;\n const left = Buffer.from(provided);\n const right = Buffer.from(serviceToken());\n // `timingSafeEqual` throws on a length mismatch, so the lengths are compared\n // first — the length of a shared secret is not what protects it.\n return left.length === right.length && timingSafeEqual(left, right);\n};\n\n/**\n * Guards config-service's fleet configuration lookup.\n *\n * `GET /api/config/:service` hands out `jwt.secret`, `mongo.uri`, `redis.uri` and\n * `rabbitmq.uri` in plaintext, because that is exactly what a booting service\n * needs — the values cannot be redacted here without breaking the boot the route\n * exists to serve. So the route is gated on a shared token instead, which closes\n * the drive-by read that being merely \"cluster-internal\" did not.\n *\n * This is deliberately **not** service identity: every caller presents the same\n * token, so it proves fleet membership and nothing more. Per-service credentials,\n * with the response scoped to the service that asked, are separate work; this is\n * the cheap step that stops the port from being self-serve.\n *\n * It lives here beside {@link loadRemoteConfiguration}, the client half of the\n * same protocol, so the header and the token cannot drift apart.\n *\n * Health endpoints are **not** guarded — a probe must answer without credentials,\n * or a readiness check becomes an authentication problem.\n */\nexport const requireServiceToken = <A, E, R>(route: Effect.Effect<A, E, R>) =>\n // The return type is inferred rather than written out: `HttpServerResponse.json`\n // contributes an `HttpBodyError` to the failure channel, and spelling the type\n // out while omitting it is what makes `tsc` refuse the declaration emit — which\n // `@nx/js:swc` hides, leaving a green build with no `.d.ts` and a TS6305\n // cascade in every consumer. `requirePrincipal` infers for the same reason.\n Effect.gen(function* () {\n const request = yield* HttpServerRequest.HttpServerRequest;\n\n if (!matches(request.headers[SERVICE_TOKEN_HEADER])) {\n return yield* HttpServerResponse.json(\n { error: 'unauthenticated', code: 'unauthenticated' },\n { status: 401 },\n );\n }\n\n return yield* route;\n });\n"],"names":["timingSafeEqual","Effect","HttpServerRequest","HttpServerResponse","SERVICE_TOKEN_HEADER","DEV_SERVICE_TOKEN","serviceToken","process","env","CONFIG_SERVICE_TOKEN","matches","provided","undefined","left","Buffer","from","right","length","requireServiceToken","route","gen","request","headers","json","error","code","status"],"mappings":"AAAA,SAASA,eAAe,QAAQ,cAAc;AAE9C,SAASC,MAAM,QAAQ,SAAS;AAChC,SAASC,iBAAiB,EAAEC,kBAAkB,QAAQ,cAAc;AAEpE,mEAAmE,GACnE,OAAO,MAAMC,uBAAuB,kBAAkB;AAEtD;;;;CAIC,GACD,OAAO,MAAMC,oBAAoB,qCAAqC;AAEtE;;;CAGC,GACD,OAAO,MAAMC,eAAe;QAC1BC;YAAAA,oCAAAA,QAAQC,GAAG,CAACC,oBAAoB,YAAhCF,oCAAoCF;EAAkB;AAExD,6EAA6E,GAC7E,MAAMK,UAAU,CAACC;IACf,IAAIA,aAAaC,WAAW,OAAO;IACnC,MAAMC,OAAOC,OAAOC,IAAI,CAACJ;IACzB,MAAMK,QAAQF,OAAOC,IAAI,CAACT;IAC1B,6EAA6E;IAC7E,iEAAiE;IACjE,OAAOO,KAAKI,MAAM,KAAKD,MAAMC,MAAM,IAAIjB,gBAAgBa,MAAMG;AAC/D;AAEA;;;;;;;;;;;;;;;;;;;CAmBC,GACD,OAAO,MAAME,sBAAsB,CAAUC,QAC3C,iFAAiF;IACjF,+EAA+E;IAC/E,gFAAgF;IAChF,yEAAyE;IACzE,4EAA4E;IAC5ElB,OAAOmB,GAAG,CAAC;QACT,MAAMC,UAAU,OAAOnB,kBAAkBA,iBAAiB;QAE1D,IAAI,CAACQ,QAAQW,QAAQC,OAAO,CAAClB,qBAAqB,GAAG;YACnD,OAAO,OAAOD,mBAAmBoB,IAAI,CACnC;gBAAEC,OAAO;gBAAmBC,MAAM;YAAkB,GACpD;gBAAEC,QAAQ;YAAI;QAElB;QAEA,OAAO,OAAOP;IAChB,GAAG"}
|