@softacus-software/elwis-mcp-core 0.1.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 +120 -0
- package/dist/cjs/app-scope.js +81 -0
- package/dist/cjs/app-scope.js.map +1 -0
- package/dist/cjs/conformance/host.conformance.js +98 -0
- package/dist/cjs/conformance/host.conformance.js.map +1 -0
- package/dist/cjs/conformance/read-port.conformance.js +131 -0
- package/dist/cjs/conformance/read-port.conformance.js.map +1 -0
- package/dist/cjs/errors.js +74 -0
- package/dist/cjs/errors.js.map +1 -0
- package/dist/cjs/host/elwis-jwt-verifier.js +40 -0
- package/dist/cjs/host/elwis-jwt-verifier.js.map +1 -0
- package/dist/cjs/host/file-link-store.js +60 -0
- package/dist/cjs/host/file-link-store.js.map +1 -0
- package/dist/cjs/host/identity.js +3 -0
- package/dist/cjs/host/identity.js.map +1 -0
- package/dist/cjs/host/index.js +24 -0
- package/dist/cjs/host/index.js.map +1 -0
- package/dist/cjs/host/link-ticket.js +59 -0
- package/dist/cjs/host/link-ticket.js.map +1 -0
- package/dist/cjs/host/oidc-token.js +96 -0
- package/dist/cjs/host/oidc-token.js.map +1 -0
- package/dist/cjs/host/oidc-verifier.js +67 -0
- package/dist/cjs/host/oidc-verifier.js.map +1 -0
- package/dist/cjs/host/serve-http.js +282 -0
- package/dist/cjs/host/serve-http.js.map +1 -0
- package/dist/cjs/host/session-broker.js +84 -0
- package/dist/cjs/host/session-broker.js.map +1 -0
- package/dist/cjs/index.js +43 -0
- package/dist/cjs/index.js.map +1 -0
- package/dist/cjs/package.json +3 -0
- package/dist/cjs/plan/expand.js +297 -0
- package/dist/cjs/plan/expand.js.map +1 -0
- package/dist/cjs/plan/resolve-existing.js +58 -0
- package/dist/cjs/plan/resolve-existing.js.map +1 -0
- package/dist/cjs/plan/split.js +92 -0
- package/dist/cjs/plan/split.js.map +1 -0
- package/dist/cjs/plan/target-state.js +120 -0
- package/dist/cjs/plan/target-state.js.map +1 -0
- package/dist/cjs/ports/elwis-api.port.js +15 -0
- package/dist/cjs/ports/elwis-api.port.js.map +1 -0
- package/dist/cjs/resolve.js +89 -0
- package/dist/cjs/resolve.js.map +1 -0
- package/dist/esm/app-scope.d.ts +26 -0
- package/dist/esm/app-scope.d.ts.map +1 -0
- package/dist/esm/app-scope.js +76 -0
- package/dist/esm/app-scope.js.map +1 -0
- package/dist/esm/conformance/host.conformance.d.ts +3 -0
- package/dist/esm/conformance/host.conformance.d.ts.map +1 -0
- package/dist/esm/conformance/host.conformance.js +95 -0
- package/dist/esm/conformance/host.conformance.js.map +1 -0
- package/dist/esm/conformance/read-port.conformance.d.ts +24 -0
- package/dist/esm/conformance/read-port.conformance.d.ts.map +1 -0
- package/dist/esm/conformance/read-port.conformance.js +127 -0
- package/dist/esm/conformance/read-port.conformance.js.map +1 -0
- package/dist/esm/errors.d.ts +40 -0
- package/dist/esm/errors.d.ts.map +1 -0
- package/dist/esm/errors.js +63 -0
- package/dist/esm/errors.js.map +1 -0
- package/dist/esm/host/elwis-jwt-verifier.d.ts +9 -0
- package/dist/esm/host/elwis-jwt-verifier.d.ts.map +1 -0
- package/dist/esm/host/elwis-jwt-verifier.js +37 -0
- package/dist/esm/host/elwis-jwt-verifier.js.map +1 -0
- package/dist/esm/host/file-link-store.d.ts +13 -0
- package/dist/esm/host/file-link-store.d.ts.map +1 -0
- package/dist/esm/host/file-link-store.js +56 -0
- package/dist/esm/host/file-link-store.js.map +1 -0
- package/dist/esm/host/identity.d.ts +13 -0
- package/dist/esm/host/identity.d.ts.map +1 -0
- package/dist/esm/host/identity.js +2 -0
- package/dist/esm/host/identity.js.map +1 -0
- package/dist/esm/host/index.d.ts +15 -0
- package/dist/esm/host/index.d.ts.map +1 -0
- package/dist/esm/host/index.js +9 -0
- package/dist/esm/host/index.js.map +1 -0
- package/dist/esm/host/link-ticket.d.ts +17 -0
- package/dist/esm/host/link-ticket.d.ts.map +1 -0
- package/dist/esm/host/link-ticket.js +54 -0
- package/dist/esm/host/link-ticket.js.map +1 -0
- package/dist/esm/host/oidc-token.d.ts +20 -0
- package/dist/esm/host/oidc-token.d.ts.map +1 -0
- package/dist/esm/host/oidc-token.js +93 -0
- package/dist/esm/host/oidc-token.js.map +1 -0
- package/dist/esm/host/oidc-verifier.d.ts +14 -0
- package/dist/esm/host/oidc-verifier.d.ts.map +1 -0
- package/dist/esm/host/oidc-verifier.js +63 -0
- package/dist/esm/host/oidc-verifier.js.map +1 -0
- package/dist/esm/host/serve-http.d.ts +42 -0
- package/dist/esm/host/serve-http.d.ts.map +1 -0
- package/dist/esm/host/serve-http.js +278 -0
- package/dist/esm/host/serve-http.js.map +1 -0
- package/dist/esm/host/session-broker.d.ts +45 -0
- package/dist/esm/host/session-broker.d.ts.map +1 -0
- package/dist/esm/host/session-broker.js +77 -0
- package/dist/esm/host/session-broker.js.map +1 -0
- package/dist/esm/index.d.ts +18 -0
- package/dist/esm/index.d.ts.map +1 -0
- package/dist/esm/index.js +10 -0
- package/dist/esm/index.js.map +1 -0
- package/dist/esm/package.json +3 -0
- package/dist/esm/plan/expand.d.ts +37 -0
- package/dist/esm/plan/expand.d.ts.map +1 -0
- package/dist/esm/plan/expand.js +294 -0
- package/dist/esm/plan/expand.js.map +1 -0
- package/dist/esm/plan/resolve-existing.d.ts +16 -0
- package/dist/esm/plan/resolve-existing.d.ts.map +1 -0
- package/dist/esm/plan/resolve-existing.js +55 -0
- package/dist/esm/plan/resolve-existing.js.map +1 -0
- package/dist/esm/plan/split.d.ts +14 -0
- package/dist/esm/plan/split.d.ts.map +1 -0
- package/dist/esm/plan/split.js +88 -0
- package/dist/esm/plan/split.js.map +1 -0
- package/dist/esm/plan/target-state.d.ts +27 -0
- package/dist/esm/plan/target-state.d.ts.map +1 -0
- package/dist/esm/plan/target-state.js +114 -0
- package/dist/esm/plan/target-state.js.map +1 -0
- package/dist/esm/ports/elwis-api.port.d.ts +56 -0
- package/dist/esm/ports/elwis-api.port.d.ts.map +1 -0
- package/dist/esm/ports/elwis-api.port.js +12 -0
- package/dist/esm/ports/elwis-api.port.js.map +1 -0
- package/dist/esm/resolve.d.ts +23 -0
- package/dist/esm/resolve.d.ts.map +1 -0
- package/dist/esm/resolve.js +79 -0
- package/dist/esm/resolve.js.map +1 -0
- package/package.json +55 -0
package/README.md
ADDED
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
# @softacus-software/elwis-mcp-core
|
|
2
|
+
|
|
3
|
+
Shared ELWIS domain logic — entity resolution, application scoping, and the access contract —
|
|
4
|
+
consumed by the ELWIS backend assistant and by the ELWIS MCP servers, so all three run the
|
|
5
|
+
same code instead of three divergent copies.
|
|
6
|
+
|
|
7
|
+
This package contains **no transport**. It never opens a socket, reads an environment
|
|
8
|
+
variable, or knows what MCP is. Everything it does goes through a port that the consumer
|
|
9
|
+
implements.
|
|
10
|
+
|
|
11
|
+
## The seam
|
|
12
|
+
|
|
13
|
+
Domain logic takes its data access as a parameter:
|
|
14
|
+
|
|
15
|
+
```ts
|
|
16
|
+
ResolveEntity(api, kind, applicationId, nameOrAlias)
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
`api` satisfies `ElwisReadPort`. The MCP servers implement it over HTTP; the backend
|
|
20
|
+
implements it in-process against tenant services. Same logic, different adapter.
|
|
21
|
+
|
|
22
|
+
```
|
|
23
|
+
ElwisReadPort whoAmI · systemAttributes · list · getOne · listRecords
|
|
24
|
+
ElwisWritePort create · update · softDelete · createArtifact · updateArtifact ·
|
|
25
|
+
softDeleteArtifact · createLinks
|
|
26
|
+
ElwisApiPort both
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
Only the read port is exercised by the current conformance suite. Write coverage lands with
|
|
30
|
+
the write slice.
|
|
31
|
+
|
|
32
|
+
## Install and build
|
|
33
|
+
|
|
34
|
+
```bash
|
|
35
|
+
npm install
|
|
36
|
+
npm run verify # build + tests + dual-module consumption check
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
`npm run build` emits `dist/esm` and `dist/cjs`, each stamped with its own `type` field.
|
|
40
|
+
|
|
41
|
+
**TypeScript is pinned to `~5.9.3` on purpose.** The ELWIS backend is on 5.9 and the Data MCP
|
|
42
|
+
on 6.0; declarations are emitted by the lower compiler so both consumers can read them.
|
|
43
|
+
|
|
44
|
+
**`main` and `types` are set alongside `exports` on purpose.** The backend compiles with
|
|
45
|
+
`module: commonjs` and no `moduleResolution`, which defaults to node10 — and node10 ignores
|
|
46
|
+
`exports` entirely. Removing the legacy fields silently breaks that consumer.
|
|
47
|
+
|
|
48
|
+
## The conformance suite
|
|
49
|
+
|
|
50
|
+
The point of this package is that two adapters behave identically. `RunReadPortConformance`
|
|
51
|
+
is how you prove it — the same cases run against the HTTP adapter and the in-process adapter.
|
|
52
|
+
|
|
53
|
+
```ts
|
|
54
|
+
import { RunReadPortConformance } from '@softacus-software/elwis-mcp-core';
|
|
55
|
+
|
|
56
|
+
const results = await RunReadPortConformance(myAdapter, {
|
|
57
|
+
applicationId, applicationName, kind: 'data-type', knownEntityName, knownEntityId,
|
|
58
|
+
});
|
|
59
|
+
const failed = results.filter(r => !r.passed);
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
It is framework-agnostic: no test runner is imported, cases return results rather than
|
|
63
|
+
asserting, so consumers can drive it from jest, node:test, or a script.
|
|
64
|
+
|
|
65
|
+
Every case carries a `why`. The load-bearing one is `excludes-soft-deleted`: the HTTP adapter
|
|
66
|
+
gets soft-delete filtering for free because it injects `deleted: null` into every filter, while
|
|
67
|
+
an adapter reading collections directly does not. Without that case, an in-process adapter
|
|
68
|
+
would quietly surface deleted entities into resolution and into user-facing pickers.
|
|
69
|
+
|
|
70
|
+
The suite is itself tested: `test/conformance.test.ts` runs it against a deliberately broken
|
|
71
|
+
adapter and asserts that it fails.
|
|
72
|
+
|
|
73
|
+
## Resolution behaviour
|
|
74
|
+
|
|
75
|
+
`ResolveEntity` and `ResolveOneId` are ported verbatim from `elwis-data-mcp`, including the
|
|
76
|
+
`$or`-to-name fallback for backends that reject `$or` with a 400. That fidelity is deliberate:
|
|
77
|
+
the extraction has to be provably behaviour-neutral before anything consumes it.
|
|
78
|
+
|
|
79
|
+
`ResolveOneStrict` is new and nothing calls it yet. It exists because the current assistant
|
|
80
|
+
answers a missed lookup with a raw validator message:
|
|
81
|
+
|
|
82
|
+
```
|
|
83
|
+
That answer won't work — Invalid string: must match pattern /^[a-f\d]{24}$/i
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
`ResolveOneStrict` instead throws `NotFoundError` carrying the entities that *do* exist, so a
|
|
87
|
+
caller can say "no data type called X here — did you mean one of these, or shall I create it?"
|
|
88
|
+
`AmbiguityError` covers the multiple-match case.
|
|
89
|
+
|
|
90
|
+
## HTTP host (`./host`)
|
|
91
|
+
|
|
92
|
+
The `./host` subpath (`ServeMcpHttp`, `ElwisJwtVerifier`) is the one exception to "no
|
|
93
|
+
transport" above: it is an actual `node:http` listener. `elwis-docs-mcp` and `elwis-data-mcp`
|
|
94
|
+
use it to serve MCP over HTTP with a bearer token instead of stdio.
|
|
95
|
+
|
|
96
|
+
It binds `127.0.0.1` unless the caller passes a different `host`; accepts only `POST /mcp` (a
|
|
97
|
+
`GET` is answered `405 Allow: POST` — neither server sends server-initiated messages, so the
|
|
98
|
+
standing `text/event-stream` a `GET` would otherwise open carries nothing and exists only to
|
|
99
|
+
be held open); and caps both concurrent token verifications (`maxConcurrentVerifications`) and
|
|
100
|
+
concurrent live sessions (`maxConcurrentSessions`), so a caller with no valid credential at all
|
|
101
|
+
cannot force unbounded verification work, and a caller who does verify cannot hold an unbounded
|
|
102
|
+
number of built servers open. Neither cap, nor the bind address, is read from an environment
|
|
103
|
+
variable by this package itself — each server that embeds `ServeMcpHttp` wires its own env
|
|
104
|
+
vars for these and documents them in its own README's operator section.
|
|
105
|
+
|
|
106
|
+
This is a library entry point, not a runnable server on its own: nothing in this package
|
|
107
|
+
listens by itself outside of a test.
|
|
108
|
+
|
|
109
|
+
## Errors
|
|
110
|
+
|
|
111
|
+
`ElwisApiError` carries `status`, so shared logic can branch on an HTTP status without
|
|
112
|
+
importing a transport. `IsElwisApiError` also accepts the Data MCP's existing `ElwisHttpError`
|
|
113
|
+
by shape, so the standalone server does not have to change its error class to adopt this
|
|
114
|
+
package.
|
|
115
|
+
|
|
116
|
+
## Status
|
|
117
|
+
|
|
118
|
+
Phase 1 of the capability-layer plan: extracted, tested, buildable, with no consumer yet.
|
|
119
|
+
Phase 2 switches `elwis-data-mcp` over and deletes its copies — until that lands, the logic
|
|
120
|
+
exists twice, which is the one thing this package is meant to prevent.
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.AppScope = void 0;
|
|
4
|
+
exports.FilterApplicationId = FilterApplicationId;
|
|
5
|
+
const errors_js_1 = require("./errors.js");
|
|
6
|
+
const resolve_js_1 = require("./resolve.js");
|
|
7
|
+
function FilterApplicationId(filter) {
|
|
8
|
+
const value = filter.applicationId;
|
|
9
|
+
if (typeof value === 'string')
|
|
10
|
+
return value;
|
|
11
|
+
if (value && typeof value === 'object' && typeof value.$oid === 'string') {
|
|
12
|
+
return value.$oid;
|
|
13
|
+
}
|
|
14
|
+
return undefined;
|
|
15
|
+
}
|
|
16
|
+
class AppScope {
|
|
17
|
+
api;
|
|
18
|
+
entries;
|
|
19
|
+
expectedTenant;
|
|
20
|
+
ids = null;
|
|
21
|
+
tenantVerified = false;
|
|
22
|
+
constructor(api, entries, expectedTenant) {
|
|
23
|
+
this.api = api;
|
|
24
|
+
this.entries = entries;
|
|
25
|
+
this.expectedTenant = expectedTenant;
|
|
26
|
+
}
|
|
27
|
+
get restricted() {
|
|
28
|
+
return this.entries.length > 0;
|
|
29
|
+
}
|
|
30
|
+
identity() {
|
|
31
|
+
return this.api.whoAmI().catch(() => null);
|
|
32
|
+
}
|
|
33
|
+
describe() {
|
|
34
|
+
return { restricted: this.restricted, entries: [...this.entries] };
|
|
35
|
+
}
|
|
36
|
+
async assertTenant() {
|
|
37
|
+
if (!this.expectedTenant || this.tenantVerified)
|
|
38
|
+
return;
|
|
39
|
+
const identity = await this.identity();
|
|
40
|
+
if (identity?.tenantId !== this.expectedTenant) {
|
|
41
|
+
throw new errors_js_1.TenantMismatchError(this.expectedTenant, identity?.tenantId ?? null);
|
|
42
|
+
}
|
|
43
|
+
this.tenantVerified = true;
|
|
44
|
+
}
|
|
45
|
+
async allowedIds() {
|
|
46
|
+
if (!this.restricted)
|
|
47
|
+
return null;
|
|
48
|
+
if (this.ids)
|
|
49
|
+
return this.ids;
|
|
50
|
+
const ids = new Set();
|
|
51
|
+
for (const entry of this.entries) {
|
|
52
|
+
if ((0, resolve_js_1.IsObjectId)(entry)) {
|
|
53
|
+
ids.add(entry);
|
|
54
|
+
continue;
|
|
55
|
+
}
|
|
56
|
+
const rows = await this.api.list('application', { name: entry });
|
|
57
|
+
for (const row of rows)
|
|
58
|
+
ids.add(row.id);
|
|
59
|
+
}
|
|
60
|
+
this.ids = ids;
|
|
61
|
+
return ids;
|
|
62
|
+
}
|
|
63
|
+
async assertAllowed(applicationId) {
|
|
64
|
+
await this.assertTenant();
|
|
65
|
+
const ids = await this.allowedIds();
|
|
66
|
+
if (ids && !ids.has(applicationId))
|
|
67
|
+
throw new errors_js_1.AppScopeError(applicationId);
|
|
68
|
+
}
|
|
69
|
+
async assertListAllowed(kind, filter) {
|
|
70
|
+
await this.assertTenant();
|
|
71
|
+
if (!this.restricted || kind === 'application')
|
|
72
|
+
return;
|
|
73
|
+
const applicationId = FilterApplicationId(filter);
|
|
74
|
+
if (!applicationId) {
|
|
75
|
+
throw new errors_js_1.AppScopeError('unspecified, when an application allowlist is set, list calls require an applicationId filter');
|
|
76
|
+
}
|
|
77
|
+
await this.assertAllowed(applicationId);
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
exports.AppScope = AppScope;
|
|
81
|
+
//# sourceMappingURL=app-scope.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"app-scope.js","sourceRoot":"","sources":["../../src/app-scope.ts"],"names":[],"mappings":";;;AAUA,kDAOC;AAjBD,2CAAiE;AAEjE,6CAA0C;AAQ1C,SAAgB,mBAAmB,CAAC,MAA+B;IACjE,MAAM,KAAK,GAAG,MAAM,CAAC,aAAa,CAAC;IACnC,IAAI,OAAO,KAAK,KAAK,QAAQ;QAAE,OAAO,KAAK,CAAC;IAC5C,IAAI,KAAK,IAAI,OAAO,KAAK,KAAK,QAAQ,IAAI,OAAQ,KAA4B,CAAC,IAAI,KAAK,QAAQ,EAAE,CAAC;QACjG,OAAQ,KAA0B,CAAC,IAAI,CAAC;IAC1C,CAAC;IACD,OAAO,SAAS,CAAC;AACnB,CAAC;AAED,MAAa,QAAQ;IACF,GAAG,CAAgB;IACnB,OAAO,CAAW;IAClB,cAAc,CAAU;IACjC,GAAG,GAAuB,IAAI,CAAC;IAC/B,cAAc,GAAG,KAAK,CAAC;IAE/B,YAAY,GAAkB,EAAE,OAAiB,EAAE,cAAuB;QACxE,IAAI,CAAC,GAAG,GAAG,GAAG,CAAC;QACf,IAAI,CAAC,OAAO,GAAG,OAAO,CAAC;QACvB,IAAI,CAAC,cAAc,GAAG,cAAc,CAAC;IACvC,CAAC;IAED,IAAI,UAAU;QACZ,OAAO,IAAI,CAAC,OAAO,CAAC,MAAM,GAAG,CAAC,CAAC;IACjC,CAAC;IAED,QAAQ;QACN,OAAO,IAAI,CAAC,GAAG,CAAC,MAAM,EAAE,CAAC,KAAK,CAAC,GAAG,EAAE,CAAC,IAAI,CAAC,CAAC;IAC7C,CAAC;IAED,QAAQ;QACN,OAAO,EAAE,UAAU,EAAE,IAAI,CAAC,UAAU,EAAE,OAAO,EAAE,CAAC,GAAG,IAAI,CAAC,OAAO,CAAC,EAAE,CAAC;IACrE,CAAC;IAED,KAAK,CAAC,YAAY;QAChB,IAAI,CAAC,IAAI,CAAC,cAAc,IAAI,IAAI,CAAC,cAAc;YAAE,OAAO;QACxD,MAAM,QAAQ,GAAG,MAAM,IAAI,CAAC,QAAQ,EAAE,CAAC;QACvC,IAAI,QAAQ,EAAE,QAAQ,KAAK,IAAI,CAAC,cAAc,EAAE,CAAC;YAC/C,MAAM,IAAI,+BAAmB,CAAC,IAAI,CAAC,cAAc,EAAE,QAAQ,EAAE,QAAQ,IAAI,IAAI,CAAC,CAAC;QACjF,CAAC;QACD,IAAI,CAAC,cAAc,GAAG,IAAI,CAAC;IAC7B,CAAC;IAED,KAAK,CAAC,UAAU;QACd,IAAI,CAAC,IAAI,CAAC,UAAU;YAAE,OAAO,IAAI,CAAC;QAClC,IAAI,IAAI,CAAC,GAAG;YAAE,OAAO,IAAI,CAAC,GAAG,CAAC;QAC9B,MAAM,GAAG,GAAG,IAAI,GAAG,EAAU,CAAC;QAC9B,KAAK,MAAM,KAAK,IAAI,IAAI,CAAC,OAAO,EAAE,CAAC;YACjC,IAAI,IAAA,uBAAU,EAAC,KAAK,CAAC,EAAE,CAAC;gBACtB,GAAG,CAAC,GAAG,CAAC,KAAK,CAAC,CAAC;gBACf,SAAS;YACX,CAAC;YACD,MAAM,IAAI,GAAG,MAAM,IAAI,CAAC,GAAG,CAAC,IAAI,CAAC,aAAa,EAAE,EAAE,IAAI,EAAE,KAAK,EAAE,CAAC,CAAC;YACjE,KAAK,MAAM,GAAG,IAAI,IAAI;gBAAE,GAAG,CAAC,GAAG,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC;QAC1C,CAAC;QACD,IAAI,CAAC,GAAG,GAAG,GAAG,CAAC;QACf,OAAO,GAAG,CAAC;IACb,CAAC;IAED,KAAK,CAAC,aAAa,CAAC,aAAqB;QACvC,MAAM,IAAI,CAAC,YAAY,EAAE,CAAC;QAC1B,MAAM,GAAG,GAAG,MAAM,IAAI,CAAC,UAAU,EAAE,CAAC;QACpC,IAAI,GAAG,IAAI,CAAC,GAAG,CAAC,GAAG,CAAC,aAAa,CAAC;YAAE,MAAM,IAAI,yBAAa,CAAC,aAAa,CAAC,CAAC;IAC7E,CAAC;IAED,KAAK,CAAC,iBAAiB,CAAC,IAAY,EAAE,MAA+B;QACnE,MAAM,IAAI,CAAC,YAAY,EAAE,CAAC;QAC1B,IAAI,CAAC,IAAI,CAAC,UAAU,IAAI,IAAI,KAAK,aAAa;YAAE,OAAO;QACvD,MAAM,aAAa,GAAG,mBAAmB,CAAC,MAAM,CAAC,CAAC;QAClD,IAAI,CAAC,aAAa,EAAE,CAAC;YACnB,MAAM,IAAI,yBAAa,CAAC,+FAA+F,CAAC,CAAC;QAC3H,CAAC;QACD,MAAM,IAAI,CAAC,aAAa,CAAC,aAAa,CAAC,CAAC;IAC1C,CAAC;CACF;AAjED,4BAiEC"}
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.RunHostConformance = RunHostConformance;
|
|
4
|
+
const serve_http_js_1 = require("../host/serve-http.js");
|
|
5
|
+
function assert(condition, message) {
|
|
6
|
+
if (!condition)
|
|
7
|
+
throw new Error(message);
|
|
8
|
+
}
|
|
9
|
+
const FETCH_TIMEOUT_MS = 5_000;
|
|
10
|
+
function ParseJsonRpcFrame(contentType, body) {
|
|
11
|
+
if (contentType?.includes('text/event-stream')) {
|
|
12
|
+
const dataLine = body.split('\n').find((line) => line.startsWith('data:'));
|
|
13
|
+
assert(dataLine, `expected an SSE data frame in the response, got: ${body}`);
|
|
14
|
+
return JSON.parse(dataLine.slice('data:'.length).trim());
|
|
15
|
+
}
|
|
16
|
+
return JSON.parse(body);
|
|
17
|
+
}
|
|
18
|
+
function AdvertisedMetadataPath(header) {
|
|
19
|
+
const match = /resource_metadata="([^"]+)"/.exec(header ?? '');
|
|
20
|
+
const url = match?.[1];
|
|
21
|
+
assert(url, 'the challenge did not advertise a resource_metadata url');
|
|
22
|
+
return new URL(url).pathname;
|
|
23
|
+
}
|
|
24
|
+
const HOST_CONFORMANCE = [
|
|
25
|
+
{
|
|
26
|
+
id: 'unauthenticated-call-challenged',
|
|
27
|
+
title: 'a call with no bearer token is challenged, not silently served',
|
|
28
|
+
why: 'A server that skips the challenge on a missing token would serve tool calls to anyone who can reach the port, defeating authentication entirely.',
|
|
29
|
+
async run(base) {
|
|
30
|
+
const res = await fetch(`${base}/mcp`, { method: 'POST', body: '{}', signal: AbortSignal.timeout(FETCH_TIMEOUT_MS) });
|
|
31
|
+
assert(res.status === 401, `expected 401 for an unauthenticated call, got ${res.status}`);
|
|
32
|
+
AdvertisedMetadataPath(res.headers.get('www-authenticate'));
|
|
33
|
+
},
|
|
34
|
+
},
|
|
35
|
+
{
|
|
36
|
+
id: 'metadata-discoverable-at-advertised-url',
|
|
37
|
+
title: 'the url the challenge advertises actually serves resource metadata',
|
|
38
|
+
why: 'A client discovers where to authenticate by following resource_metadata from the challenge. If that route is missing or disconnected from the advertised url, discovery dead-ends and no client can ever authenticate.',
|
|
39
|
+
async run(base) {
|
|
40
|
+
const challenged = await fetch(`${base}/mcp`, { method: 'POST', body: '{}', signal: AbortSignal.timeout(FETCH_TIMEOUT_MS) });
|
|
41
|
+
const metadataPath = AdvertisedMetadataPath(challenged.headers.get('www-authenticate'));
|
|
42
|
+
const metadata = await fetch(`${base}${metadataPath}`, { signal: AbortSignal.timeout(FETCH_TIMEOUT_MS) });
|
|
43
|
+
assert(metadata.status === 200, `expected 200 fetching the advertised metadata url, got ${metadata.status}`);
|
|
44
|
+
const body = await metadata.json();
|
|
45
|
+
assert(typeof body.resource === 'string', 'metadata document is missing a resource field');
|
|
46
|
+
assert(Array.isArray(body.authorization_servers), 'metadata document is missing authorization_servers');
|
|
47
|
+
},
|
|
48
|
+
},
|
|
49
|
+
{
|
|
50
|
+
id: 'authenticated-call-served',
|
|
51
|
+
title: 'a call bearing a token the verifier accepts is served',
|
|
52
|
+
why: 'This is the point of hosting a server at all: the transport must actually be wired to the factory-built server, not merely accept the connection and then never respond. A host that calls handleRequest without first connecting the server still answers 200 with SSE headers and then hangs, so the body must be read and parsed as JSON-RPC, not just the status code checked.',
|
|
53
|
+
async run(base) {
|
|
54
|
+
const listed = await fetch(`${base}/mcp`, {
|
|
55
|
+
method: 'POST',
|
|
56
|
+
headers: {
|
|
57
|
+
authorization: 'Bearer conformance-token',
|
|
58
|
+
'content-type': 'application/json',
|
|
59
|
+
accept: 'application/json, text/event-stream',
|
|
60
|
+
},
|
|
61
|
+
body: JSON.stringify({ jsonrpc: '2.0', id: 1, method: 'tools/list' }),
|
|
62
|
+
signal: AbortSignal.timeout(FETCH_TIMEOUT_MS),
|
|
63
|
+
});
|
|
64
|
+
assert(listed.status === 200, `expected 200 for an authenticated call, got ${listed.status}`);
|
|
65
|
+
const body = await listed.text();
|
|
66
|
+
const frame = ParseJsonRpcFrame(listed.headers.get('content-type'), body);
|
|
67
|
+
assert(frame.jsonrpc === '2.0', `expected a jsonrpc 2.0 response, got: ${body}`);
|
|
68
|
+
assert(frame.id === 1, `expected the response to answer request id 1, got: ${body}`);
|
|
69
|
+
assert('result' in frame || 'error' in frame, `expected a result or error frame, got: ${body}`);
|
|
70
|
+
},
|
|
71
|
+
},
|
|
72
|
+
];
|
|
73
|
+
async function RunHostConformance(factory, verifier) {
|
|
74
|
+
const handle = await (0, serve_http_js_1.ServeMcpHttp)({
|
|
75
|
+
factory,
|
|
76
|
+
verifier,
|
|
77
|
+
resource: 'http://127.0.0.1/mcp',
|
|
78
|
+
authorizationServers: ['https://id.example.com'],
|
|
79
|
+
port: 0,
|
|
80
|
+
});
|
|
81
|
+
try {
|
|
82
|
+
const base = `http://127.0.0.1:${handle.port}`;
|
|
83
|
+
const failures = [];
|
|
84
|
+
for (const hostCase of HOST_CONFORMANCE) {
|
|
85
|
+
try {
|
|
86
|
+
await hostCase.run(base);
|
|
87
|
+
}
|
|
88
|
+
catch (error) {
|
|
89
|
+
failures.push(`${hostCase.id}: ${error instanceof Error ? error.message : String(error)}`);
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
assert(failures.length === 0, `host conformance failed:\n${failures.join('\n')}`);
|
|
93
|
+
}
|
|
94
|
+
finally {
|
|
95
|
+
await handle.close();
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
//# sourceMappingURL=host.conformance.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"host.conformance.js","sourceRoot":"","sources":["../../../src/conformance/host.conformance.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.READ_PORT_CONFORMANCE = void 0;
|
|
4
|
+
exports.RunReadPortConformance = RunReadPortConformance;
|
|
5
|
+
const errors_js_1 = require("../errors.js");
|
|
6
|
+
const resolve_js_1 = require("../resolve.js");
|
|
7
|
+
function assert(condition, message) {
|
|
8
|
+
if (!condition)
|
|
9
|
+
throw new Error(message);
|
|
10
|
+
}
|
|
11
|
+
exports.READ_PORT_CONFORMANCE = [
|
|
12
|
+
{
|
|
13
|
+
id: 'excludes-soft-deleted',
|
|
14
|
+
title: 'list never returns soft-deleted rows',
|
|
15
|
+
why: 'The HTTP adapter injects deleted:null for free. An adapter reading collections directly does not, and would leak deleted entities into resolution and into pickers.',
|
|
16
|
+
async run(api, fx) {
|
|
17
|
+
const rows = await api.list(fx.kind, { applicationId: { $oid: fx.applicationId } }, 200);
|
|
18
|
+
const leaked = rows.filter((row) => row.deleted !== null && row.deleted !== undefined);
|
|
19
|
+
assert(leaked.length === 0, `list returned ${leaked.length} soft-deleted ${fx.kind} rows`);
|
|
20
|
+
},
|
|
21
|
+
},
|
|
22
|
+
{
|
|
23
|
+
id: 'scopes-by-application',
|
|
24
|
+
title: 'list honours an applicationId filter',
|
|
25
|
+
why: 'Application scoping is the only thing preventing cross-application reads.',
|
|
26
|
+
async run(api, fx) {
|
|
27
|
+
const rows = await api.list(fx.kind, { applicationId: { $oid: fx.applicationId } }, 200);
|
|
28
|
+
const foreign = rows.filter((row) => row.applicationId !== undefined && String(row.applicationId) !== fx.applicationId);
|
|
29
|
+
assert(foreign.length === 0, `list returned ${foreign.length} rows from another application`);
|
|
30
|
+
},
|
|
31
|
+
},
|
|
32
|
+
{
|
|
33
|
+
id: 'honours-limit',
|
|
34
|
+
title: 'list honours the limit argument',
|
|
35
|
+
why: 'Silent over-return breaks pagination honesty and can blow the model context.',
|
|
36
|
+
async run(api, fx) {
|
|
37
|
+
const rows = await api.list(fx.kind, { applicationId: { $oid: fx.applicationId } }, 1);
|
|
38
|
+
assert(rows.length <= 1, `limit 1 returned ${rows.length} rows`);
|
|
39
|
+
},
|
|
40
|
+
},
|
|
41
|
+
{
|
|
42
|
+
id: 'or-filter-or-400',
|
|
43
|
+
title: 'list either supports $or on name/alias or rejects it with status 400',
|
|
44
|
+
why: 'ResolveEntity falls back to a name-only lookup on 400. An adapter that throws an untyped error, or silently returns nothing, breaks alias resolution without any signal.',
|
|
45
|
+
async run(api, fx) {
|
|
46
|
+
try {
|
|
47
|
+
const rows = await api.list(fx.kind, {
|
|
48
|
+
applicationId: { $oid: fx.applicationId },
|
|
49
|
+
$or: [{ name: fx.knownEntityName }, { alias: fx.knownEntityName }],
|
|
50
|
+
});
|
|
51
|
+
assert(Array.isArray(rows), '$or lookup did not return an array');
|
|
52
|
+
}
|
|
53
|
+
catch (error) {
|
|
54
|
+
assert((0, errors_js_1.ErrorStatus)(error) === 400, `$or lookup failed with a non-400, untyped error: ${String(error)}`);
|
|
55
|
+
}
|
|
56
|
+
},
|
|
57
|
+
},
|
|
58
|
+
{
|
|
59
|
+
id: 'get-one-addresses-entity',
|
|
60
|
+
title: 'getOne returns the addressed entity',
|
|
61
|
+
why: 'Read-back verification depends on addressing a single entity by id.',
|
|
62
|
+
async run(api, fx) {
|
|
63
|
+
const row = await api.getOne(fx.kind, fx.knownEntityId);
|
|
64
|
+
const id = row.id;
|
|
65
|
+
assert(String(id) === fx.knownEntityId, `getOne returned id ${String(id)}, expected ${fx.knownEntityId}`);
|
|
66
|
+
},
|
|
67
|
+
},
|
|
68
|
+
{
|
|
69
|
+
id: 'identity-has-tenant',
|
|
70
|
+
title: 'whoAmI reports a tenant and a role',
|
|
71
|
+
why: 'Every access decision keys off tenant and role. An adapter that leaves tenantId null degrades callers to an anonymous bucket.',
|
|
72
|
+
async run(api) {
|
|
73
|
+
const identity = await api.whoAmI();
|
|
74
|
+
assert(typeof identity.role === 'string', 'identity.role missing');
|
|
75
|
+
assert(identity.tenantId !== undefined, 'identity.tenantId missing');
|
|
76
|
+
},
|
|
77
|
+
},
|
|
78
|
+
{
|
|
79
|
+
id: 'resolve-by-name',
|
|
80
|
+
title: 'ResolveEntity finds a known entity by name',
|
|
81
|
+
why: 'Name resolution is the contract that lets a person type a name instead of an id.',
|
|
82
|
+
async run(api, fx) {
|
|
83
|
+
const { matches } = await (0, resolve_js_1.ResolveEntity)(api, fx.kind, fx.applicationId, fx.knownEntityName);
|
|
84
|
+
assert(matches.some((match) => match.id === fx.knownEntityId), `did not resolve "${fx.knownEntityName}" to ${fx.knownEntityId}`);
|
|
85
|
+
},
|
|
86
|
+
},
|
|
87
|
+
{
|
|
88
|
+
id: 'resolve-application-by-name',
|
|
89
|
+
title: 'ResolveApplicationId maps an application name to its id',
|
|
90
|
+
why: 'Every application-scoped call starts here.',
|
|
91
|
+
async run(api, fx) {
|
|
92
|
+
const id = await (0, resolve_js_1.ResolveApplicationId)(api, fx.applicationName);
|
|
93
|
+
assert(id === fx.applicationId, `resolved ${id}, expected ${fx.applicationId}`);
|
|
94
|
+
},
|
|
95
|
+
},
|
|
96
|
+
{
|
|
97
|
+
id: 'not-found-carries-candidates',
|
|
98
|
+
title: 'ResolveOneStrict reports a miss as NotFoundError with candidates',
|
|
99
|
+
why: 'A miss must offer what does exist. Surfacing a validator message instead is the defect that produced wrong-entity mutations.',
|
|
100
|
+
async run(api, fx) {
|
|
101
|
+
const absent = fx.absentEntityName ?? '__definitely_absent_entity__';
|
|
102
|
+
try {
|
|
103
|
+
await (0, resolve_js_1.ResolveOneStrict)(api, fx.kind, fx.applicationId, absent);
|
|
104
|
+
throw new Error('ResolveOneStrict resolved a name that should not exist');
|
|
105
|
+
}
|
|
106
|
+
catch (error) {
|
|
107
|
+
assert(error instanceof errors_js_1.NotFoundError, `expected NotFoundError, got ${String(error)}`);
|
|
108
|
+
assert(Array.isArray(error.candidates), 'NotFoundError carried no candidates array');
|
|
109
|
+
}
|
|
110
|
+
},
|
|
111
|
+
},
|
|
112
|
+
];
|
|
113
|
+
async function RunReadPortConformance(api, fixtures) {
|
|
114
|
+
const results = [];
|
|
115
|
+
for (const testCase of exports.READ_PORT_CONFORMANCE) {
|
|
116
|
+
try {
|
|
117
|
+
await testCase.run(api, fixtures);
|
|
118
|
+
results.push({ id: testCase.id, title: testCase.title, passed: true });
|
|
119
|
+
}
|
|
120
|
+
catch (error) {
|
|
121
|
+
results.push({
|
|
122
|
+
id: testCase.id,
|
|
123
|
+
title: testCase.title,
|
|
124
|
+
passed: false,
|
|
125
|
+
error: error instanceof Error ? error.message : String(error),
|
|
126
|
+
});
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
return results;
|
|
130
|
+
}
|
|
131
|
+
//# sourceMappingURL=read-port.conformance.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"read-port.conformance.js","sourceRoot":"","sources":["../../../src/conformance/read-port.conformance.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.NoTenantError = exports.TenantMismatchError = exports.AppScopeError = exports.NotFoundError = exports.AmbiguityError = exports.ElwisApiError = void 0;
|
|
4
|
+
exports.IsElwisApiError = IsElwisApiError;
|
|
5
|
+
exports.ErrorStatus = ErrorStatus;
|
|
6
|
+
class ElwisApiError extends Error {
|
|
7
|
+
status;
|
|
8
|
+
path;
|
|
9
|
+
body;
|
|
10
|
+
constructor(status, path, body) {
|
|
11
|
+
super(`ELWIS API ${status} for ${path}`);
|
|
12
|
+
this.name = 'ElwisApiError';
|
|
13
|
+
this.status = status;
|
|
14
|
+
this.path = path;
|
|
15
|
+
this.body = body;
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
exports.ElwisApiError = ElwisApiError;
|
|
19
|
+
function IsElwisApiError(error) {
|
|
20
|
+
if (error instanceof ElwisApiError)
|
|
21
|
+
return true;
|
|
22
|
+
return (typeof error === 'object' &&
|
|
23
|
+
error !== null &&
|
|
24
|
+
typeof error.status === 'number' &&
|
|
25
|
+
typeof error.name === 'string' &&
|
|
26
|
+
/^Elwis(Api|Http)Error$/.test(error.name));
|
|
27
|
+
}
|
|
28
|
+
function ErrorStatus(error) {
|
|
29
|
+
return IsElwisApiError(error) ? error.status : undefined;
|
|
30
|
+
}
|
|
31
|
+
class AmbiguityError extends Error {
|
|
32
|
+
candidates;
|
|
33
|
+
constructor(message, candidates) {
|
|
34
|
+
super(message);
|
|
35
|
+
this.name = 'AmbiguityError';
|
|
36
|
+
this.candidates = candidates;
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
exports.AmbiguityError = AmbiguityError;
|
|
40
|
+
class NotFoundError extends Error {
|
|
41
|
+
kind;
|
|
42
|
+
nameOrAlias;
|
|
43
|
+
candidates;
|
|
44
|
+
constructor(kind, nameOrAlias, candidates = []) {
|
|
45
|
+
super(`No active ${kind} named "${nameOrAlias}" in this application`);
|
|
46
|
+
this.name = 'NotFoundError';
|
|
47
|
+
this.kind = kind;
|
|
48
|
+
this.nameOrAlias = nameOrAlias;
|
|
49
|
+
this.candidates = candidates;
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
exports.NotFoundError = NotFoundError;
|
|
53
|
+
class AppScopeError extends Error {
|
|
54
|
+
constructor(ref) {
|
|
55
|
+
super(`Application "${ref}" is outside the configured application allowlist`);
|
|
56
|
+
this.name = 'AppScopeError';
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
exports.AppScopeError = AppScopeError;
|
|
60
|
+
class TenantMismatchError extends Error {
|
|
61
|
+
constructor(expected, actual) {
|
|
62
|
+
super(`No data was read or written. This connection is locked to tenant ${expected}, but the access token belongs to a different tenant (${actual ?? 'unknown'}).`);
|
|
63
|
+
this.name = 'TenantMismatchError';
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
exports.TenantMismatchError = TenantMismatchError;
|
|
67
|
+
class NoTenantError extends Error {
|
|
68
|
+
constructor() {
|
|
69
|
+
super('No tenant is set, so there is no way to know which ELWIS environment to work in.');
|
|
70
|
+
this.name = 'NoTenantError';
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
exports.NoTenantError = NoTenantError;
|
|
74
|
+
//# sourceMappingURL=errors.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"errors.js","sourceRoot":"","sources":["../../src/errors.ts"],"names":[],"mappings":";;;AAcA,0CASC;AAED,kCAEC;AA3BD,MAAa,aAAc,SAAQ,KAAK;IAC7B,MAAM,CAAS;IACf,IAAI,CAAS;IACb,IAAI,CAAU;IAEvB,YAAY,MAAc,EAAE,IAAY,EAAE,IAAa;QACrD,KAAK,CAAC,aAAa,MAAM,QAAQ,IAAI,EAAE,CAAC,CAAC;QACzC,IAAI,CAAC,IAAI,GAAG,eAAe,CAAC;QAC5B,IAAI,CAAC,MAAM,GAAG,MAAM,CAAC;QACrB,IAAI,CAAC,IAAI,GAAG,IAAI,CAAC;QACjB,IAAI,CAAC,IAAI,GAAG,IAAI,CAAC;IACnB,CAAC;CACF;AAZD,sCAYC;AAED,SAAgB,eAAe,CAAC,KAAc;IAC5C,IAAI,KAAK,YAAY,aAAa;QAAE,OAAO,IAAI,CAAC;IAChD,OAAO,CACL,OAAO,KAAK,KAAK,QAAQ;QACzB,KAAK,KAAK,IAAI;QACd,OAAQ,KAA8B,CAAC,MAAM,KAAK,QAAQ;QAC1D,OAAQ,KAA4B,CAAC,IAAI,KAAK,QAAQ;QACtD,wBAAwB,CAAC,IAAI,CAAE,KAA0B,CAAC,IAAI,CAAC,CAChE,CAAC;AACJ,CAAC;AAED,SAAgB,WAAW,CAAC,KAAc;IACxC,OAAO,eAAe,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC,CAAC,SAAS,CAAC;AAC3D,CAAC;AAED,MAAa,cAAe,SAAQ,KAAK;IAC9B,UAAU,CAAsC;IAEzD,YAAY,OAAe,EAAE,UAA+C;QAC1E,KAAK,CAAC,OAAO,CAAC,CAAC;QACf,IAAI,CAAC,IAAI,GAAG,gBAAgB,CAAC;QAC7B,IAAI,CAAC,UAAU,GAAG,UAAU,CAAC;IAC/B,CAAC;CACF;AARD,wCAQC;AAED,MAAa,aAAc,SAAQ,KAAK;IAC7B,IAAI,CAAS;IACb,WAAW,CAAS;IACpB,UAAU,CAAsC;IAEzD,YAAY,IAAY,EAAE,WAAmB,EAAE,aAAkD,EAAE;QACjG,KAAK,CAAC,aAAa,IAAI,WAAW,WAAW,uBAAuB,CAAC,CAAC;QACtE,IAAI,CAAC,IAAI,GAAG,eAAe,CAAC;QAC5B,IAAI,CAAC,IAAI,GAAG,IAAI,CAAC;QACjB,IAAI,CAAC,WAAW,GAAG,WAAW,CAAC;QAC/B,IAAI,CAAC,UAAU,GAAG,UAAU,CAAC;IAC/B,CAAC;CACF;AAZD,sCAYC;AAED,MAAa,aAAc,SAAQ,KAAK;IACtC,YAAY,GAAW;QACrB,KAAK,CAAC,gBAAgB,GAAG,mDAAmD,CAAC,CAAC;QAC9E,IAAI,CAAC,IAAI,GAAG,eAAe,CAAC;IAC9B,CAAC;CACF;AALD,sCAKC;AAED,MAAa,mBAAoB,SAAQ,KAAK;IAC5C,YAAY,QAAgB,EAAE,MAAqB;QACjD,KAAK,CACH,oEAAoE,QAAQ,yDAAyD,MAAM,IAAI,SAAS,IAAI,CAC7J,CAAC;QACF,IAAI,CAAC,IAAI,GAAG,qBAAqB,CAAC;IACpC,CAAC;CACF;AAPD,kDAOC;AAED,MAAa,aAAc,SAAQ,KAAK;IACtC;QACE,KAAK,CAAC,kFAAkF,CAAC,CAAC;QAC1F,IAAI,CAAC,IAAI,GAAG,eAAe,CAAC;IAC9B,CAAC;CACF;AALD,sCAKC"}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.ElwisJwtVerifier = ElwisJwtVerifier;
|
|
4
|
+
const TENANT_ID_PATTERN = /^[a-f0-9]{24}$/i;
|
|
5
|
+
function AssertLooksLikeAToken(token) {
|
|
6
|
+
const parts = token.split('.');
|
|
7
|
+
const payload = parts[1];
|
|
8
|
+
if (parts.length !== 3 || payload === undefined) {
|
|
9
|
+
throw new Error('the bearer value is not a valid token');
|
|
10
|
+
}
|
|
11
|
+
try {
|
|
12
|
+
JSON.parse(Buffer.from(payload, 'base64url').toString('utf8'));
|
|
13
|
+
}
|
|
14
|
+
catch {
|
|
15
|
+
throw new Error('the bearer value is not a valid token');
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
function ElwisJwtVerifier(options) {
|
|
19
|
+
return {
|
|
20
|
+
async verify(token) {
|
|
21
|
+
AssertLooksLikeAToken(token);
|
|
22
|
+
let resolved;
|
|
23
|
+
try {
|
|
24
|
+
resolved = await options.resolveIdentity(token);
|
|
25
|
+
}
|
|
26
|
+
catch (e) {
|
|
27
|
+
throw new Error(`could not resolve the bearer token against ELWIS: ${e instanceof Error ? e.message : String(e)}`);
|
|
28
|
+
}
|
|
29
|
+
if (resolved.tenantId === null || !TENANT_ID_PATTERN.test(resolved.tenantId)) {
|
|
30
|
+
throw new Error(`the resolved identity has no valid tenant id (got: ${resolved.tenantId ?? 'null'})`);
|
|
31
|
+
}
|
|
32
|
+
return {
|
|
33
|
+
token,
|
|
34
|
+
baseUrl: options.baseUrl,
|
|
35
|
+
tenantId: resolved.tenantId,
|
|
36
|
+
};
|
|
37
|
+
},
|
|
38
|
+
};
|
|
39
|
+
}
|
|
40
|
+
//# sourceMappingURL=elwis-jwt-verifier.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"elwis-jwt-verifier.js","sourceRoot":"","sources":["../../../src/host/elwis-jwt-verifier.ts"],"names":[],"mappings":";;AAmBA,4CA0BC;AA3CD,MAAM,iBAAiB,GAAG,iBAAiB,CAAC;AAE5C,SAAS,qBAAqB,CAAC,KAAa;IAC1C,MAAM,KAAK,GAAG,KAAK,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC;IAC/B,MAAM,OAAO,GAAG,KAAK,CAAC,CAAC,CAAC,CAAC;IACzB,IAAI,KAAK,CAAC,MAAM,KAAK,CAAC,IAAI,OAAO,KAAK,SAAS,EAAE,CAAC;QAChD,MAAM,IAAI,KAAK,CAAC,uCAAuC,CAAC,CAAC;IAC3D,CAAC;IACD,IAAI,CAAC;QACH,IAAI,CAAC,KAAK,CAAC,MAAM,CAAC,IAAI,CAAC,OAAO,EAAE,WAAW,CAAC,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAC,CAAC;IACjE,CAAC;IAAC,MAAM,CAAC;QACP,MAAM,IAAI,KAAK,CAAC,uCAAuC,CAAC,CAAC;IAC3D,CAAC;AACH,CAAC;AAID,SAAgB,gBAAgB,CAAC,OAGhC;IACC,OAAO;QACL,KAAK,CAAC,MAAM,CAAC,KAAa;YACxB,qBAAqB,CAAC,KAAK,CAAC,CAAC;YAE7B,IAAI,QAAqC,CAAC;YAC1C,IAAI,CAAC;gBACH,QAAQ,GAAG,MAAM,OAAO,CAAC,eAAe,CAAC,KAAK,CAAC,CAAC;YAClD,CAAC;YAAC,OAAO,CAAC,EAAE,CAAC;gBACX,MAAM,IAAI,KAAK,CAAC,qDAAqD,CAAC,YAAY,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC;YACrH,CAAC;YAED,IAAI,QAAQ,CAAC,QAAQ,KAAK,IAAI,IAAI,CAAC,iBAAiB,CAAC,IAAI,CAAC,QAAQ,CAAC,QAAQ,CAAC,EAAE,CAAC;gBAC7E,MAAM,IAAI,KAAK,CAAC,sDAAsD,QAAQ,CAAC,QAAQ,IAAI,MAAM,GAAG,CAAC,CAAC;YACxG,CAAC;YAED,OAAO;gBACL,KAAK;gBACL,OAAO,EAAE,OAAO,CAAC,OAAO;gBACxB,QAAQ,EAAE,QAAQ,CAAC,QAAQ;aAC5B,CAAC;QACJ,CAAC;KACF,CAAC;AACJ,CAAC"}
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.FileLinkStore = void 0;
|
|
4
|
+
const node_fs_1 = require("node:fs");
|
|
5
|
+
const node_path_1 = require("node:path");
|
|
6
|
+
const FILE_MODE = 0o600;
|
|
7
|
+
const DIRECTORY_MODE = 0o700;
|
|
8
|
+
class FileLinkStore {
|
|
9
|
+
path;
|
|
10
|
+
entries;
|
|
11
|
+
writing = Promise.resolve();
|
|
12
|
+
constructor(path) {
|
|
13
|
+
this.path = path;
|
|
14
|
+
this.entries = this.Load();
|
|
15
|
+
}
|
|
16
|
+
Load() {
|
|
17
|
+
let raw;
|
|
18
|
+
try {
|
|
19
|
+
raw = (0, node_fs_1.readFileSync)(this.path, 'utf8');
|
|
20
|
+
}
|
|
21
|
+
catch {
|
|
22
|
+
return {};
|
|
23
|
+
}
|
|
24
|
+
try {
|
|
25
|
+
const parsed = JSON.parse(raw);
|
|
26
|
+
if (typeof parsed !== 'object' || parsed === null || Array.isArray(parsed))
|
|
27
|
+
return {};
|
|
28
|
+
return parsed;
|
|
29
|
+
}
|
|
30
|
+
catch {
|
|
31
|
+
return {};
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
Flush() {
|
|
35
|
+
const snapshot = JSON.stringify(this.entries);
|
|
36
|
+
this.writing = this.writing.then(() => {
|
|
37
|
+
(0, node_fs_1.mkdirSync)((0, node_path_1.dirname)(this.path), { recursive: true, mode: DIRECTORY_MODE });
|
|
38
|
+
const temporary = `${this.path}.${process.pid}.tmp`;
|
|
39
|
+
(0, node_fs_1.writeFileSync)(temporary, snapshot, { mode: FILE_MODE });
|
|
40
|
+
(0, node_fs_1.renameSync)(temporary, this.path);
|
|
41
|
+
}, () => { });
|
|
42
|
+
return this.writing;
|
|
43
|
+
}
|
|
44
|
+
async get(key) {
|
|
45
|
+
return this.entries[key] ?? null;
|
|
46
|
+
}
|
|
47
|
+
async put(key, session) {
|
|
48
|
+
this.entries = { ...this.entries, [key]: session };
|
|
49
|
+
await this.Flush();
|
|
50
|
+
}
|
|
51
|
+
async delete(key) {
|
|
52
|
+
const { [key]: removed, ...rest } = this.entries;
|
|
53
|
+
if (removed === undefined)
|
|
54
|
+
return;
|
|
55
|
+
this.entries = rest;
|
|
56
|
+
await this.Flush();
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
exports.FileLinkStore = FileLinkStore;
|
|
60
|
+
//# sourceMappingURL=file-link-store.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"file-link-store.js","sourceRoot":"","sources":["../../../src/host/file-link-store.ts"],"names":[],"mappings":";;;AAAA,qCAA6E;AAC7E,yCAAoC;AAGpC,MAAM,SAAS,GAAG,KAAK,CAAC;AACxB,MAAM,cAAc,GAAG,KAAK,CAAC;AAE7B,MAAa,aAAa;IACP,IAAI,CAAS;IACtB,OAAO,CAAgC;IACvC,OAAO,GAAkB,OAAO,CAAC,OAAO,EAAE,CAAC;IAEnD,YAAY,IAAY;QACtB,IAAI,CAAC,IAAI,GAAG,IAAI,CAAC;QACjB,IAAI,CAAC,OAAO,GAAG,IAAI,CAAC,IAAI,EAAE,CAAC;IAC7B,CAAC;IAEO,IAAI;QACV,IAAI,GAAW,CAAC;QAChB,IAAI,CAAC;YACH,GAAG,GAAG,IAAA,sBAAY,EAAC,IAAI,CAAC,IAAI,EAAE,MAAM,CAAC,CAAC;QACxC,CAAC;QAAC,MAAM,CAAC;YACP,OAAO,EAAE,CAAC;QACZ,CAAC;QACD,IAAI,CAAC;YACH,MAAM,MAAM,GAAG,IAAI,CAAC,KAAK,CAAC,GAAG,CAAY,CAAC;YAC1C,IAAI,OAAO,MAAM,KAAK,QAAQ,IAAI,MAAM,KAAK,IAAI,IAAI,KAAK,CAAC,OAAO,CAAC,MAAM,CAAC;gBAAE,OAAO,EAAE,CAAC;YACtF,OAAO,MAAuC,CAAC;QACjD,CAAC;QAAC,MAAM,CAAC;YACP,OAAO,EAAE,CAAC;QACZ,CAAC;IACH,CAAC;IAEO,KAAK;QACX,MAAM,QAAQ,GAAG,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC;QAC9C,IAAI,CAAC,OAAO,GAAG,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC,GAAG,EAAE;YACpC,IAAA,mBAAS,EAAC,IAAA,mBAAO,EAAC,IAAI,CAAC,IAAI,CAAC,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,IAAI,EAAE,cAAc,EAAE,CAAC,CAAC;YACzE,MAAM,SAAS,GAAG,GAAG,IAAI,CAAC,IAAI,IAAI,OAAO,CAAC,GAAG,MAAM,CAAC;YACpD,IAAA,uBAAa,EAAC,SAAS,EAAE,QAAQ,EAAE,EAAE,IAAI,EAAE,SAAS,EAAE,CAAC,CAAC;YACxD,IAAA,oBAAU,EAAC,SAAS,EAAE,IAAI,CAAC,IAAI,CAAC,CAAC;QACnC,CAAC,EAAE,GAAG,EAAE,GAAE,CAAC,CAAC,CAAC;QACb,OAAO,IAAI,CAAC,OAAO,CAAC;IACtB,CAAC;IAED,KAAK,CAAC,GAAG,CAAC,GAAW;QACnB,OAAO,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,IAAI,IAAI,CAAC;IACnC,CAAC;IAED,KAAK,CAAC,GAAG,CAAC,GAAW,EAAE,OAAsB;QAC3C,IAAI,CAAC,OAAO,GAAG,EAAE,GAAG,IAAI,CAAC,OAAO,EAAE,CAAC,GAAG,CAAC,EAAE,OAAO,EAAE,CAAC;QACnD,MAAM,IAAI,CAAC,KAAK,EAAE,CAAC;IACrB,CAAC;IAED,KAAK,CAAC,MAAM,CAAC,GAAW;QACtB,MAAM,EAAE,CAAC,GAAG,CAAC,EAAE,OAAO,EAAE,GAAG,IAAI,EAAE,GAAG,IAAI,CAAC,OAAO,CAAC;QACjD,IAAI,OAAO,KAAK,SAAS;YAAE,OAAO;QAClC,IAAI,CAAC,OAAO,GAAG,IAAI,CAAC;QACpB,MAAM,IAAI,CAAC,KAAK,EAAE,CAAC;IACrB,CAAC;CACF;AApDD,sCAoDC"}
|